"""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".*?", re.S) assert pat.search(md), f"marcatorul {name} lipsește din .md" md = pat.sub(lambda _m: f"\n{svg}\n", md) MD.write_text(md, encoding="utf-8") print("scris", MD)