"""Diagrama din `gen/strategii/intrarea_doua_variante.spec.txt` (TIP: PRET). Același setup cu cele două intrări pe același grafic: A la close-ul candelei de semnal, B la retestul de 1/3 și 1/2 din corpul ei. Un singur stop, comun (sub structură), și trei paranteze de risc în dreapta, la aceeași scară cu prețul — diferența de risc vine din intrare, nu din stop. Seria e CONSTRUITĂ (fără ticker), 32 de candele de 15 min, fără volum. Etichetele de sub candele (suport atins / atinge 1/3 / atinge 1/2) sunt desenate de mână sub linia de SL, cu linii de legătură: ca Mark-uri „below" ar cădea peste liniile de 1/2 și 1/3 și peste wick-ul candelei vecine. Rulează: .venv/bin/python gen/strategii/intrarea_doua_variante.py -> gen/strategii/intrarea_doua_variante.svg """ import math import re import sys from pathlib import Path sys.path.insert(0, "/workspace/atm-curs-dl") from svg_candles import Chart, Level, Mark, from_closes # noqa: E402 OUT = Path(__file__).resolve().parent / "intrarea_doua_variante.svg" GRI, ROSU, VERDE, PORT, ALB = "#777", "#c00", "#188a3e", "#b06000", "#1a5fb4" closes = [103.60, 103.35, 103.45, 103.10, 102.80, 102.90, 102.50, 102.20, 101.85, 101.95, 101.50, 101.10, # 0-11 100.60, 100.40, 100.35, # 12-14 suport 102.15, # 15 semnal 101.70, 101.40, 101.60, # 16-18 retest 102.00, 102.40, 102.30, 102.80, 103.20, 103.00, 103.50, 103.90, 104.20, 104.10, 104.50, 104.80, 105.00] wicks = [(0.15, 0.15)] * 12 \ + [(0.10, 0.15), (0.10, 0.50), (0.10, 0.15)] \ + [(0.15, 0.10)] \ + [(0.10, 0.20), (0.10, 0.20), (0.10, 0.10)] \ + [(0.10, 0.03)] + [(0.15, 0.12)] * 12 assert len(closes) == len(wicks) == 32 cds = from_closes(closes, wicks=wicks, start=103.80) # ── assert-urile din spec ─────────────────────────────────────────────────── S = cds[15] for a, b in zip(cds, cds[1:]): # 1 assert abs(a.c - b.o) < 1e-9 assert abs(S.o - 100.35) < 1e-9 and abs(S.c - 102.15) < 1e-9 # 2 assert abs(S.h - 102.30) < 1e-9 and abs(S.l - 100.25) < 1e-9 SL = min(c.l for c in cds[11:15]) # 3 assert abs(SL - 99.90) < 1e-9 and abs(cds[13].l - SL) < 1e-9 and S.l > SL assert all(c.l > SL for i, c in enumerate(cds[11:15], 11) if i != 13) half = (S.o + S.c) / 2 # 4 third = S.c - (S.c - S.o) / 3 assert abs(half - 101.25) < 1e-9 and abs(third - 101.55) < 1e-9 assert min(c.l for c in cds[16:19]) <= half and cds[16].l <= third # 5 assert cds[16].l > half # 16 atinge doar 1/3, 17 atinge 1/2 assert all(c.l > third for c in cds[19:]) # după retest nu mai revine la 1/3 RISK_PER_TRADE = 50.0 intrari = {"A": S.c, "1/3": third, "1/2": half} riscuri = {k: e - SL for k, e in intrari.items()} # 6 for k, r in zip(intrari, (2.25, 1.65, 1.35)): assert abs(riscuri[k] - r) < 1e-6, (k, riscuri[k]) actiuni = {k: math.floor(RISK_PER_TRADE / r + 1e-9) for k, r in riscuri.items()} assert list(actiuni.values()) == [22, 30, 37], actiuni tp = {k: e + (e - SL) for k, e in intrari.items()} # 7 for k, v in zip(intrari, (104.40, 103.20, 102.60)): assert abs(tp[k] - v) < 1e-9, (k, tp[k]) assert max(c.h for c in cds) >= tp["A"] # 9 # ── desenul ───────────────────────────────────────────────────────────────── W = 880 levels = [ Level(100.00, "suport 100.00", GRI), Level(SL, "", ROSU), # eticheta SL e scrisă sub linie, mai jos Level(S.c, f"A: intrare la close {S.c:.2f}", VERDE), Level(third, f"B: 1/3 = {third:.2f}", PORT), Level(half, f"B: 1/2 = {half:.2f}", PORT), Level(tp["A"], f"TP1 A {tp['A']:.2f}", ALB), Level(tp["1/2"], f"TP1 B (1/2) {tp['1/2']:.2f}", ALB, dash="2 3"), Level(tp["1/3"], f"TP1 B (1/3) {tp['1/3']:.2f}", ALB, dash="2 3"), ] ch = Chart(candles=cds, levels=levels, marks=[Mark(15, "semnal: candela verde mare", VERDE, "above", dy=-14)], scale=60, gap=15, body_w=9, left=44, width=W, title="Exemplu construit (fără ticker) — 15 min") svg = ch.render() svg = re.sub(r'\s*]*>', "", svg) # eticheta goală a SL-ului y, cx = ch._y, ch._cx ySL = y(SL) extra = [] def txt(x, yy, s, fill, anchor="start", bold=False): st = ' style="font-weight:bold"' if bold else "" extra.append(f'{s}') def ln(x1, y1, x2, y2, stroke, w=1.2, dash=""): d = f' stroke-dasharray="{dash}"' if dash else "" extra.append(f'') # eticheta SL, sub linia lui, în dreapta txt(W - 28, ySL + 16, f"SL {SL:.2f} = low-ul ultimelor 4 candele (comun A și B)", ROSU, "end") # etichetele de sub candele, sub linia de SL, cu linii de legătură spre wick R1, R2 = ySL + 16, ySL + 31 txt(cx(13), R1, "suport atins", GRI, "middle", True) ln(cx(16), y(cds[16].l) + 2, cx(16), R2 - 11, PORT, 1, "2 2") txt(cx(16) + 4, R2, "atinge 1/3", PORT, "end", True) ln(cx(17), y(cds[17].l) + 2, cx(17), R1 - 11, PORT, 1, "2 2") txt(cx(17) - 4, R1, "atinge 1/2", PORT, "start", True) # parantezele de risc: intrare → SL, la aceeași scară cu prețul BX = {"A": 560, "1/3": 605, "1/2": 650} col = {"A": VERDE, "1/3": PORT, "1/2": PORT} eticheta = {"A": "A", "1/3": "B 1/3", "1/2": "B 1/2"} px = {} for k, e in intrari.items(): x, y0, y1 = BX[k], y(e), ySL px[k] = y1 - y0 ln(x, y0, x, y1, col[k], 2.2) ln(x - 6, y0, x + 6, y0, col[k], 2.2) ln(x - 6, y1, x + 6, y1, ROSU, 2.2) txt(x, y0 - 6, eticheta[k], col[k], "middle", True) txt(x + 6, (y0 + y1) / 2 + 4, f"{riscuri[k]:.2f}", col[k], "start") # 8. scara: raportul pixelilor == raportul riscurilor, toleranță 1% for k in intrari: assert abs(px[k] / px["A"] - riscuri[k] / riscuri["A"]) < 0.01 * riscuri[k] / riscuri["A"], k # blocul cu riscurile, sub SL, sub paranteze bloc = [(f"risc A {riscuri['A']:.2f} → {actiuni['A']} acțiuni la 50 $", VERDE), (f"risc B 1/3 {riscuri['1/3']:.2f} → {actiuni['1/3']} acțiuni", PORT), (f"risc B 1/2 {riscuri['1/2']:.2f} → {actiuni['1/2']} acțiuni", PORT)] for i, (s, c) in enumerate(bloc): txt(530, ySL + 34 + 15 * i, s, c) ylegenda = ySL + 34 + 15 * len(bloc) + 12 txt(24, ylegenda, "Exemplu construit, fără ticker. Cifrele nu vin din curs. " "Risc permis 50 $ (0,5% din 10.000 $).", "#555") H = ylegenda + 12 svg = re.sub(r'viewBox="0 0 \d+ \d+"', f'viewBox="0 0 {W} {H:.0f}"', svg) svg = svg.replace("\n", "\n " + "\n ".join(extra) + "\n") assert "\n\n" not in svg, "linie goală în SVG" # ── verificare: etichete în pânză, fără suprapuneri între ele ───────────── cutii = [] for xs, ys, attrs, s in re.findall(r']*)>([^<]*)', svg): x, yy = float(xs), float(ys) w = len(s) * (7.0 if "bold" in attrs else 6.3) x0 = x - w if 'anchor="end"' in attrs else (x - w / 2 if 'anchor="middle"' in attrs else x) assert -2 <= x0 and x0 + w <= W + 2 and 0 <= yy <= H, f"în afara pânzei: {s!r}" cutii.append((x0, x0 + w, yy, s)) for i, a in enumerate(cutii): for b in cutii[i + 1:]: if abs(a[2] - b[2]) < 13 and a[0] < b[1] - 2 and b[0] < a[1] - 2: raise AssertionError(f"texte suprapuse: {a[3]!r} / {b[3]!r}") OUT.write_text(svg + "\n", encoding="utf-8") print(f"SL {SL:.2f} | 1/3 {third:.2f} | 1/2 {half:.2f} | riscuri " + ", ".join(f"{k} {r:.2f}→{actiuni[k]}" for k, r in riscuri.items()) + " | px " + ", ".join(f"{v:.1f}" for v in px.values()))