Analiza Zilnica 05: volum (11 diagrame cu panou de volum)

Al patrulea document de strategie, pe modelul aprobat al lui 01.

Acoperite: volum "teava" = institutional, acumulare vs. distributie,
divergenta de volum la al doilea picior, volumul care conduce pretul,
breakout fara volum = esec, monthly vs. weekly, alegerea simbolului din
sector dupa volum. Fiecare setup are perechea DA/NU desenata.

Toate cele 11 diagrame sunt serii continue de 36-42 de bare cu panou de
volum, generate din preturi cu svg_candles, cu assert pe fiecare nivel,
pe raportul R si pe raportul de volum afirmat in text.

Verificare in doua runde de screenshot citite efectiv: 6 probleme
reparate, printre care o linie de suport la 12px de eticheta de SL si o
cifra greșita in Gotchas (intrarea era 39.80, nu 38.40). Audit: 0.

Marcat explicit ca adaugire proprie: "volumul conduce pretul" nu e o
regula pe care o spune el, e construita din trei remarci (19.08, 17.08,
04.09); tabelul celor patru stari de acumulare/distributie e
interpretare; diagramele arata volum brut, indicatorul lui nu se poate
desena cu svg_candles.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MtSTyTmt6AbCL9j5ajEDm1
This commit is contained in:
Claude Agent
2026-09-14 05:49:04 +00:00
parent ac4898b8d7
commit 60cd37826a
3 changed files with 4218 additions and 0 deletions

464
gen/az_volum.py Normal file
View File

@@ -0,0 +1,464 @@
"""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 <!--SVG:nume--> ... <!--/SVG--> 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),
Level(tp, f"TP {tp:.2f} = 2R", BLUE)],
marks=[Mark(17, "consolidare, volum mic", GRAY, "below", dy=6),
Mark(B, "breakout", GREEN, "above", dy=-95),
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, f"picior 2: {leg2:.2f}", RED, "below", dy=12),
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.0, # 16-21 breakout, vârf
42.1, 41.3, 41.8, 40.9, 41.5, 42.4] # 22-27 pullback
mo_w = [(0.4, 0.4)] * len(mo_cl)
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, 34, 58, 32, 60, 64]
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)
assert CRIME == 38.40 and PEAK == 43.40, (CRIME, PEAK)
u_m, d_m = up_down_vol(cds, 0, len(cds))
u_pb, d_pb = up_down_vol(cds, 22, len(cds))
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=4),
Mark(13, "breakout cu volum", GREEN, "above", dy=-35),
Mark(24, "pullback, volum mic", GRAY, "above", dy=-20)],
mas=[MA(20, GRAY, dash="4 3", text="SMA-20")],
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
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, "below", dy=12)],
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"<!--SVG:{name}-->", md), f"marcatorul {name} lipsește din .md"
md = re.sub(rf"<!--SVG:{name}-->(?:\n<svg.*?</svg>\n<!--/SVG-->)?",
lambda _m, name=name, svg=svg: f"<!--SVG:{name}-->\n{svg}\n<!--/SVG-->", md, flags=re.S)
MD.write_text(md, encoding="utf-8")
print("scris", MD)

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff