Files
atm-curs-dl/gen/az_sinteza.py
Claude Agent f647874a52 datoria tehnica: garda pe dash, si PChart intr-un singur loc
Garda: `Level(p, text, color, "left")` punea tacut "left" in `dash` in loc
de `label_at`, iar SVG-ul iesea fara linie punctata si fara nicio eroare -
trei agenti au pierdut timp pe asta pe 13.09. Acum `__post_init__` crapa
cu mesaj care spune ce argument voiai. Verificat pe cazurile valide si pe
cele doua gresite.

PChart: era copiat in cinci generatoare, iar copiile divergisera - toate
cinci aveau hash-uri diferite si patru pastrau `spread()` cel vechi.
Nucleul comun trece in gen/pchart.py; 385 de linii sterse, 11 adaugate.

az_executie.py isi pastreaza PChart-ul: varianta lui calculeaza mediile
dintr-un istoric dinainte de bara 0 si si le deseneaza singura. Unirea
celor doua comportamente ca sa scap de un fisier ar fi riscat cele 10
diagrame de acolo, pentru zero castig. Ia doar `spread` reparat.

Reparatia schimba 4 documente, deci PROGRESS gresea cand spunea ca nicio
diagrama comisa nu sufera de bug: 08 si 10 se misca cu 0,2 px, dar 07 si
09 aveau etichete la 6 px de linia lor. Verificat cu ochii pe 3 diagrame
randate - etichetele stau acum pe linia lor, cu liniuta de legatura unde
a trebuit mutata. audit_diagrams: 0 probleme.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ThN7ixZrowca8aTGrK1MUJ
2026-09-18 11:57:57 +00:00

239 lines
13 KiB
Python

