Verificarea pe diff nu vedea decat ce s-a schimbat azi; cea integrala a
citit fiecare citat din document fata de transcriere. Pe 02: o parafraza
pusa in ghilimele ('macar revenirea la medie'), o cifra inventata
('15 cu 10'), si doua deductii date ca reguli ale lui. Eticheta din
diagrama care repeta regula operationala s-a reparat in gen/az_mbt10.py.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ThN7ixZrowca8aTGrK1MUJ
289 lines
16 KiB
Python
289 lines
16 KiB
Python
"""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 <!--SVG:nume--> ... <!--/SVG--> 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: el îl dă ca explicație a căderii. Ce faci cu acțiunile e deducție, vezi §6.", **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"<!--SVG:{name}-->.*?<!--/SVG-->", re.S)
|
|
assert pat.search(md), f"marcatorul {name} lipsește din .md"
|
|
md = pat.sub(lambda _m: f"<!--SVG:{name}-->\n{svg}\n<!--/SVG-->", md)
|
|
MD.write_text(md, encoding="utf-8")
|
|
print("scris", MD)
|