"""Diagramele pentru summaries/08_ANALIZA_ZILNICA/02_mbt10.md. Toate prețurile sunt construite (fără ticker). Rulează: .venv/bin/python gen/az_mbt10.py Înlocuiește în .md fiecare bloc ... cu output-ul generatorului. Ca să schimbi o cifră, o schimbi aici și rulezi din nou. MBT-10 nu e desenat ca indicator (definiția lui tehnică nu apare în material). Diagramele marchează doar bara pe care semnalul s-a închis — sau nu. """ import re import sys 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/02_mbt10.md") GRAY, RED, GREEN, BLUE, ORANGE, PURPLE = "#777", "#c00", "#188a3e", "#1a5fb4", "#b06000", "#6a3d9a" STYLE = dict(gap=15, body_w=9, left=44, width=620) svgs = {} def px_ratio(ch, a, b, c): """raportul (c-b)/(b-a) măsurat în pixeli, după render""" return (ch._y(b) - ch._y(c)) / (ch._y(a) - ch._y(b)) # ---------------------------------------------------------------- 1. MBT-10 închis la close: DA base = [46.8, 47.9, 47.4, 48.6, 49.8, 50.9, 51.8, # 0-6 uptrend, 6 = swing high (viitorul loc al crimei) 51.0, 50.4, 51.2, # 7-9 pullback mic 52.4, 53.6, 54.8, 54.2, 55.6, 56.8, 58.6, # 10-16 breakout peste 6, raliu până la vârf 57.9, 56.9, 56.0, 56.4, 55.1, 54.0, 53.3, # 17-23 pullback spre locul crimei 53.4] # 24 hammer în locul crimei bw = [(0.3, 0.3)] * 7 + [(0.1, 0.3)] + [(0.3, 0.3)] * 8 + [(0.3, 0.3), (0.1, 0.3)] + [(0.3, 0.3)] * 6 + [(0.1, 1.2)] da_cl = base + [54.6, # 25 ziua se ÎNCHIDE în MBT-10: intrare la close 55.5, 55.1, 56.4, 57.5, 58.4, 59.2] da_w = bw + [(0.2, 0.3)] + [(0.3, 0.3)] * 6 cds = from_closes(da_cl, wicks=da_w, start=46.0) SWING, HAM, MBT = 6, 24, 25 crime = cds[SWING].h assert crime == max(c.h for c in cds[:10]), "bara 6 nu e vârful primei zone" assert all(c.c > crime for c in cds[10:24]), "după breakout prețul trebuie să stea deasupra locului crimei" assert abs(cds[HAM].l - crime) <= 0.05, "codița hammer-ului nu atinge locul crimei" assert cds[HAM].o - cds[HAM].l >= 2.5 * abs(cds[HAM].c - cds[HAM].o), "bara 24 nu arată a hammer" top = max(c.h for c in cds[14:18]) entry = cds[MBT].c sl = round(cds[HAM].l - 0.1, 2) risk = entry - sl tp = round(top, 2) R = (tp - entry) / risk hit = next(i for i in range(MBT + 1, len(cds)) if cds[i].h >= tp) assert R >= 1.5, R assert hit == len(cds) - 1 print(f"D1 loc crimă {crime:.2f} hammer low {cds[HAM].l:.2f} entry {entry:.2f} SL {sl:.2f} " f"risc {risk:.2f} TP {tp:.2f} = {R:.2f}R atins la {hit}") DA_ENTRY, DA_CRIME = entry, crime ch = Chart(candles=cds, levels=[Level(crime, f"locul crimei {crime:.2f}", ORANGE, label_at="left"), Level(sl, f"SL {sl:.2f}", RED), Level(entry, f"intrare {entry:.2f}", GREEN), Level(tp, f"TP {tp:.2f} = {R:.1f}R", BLUE)], marks=[Mark(SWING, "fost vârf", ORANGE, "above", dy=-26), Mark(16, "vârf", BLUE, "above", dy=-16), Mark(HAM, "hammer", "#333", "below", dy=18), Mark(MBT, "MBT-10 închis", GREEN, "above", dy=-50)], scale=20, title="Exemplu construit (fără ticker) — daily", caption="Intrarea e la close-ul zilei care s-a închis în MBT-10, nu în timpul zilei.", **{**STYLE, "width": 700}) svg_ = ch.render() assert abs(px_ratio(ch, sl, entry, tp) - R) < 0.01 svgs["mbt_da"] = svg_ # ---------------------------------------------------------------- 2. MBT-10 doar „în formare": NU nu_cl = base + [52.9, # 25 intraday urcă până peste 54.60, închide jos 51.5, 51.9, 50.7, 50.0, 50.4, 49.5] nu_w = bw + [(1.9, 0.2)] + [(0.3, 0.3)] * 6 cds = from_closes(nu_cl, wicks=nu_w, start=46.0) assert cds[MBT].h >= DA_ENTRY, "intraday bara trebuie să fi fost unde DA a închis" assert cds[MBT].c < cds[MBT].o, "bara 25 trebuie să închidă în scădere" assert cds[MBT + 1].c < DA_CRIME, "a doua zi trebuie să închidă sub locul crimei" print(f"D2 bara 25 high {cds[MBT].h:.2f} close {cds[MBT].c:.2f}; bara 26 close {cds[MBT + 1].c:.2f} sub {DA_CRIME:.2f}") ch = Chart(candles=cds, levels=[Level(cds[MBT].h, f"intraday {cds[MBT].h:.2f}", GRAY), Level(DA_CRIME, f"locul crimei {DA_CRIME:.2f}", ORANGE, label_at="left")], marks=[Mark(HAM, "hammer", "#333", "below", dy=18), Mark(MBT, "MBT-10 în formare", GRAY, "above", dy=-22), Mark(MBT + 1, "sub locul crimei", RED, "below", dy=30)], scale=20, title="Exemplu construit (fără ticker) — daily", caption=f"Intraday a fost la {cds[MBT].h:.2f}, dar ziua a închis la {cds[MBT].c:.2f}: semnalul nu a existat.", **{**STYLE, "width": 700}) svgs["mbt_nu"] = ch.render() # ---------------------------------------------------------------- 3. gap up în MBT-10 după downtrend masiv + divergență de volum down = [49.3, 48.2, 48.7, 47.3, 46.0, 46.5, 44.9, 43.6, 44.1, 42.5, 41.2, 41.8, # 0-11 40.1, 38.8, 38.2, 37.4, 38.9, 37.6, # 12-17 36.1, 34.6, 33.4, 32.6, # 18-21 picior 1 33.3, 33.8, 33.4, # 22-24 bounce (neckline) 32.7, 31.9, 31.4, # 25-27 picior 2, mai jos 31.5] # 28 doji dw = [(0.3, 0.3)] * 17 + [(0.1, 0.3)] + [(0.3, 0.3)] * 3 + [(0.2, 0.5)] + [(0.3, 0.3)] * 5 + [(0.2, 0.5), (0.5, 0.5)] dv = [40, 42, 38, 45, 48, 36, 50, 52, 38, 55, 58, 40, 60, 62, 42, 66, 44, 60, 78, 86, 92, 96, 44, 40, 42, 50, 44, 38, 38] part1 = from_closes(down, wicks=dw, vols=dv, start=50.0) GAP = len(part1) gap = Candle(o=33.2, h=34.4, l=33.0, c=34.2, vol=110) # 29: gap up, închis în MBT-10 part2 = from_closes([34.9, 34.6, 35.5, 36.3, 36.0, 36.9, 37.6, 38.3, 39.3], wicks=[(0.3, 0.3)] * 9, vols=[70, 50, 64, 60, 44, 58, 55, 52, 56], start=gap.c) cds = part1 + [gap] + part2 leg1_i = min(range(18, 22), key=lambda i: cds[i].l) leg2_i = min(range(25, 29), key=lambda i: cds[i].l) leg1, leg2 = cds[leg1_i].l, cds[leg2_i].l neck = max(c.h for c in cds[22:25]) assert cds[0].h - leg2 > 17, "downtrend-ul nu e masiv" assert leg2 < leg1, "al doilea picior trebuie să fie mai jos" assert max(dv[25:28]) < min(dv[18:22]) * 0.7, "divergența de volum: vânzarea pe piciorul 2 trebuie să fie clar mai mică" assert gap.l > cds[GAP - 1].h, "bara 29 nu are gap up" assert gap.c > neck, "gap-ul trebuie să închidă peste neckline" assert gap.vol == max(c.vol for c in cds), "gap-ul are cel mai mare volum" entry = gap.c sl = round(gap.l - 0.2, 2) risk = entry - sl lh_i = 16 # ultimul lower high înainte de piciorul 1 tp = round(cds[lh_i].h, 2) assert cds[lh_i].h > cds[lh_i - 1].h and cds[lh_i].h > cds[lh_i + 1].h, "bara 16 nu e swing high" R = (tp - entry) / risk hit = next(i for i in range(GAP + 1, len(cds)) if cds[i].h >= tp) assert R >= 2.5, R print(f"D3 leg1 {leg1:.2f} (v {dv[leg1_i]}) leg2 {leg2:.2f} (v {dv[leg2_i]}) neck {neck:.2f} gap {cds[GAP - 1].h:.2f}->{gap.l:.2f} " f"entry {entry:.2f} SL {sl:.2f} risc {risk:.2f} TP {tp:.2f} = {R:.2f}R atins la {hit}") ch = Chart(candles=cds, levels=[Level(sl, f"SL {sl:.2f} (sub gap)", RED, label_at="left"), Level(entry, f"intrare {entry:.2f}", GREEN, label_at="left"), Level(tp, f"TP {tp:.2f} = {R:.1f}R", BLUE, label_at="left")], marks=[Mark(16, "downtrend masiv", GRAY, "above", dy=-30), Mark(leg1_i, "picior 1", RED, "below"), Mark(leg2_i, "picior 2", RED, "below"), Mark(GAP, "gap up în MBT-10", GREEN, "above", dy=-36)], mas=[MA(20, ORANGE, text="SMA 20")], scale=14, vol_h=70, title="Exemplu construit (fără ticker) — daily", caption="Piciorul 2 e mai jos, dar cu volum de vânzare mai mic: divergență. Gap-ul up e trigger-ul.", **{**STYLE, "width": 680}) svg_ = ch.render() assert abs(px_ratio(ch, sl, entry, tp) - R) < 0.01 svgs["mbt_gap_up"] = svg_ # ---------------------------------------------------------------- 4. MBT-10 pe 60 min, în suport weekly S = 88.00 h60 = [94.6, 94.0, 95.0, 93.5, 92.8, 93.2, 92.1, 91.4, 91.8, 90.7, 90.0, 89.2, # 0-11 coborâre 88.6, # 12 test 1 89.4, 90.3, 89.9, # 13-15 89.1, 88.5, # 16-17 test 2 89.0, 89.5, 89.1, # 18-20 88.7, 88.4, # 21-22 test 3 89.0, # 23 89.9, # 24 MBT-10 închis pe 60 min 90.6, 91.5, 91.1, 92.2, 93.1, 93.7, 94.4, 95.0] w60 = [(0.3, 0.3)] * 2 + [(0.2, 0.3)] + [(0.1, 0.3)] + [(0.3, 0.3)] * 8 + [(0.2, 0.6)] + [(0.3, 0.3)] * 4 + [(0.2, 0.5)] + \ [(0.3, 0.3)] * 4 + [(0.2, 0.4)] + [(0.3, 0.3)] + [(0.2, 0.2)] + [(0.3, 0.3)] * 8 cds = from_closes(h60, wicks=w60, start=95.2) M60 = 24 tests = [i for i in range(10, M60) if abs(cds[i].l - S) <= 0.1] assert len(tests) >= 3, tests assert all(c.l >= S - 0.05 for c in cds[:M60 + 1]), "niciun low nu are voie să spargă suportul weekly" assert cds[M60].c > max(c.h for c in cds[M60 - 5:M60]), "bara MBT-10 nu închide peste ultimele 5 bare" entry = cds[M60].c sl = round(S - 0.7, 2) risk = entry - sl tp = round(cds[2].h, 2) assert cds[2].h > cds[1].h and cds[2].h > cds[3].h R = (tp - entry) / risk hit = next(i for i in range(M60 + 1, len(cds)) if cds[i].h >= tp) assert R >= 2, R print(f"D4 teste suport la {tests}, entry {entry:.2f} SL {sl:.2f} risc {risk:.2f} TP {tp:.2f} = {R:.2f}R atins la {hit}") ch = Chart(candles=cds, levels=[Level(S, f"suport weekly {S:.2f}", ORANGE, label_at="left"), Level(sl, f"SL {sl:.2f}", RED), Level(entry, f"intrare {entry:.2f}", GREEN), Level(tp, f"TP {tp:.2f} = {R:.1f}R", BLUE)], marks=[Mark(tests[0], "test 1", ORANGE, "below", dy=40), Mark(tests[1], "test 2", ORANGE, "below", dy=40), Mark(tests[-1], "test 3", ORANGE, "below", dy=40), Mark(M60, "MBT-10 (60 min)", GREEN, "above", dy=-62)], scale=30, title="Exemplu construit (fără ticker) — 60 min", caption="Suportul weekly ține de trei ori; bara de 60 min închisă în MBT-10 e trigger-ul.", **{**STYLE, "width": 700}) svg_ = ch.render() assert abs(px_ratio(ch, sl, entry, tp) - R) < 0.01 svgs["mbt_60min"] = svg_ # ---------------------------------------------------------------- 5. MBT-10 short pe indice ix = [50.8, 51.9, 51.4, 52.6, 53.7, 53.2, 54.5, 55.6, # 0-7 uptrend 54.3, # 8 swing low (baza) 56.3, 57.2, 56.8, 57.9, 58.7, 59.3, # 9-14 vârf 1 58.4, 57.6, 58.2, 59.0, 59.6, 60.0, # 15-20 vârf 2, mai sus 59.4, 58.1, # 21-22 MBT-10 short închis 57.2, 57.6, 56.4, 55.3, 55.8, 54.6, 53.9, 54.3, 53.4] iw = [(0.3, 0.3)] * 9 + [(0.3, 0.1)] + [(0.3, 0.3)] * 11 + [(0.1, 0.3)] + [(0.3, 0.3)] * 10 cds = from_closes(ix, wicks=iw, start=50.0) T1, T2, SH = 14, 20, 22 assert cds[T2].h > cds[T1].h, "al doilea vârf trebuie să fie mai sus" assert cds[T2].h == max(c.h for c in cds), "bara 20 e maximul seriei" base_low = cds[8].l assert cds[8].l < cds[7].l and cds[8].l < cds[9].l, "bara 8 nu e swing low" entry = cds[SH].c sl = round(cds[T2].h + 0.1, 2) risk = sl - entry tp = round(base_low, 2) R = (entry - tp) / risk hit = next(i for i in range(SH + 1, len(cds)) if cds[i].l <= tp) assert R >= 1.5, R print(f"D5 vârf1 {cds[T1].h:.2f} vârf2 {cds[T2].h:.2f} entry short {entry:.2f} SL {sl:.2f} risc {risk:.2f} " f"TP {tp:.2f} = {R:.2f}R atins la {hit}") ch = Chart(candles=cds, levels=[Level(sl + 1.6, "", "#fff", dash=""), # doar spațiu sub titlu, invizibil Level(sl, f"SL {sl:.2f}", RED), Level(entry, f"intrare short {entry:.2f}", GREEN, label_at="left"), Level(tp, f"TP {tp:.2f} = {R:.1f}R", BLUE, label_at="left")], marks=[Mark(T1, "vârf 1", GRAY, "above", dy=-26), Mark(T2, "vârf 2", GRAY, "above", dy=-26), Mark(SH, "MBT-10 short", RED, "below", dy=22), Mark(8, "bază", BLUE, "below", dy=10)], scale=22, title="Exemplu construit (fără ticker) — daily, indice", caption="MBT-10 short pe indice: nu cumperi acțiuni în corecție; short doar dacă ai regulile lui.", **STYLE) svg_ = ch.render() assert abs(px_ratio(ch, sl, entry, tp) - R) < 0.01 svgs["mbt_short"] = svg_ # ---------------------------------------------------------------- 6. după gap down: primul MBT-10 cade, al doilea merge pre = from_closes([70.4, 70.9, 70.3, 71.0, 70.6, 71.2, 70.8, 71.3, 70.9, 71.1], wicks=[(0.3, 0.3)] * 10, start=70.0) G = len(pre) gd = Candle(o=66.4, h=66.7, l=64.6, c=65.0) # 10: gap down masiv mid = from_closes([64.3, 63.7, # 11-12 64.8, # 13 MBT-10 #1 închis 64.4, 63.3, 62.4, # 14-16 cade sub stop-ul 1 61.0, 61.2, # 17-18 62.2, # 19 MBT-10 #2 închis 64.1, 63.8, 65.0, 66.0, 65.7, 66.9, 67.5, 67.2, 68.4, 69.3, 70.2, 71.0], wicks=[(0.3, 0.3), (0.2, 0.4)] + [(0.2, 0.3)] + [(0.3, 0.3)] * 3 + [(0.2, 0.5), (0.3, 0.3)] + [(0.2, 0.3)] + [(0.3, 0.3)] * 12, start=gd.c) cds = pre + [gd] + mid M1, M2 = 13, 19 assert gd.h < cds[G - 1].l - 3, "gap-ul down nu e masiv" e1 = cds[M1].c s1 = round(min(c.l for c in cds[G:M1 + 1]) - 0.1, 2) stop1 = next(i for i in range(M1 + 1, len(cds)) if cds[i].l <= s1) e2 = cds[M2].c s2 = round(min(c.l for c in cds[M1:M2 + 1]) - 0.1, 2) risk2 = e2 - s2 tp2 = round(cds[G - 1].l, 2) # închiderea gap-ului R2 = (tp2 - e2) / risk2 hit2 = next(i for i in range(M2 + 1, len(cds)) if cds[i].h >= tp2) assert stop1 < M2, "primul trebuie să fie stopat înainte de al doilea semnal" assert all(c.l > s2 for c in cds[M2 + 1:]), "al doilea stop nu are voie să fie atins" assert e2 < e1, "al doilea semnal vine mai jos" assert R2 >= 2, R2 print(f"D6 #1 entry {e1:.2f} SL {s1:.2f} stopat la {stop1}; #2 entry {e2:.2f} SL {s2:.2f} risc {risk2:.2f} " f"TP gap {tp2:.2f} = {R2:.2f}R atins la {hit2}") ch = Chart(candles=cds, levels=[Level(tp2, f"TP {tp2:.2f} = {R2:.1f}R (gap închis)", BLUE), Level(e1, f"#1 intrare {e1:.2f}", GRAY, label_at="left"), Level(s1, f"#1 SL {s1:.2f}", GRAY), Level(e2, f"#2 intrare {e2:.2f}", GREEN, label_at="left"), Level(s2, f"#2 SL {s2:.2f}", RED)], marks=[Mark(G, "gap down", RED, "above", dy=-4), Mark(M1, "MBT-10 #1", GRAY, "above", dy=-30), Mark(stop1, "stop", RED, "below", dy=26), Mark(M2, "MBT-10 #2", GREEN, "below", dy=12)], scale=24, title="Exemplu construit (fără ticker) — daily", caption="Primul MBT-10 după gap down te scoate; al doilea, cu același risc, merge.", **{**STYLE, "width": 760}) svg_ = ch.render() assert abs(px_ratio(ch, s2, e2, tp2) - R2) < 0.01 svgs["mbt_a_doua"] = svg_ # ---------------------------------------------------------------- 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)