Verificare independenta fata de harvest/ si transcrieri, urmata de o
corectura pe care am facut-o eu in sesiunea principala.
Cazul principal, greșit in ambele sensuri: secțiunea "volumul conduce
pretul" era marcata in completari drept construcția autorului, din trei
remarci de recolta. De fapt e regula lui, si e spusa explicit: Q&A
11.06.2025 are o secțiune intreaga cu exact acest titlu, cu regula
formulata de el - volum in breakout peste vârfurile anterioare cu prețul
inca sub ele, "Asta e strategie de sine stătătoare", pe orice timeframe,
aratata live pe un minut. Deci:
- atribuita la Q&A 11.06.2025, cu ancora verificata, si scoasa din
completari;
- formularea tare pastrata, pentru ca sursa o susține la putere plina;
- dar scoase cele doua remarci prost citate, unde verificatorul avea
dreptate: 17.08 e contractie de volum, nu volum care urca, iar la 04.09
el CERE mai mult volum, deci volumul nu iesise primul (si citatul era
modificat, cu conditia MBT-10 tăiată). Rămâne PGNY 19.08, care susține.
- adaugat OBV, din aceeasi secțiune de sursa.
Restul:
- patru parafraze ale recoltei nu mai apar ca citate ale lui; citatul din
10.09 e completat ("deși nu pompeaza bani in compania asta de energie"),
iar cel de la Bloom are forma reala, cu nota ca "Mantri" e probabil
"monthly" transcris greșit;
- "volumul e la cer" era atribuit lui 20.08, unde nu apare deloc; mutat la
03.09;
- "varianta de pe indici" era ilustrata cu doua actiuni, nu cu indici, si
citatul DHI era la el trigger NU. Rescrisa ca regula pe actiuni, cu
citatul exact de la LEU.
Desen:
- svg7: "breakout cu volum" era ancorata la 3 bare de bara de breakout
reala. Mutata pe bara 16, cu assert ca bara etichetata e prima închidere
peste 38.40 si are volumul maxim de pana la ea.
- svg7 fata de svg8: textul pretindea "aceeasi perioada" si acelasi nivel
atins, dar pe monthly pullback-ul rămânea mult deasupra lui 38.40. Acum
pullback-ul monthly se termina cu un hammer care atinge exact 38.40, cu
varful egal cu cel de pe weekly, si assert pe relatia dintre cele doua
diagrame.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MtSTyTmt6AbCL9j5ajEDm1
472 lines
28 KiB
Python
472 lines
28 KiB
Python
"""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, 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"<!--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)
|