Cate un agent grafician per diagrama, fiecare cu generatorul si SVG-ul lui. Graficele de pret trec prin svg_candles, cu assert-urile din specificatie scrise in generator — verifica structura, nu doar ca ruleaza: ca al doilea picior e mai jos, ca intrarea e close-ul barei de confirmare, ca 1:1 e exact. Verificare prin sondaj pe buy_doji_ansamblu, cu screenshot citit: serie continua de 32 de candele, panou de volum, MA(20), doji-ul si ieșirea pe trail etichetate, cifrele identice cu specificatia. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ThN7ixZrowca8aTGrK1MUJ
124 lines
6.2 KiB
Python
124 lines
6.2 KiB
Python
"""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'<rect x="24" y="{y_top:.1f}" width="{ch.width - 38}" '
|
|
f'height="{y_bot - y_top:.1f}" fill="{color}" opacity="0.10"/>'
|
|
f'<text x="28" y="{y_top - 4:.1f}" fill="{color}">{label}</text>')
|
|
|
|
|
|
# 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'<text x="{ch._cx(ENTRY):.0f}" y="{ch._y(entry) + 63:.1f}" fill="{RED}" '
|
|
f'text-anchor="middle">fără stop-loss standard</text>'
|
|
f'<text x="{ch._cx(ENTRY):.0f}" y="{ch._y(entry) + 76:.1f}" fill="{GRAY}" '
|
|
f'text-anchor="middle">(03_strategia_2_atmi.md:164-169)</text>')
|
|
# 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 = ('<text x="24" y="26" fill="#333" style="font-weight:bold">'
|
|
'SPY — daily: corecție ≥4 % → SQ/PPQ → BIM + verde → intrare → TP +10 %</text>')
|
|
|
|
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}")
|