"""Diagrama de ansamblu ATMI (strategii/05_atmi.md, marcajul `ansamblu`). „SPY — daily: corecție ≥4 % → zona 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 = [96.60, 97.10, 97.60, 98.00, 98.50, 98.20, 98.70, 99.20, 99.60, 99.30, 99.70, 100.00] # 0-11 CORR = [99.30, 98.50, 97.80, 97.20, 96.70, 96.30, 95.90, 96.00] # 12-19 (18 = BIM) UP_B = [96.60, 97.20, 97.90, 98.60, 99.40, 100.30, 101.40, 102.60, 103.40, 104.20, 104.90, 105.60] # 20-31 CLOSES = UP_A + CORR + UP_B assert len(CLOSES) == 32, len(CLOSES) PEAK, BIM, ENTRY, LAST = 11, 18, 19, 31 # fitile (sus, jos) per bară; jos pe corecție ținut ≥ 0 ca minimul să fie fix 96.00 WICKS = ([(0.25, 0.25)] * 11 + [(0.00, 0.25)] # 11: vârful atinge exact 100.00 + [(0.00, 0.20), (0.05, 0.20), (0.05, 0.20), (0.05, 0.20), (0.05, 0.20), (0.05, 0.20), (0.05, 0.35), # 18: bara de BIM, fundul la 95.55 (0.15, 0.05)] # 19: candela verde de intrare + [(0.25, 0.00)] + [(0.25, 0.25)] * 10 + [(0.00, 0.20)]) # 31: închide/atinge exact 105.60 assert len(WICKS) == len(CLOSES), (len(WICKS), len(CLOSES)) VOLS = ([44, 47, 42, 50, 45, 41, 48, 43, 46, 51, 44, 58] # 0-11 urcare + [64, 70, 66, 61, 58, 74, 96, 88] # 12-19 corecție + BIM/intrare + [55, 58, 52, 60, 57, 63, 59, 66, 70, 74, 78, 84]) # 20-31 urcare spre TP assert len(VOLS) == len(CLOSES), (len(VOLS), len(CLOSES)) cds = from_closes(CLOSES, wicks=WICKS, vols=VOLS, start=96.20) # ---------------------------------------------------------------- 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[-12:])) < 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[12:20]) - 95.55) < 1e-9 # fundul corecției = 95.55 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(13, "corecție 4,0 %", ORANGE, "below", dy=26), Mark(BIM, "BIM", "#0a7f9e", "above"), Mark(ENTRY, "prima candelă verde → intrare", GREEN, "below", dy=30), Mark(LAST, "ieși cu tot", BLUE, "below", dy=22), ] ch = Chart(candles=cds, levels=levels, marks=marks, scale=22.0, gap=17, body_w=9, left=44, width=720, vol_h=70, vol_gap=72, top=88, vol_label="volum", caption="100.00 / 96.00 / 105.60 sunt cifrele exemplului din curs; PPQ 95.80 e calculat " "în diagrama pasului 1 (cursul nu dă valorile pivoților).") 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}') # PPQ 95.80 e pivotul trimestrial calculat în diagrama pasului 1; banda e # zona de cumpărare din jurul lui. SQ 90.80 e sub scara acestui desen, deci # nu se desenează aici — apare pe diagrama pasului 1. PPQ = 95.80 assert abs(min(c.l for c in cds) - 95.55) < 1e-9 assert PPQ - 0.30 <= min(c.l for c in cds) <= PPQ + 0.30, "fundul nu cade în banda PPQ" assert entry > PPQ, "candela verde trebuie să închidă peste pivot" ppq = band(PPQ, 0.30, PURPLE, f"zona PPQ {PPQ:.2f} — aici cumperi") sq = "" # „fără stop-loss standard" stă sub zona de intrare, unde nu are candele no_sl = (f'fără stop-loss standard — nicio linie sub intrare') # 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 % → zona PPQ → BIM + verde → intrare → TP +10 %') i = svg.index(">") + 1 svg = svg[:i] + "\n " + "\n ".join([ppq, no_sl, titlu]) + svg[i:] assert svg.count("\n\n") == 0, "linie goală în SVG rupe blocul din markdown" assert ".md:" not in svg, "trimitere fișier:linie în SVG" OUT.write_text(svg) print(f"scris {OUT.name}: {len(cds)} candele, intrare {entry:.2f}, TP {tp:.2f}")