"""Diagrama de ansamblu ATMI (strategii/05_atmi.md, marcajul `ansamblu`). „SPY — daily: corecție ≥4 % → SQ/PPQ → BIM + verde → intrare → TP +10 %". Structura seriei și nivelurile vin din spec-ul `gen/strategii/atmi_ansamblu.spec.txt`. Toate cifrele desenate (100.00 swing high, 96.00 intrare, 105.60 = +10 %) sunt din exemplul „DA" al cursului (`summaries/04_MODUL_3/03_strategia_2_atmi.md:185-247`). Benzile PPQ/SQ sunt schematice: cursul spune explicit că nu dă cifrele lor (`...:143`), deci nu poartă prețuri. Rulează: python3 gen/strategii/atmi_ansamblu.py Scrie lângă el `atmi_ansamblu.svg`. """ 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 / "atmi_ansamblu.svg" GRAY, RED, GREEN, BLUE, ORANGE = "#777", "#c00", "#188a3e", "#1a5fb4", "#b06000" PURPLE = "#7b2fbf" # folosit în materialul de curs pentru PPQ # ---------------------------------------------------------------- seria (33 bare) # 15 bare de urcare ~88 -> 100, 8 de corecție 100 -> 96 (ultima = intrarea), # 10 de urcare 96 -> 105.60. open-ul fiecărei bare = close-ul precedentei. UP_A = [88.2, 88.8, 89.5, 90.1, 90.8, 91.6, 92.5, 93.3, 94.2, 95.1, 96.0, 96.9, 97.8, 98.9, 100.00] # 0-14 CORR = [99.3, 98.5, 97.8, 97.2, 96.7, 96.3, 96.00, 96.00] # 15-22 UP_B = [96.6, 97.2, 97.9, 98.6, 99.4, 100.3, 101.4, 102.6, 104.0, 105.60] # 23-32 CLOSES = UP_A + CORR + UP_B assert len(CLOSES) == 33, len(CLOSES) PEAK, BIM, ENTRY, LAST = 14, 21, 22, 32 # fitile (sus, jos) per bară; jos pe corecție ținut ≥ 0 ca minimul să fie fix 96.00 WICKS = ([(0.30, 0.30)] * 14 + [(0.00, 0.30)] # 14: vârful atinge exact 100.00 + [(0.00, 0.00), (0.05, 0.10), (0.05, 0.10), (0.05, 0.15), (0.05, 0.15), (0.05, 0.20), (0.05, 0.00), (0.12, 0.00)] # 15-22 + [(0.25, 0.25)] * 9 + [(0.00, 0.20)]) # 32: închide/atinge exact 105.60 assert len(WICKS) == len(CLOSES) VOLS = ([44, 47, 42, 50, 45, 41, 48, 43, 46, 51, 44, 49, 46, 52, 58] # 0-14 urcare + [64, 70, 66, 61, 58, 74, 96, 88] # 15-22 corecție + BIM/intrare + [55, 58, 52, 60, 57, 63, 59, 66, 72, 84]) # 23-32 urcare spre TP assert len(VOLS) == len(CLOSES) cds = from_closes(CLOSES, wicks=WICKS, vols=VOLS, start=88.0) # ---------------------------------------------------------------- assert-urile din spec high = 100.00 entry = cds[ENTRY].c tp = cds[LAST].h assert abs((high - 96.00) / high * 100 - 4.0) < 0.05 # corecția e 4.0 % assert abs(entry - 96.00) < 0.05 # intrarea = close-ul candelei verzi assert abs(tp / entry - 1.10) < 0.005 # TP = +10 % assert abs(tp - max(c.h for c in cds[-10:])) < 1e-9 # TP atins la final, e maximul barelor assert BIM < ENTRY # BIM înainte de candela verde assert abs(min(c.l for c in cds[15:23]) - 96.00) < 1e-9 # minimul corecției = 96.00 assert cds[ENTRY].c >= cds[ENTRY].o # candela de intrare e verde assert abs(cds[PEAK].h - high) < 1e-9 # swing high = 100.00 assert abs(cds[LAST].c - tp) < 1e-9 # ultima bară închide pe TP levels = [ Level(high, "100.00 — swing high recent", "#333", label_at="left"), Level(entry, f"intrare {entry:.2f}", GREEN, label_at="right"), Level(tp, f"TP +10 % = {tp:.2f}", BLUE, label_at="right"), ] # nicio linie de stop-loss și niciun nivel sub intrare (absența SL e regula) assert all("stop" not in lv.text.lower() for lv in levels) assert all(lv.price >= entry - 1e-9 for lv in levels) marks = [ Mark(15, "corecție 4.0 %", ORANGE, "above"), Mark(BIM, "BIM", ORANGE, "above"), Mark(ENTRY, "BIM + candelă verde → intrare", GREEN, "below", dy=20), ] ch = Chart(candles=cds, levels=levels, marks=marks, mas=[MA(20, ORANGE, dash="4 3", text="SMA-20 (ilustrativ)")], scale=18.0, gap=16, body_w=9, left=44, width=720, vol_h=70, top=70, vol_label="volum", caption="Niveluri (100.00 / 96.00 / 105.60) din curs; PPQ/SQ schematice, fără cifre în material.") svg = ch.render() def band(price: float, half: float, color: str, label: str) -> str: """Bandă schematică orizontală, desenată în spatele candelelor. Se injectează în SVG după randare, pentru că `Chart` nu are benzi; y-ul se calculează cu aceeași scară (`ch._y`), deci banda e aliniată corect. """ y_top, y_bot = ch._y(price + half), ch._y(price - half) return (f'' f'{label}') # SQ e zona joasă (intrarea exact pe ea), PPQ zona mai sus, retragere mai mică assert (entry + 1.60 - 0.35) > (entry + 0.35), "benzile SQ și PPQ nu se suprapun" sq = band(entry, 0.35, GREEN, "SQ — zonă quarterly") ppq = band(entry + 1.60, 0.35, PURPLE, "PPQ — zonă quarterly") # „fără stop-loss standard" stă sub zona de intrare, unde nu are candele no_sl = (f'fără stop-loss standard' f'(03_strategia_2_atmi.md:164-169)') # titlul e injectat deasupra, ca rând separat de eticheta TP (care stă chiar # sub marginea de sus a panoului); altfel cele două texte se calcă la dreapta titlu = ('' 'SPY — daily: corecție ≥4 % → SQ/PPQ → BIM + verde → intrare → TP +10 %') i = svg.index(">") + 1 svg = svg[:i] + "\n " + "\n ".join([sq, ppq, no_sl, titlu]) + svg[i:] assert svg.count("\n\n") == 0, "linie goală în SVG rupe blocul din markdown" OUT.write_text(svg) print(f"scris {OUT.name}: {len(cds)} candele, intrare {entry:.2f}, TP {tp:.2f}")