"""Diagramele pentru summaries/08_ANALIZA_ZILNICA/05_volum.md. Toate prețurile și volumele sunt construite (fără ticker). Rulează: .venv/bin/python gen/az_volum.py Înlocuiește în .md fiecare bloc ... cu output-ul generatorului. Ca să schimbi o cifră, o schimbi aici și rulezi din nou. """ import re import sys from pathlib import Path sys.path.insert(0, "/workspace/atm-curs-dl") from svg_candles import Chart as _Chart, Level, Mark, MA, from_closes # noqa: E402 def Chart(**kw): """lățimea crește cu numărul de bare și lasă o margine liberă în dreapta, ca etichetele nivelurilor și SMA-20 să nu stea peste ultimele candele""" kw["width"] = kw["left"] + (len(kw["candles"]) - 1) * kw["gap"] + 150 return _Chart(**kw) MD = Path("/workspace/atm-curs-dl/summaries/08_ANALIZA_ZILNICA/05_volum.md") GRAY, RED, GREEN, BLUE, ORANGE = "#777", "#c00", "#188a3e", "#1a5fb4", "#b06000" STYLE = dict(gap=15, body_w=9, left=44, width=620, vol_h=70) svgs = {} def avg(xs): xs = list(xs) return sum(xs) / len(xs) 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)) def up_down_vol(cds, a, b): """volumul mediu pe barele de cumpărători vs. pe barele de vânzători, în [a, b)""" up = [c.vol for c in cds[a:b] if c.c >= c.o] dn = [c.vol for c in cds[a:b] if c.c < c.o] return avg(up), avg(dn) # ---------------------------------------------------------------- 1. breakout cu volum țeavă (DA) base_cl = [44.0, 44.8, 44.5, 45.6, 46.4, 46.1, 47.3, 48.2, 47.9, 49.3, # 0-9 uptrend 49.0, 48.2, 48.6, 47.9, 48.8, 49.5, 49.1, 48.3, 48.0, 48.7, # 10-25 consolidare 49.4, 49.0, 48.5, 49.2, 49.7, 49.6] base_w = [(0.3, 0.3)] * 26 base_v = [52, 55, 48, 58, 60, 50, 62, 64, 50, 60, 42, 46, 40, 44, 41, 40, 38, 42, 40, 39, 41, 38, 40, 42, 40, 39] B = 26 da_cl = base_cl + [52.6, # 26 breakout marubozu 53.2, 52.8, 51.9, 51.4, # 27-30 pullback; 30 atinge 1/2 marubozu 52.3, 53.1, 53.8, 53.5, 54.3, 54.9, 54.6] da_w = base_w + [(0.1, 0.1), (0.3, 0.3), (0.3, 0.3), (0.3, 0.3), (0.3, 0.3)] + [(0.3, 0.3)] * 7 da_v = base_v + [150, 72, 58, 36, 33, 52, 60, 58, 45, 62, 55, 48] cds = from_closes(da_cl, wicks=da_w, vols=da_v, start=43.6) RES = round(max(c.h for c in cds[7:B]), 2) RLO = round(min(c.l for c in cds[10:B]), 2) assert RES == 50.00 and RLO == 47.60, (RES, RLO) assert all(c.c < RES for c in cds[:B]) and cds[B].c > RES vavg = avg(da_v[B - 20:B]) assert da_v[B] >= 3 * vavg, "textul spune: volumul barei de breakout e peste 3x media ultimelor 20" half = round((cds[B].o + cds[B].c) / 2, 2) assert half == 51.10, half PB = 30 assert abs(cds[PB].l - half) < 0.001 and all(c.l > half for c in cds[B + 1:PB]) assert avg(da_v[B + 3:PB + 1]) < vavg, "pullback-ul trebuie să aibă volum sub medie" entry = half sl = round(cds[B].o - 0.2, 2) risk = entry - sl tp = round(entry + 2 * risk, 2) hit = next(i for i in range(PB + 1, len(cds)) if cds[i].h >= tp) assert sl == 49.40 and tp == 54.50 and hit == 35, (sl, tp, hit) print(f"D1 rez {RES} breakout close {cds[B].c} vol {da_v[B]} = {da_v[B] / vavg:.1f}x media; 1/2 {half}; " f"SL {sl} risc {risk:.2f} TP {tp} atins la {hit}") ch = Chart(candles=cds, levels=[Level(RES, f"rezistență {RES:.2f}", ORANGE, label_at="left"), Level(sl, f"SL {sl:.2f}", RED), Level(entry, f"intrare {entry:.2f} (1/2 marubozu)", GREEN, label_at="left"), Level(tp, f"TP {tp:.2f} = 2R", BLUE)], marks=[Mark(17, "consolidare, volum mic", GRAY, "below", dy=6), Mark(B, "breakout", GREEN, "above", dy=-35), Mark(PB, "retest", GREEN, "below", dy=4)], mas=[MA(20, ORANGE, dash="4 3", text="SMA-20")], scale=40, title="Exemplu construit (fără ticker) — daily", caption=f"Bara de breakout: volum {da_v[B] / vavg:.1f}x media ultimelor 20 de bare. Intri la retest, nu pe ea.", **STYLE) svg_ = ch.render() assert abs(px_ratio(ch, sl, entry, tp) - 2.0) < 0.01 svgs["teava_da"] = svg_ # ---------------------------------------------------------------- 2. breakout fără volum (NU) nu_cl = base_cl + [50.6, # 26 breakout fără volum 50.3, 49.3, 48.4, 47.8, 47.1, # 27-31 înapoi în range, apoi sub el 47.4, 46.6, 46.0, 46.5, 45.8, 45.3] nu_w = base_w + [(0.2, 0.2), (0.3, 0.2)] + [(0.3, 0.3)] * 10 nu_v = base_v + [37, 34, 55, 60, 64, 72, 45, 66, 62, 44, 60, 58] cds = from_closes(nu_cl, wicks=nu_w, vols=nu_v, start=43.6) assert round(max(c.h for c in cds[7:B]), 2) == RES assert cds[B].c > RES and nu_v[B] < avg(nu_v[B - 20:B]), "breakout-ul NU trebuie să aibă volum sub medie" back = next(i for i in range(B + 1, len(cds)) if cds[i].c < RES) assert back == B + 2, back sl_nu = 47.40 stop = next(i for i in range(B + 1, len(cds)) if cds[i].l <= sl_nu) assert sl_nu < RLO and cds[stop].c < RLO assert avg(nu_v[back:stop + 1]) > avg(nu_v[B:back]) * 1.5, "la cădere volumul trebuie să crească" print(f"D2 breakout close {cds[B].c} vol {nu_v[B]} ({nu_v[B] / avg(nu_v[B - 20:B]):.2f}x media); " f"înapoi sub rez la {back}; SL {sl_nu} lovit la {stop}") ch = Chart(candles=cds, levels=[Level(RES, f"rezistență {RES:.2f}", ORANGE, label_at="left"), Level(sl_nu, f"SL {sl_nu:.2f} lovit", RED)], marks=[Mark(B, "breakout fără volum", RED, "above", dy=-4), Mark(stop, "stop", RED, "below", dy=2)], mas=[MA(20, ORANGE, dash="4 3", text="SMA-20")], scale=40, title="Exemplu construit (fără ticker) — daily", caption=f"Aceeași bază. Bara de breakout are volum sub media ultimelor 20: nimeni mare nu cumpără.", **STYLE) svgs["teava_nu"] = ch.render() # ---------------------------------------------------------------- 3. acumulare la bottom (weekly) ac_cl = [38.0, 37.1, 37.5, 36.2, 35.3, 35.7, 34.4, 33.5, 33.9, 32.6, 31.8, 31.2, # 0-11 downtrend 31.9, 31.4, 32.3, 31.7, 31.2, 32.0, 32.6, 32.1, 31.6, 32.4, 32.9, # 12-27 bază 32.4, 32.0, 32.8, 33.1, 32.7, 34.6, 35.3, 35.0, 36.1, 36.8, 36.4, 37.5] # 28-34 breakout ac_w = [(0.3, 0.3)] * len(ac_cl) ac_v = [50, 62, 40, 64, 66, 42, 68, 70, 44, 72, 74, 76, 58, 30, 62, 28, 32, 64, 66, 30, 28, 68, 70, 29, 31, 72, 74, 30, 130, 80, 50, 82, 78, 48, 80] cds = from_closes(ac_cl, wicks=ac_w, vols=ac_v, start=38.6) BA = 28 AC_RES = round(max(c.h for c in cds[12:BA]), 2) AC_LO = round(min(c.l for c in cds[12:BA]), 2) u_dt, d_dt = up_down_vol(cds, 0, 12) u_b, d_b = up_down_vol(cds, 12, BA) assert d_dt > u_dt * 1.3, "în downtrend volumul vânzătorilor trebuie să domine" assert u_b >= d_b * 2.0, "textul spune: în bază volumul cumpărătorilor e de peste 2x cel al vânzătorilor" assert all(c.c < AC_RES for c in cds[12:BA]) and cds[BA].c > AC_RES assert ac_v[BA] >= 2.5 * avg(ac_v[BA - 20:BA]) print(f"D3 bază {AC_LO}-{AC_RES}; în bază vol cumpărători {u_b:.0f} vs vânzători {d_b:.0f} = {u_b / d_b:.1f}x; " f"breakout {cds[BA].c} vol {ac_v[BA] / avg(ac_v[BA - 20:BA]):.1f}x") ch = Chart(candles=cds, levels=[Level(AC_RES, f"top bază {AC_RES:.2f}", ORANGE, label_at="left"), Level(AC_LO, f"low bază {AC_LO:.2f}", GRAY)], marks=[Mark(0, "volum pe vânzători", RED, "below", dy=139), Mark(20, "acumulare", GREEN, "below", dy=8), Mark(BA, "breakout", GREEN, "above", dy=-4)], mas=[MA(20, ORANGE, dash="4 3", text="SMA-20")], scale=26, title="Exemplu construit (fără ticker) — weekly", caption=f"În bază: barele de cumpărători au volum mediu {u_b / d_b:.1f}x față de barele de vânzători.", **STYLE) svgs["acumulare"] = ch.render() # ---------------------------------------------------------------- 4. distribuție la top (weekly, short) di_cl = [62.0, 62.9, 62.5, 63.8, 64.7, 64.3, 65.6, 66.5, 66.1, 67.4, 68.2, 68.8, # 0-11 uptrend 68.1, 68.6, 67.7, 68.3, 68.8, 68.0, 67.4, 67.9, 68.4, 67.6, 67.1, # 12-27 top lateral 67.6, 68.0, 67.2, 66.9, 67.3, 65.3, # 28 breakdown 64.8, 65.9, 64.2, # 29-31 retest de jos 63.1, 62.4, 62.8, 61.5, 60.6, 60.0, 59.2, 58.8] di_w = [(0.3, 0.3)] * 30 + [(0.7, 0.3), (0.3, 0.3), (0.0, 0.3)] + [(0.3, 0.3)] * 7 di_v = [62, 64, 40, 66, 68, 42, 70, 72, 44, 74, 76, 78, 66, 30, 68, 28, 30, 70, 72, 29, 28, 70, 74, 30, 28, 72, 70, 30, 135, 60, 34, 90, 80, 76, 40, 82, 78, 74, 80, 70] cds = from_closes(di_cl, wicks=di_w, vols=di_v, start=61.4) BD = 28 DI_HI = round(max(c.h for c in cds[12:BD]), 2) DI_LO = round(min(c.l for c in cds[12:BD]), 2) u_up, d_up = up_down_vol(cds, 0, 12) u_t, d_t = up_down_vol(cds, 12, BD) assert u_up > d_up * 1.3, "în uptrend volumul cumpărătorilor trebuie să domine" assert d_t >= u_t * 2.0, "textul spune: la top volumul vânzătorilor e de peste 2x cel al cumpărătorilor" assert all(c.c > DI_LO for c in cds[12:BD]) and cds[BD].c < DI_LO assert di_v[BD] >= 2.5 * avg(di_v[BD - 20:BD]) RT = 30 # retest de jos al fostului suport assert abs(cds[RT].h - DI_LO) <= 0.05 and cds[RT].c < DI_LO, "retestul nu atinge fostul suport" assert di_v[RT] < avg(di_v[BD - 20:BD]), "retestul trebuie să aibă volum mic" CF = 31 assert cds[CF].c < cds[RT].l entry_s = cds[CF].c sl_s = round(cds[RT].h + 0.2, 2) risk_s = sl_s - entry_s tp_s = round(entry_s - 2 * risk_s, 2) hit_s = next(i for i in range(CF + 1, len(cds)) if cds[i].l <= tp_s) print(f"D4 top {DI_LO}-{DI_HI}; la top vol vânzători {d_t:.0f} vs cumpărători {u_t:.0f} = {d_t / u_t:.1f}x; " f"retest high {cds[RT].h:.2f}; short {entry_s} SL {sl_s} risc {risk_s:.2f} TP {tp_s} atins la {hit_s}") ch = Chart(candles=cds, levels=[Level(sl_s, f"SL {sl_s:.2f}", RED), Level(entry_s, f"intrare short {entry_s:.2f}", GREEN), Level(tp_s, f"TP {tp_s:.2f} = 2R", BLUE, label_at="left")], marks=[Mark(0, "uptrend: volum pe cumpărători", GREEN, "below", dy=-6), Mark(20, "distribuție", RED, "above", dy=-6), Mark(BD, "breakdown", RED, "below", dy=30), Mark(RT, "retest", ORANGE, "above", dy=-50), Mark(hit_s, "TP", BLUE, "below")], scale=22, title="Exemplu construit (fără ticker) — weekly", caption=f"La top: barele de vânzători au volum mediu {d_t / u_t:.1f}x față de cele de cumpărători.", **STYLE) svg_ = ch.render() assert abs(px_ratio(ch, sl_s, entry_s, tp_s) - 2.0) < 0.01 svgs["distributie"] = svg_ # ---------------------------------------------------------------- 5. divergență de volum la al doilea picior (DA) dv_head = [47.0, 46.2, 46.6, 45.5, 44.6, 45.0, 43.9, 43.0, 43.4, 42.2, 41.3, # 0-10 downtrend 40.4, # 11 picior 1 41.2, 42.1, 41.8, 42.9, 43.3, # 12-16 revenire (neckline) 42.5, 41.6, 40.9, 40.1] # 17-20 coborâre spre picior 2 dv_head_w = [(0.3, 0.3)] * 11 + [(0.3, 0.6)] + [(0.3, 0.3)] * 9 dv_head_v = [50, 60, 44, 62, 64, 42, 66, 68, 44, 70, 76, 95, 60, 58, 40, 56, 52, 44, 50, 52, 56] da2_cl = dv_head + [40.0, # 21 doji (picior 2, mai jos) 41.1, # 22 buy doji 41.6, 41.4, 42.3, 43.1, 43.6, 43.3, 44.2, 44.7, 45.3, 45.0, 45.9, 46.6] da2_w = dv_head_w + [(0.5, 0.9), (0.2, 0.2)] + [(0.3, 0.3)] * 12 da2_v = dv_head_v + [48, 80, 66, 44, 72, 76, 70, 45, 74, 70, 72, 46, 74, 70] cds = from_closes(da2_cl, wicks=da2_w, vols=da2_v, start=47.6) L1, NK, L2, T = 11, 16, 21, 22 leg1, leg2 = cds[L1].l, cds[L2].l neck = round(max(c.h for c in cds[L1:L2]), 2) assert leg1 == min(c.l for c in cds[:L2]) and leg2 == min(c.l for c in cds), "picioarele nu sunt minimele" assert leg2 < leg1, "al doilea picior trebuie să fie mai jos" v1, v2 = max(da2_v[L1 - 2:L1 + 1]), max(da2_v[L2 - 3:L2 + 1]) assert v2 <= 0.6 * v1, "textul spune: la al doilea picior volumul maxim e sub 60% din primul" assert abs(cds[L2].o - cds[L2].c) <= 0.15 and cds[T].c > cds[L2].h, "buy doji-ul nu închide peste high-ul doji-ului" entry = cds[T].c sl = round(leg2 - 0.1, 2) risk = entry - sl tp = round(entry + 2 * risk, 2) hit = next(i for i in range(T + 1, len(cds)) if cds[i].h >= tp) assert tp > neck, "ținta de 2R trebuie să fie peste neckline" print(f"D5 picior1 {leg1:.2f} vol {v1}; picior2 {leg2:.2f} vol {v2} ({v2 / v1:.0%}); neck {neck}; " f"intrare {entry} SL {sl} risc {risk:.2f} TP {tp} atins la {hit}") ch = Chart(candles=cds, levels=[Level(neck, f"neckline {neck:.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} = 2R", BLUE)], marks=[Mark(L1, f"picior 1: {leg1:.2f}", RED, "below", dy=-2), Mark(L2, "picior 2", RED, "above", dy=-22), Mark(T, "buy doji", GREEN, "above", dy=-30)], mas=[MA(20, GRAY, dash="4 3", text="SMA-20")], scale=34, title="Exemplu construit (fără ticker) — daily", caption=f"Prețul face minim nou, volumul vânzătorilor nu: la picior 2 e {v2 / v1:.0%} din picior 1.", **STYLE) svg_ = ch.render() assert abs(px_ratio(ch, sl, entry, tp) - 2.0) < 0.01 svgs["divergenta_da"] = svg_ # ---------------------------------------------------------------- 6. fără divergență: bear flag (NU) nu2_cl = dv_head + [40.0, # 21 doji (picior 2, mai jos) 41.1, # 22 buy doji fără volum 41.0, 41.4, 41.2, 41.5, # 23-26 bear flag 40.2, 39.1, 38.4, 38.8, 37.9, 37.2, 37.6, 36.8] nu2_w = dv_head_w + [(0.5, 0.9), (0.2, 0.2)] + [(0.3, 0.3)] * 12 nu2_v = dv_head_v[:17] + [60, 78, 90, 104] + [118, 40, 30, 36, 32, 34, 112, 120, 98, 50, 104, 96, 48, 90] cds = from_closes(nu2_cl, wicks=nu2_w, vols=nu2_v, start=47.6) leg2n = cds[L2].l assert leg2n < leg1 and cds[T].c > cds[L2].h v1n, v2n = max(nu2_v[L1 - 2:L1 + 1]), max(nu2_v[L2 - 3:L2 + 1]) assert v2n >= 1.2 * v1n, "pe NU volumul la picior 2 trebuie să fie mai mare decât la picior 1" assert nu2_v[T] < avg(nu2_v[T - 20:T]), "buy doji-ul NU are volum sub medie" sl_n = round(leg2n - 0.1, 2) stop_n = next(i for i in range(T + 1, len(cds)) if cds[i].l <= sl_n) assert all(c.h < neck for c in cds[T:stop_n]) and nu2_v[stop_n] >= 3 * avg(nu2_v[T:T + 5]) print(f"D6 picior2 vol {v2n} = {v2n / v1n:.1f}x picior1; buy doji vol {nu2_v[T]}; SL {sl_n} lovit la {stop_n}") ch = Chart(candles=cds, levels=[Level(neck, f"neckline {neck:.2f}", ORANGE, label_at="left"), Level(sl_n, f"SL {sl_n:.2f} lovit", RED)], marks=[Mark(L1, "picior 1", RED, "below", dy=-2), Mark(L2, "picior 2: vânzare mai mare", RED, "below", dy=12), Mark(24, "bear flag", GRAY, "above", dy=-30), Mark(stop_n, "stop", RED, "below", dy=2)], mas=[MA(20, GRAY, dash="4 3", text="SMA-20")], scale=34, title="Exemplu construit (fără ticker) — daily", caption=f"Același minim nou, dar volumul vânzătorilor crește ({v2n / v1n:.1f}x). Buy doji-ul fără volum cade.", **STYLE) svgs["divergenta_nu"] = ch.render() # ---------------------------------------------------------------- 7. volumul iese primul (weekly) vc_cl = [25.0, 24.3, 24.6, 23.7, 23.0, 23.3, 22.4, 21.6, # 0-7 downtrend 21.0, 21.6, 21.2, 20.6, 21.3, 21.8, 21.1, 20.7, 21.4, 21.0, # 8-17 bază, volum mort 21.5, 21.2, 21.7, 21.4, 21.8, 21.5, 21.9, 21.6, 21.7, 21.5, 21.9, # 18-28 bază, volumul crește 23.1, # 29 breakout de preț 23.6, 23.4, 24.2, 24.8, 24.5, 25.3, 25.1, 25.9, 26.6, 26.3, 27.1, 27.6] vc_w = [(0.3, 0.3)] * len(vc_cl) vc_v = [40, 46, 30, 48, 50, 30, 52, 54, 30, 28, 26, 30, 27, 26, 28, 29, 26, 27, 44, 26, 52, 28, 58, 30, 64, 30, 66, 29, 72, 110, 80, 50, 84, 82, 52, 86, 50, 84, 82, 48, 86, 84] cds = from_closes(vc_cl, wicks=vc_w, vols=vc_v, start=25.6) BP = 29 VC_RES = round(max(c.h for c in cds[8:BP]), 2) dead_max = max(vc_v[8:18]) vfirst = next(i for i in range(18, BP) if vc_v[i] > dead_max * 1.5) assert VC_RES == 22.20 and all(c.c < VC_RES for c in cds[8:BP]) and cds[BP].c > VC_RES, VC_RES assert BP - vfirst >= 8, "volumul trebuie să iasă cu cel puțin 8 bare înaintea prețului" ups = [vc_v[i] for i in range(18, BP) if cds[i].c >= cds[i].o] assert ups == sorted(ups) and ups[-1] >= 2.4 * dead_max, "volumul cumpărătorilor în bază trebuie să urce treaptă cu treaptă" hl = round(min(c.l for c in cds[22:BP]), 2) assert hl > round(min(c.l for c in cds[8:18]), 2), "low-urile din a doua parte a bazei trebuie să fie mai sus" entry = cds[BP].c sl = round(hl - 0.1, 2) risk = entry - sl tp = round(entry + 2 * risk, 2) hit = next(i for i in range(BP + 1, len(cds)) if cds[i].h >= tp) print(f"D7 rez {VC_RES}; volum mort max {dead_max}; volumul iese la {vfirst} (vol {vc_v[vfirst]}), prețul la {BP}; " f"ultimul vol cumpărători în bază {ups[-1]} = {ups[-1] / dead_max:.1f}x; intrare {entry} SL {sl} " f"risc {risk:.2f} TP {tp} atins la {hit}") ch = Chart(candles=cds, levels=[Level(VC_RES, f"rezistență {VC_RES:.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} = 2R", BLUE)], marks=[Mark(12, "volum mort", GRAY, "below", dy=40), Mark(vfirst + 4, "volumul urcă", GREEN, "above", dy=-50), Mark(BP, "prețul iese", GREEN, "above", dy=-4)], mas=[MA(20, GRAY, dash="4 3", text="SMA-20")], scale=34, title="Exemplu construit (fără ticker) — weekly", caption=f"Volumul cumpărătorilor urcă {BP - vfirst} săptămâni sub rezistență, înainte ca prețul să iasă.", **STYLE) svg_ = ch.render() assert abs(px_ratio(ch, sl, entry, tp) - 2.0) < 0.01 svgs["volum_conduce"] = svg_ # ---------------------------------------------------------------- 8. monthly: contextul (volum pozitiv) mo_cl = [30.0, 31.2, 30.6, 32.1, 33.4, 32.8, 34.5, 35.6, 35.0, 36.8, 38.0, # 0-10 uptrend 1 37.2, 36.5, 37.0, 36.1, 36.8, # 11-15 consolidare 38.4, 39.6, 40.9, 40.2, 41.8, 43.1, # 16-21 breakout, vârf 42.1, # 22 pullback 42.3] # 23 hammer: codița atinge locul crimei mo_w = [(0.4, 0.4)] * 23 + [(0.2, 3.7)] mo_v = [60, 70, 40, 72, 76, 42, 78, 80, 44, 82, 84, 40, 38, 50, 36, 48, 96, 88, 90, 44, 92, 94, 36, 58] cds = from_closes(mo_cl, wicks=mo_w, vols=mo_v, start=29.4) CRIME = round(max(c.h for c in cds[8:16]), 2) PEAK = round(max(c.h for c in cds), 2) MBO = 16 assert CRIME == 38.40 and PEAK == 43.50, (CRIME, PEAK) assert next(i for i, c in enumerate(cds) if c.c >= CRIME) == MBO and mo_v[MBO] == max(mo_v[:MBO + 1]), \ "eticheta «breakout cu volum» trebuie să stea pe prima închidere peste 38.40, cu volumul maxim de până atunci" MO_PB_LOW = round(min(c.l for c in cds[22:]), 2) u_m, d_m = up_down_vol(cds, 0, len(cds)) d_pb = avg(c.vol for c in cds[22:] if c.c < c.o) assert u_m >= 1.6 * d_m, "pe monthly volumul cumpărătorilor trebuie să domine" assert d_pb < avg(mo_v[16:22]) * 0.5, "pullback-ul monthly are volum de vânzare mic" print(f"D8 monthly: vol cumpărători {u_m:.0f} vs vânzători {d_m:.0f} = {u_m / d_m:.1f}x; locul crimei {CRIME}; vârf {PEAK}") ch = Chart(candles=cds, levels=[Level(CRIME, f"fost vârf {CRIME:.2f}", ORANGE, label_at="left")], marks=[Mark(12, "consolidare", GRAY, "below", dy=24), Mark(MBO, "breakout cu volum", GREEN, "below", dy=6), Mark(23, "hammer", GREEN, "below", dy=0)], scale=14, title="Exemplu construit (fără ticker) — monthly", caption=f"Monthly: barele de cumpărători au volum mediu {u_m / d_m:.1f}x față de barele de vânzători.", **STYLE) svgs["monthly"] = ch.render() # ---------------------------------------------------------------- 9. weekly: volumul deteriorat, retestul locului crimei wk_cl = [36.2, 36.8, 36.4, 37.1, 36.7, 37.3, 37.9, 38.1, # 0-7 sub fostul vârf 39.5, 40.4, 40.0, 41.2, 41.9, 41.5, 42.4, 43.0, 42.6, 43.2, # 8-17 raliu 42.4, 41.7, 41.0, 40.4, 39.8, 39.4, 39.3, # 18-24 pullback 39.5, # 25 hammer în locul crimei 39.8, # 26 confirmare 40.5, 41.2, 40.9, 41.8, 42.5, 42.2, 43.1, 43.6] wk_w = [(0.3, 0.3)] * 25 + [(0.1, 0.9), (0.1, 0.2)] + [(0.3, 0.3)] * 8 wk_v = [40, 44, 34, 46, 32, 44, 48, 50, 90, 84, 50, 88, 86, 48, 82, 80, 46, 78, 44, 42, 40, 38, 36, 34, 32, 58, 70, 72, 76, 44, 74, 78, 46, 76, 80] cds = from_closes(wk_cl, wicks=wk_w, vols=wk_v, start=36.6) HM, CFW = 25, 26 assert abs(cds[HM].l - CRIME) < 0.001, "codița hammer-ului trebuie să atingă exact locul crimei" assert cds[HM].l == min(c.l for c in cds[18:]) and round(max(c.h for c in cds[:8]), 2) == CRIME, "hammer-ul e low-ul pullback-ului" rally_v, pb_v = avg(wk_v[8:18]), avg(wk_v[18:25]) assert pb_v <= 0.6 * rally_v, "textul spune: volumul pe pullback e sub 60% din cel de pe raliu" assert pb_v > 0, "volumul rămâne (pozitiv)" assert cds[CFW].c > cds[HM].h top = round(max(c.h for c in cds[8:18]), 2) entry = cds[CFW].c sl = round(cds[HM].l - 0.3, 2) risk = entry - sl R = (top - entry) / risk hit = next(i for i in range(CFW + 1, len(cds)) if cds[i].h >= top) assert R >= 2.0, R assert MO_PB_LOW == round(cds[HM].l, 2) == CRIME and top == PEAK, \ "monthly și weekly trebuie să spună același lucru: pullback până la 38.40, vârf la același preț" print(f"D9 weekly: raliu vol {rally_v:.0f}, pullback vol {pb_v:.0f} ({pb_v / rally_v:.0%}); hammer low {cds[HM].l:.2f}; " f"intrare {entry} SL {sl} risc {risk:.2f} TP {top} = {R:.2f}R atins la {hit}") 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(top, f"TP {top:.2f} = {R:.1f}R", BLUE)], marks=[Mark(24, "volum în scădere", GRAY, "above", dy=-100), Mark(HM, "hammer", GREEN, "above", dy=-20)], mas=[MA(20, GRAY, dash="4 3", text="SMA-20")], scale=60, title="Exemplu construit (fără ticker) — weekly", caption="Weekly-ul arată vânzare, dar pe volum mic: e retestul, nu ieșirea instituționalilor.", **STYLE) svg_ = ch.render() assert abs(px_ratio(ch, sl, entry, top) - R) < 0.01 svgs["weekly_retest"] = svg_ # ---------------------------------------------------------------- 10-11. doi candidați din același sector sec_cl = [58.0, 57.1, 57.6, 56.2, 55.3, 55.8, 54.4, 53.5, 54.0, 52.6, 51.6, # 0-10 downtrend 50.6, # 11 picior 1 51.5, 52.4, 52.0, 53.1, 52.6, # 12-16 revenire 51.8, 51.0, 50.3, # 17-19 coborâre 50.2, # 20 picior 2 (mai jos) 51.2, 51.0, 51.9, 51.6, 52.3] # 21-25 până la decizie DEC = len(sec_cl) - 1 # 25 bara de decizie SUP = 50.00 sec_w = [(0.3, 0.3)] * 11 + [(0.3, 0.6)] + [(0.3, 0.3)] * 8 + [(0.3, 0.5)] + [(0.3, 0.3)] * 5 a_cl = sec_cl + [53.4, 54.1, 53.8, 54.9, 55.6, 55.2, 56.4, 57.1, 57.8, 58.6] b_cl = sec_cl + [51.8, 51.1, 51.6, 50.7, 49.9, 50.3, 49.4, 48.8, 49.2, 48.5] tail_w = [(0.3, 0.3)] * 10 a_v = [50, 60, 42, 62, 64, 42, 66, 68, 44, 70, 74, 90, 58, 60, 32, 56, 30, 30, 34, 36, 38, 66, 30, 70, 28, 74] + [86, 80, 44, 84, 82, 46, 84, 80, 78, 80] b_v = [50, 60, 42, 62, 64, 42, 66, 68, 44, 70, 74, 90, 40, 38, 58, 36, 60, 64, 70, 80, 98, 40, 62, 38, 60, 36] + [72, 76, 40, 80, 84, 42, 86, 88, 40, 90] ca = from_closes(a_cl, wicks=sec_w + tail_w, vols=a_v, start=58.6) cb = from_closes(b_cl, wicks=sec_w + tail_w, vols=b_v, start=58.6) assert [(c.o, c.h, c.l, c.c) for c in ca[:DEC + 1]] == [(c.o, c.h, c.l, c.c) for c in cb[:DEC + 1]], \ "până la decizie prețul trebuie să fie identic" p1, p2 = ca[11].l, ca[20].l assert p2 < p1 and abs(p2 - SUP) < 0.35 and abs(p1 - SUP) < 0.35, (p1, p2) ua, da_ = up_down_vol(ca, 12, DEC + 1) ub, db = up_down_vol(cb, 12, DEC + 1) assert ua >= 1.8 * da_ and db >= 1.8 * ub, "A = acumulare, B = distribuție, după picior 1" assert a_v[20] <= 0.6 * a_v[11] and b_v[20] > b_v[11], "A are divergență la picior 2, B nu" entry = ca[DEC].c sl = round(p2 - 0.4, 2) risk = entry - sl tp = round(entry + 2 * risk, 2) hit = next(i for i in range(DEC + 1, len(ca)) if ca[i].h >= tp) stop_b = next(i for i in range(DEC + 1, len(cb)) if cb[i].l <= sl) print(f"D10/11 picior1 {p1:.2f} picior2 {p2:.2f}; A: cumpărători {ua:.0f} vs vânzători {da_:.0f} ({ua / da_:.1f}x), " f"picior2 vol {a_v[20] / a_v[11]:.0%}; B: vânzători {db:.0f} vs cumpărători {ub:.0f} ({db / ub:.1f}x); " f"intrare A {entry} SL {sl} risc {risk:.2f} TP {tp} atins la {hit}; B ar fi lovit SL la {stop_b}") for key, cds, vtxt, extra_marks, cap in ( ("sector_a", ca, "acumulare", [], f"Candidatul A: după picior 1, volumul cumpărătorilor e {ua / da_:.1f}x cel al vânzătorilor."), ("sector_b", cb, "distribuție", [Mark(stop_b, "ar fi lovit SL", RED, "above", dy=-30)], f"Candidatul B, același preț: volumul vânzătorilor e {db / ub:.1f}x cel al cumpărătorilor.")): ch = Chart(candles=cds, levels=[Level(SUP, f"suport de an {SUP:.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} = 2R", BLUE)], marks=[Mark(11, "picior 1", RED, "below", dy=14), Mark(20, "picior 2", RED, "below", dy=14), Mark(DEC, "decizia", GRAY, "above", dy=-30), Mark(16, vtxt, GREEN if key == "sector_a" else RED, "above", dy=-24)] + extra_marks, scale=34, title=f"Exemplu construit (fără ticker) — weekly, candidat {key[-1].upper()}", caption=cap, **STYLE) svg_ = ch.render() assert abs(px_ratio(ch, sl, entry, tp) - 2.0) < 0.01 svgs[key] = svg_ # ---------------------------------------------------------------- substituție în .md if MD.exists(): md = MD.read_text(encoding="utf-8") for name, svg in svgs.items(): assert re.search(rf"", md), f"marcatorul {name} lipsește din .md" md = re.sub(rf"(?:\n\n)?", lambda _m, name=name, svg=svg: f"\n{svg}\n", md, flags=re.S) MD.write_text(md, encoding="utf-8") print("scris", MD)