"""Diagrama din `gen/strategii/mp2_exemplu.spec.txt` (TIP: PRET). Exemplul complet din curs: SPY long la bottom-ul de aprilie 2020 — double bottom + Inverted Head & Shoulders la suport quarterly, trigger MP² long (pivotul mic închide peste cel mare), prima candelă verde după umărul drept. Intrare 577.00, SL 554.40 (1R = 22.60), TP1 599.60, ținta 4R era 667.00, iar mișcarea reală s-a oprit la 646.00 (~3R) într-o lună. Toate cifrele vin din `summaries/09_QA/01_2026-08-25.md:384-394,485-494` (liniile citate în spec). Serie continuă de 38 de candele daily (`from_closes`), panou de volum, SMA-20, niveluri reale și marcaje ancorate. """ import sys from pathlib import Path sys.path.insert(0, "/workspace/atm-curs-dl") from svg_candles import Chart, Level, Mark, MA, from_closes # noqa: E402 OUT = Path(__file__).resolve().parent / "mp2_exemplu.svg" # ── cifrele din material (09_QA/01_2026-08-25.md:384-394,485-494) ─────────── ENTRY = 577.00 # candela de intrare (prima verde după umărul drept) SL = 554.40 # sub umărul drept, chiar peste suportul quarterly TP1 = 599.60 # 577.00 + 22.60 (1R) TARGET_4R = 667.00 # ținta declarată; 4R exact = 667.40 REAL = 646.00 # mișcarea reală (~3R, +12% efectiv) QUARTERLY = 554.00 # suport quarterly SHOULDER_L = 566.0 # umăr stâng HEAD = 555.0 # head (cel mai jos) SHOULDER_R = 568.0 # umăr drept NECKLINE = 578.00 # ── structura seriei (spec): ~14 coborâre → ~10 bază → intrare → ~12 urcare ─ closes = [ # 0-13: ~14 candele de coborâre de la ~600 spre umărul stâng 566 600.0, 597.0, 594.0, 596.5, 591.0, 587.5, 590.0, 584.0, 579.5, 581.5, 576.0, 571.0, 567.0, 566.0, # 13 umăr stâng # 14-24: baza — double bottom + Inverted Head & Shoulders 570.5, 578.0, 572.0, 567.0, 560.0, 555.0, # 19 head (cel mai jos) 561.5, 568.0, 578.0, 573.0, 568.0, # 24 umăr drept # 25: candela de intrare, prima verde după umărul drept 577.00, # 26-37: ~12 candele de urcare spre 646.00 (marcajul „real") 581.0, 585.5, 589.0, 584.0, 590.5, 596.0, 602.0, 609.0, 616.0, 624.0, 635.0, 646.0, ] assert len(closes) == 38, len(closes) ENTRY_I = 25 HEAD_I = 19 SHL_I = 13 SHR_I = 24 wicks = [(0.4, 0.4)] * len(closes) wicks[SHL_I] = (0.4, 0.5) # low umăr stâng 565.50 wicks[HEAD_I] = (0.4, 0.6) # low head = 555.0 - 0.6 = 554.40 (suportul atins) wicks[SHR_I] = (0.4, 0.5) wicks[15] = (0.4, 0.4) # vârf 578.0 = neckline wicks[22] = (0.4, 0.4) # vârf 578.0 = neckline wicks[ENTRY_I] = (0.7, 0.4) # intrare verde, wick mic wicks[37] = (0.0, 0.3) # high = 646.00 exact (mișcarea reală se oprește aici) vols = [ # coborâre 48, 46, 50, 44, 52, 49, 45, 53, 55, 47, 50, 54, 58, 62, # bază (head-ul are volum mare — capitulare) 60, 72, 65, 58, 66, 88, 70, 64, 74, 60, 56, # intrare 92, # urcare 78, 72, 68, 60, 66, 74, 82, 88, 80, 84, 90, 95, ] assert len(vols) == len(closes) cds = from_closes(closes, wicks=wicks, vols=vols, start=602.0) # ── assert-urile din specificație ─────────────────────────────────────────── entry = cds[ENTRY_I].c assert abs(entry - 577.00) < 0.05, entry assert abs(SL - 554.40) < 1e-9, SL assert SL > QUARTERLY, (SL, QUARTERLY) # peste suportul quarterly assert abs((ENTRY - SL) - 22.60) < 0.05, ENTRY - SL # 1R assert abs(TP1 - (ENTRY + 22.60)) < 0.05, TP1 # TP1 = intrare + 1R target_4r = ENTRY + 4 * (ENTRY - SL) assert abs(target_4r - 667.40) < 0.05, target_4r # 4R exact assert abs(target_4r - TARGET_4R) < 0.5, target_4r # ≈ 667.00, rotunjit în curs assert abs((REAL - ENTRY) / (ENTRY - SL) - 3.05) < 0.02, (REAL - ENTRY) / (ENTRY - SL) assert abs((REAL - ENTRY) / (ENTRY - SL) - 4.0) > 0.5, "mișcarea reală NU e 4R" # structura care susține afirmațiile din text assert abs(cds[SHL_I].c - SHOULDER_L) < 0.05, cds[SHL_I].c assert abs(cds[HEAD_I].c - HEAD) < 0.05, cds[HEAD_I].c assert abs(cds[SHR_I].c - SHOULDER_R) < 0.05, cds[SHR_I].c assert HEAD < SHOULDER_L and HEAD < SHOULDER_R, (HEAD, SHOULDER_L, SHOULDER_R) assert cds[SHL_I].c == min(c.c for c in cds[SHL_I - 1:SHL_I + 2]), "umărul stâng nu e minim local" assert cds[HEAD_I].c == min(c.c for c in cds[HEAD_I - 1:HEAD_I + 2]), "head-ul nu e minim local" assert cds[SHR_I].c == min(c.c for c in cds[SHR_I - 1:SHR_I + 2]), "umărul drept nu e minim local" min_pre = min(c.l for c in cds[:ENTRY_I]) assert abs(min_pre - 554.0) < 1.0, min_pre # suportul quarterly atins assert abs(min_pre - SL) < 0.05, (min_pre, SL) # SL chiar la minimul head-ului assert cds[ENTRY_I].c > cds[ENTRY_I].o, "candela de intrare trebuie verde" assert cds[ENTRY_I - 1].c < cds[ENTRY_I - 1].o, "umărul drept e roșu" assert abs(cds[37].c - REAL) < 0.05, cds[37].c assert all(c.vol is not None for c in cds), "lipsește panoul de volum" assert len(cds) == 38 # ── desenul ───────────────────────────────────────────────────────────────── levels = [ Level(TARGET_4R, "ținta 4R era 667.00 (+16%)", "#555"), Level(REAL, "646.00 — +12% efectiv (~3R)", "#188a3e", label_at="left"), Level(TP1, "TP1 599.60", "#1a5fb4"), Level(NECKLINE, "neckline 578.00", "#1a5fb4"), Level(SL, "SL 554.40", "#c00"), Level(QUARTERLY, "suport quarterly 554.00", "#777", label_at="left"), ] marks = [ Mark(SHL_I, "umăr stâng 566", "#1a1a1a", "below"), Mark(HEAD_I, "head 555", "#1a1a1a", "below"), Mark(SHR_I, "umăr drept 568", "#1a1a1a", "below"), Mark(ENTRY_I, "intrare 577.00", "#188a3e", "above", dy=-10), ] ch = Chart( candles=cds, levels=levels, marks=marks, mas=[MA(20, "#b06000", dash="4 3", text="SMA-20")], scale=3.8, width=720, gap=15, body_w=9, left=44, vol_h=70, vol_label="volum", title="SPY — daily", caption=("Intrare 577.00 = prima verde după umărul drept (MP² long). " "1R = 22.60; 646.00 ~3R, într-o lună."), ) svg = ch.render() assert svg.count("\n\n") == 0, "linie goală în SVG — rupe markdown-ul" OUT.write_text(svg) print(f"serie {len(cds)} candele | intrare {entry:.2f} @ {ENTRY_I} | " f"SL {SL:.2f} | 1R {ENTRY - SL:.2f} | TP1 {TP1:.2f} | " f"4R {target_4r:.2f} (ținta declarată {TARGET_4R:.2f}) | " f"real {REAL:.2f} = {(REAL - ENTRY) / (ENTRY - SL):.2f}R | " f"min pre-intrare {min_pre:.2f}") print(f"scris {OUT}")