"""Diagramele pentru summaries/08_ANALIZA_ZILNICA/10_sinteza.md.
Un singur setup construit (fără ticker), urmărit prin toată grila:
weekly (structură, pivot, volum, buy doji W, higher low) -> daily (trigger,
MBT-10, intrare, SL, ținte în trepte, break-even, trailing) -> varianta NU.
Barele weekly 24-31 sunt AGREGATE din seria daily (5 zile = 1 săptămână),
deci cele două grafice spun aceeași poveste cu aceleași cifre. Pivoții
monthly se calculează din lunile de 4 săptămâni ale seriei weekly.
Rulează: .venv/bin/python gen/az_sinteza.py
"""
import re
import sys
from dataclasses import dataclass, field
from pathlib import Path
sys.path.insert(0, "/workspace/atm-curs-dl")
from svg_candles import Candle, Chart, Level, Mark, MA, from_closes # noqa: E402
MD = Path("/workspace/atm-curs-dl/summaries/08_ANALIZA_ZILNICA/10_sinteza.md")
GRAY, RED, GREEN, BLUE, ORANGE, PURPLE = "#777", "#c00", "#188a3e", "#1a5fb4", "#b06000", "#6a3d9a"
STYLE = dict(gap=16, body_w=10, left=44, width=700)
svgs = {}
# ------------------------------------------------ PChart, copiat din gen/az_pivoti.py
from pchart import PChart, Seg, spread # noqa: E402
def pivots(bars):
"""floor pivots din H/L/C ale perioadei încheiate"""
H, L, C = max(c.h for c in bars), min(c.l for c in bars), bars[-1].c
P = (H + L + C) / 3
return dict(H=H, L=L, C=C, P=P, R1=2 * P - L, S1=2 * P - H, R2=P + (H - L), S2=P - (H - L))
def px(ch, p0, p1):
"""distanța în pixeli între două prețuri, după render"""
return ch._y(p0) - ch._y(p1)
def f2(x):
return f"{x:.2f}"
# ================================================================ seria WEEKLY, barele 0-23
wk_cl = [88.4, 87.0, 85.2, 83.6, # M0 downtrend
85.4, 86.6, 84.8, 82.6, # M1 bounce: bara 5 = vârful anterior (lower high)
80.4, 78.2, 76.6, 74.2, # M2
72.4, 70.2, 69.0, 70.8, # M3 bara 14 = picior 1
72.0, 70.6, 68.8, 67.9, # M4 bara 19 = picior 2, mai jos
68.0, 72.6, 74.4, 75.8] # M5 bara 20 doji, 21 buy doji W, raliu
wk_w = [(0.4, 0.4)] * 6 + [(0.1, 0.4)] + [(0.4, 0.4)] * 6 + [(0.3, 0.5), (0.3, 0.7), (0.3, 0.3), (0.3, 0.3), (0.3, 0.3), (0.3, 0.4),
(0.3, 0.5), (0.6, 0.5), (0.3, 0.3), (0.3, 0.3), (0.3, 0.3)]
wk_v = [60, 62, 66, 64, 45, 42, 64, 68, 70, 72, 74, 78, 82, 90, 96, 60, 55, 58, 62, 54, 50, 118, 90, 80]
weekly = from_closes(wk_cl, wicks=wk_w, vols=wk_v, start=90.0)
# ================================================================ seria DAILY = săptămânile 24-31
d_cl = [76.6, 77.3, 77.0, 77.9, 78.2, # w24 raliul continuă, vârf de raliu
77.4, 76.5, 76.9, 75.8, 75.0, # w25 pullback
75.3, 74.4, 74.3, # w26 z12 = hammer: higher low
74.9, # z13 buy hammer D: close peste high-ul hammer-ului
75.6, # z14 close în MBT-10 -> intrare
76.4, 77.3, 78.1, 79.0, 79.6, # w27
79.1, 78.8, 79.4, 80.3, 80.9, # w28 (M7 începe la z20)
81.6, 82.3, 81.8, 82.6, 83.2, # w29
83.0, 83.8, 84.4, 84.1, 83.5, # w30
82.6, 82.9, 81.9, 81.2, 81.6] # w31
d_w = ([(0.3, 0.3)] * 5 + [(0.3, 0.3)] * 5 + [(0.3, 0.3), (0.2, 0.3), (0.1, 0.5), (0.2, 0.3), (0.3, 0.2)]
+ [(0.3, 0.3)] * 25)
d_v = [55, 52, 44, 50, 48,
36, 38, 30, 40, 36,
32, 34, 38, 92, 80,
66, 60, 58, 62, 56,
44, 40, 50, 58, 54,
56, 60, 42, 55, 52,
40, 50, 54, 46, 58,
60, 44, 62, 58, 40]
daily = from_closes(d_cl, wicks=d_w, vols=d_v, start=wk_cl[-1])
assert len(daily) == 40
def agg(bars):
return Candle(bars[0].o, max(c.h for c in bars), min(c.l for c in bars), bars[-1].c,
vol=sum(c.vol for c in bars) / 5)
weekly += [agg(daily[k:k + 5]) for k in range(0, 40, 5)]
assert len(weekly) == 32
for a, b in zip(weekly, weekly[1:]):
assert abs(a.c - b.o) < 1e-9, "seria weekly are gol între bare"
# ------------------------------------------------ pivoți monthly (luni de 4 săptămâni)
month = lambda m: weekly[4 * m:4 * m + 4] # noqa: E731
PV = {m: pivots(month(m - 1)) for m in range(1, 8)} # PV[m] = nivelurile lunii m
P5, P6, P7 = PV[5]["P"], PV[6]["P"], PV[7]["P"]
# ------------------------------------------------ afirmațiile din text, pe weekly
V_IDX, LEG1, LEG2, DOJI, BDW, HLW = 5, 14, 19, 20, 21, 26
VARF = weekly[V_IDX].h
assert VARF > weekly[4].h and VARF > weekly[6].h and VARF < weekly[0].o, "bara 5 nu e lower high"
leg1, leg2 = weekly[LEG1].l, min(c.l for c in weekly[17:21])
assert leg1 == min(c.l for c in weekly[12:16]) and leg2 < leg1, "piciorul 2 nu e mai jos"
assert wk_v[LEG2] < wk_v[LEG1] and wk_v[18] < wk_v[13], "fără divergență de volum la piciorul 2"
assert abs(weekly[DOJI].o - weekly[DOJI].c) <= 0.15, "bara 20 nu e doji"
assert weekly[BDW].c > weekly[DOJI].h and weekly[BDW].c > P5, "buy doji W nu închide peste high doji și peste pivot"
assert wk_v[BDW] == max(c.vol for c in weekly), "buy doji W nu are cel mai mare volum"
assert weekly[DOJI].c < P5, "doji-ul trebuie să închidă sub pivotul lunii"
assert P6 > P5, "luna 6 trebuie să fie în proiecție bull"
hl = daily[12].l
assert weekly[HLW].l == hl, "low-ul săptămânii 26 trebuie să fie higher low-ul din daily"
assert hl > P6 and hl > leg2, "higher low-ul nu e deasupra pivotului lunii"
top_raliu = max(c.h for c in daily[:5])
assert top_raliu == max(c.h for c in daily[:12]), "vârful raliului trebuie să fie în w24"
print(f"W varf {VARF:.2f} leg1 {leg1:.2f} leg2 {leg2:.2f} BDW close {weekly[BDW].c:.2f} P5 {P5:.2f} "
f"P6 {P6:.2f} P7 {P7:.2f} HL {hl:.2f} top raliu {top_raliu:.2f}")
# ------------------------------------------------ execuția, pe daily
HAM, TRG, ENT = 12, 13, 14
ham = daily[HAM]
assert ham.h - max(ham.o, ham.c) <= 0.15 and min(ham.o, ham.c) - ham.l >= 2 * abs(ham.o - ham.c), "z12 nu e hammer"
assert daily[TRG].c > ham.h and daily[TRG].l > ham.l, "z13 nu confirmă swing low-ul (candela din dreapta)"
avg20 = sum(d_v[:TRG]) / TRG
assert d_v[TRG] >= 1.8 * avg20, "trigger-ul nu are volum țeavă"
assert sum(d_v[5:12]) / 7 < 0.8 * sum(d_v[:5]) / 5, "pullback-ul nu are volum în scădere"
entry = daily[ENT].c
SL = round(hl - 0.2, 2)
risk = entry - SL
R1, R2 = PV[7]["R1"], PV[7]["R2"]
assert entry < R1 < R2 < VARF, "treptele nu sunt în ordine"
tp1_hit = next(i for i in range(20, 40) if daily[i].h >= R1)
tp2_hit = next(i for i in range(tp1_hit, 40) if daily[i].h >= R2)
assert all(c.l > entry for c in daily[tp1_hit:]), "după TP1, stopul pe break-even ar fi fost lovit"
assert all(c.l > SL for c in daily[ENT + 1:]), "SL-ul ar fi fost lovit"
# trailing stop (08 §5): low-ul ultimei candele care a închis peste high-ul precedentei; urcă doar
trail, t_exit, trail_at_exit = None, None, None
for i in range(ENT + 1, 40):
if trail is not None and daily[i].c < trail:
t_exit, trail_at_exit = i, trail
break
if daily[i].c > daily[i - 1].h:
trail = max(trail or 0, daily[i].l)
assert t_exit is not None, "trailing-ul nu iese în fereastra diagramei"
exit3 = daily[t_exit].c
rr = lambda p: (p - entry) / risk # noqa: E731
print(f"D entry {entry:.2f} SL {SL:.2f} risc {risk:.2f} | R1 {R1:.2f} ({rr(R1):.2f}R) z{tp1_hit} | "
f"R2 {R2:.2f} ({rr(R2):.2f}R) z{tp2_hit} | trailing {trail_at_exit:.2f} ieșire z{t_exit} close {exit3:.2f} ({rr(exit3):.2f}R)"
f" | vol trigger {d_v[TRG]} = {d_v[TRG] / avg20:.1f}x | P6 {P6:.2f} P7 {P7:.2f}")
total_R = (rr(R1) + rr(R2) + rr(exit3)) / 3
print(f" medie pe 3 poziții: {total_R:.2f}R; vârful anterior {VARF:.2f} = {rr(VARF):.2f}R, neatins")
assert max(c.h for c in daily) < VARF
# ================================================================ diagrama 1: weekly
ch = PChart(candles=weekly,
segs=[Seg(20, 23, P5, f"pivot M5 {f2(P5)}", ORANGE),
Seg(24, 27, P6, f"pivot M6 {f2(P6)} (bull)", ORANGE),
Seg(28, 31, P7, f"pivot M7 {f2(P7)}", ORANGE)],
seps=[20, 24, 28],
levels=[Level(VARF, f"vârf anterior {f2(VARF)}", BLUE)],
marks=[Mark(V_IDX, "vârf anterior", BLUE, "above"),
Mark(LEG1, "picior 1", RED, "below"),
Mark(LEG2, "picior 2", RED, "below", dy=8),
Mark(BDW, "④ buy doji W", GREEN, "above", dy=-56),
Mark(HLW, "⑤ higher low", GREEN, "below", dy=4)],
mas=[MA(20, PURPLE, text="SMA 20")],
scale=13, vol_h=70, pad_bot_px=30, title="Exemplu construit (fără ticker) — weekly",
caption="③ picior 2 mai jos, volum mai mic; ④ buy doji peste pivot; ⑤ HL peste pivot; ⑥-⑧ pe daily.",
**STYLE)
svgs["sinteza_weekly"] = ch.render()
assert px(ch, P6, hl) > 3, "higher low-ul trebuie să se vadă deasupra pivotului"
# ================================================================ diagrama 2: daily, execuția
ch = PChart(candles=daily,
segs=[Seg(0, 19, P6, f"pivot M6 {f2(P6)}", ORANGE),
Seg(20, 39, P7, f"pivot M7 {f2(P7)}", ORANGE),
Seg(20, 39, R1, f"TP1 = R1 M7 {f2(R1)} ({rr(R1):.1f}R)", BLUE),
Seg(20, 39, R2, f"TP2 = R2 M7 {f2(R2)} ({rr(R2):.1f}R)", BLUE)],
seps=[20],
levels=[Level(entry, f"intrare {f2(entry)}", GREEN),
Level(SL, f"SL {f2(SL)} (sub HL)", RED)],
marks=[Mark(4, "vârf raliu", GRAY, "above"),
Mark(HAM, "HL", GREEN, "below", dy=14),
Mark(TRG, "⑥ buy hammer", GREEN, "below", dy=36),
Mark(ENT, "⑦ MBT-10", GREEN, "above", dy=-34),
Mark(tp1_hit, "TP1, SL→BE", BLUE, "above", dy=-16),
Mark(tp2_hit, "TP2", BLUE, "above"),
Mark(t_exit, "trailing", PURPLE, "below", dy=4)],
scale=26, vol_h=70, pad_bot_px=64, title="Exemplu construit (fără ticker) — daily",
caption=f"⑥ trigger pe volum {d_v[TRG] / avg20:.1f}x; ⑦ intrare la close; ⑧ 3 poziții: R1, R2, trailing.",
**STYLE)
svgs["sinteza_daily"] = ch.render()
for p in (R1, R2):
assert abs(px(ch, entry, p) / px(ch, SL, entry) - rr(p)) < 0.01, "R desenat greșit"
# ================================================================ diagrama 3: aceeași zi 13, varianta NU
n_cl = d_cl[:13] + [73.9, # z13: intraday peste high-ul hammer-ului, close înapoi sub
72.7, # z14: close sub pivot și sub HL
73.1, 72.2, 71.5, 71.9, 71.0, 70.6, 71.2, 70.4, 70.9, 70.2, 70.7, 71.3, 70.8, 71.6]
n_w = d_w[:13] + [(1.1, 0.3), (0.3, 0.3)] + [(0.3, 0.3)] * 14
n_v = d_v[:13] + [30, 58, 50, 60, 56, 44, 60, 52, 40, 54, 42, 38, 36, 46, 34, 40]
nd = from_closes(n_cl, wicks=n_w, vols=n_v, start=wk_cl[-1])
S1 = PV[6]["S1"]
assert nd[13].h > ham.h and nd[13].c < ham.h, "z13 trebuie să treacă intraday peste hammer și să închidă sub"
assert n_v[13] < 0.8 * avg20, "z13 trebuie să aibă volum mic"
assert nd[14].c < P6 and nd[14].l < hl, "z14 nu anulează higher low-ul"
low_after = min(c.l for c in nd[14:])
assert abs(low_after - S1) < 0.4, "căderea trebuie să se oprească în zona S1"
print(f"NU z13 high {nd[13].h:.2f} close {nd[13].c:.2f} vol {n_v[13]} ({n_v[13] / avg20:.1f}x); "
f"z14 close {nd[14].c:.2f}; low ulterior {low_after:.2f}; S1 {S1:.2f}")
ch = PChart(candles=nd,
segs=[Seg(0, len(nd) - 1, P6, f"pivot M6 {f2(P6)}", ORANGE),
Seg(0, len(nd) - 1, S1, f"S1 M6 {f2(S1)}", RED)],
levels=[Level(hl, f"HL? {f2(hl)}", GRAY)],
marks=[Mark(4, "vârf raliu", GRAY, "above"),
Mark(13, "fără trigger", RED, "above", dy=-6),
Mark(14, "sub pivot", RED, "below", dy=4)],
scale=26, vol_h=70, pad_bot_px=20, title="Exemplu construit (fără ticker) — daily, varianta NU",
caption=f"Z13 trece intraday peste hammer, închide sub, volum {n_v[13] / avg20:.1f}x: grila se oprește la ⑥.",
**STYLE)
svgs["sinteza_nu"] = ch.render()
# ------------------------------------------------ cifre exportate pentru text (verificate de ochi în .md)
FACTS = dict(varf=VARF, leg1=leg1, leg2=leg2, bdw=weekly[BDW].c, P5=P5, P6=P6, P7=P7, hl=hl, top=top_raliu,
entry=entry, SL=SL, risk=risk, R1=R1, R2=R2, rR1=rr(R1), rR2=rr(R2), trail=trail_at_exit,
exit3=exit3, rexit=rr(exit3), avgR=total_R, volx=d_v[TRG] / avg20, rvarf=rr(VARF),
ham_h=ham.h, n13h=nd[13].h, n13c=nd[13].c, n14c=nd[14].c, S1=S1, n13vx=n_v[13] / avg20,
tp1z=tp1_hit, tp2z=tp2_hit, exz=t_exit)
print({k: round(v, 2) for k, v in FACTS.items()})
# ------------------------------------------------ substituție în .md
if MD.exists():
md = MD.read_text(encoding="utf-8")
for name, svg in svgs.items():
pat = re.compile(rf"<!--SVG:{name}-->.*?<!--/SVG-->", re.S)
assert pat.search(md), f"marcatorul {name} lipsește din .md"
md = pat.sub(lambda _m: f"<!--SVG:{name}-->\n{svg}\n<!--/SVG-->", md)
MD.write_text(md, encoding="utf-8")
print("scris", MD)