Doua proceduri complet separate, nu una amestecata, pentru ca se fac altfel: buy hammer e pattern public, desenabil si exersabil retroactiv; MBT-10 e cutie neagra din care se poate reface o singura conditie. Nivelul 1 le pune fata in fata, ca cititorul sa stie din prima de ce nu le poate trata la fel. Cate 5 pasi fiecare, oglinda pe short la hammer, checklist dublu. Sursele in fise/06_hammer_mbt10_surse.md. Zece diagrame noi: hammer-ul nu era definit vizual nicaieri, iar hanging man era doar text — acum e aceeasi bara in doua contexte, cu assert ca e identica. Cele 7 vechi si-au pierdut trimiterile fisier:linie din legende; toate le aveau. Doua serii partajate (bh_ansamblu si mbt_confluente expun serie()), deci un trade nu poate avea doua variante. Oglinda pe short e trade-ul long oglindit in jurul lui K=162.30, deci riscul ramane 1.30 in ambele. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ThN7ixZrowca8aTGrK1MUJ
197 lines
9.1 KiB
Python
197 lines
9.1 KiB
Python
#!/usr/bin/env python3
|
|
"""MBT-10 — pullback în locul crimei + hammer, intrare la close 54.60 (ansamblu).
|
|
|
|
Citește `gen/strategii/hammer_mbt10_mbt_ansamblu.spec.txt` și scrie
|
|
`gen/strategii/hammer_mbt10_mbt_ansamblu.svg`: 41 de candele daily pe exemplul
|
|
construit al sumarizatorului (`summaries/08_ANALIZA_ZILNICA/02_mbt10.md:165-173`,
|
|
`978-984`) — uptrend până la fostul vârf 52.10, spargere, vârf nou 58.90,
|
|
pullback în locul crimei, hammer cu codița exact pe 52.10, iar bara următoare
|
|
se închide în MBT-10 la 54.60 (intrare la close). SL 52.00, TP 58.90 = 1.65R.
|
|
|
|
Toate prețurile/nivelurile sunt cele din specificație. Seria de close-uri e
|
|
cea dată; open-ul fiecărei bare = close-ul precedentului. MBT-10 nu se
|
|
desenează (formula nu e publică) — se marchează doar bara la care semnalul
|
|
s-a închis.
|
|
|
|
Rulare: python3 gen/strategii/hammer_mbt10_mbt_ansamblu.py
|
|
"""
|
|
import re
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
sys.path.insert(0, "/workspace/atm-curs-dl")
|
|
from svg_candles import Chart, Level, Mark, from_closes # noqa: E402
|
|
|
|
OUT = Path(__file__).with_suffix(".svg")
|
|
|
|
# --- nivelurile din specificație -------------------------------------------
|
|
CRIMEA = 52.10 # locul crimei = fostul vârf spart, retestat ca suport
|
|
HAMMER_H = 53.40 # high = close al hammer-ului
|
|
HAMMER_L = 52.10 # codița hammer-ului, exact pe locul crimei
|
|
ENTRY = 54.60 # close-ul barei care s-a închis în MBT-10 = intrare
|
|
SL = 52.00 # sub codița hammer-ului și sub locul crimei
|
|
TP = 58.90 # vârful anterior = 1.65R (risc 2.60)
|
|
|
|
# --- seria: 41 de close-uri, exact ca în specificație -----------------------
|
|
closes = [
|
|
# 0-8: uptrend care duce spre fostul vârf
|
|
46.00, 46.80, 47.50, 48.30, 49.00, 49.80, 50.50, 51.20, 52.00,
|
|
52.10, # 9 = fostul vârf 52.10 (high cu wick 52.40)
|
|
# 10-20: spargerea nivelului și urcarea spre vârful nou
|
|
51.40, 50.80, 51.50, 52.40, 53.20, 54.00, 54.80, 55.60, 56.40, 57.20,
|
|
58.00,
|
|
58.90, # 21 = vârf nou
|
|
# 22-28: pullback înapoi în locul crimei
|
|
58.20, 57.40, 56.50, 55.60, 54.70, 53.60, 52.20,
|
|
# 29-30: hammer pe locul crimei + ziua care se închide în MBT-10
|
|
53.40, # 29 = hammer: o 52.20, h/c 53.40, l 52.10 (H = C)
|
|
54.60, # 30 = MBT-10 închis la close => intrare
|
|
# 31-40: urcarea spre ținta de la vârful anterior
|
|
55.20, 55.80, 56.40, 57.00, 57.60, 58.10, 58.60,
|
|
58.90, # 38 = TP (vârful anterior)
|
|
58.70, 58.30,
|
|
]
|
|
assert len(closes) == 41, f"seria are {len(closes)} bare, spec cere 41"
|
|
|
|
# wick-uri (sus, jos): restul (0.30, 0.40); excepțiile din specificație.
|
|
# `from_closes` măsoară wick-ul de jos de la min(o, c), nu de la close: pentru
|
|
# hammer-ul cu o 52.20 / c 53.40, ca low-ul să iasă exact 52.10, wick-ul de jos
|
|
# e 0.10 (nu 1.30, cum dă specificația măsurând de la close).
|
|
wicks = [(0.30, 0.40)] * 41
|
|
wicks[9] = (0.30, 0.30) # high 52.40, low 51.70 (fostul vârf)
|
|
wicks[28] = (0.30, 0.10) # low 52.10: pullback-ul atinge locul crimei
|
|
wicks[29] = (0.00, 0.10) # hammer: high = close = 53.40, low = 52.10
|
|
# bara 30: specificația dă (0.20, 0.20) cu „low 54.40"; 54.40 ar fi peste
|
|
# open-ul 53.40, deci imposibil. Cu (0.20, 0.20) low-ul iese 53.20; nu e
|
|
# etichetat nicăieri și nu intră în niciun assert.
|
|
wicks[30] = (0.20, 0.20)
|
|
|
|
# volum „normal", fără argument de volum (specificația o spune explicit):
|
|
# doar panoul de volum cerut de stil, fără spike-uri pe hammer/confirmare.
|
|
vols = [
|
|
50, 49, 51, 50, 52, 51, 50, 52, 51,
|
|
52,
|
|
50, 49, 51, 50, 52, 51, 50, 52, 51, 52,
|
|
51,
|
|
52,
|
|
50, 49, 50, 51, 50, 49, 50,
|
|
51, 52,
|
|
50, 51, 52, 51, 50, 52, 51,
|
|
52,
|
|
51, 50,
|
|
]
|
|
assert len(vols) == 41
|
|
|
|
cds = from_closes(closes, wicks=wicks, vols=vols, start=45.60)
|
|
|
|
# seria continuă: open = close-ul barei anterioare
|
|
for a, b in zip(cds, cds[1:]):
|
|
assert abs(a.c - b.o) < 1e-9, "open != close-ul barei anterioare"
|
|
|
|
intrare = cds[30].c
|
|
sl, tp = SL, TP
|
|
|
|
# --- cele 8 assert-uri cerute de specificație ------------------------------
|
|
# 1. hammer 29: low exact pe locul crimei, close == high (fără fitil de sus)
|
|
assert abs(cds[29].l - CRIMEA) < 1e-9, f"low[29]={cds[29].l} != 52.10"
|
|
assert abs(cds[29].c - HAMMER_H) < 1e-9 and abs(cds[29].h - HAMMER_H) < 1e-9, \
|
|
f"close[29]/high[29] != 53.40 ({cds[29].c}/{cds[29].h})"
|
|
assert cds[29].c == cds[29].h, "hammer-ul are fitil de sus"
|
|
# 2. bara 30 se închide la 54.60 = intrare și peste high-ul hammer-ului
|
|
assert abs(intrare - ENTRY) < 1e-9, f"close[30]={intrare} != intrare 54.60"
|
|
assert intrare > cds[29].h == HAMMER_H, "close[30] nu e peste high[29] == 53.40"
|
|
# 3. SL = low-ul hammer-ului - 0.10
|
|
assert abs(sl - (cds[29].l - 0.10)) < 1e-9, "SL != low[29] - 0.10"
|
|
# 4. risc 2.60; TP 58.90 = 1.65R
|
|
risc = intrare - sl
|
|
assert abs(risc - 2.60) < 1e-9, f"risc={risc} != 2.60"
|
|
assert abs(tp - 58.90) < 1e-9, "TP != 58.90"
|
|
assert round((tp - intrare) / (intrare - sl), 2) == 1.65, "TP != 1.65R"
|
|
# 5. fostul vârf 52.10 există înainte de breakout (bara 9, high 52.40)
|
|
assert any(abs(cds[i].h - 52.10) <= 0.30 for i in range(15)), \
|
|
"niciun high din primele 15 bare nu e în 0.30 de 52.10"
|
|
assert abs(cds[9].h - 52.40) < 1e-9, f"high[9]={cds[9].h} != 52.40"
|
|
# 6. TP atins după intrare
|
|
assert max(c.c for c in cds[30:41]) >= 58.90, "TP 58.90 nu e atins după intrare"
|
|
# 7. vârful nou 58.90 e la bara 21, înainte de pullback; pullback-ul coboară
|
|
# exact la locul crimei (minimul secțiunii 21-29 e codița hammer-ului)
|
|
assert abs(cds[21].c - 58.90) < 1e-9, f"close[21]={cds[21].c} != 58.90"
|
|
assert abs(min(c.l for c in cds[21:30]) - CRIMEA) < 1e-9, \
|
|
f"min low[21:30]={min(c.l for c in cds[21:30])} != 52.10"
|
|
# 8. prima etapă e urcare
|
|
assert cds[9].c > cds[0].c, "prima etapă nu e uptrend"
|
|
|
|
print(f"hammer H/C {cds[29].h:.2f} L {cds[29].l:.2f} intrare {intrare:.2f} "
|
|
f"SL {sl:.2f} TP {tp:.2f} = {round((tp-intrare)/(intrare-sl),2)}R "
|
|
f"vârf nou {cds[21].c:.2f} (bara 21)")
|
|
|
|
# --- desenul ----------------------------------------------------------------
|
|
ch = Chart(
|
|
candles=cds,
|
|
levels=[
|
|
Level(CRIMEA, "locul crimei 52.10 (fost vârf spart)", "#777",
|
|
label_at="left"),
|
|
Level(SL, "SL 52.00 — sub codița hammer-ului", "#c00"),
|
|
Level(ENTRY, "intrare 54.60 (close în MBT-10)", "#188a3e"),
|
|
Level(TP, "TP 58.90 = 1.65R", "#1a5fb4"),
|
|
],
|
|
marks=[
|
|
Mark(29, "hammer — codița pe 52.10", "#b06000", "below"),
|
|
# dy negativ: bara 30 e urmată imediat de bare mai înalte (31-32), iar
|
|
# textul lipit de high-ul barei 30 ar cădea peste corpurile lor.
|
|
Mark(30, "MBT-10 închis", "#188a3e", "above", dy=-24),
|
|
],
|
|
scale=20.0, gap=15, body_w=9, left=44, width=780, vol_h=70,
|
|
vol_label="volum",
|
|
title="MBT-10 — pullback în locul crimei + hammer, închidere la 54.60 (exemplu construit)",
|
|
)
|
|
|
|
# explicațiile și legenda obligatorie: bloc de text sub grafic (nu se
|
|
# suprapun cu candelele, iar textele lungi nu încap pe verticală lângă bare).
|
|
cds[0].note = ("Legendă: MBT-10 nu se desenează — formula nu e publică "
|
|
"; se marchează doar bara la care")
|
|
cds[1].note = ("semnalul s-a închis. Nivelurile sunt exemplul construit al "
|
|
"sumarizatorului.")
|
|
cds[2].note = ("bara 9 = fostul vârf 52.10 (locul crimei); bara 21 = vârful "
|
|
"nou 58.90.")
|
|
cds[3].note = ("bara 29 = hammer: o 52.20, h/c 53.40, l 52.10 (codița exact "
|
|
"pe locul crimei); bara 30 = ziua închisă în MBT-10.")
|
|
|
|
svg = ch.render()
|
|
assert "<svg" in svg and "\n\n" not in svg, "SVG invalid (linie goală?)"
|
|
|
|
# --- citirea SVG-ului: etichete în pânză, fără suprapuneri -----------------
|
|
TEXT = re.compile(r'<text x="([-\d.]+)" y="([-\d.]+)"([^>]*)>(.*?)</text>', re.S)
|
|
W = float(re.search(r'viewBox="0 0 (\d+) ', svg).group(1))
|
|
H = float(re.search(r'viewBox="0 0 \d+ ([\d.]+)"', svg).group(1))
|
|
labels = []
|
|
for m in TEXT.finditer(svg):
|
|
x, y, attrs, s = float(m.group(1)), float(m.group(2)), m.group(3), m.group(4)
|
|
anchor = "end" if 'text-anchor="end"' in attrs else \
|
|
("middle" if 'text-anchor="middle"' in attrs else "start")
|
|
bold = "bold" in attrs
|
|
wpx = len(s) * 12 * (0.62 if bold else 0.52)
|
|
left = x - wpx if anchor == "end" else (x - wpx / 2 if anchor == "middle" else x)
|
|
labels.append((left, left + wpx, y, s))
|
|
|
|
for left, right, y, s in labels:
|
|
assert left >= -2 and right <= W + 2, \
|
|
f"text iese din pânză: {s!r} [{left:.0f},{right:.0f}]"
|
|
assert 0 <= y <= H, f"text în afara înălțimii: {s!r} (y={y:.0f})"
|
|
|
|
for i in range(len(labels)):
|
|
li, ri, yi, si = labels[i]
|
|
for j in range(i + 1, len(labels)):
|
|
lj, rj, yj, sj = labels[j]
|
|
if abs(yi - yj) < 12 and li < rj - 4 and lj < ri - 4:
|
|
raise AssertionError(f"texte suprapuse: {si!r} / {sj!r}\n"
|
|
f" {labels[i]}\n {labels[j]}")
|
|
|
|
# candelele sunt o serie continuă, lipită, în pânză
|
|
xs = [ch._cx(i) for i in range(len(cds))]
|
|
assert all(xs[i + 1] - xs[i] == ch.gap for i in range(len(xs) - 1)), "serie cu goluri"
|
|
assert xs[-1] + ch.body_w / 2 < W, f"ultima candelă iese din pânză (x={xs[-1]})"
|
|
|
|
print(f"scris {OUT} ({W:.0f}x{H:.0f}); {len(labels)} etichete verificate")
|
|
OUT.write_text(svg, encoding="utf-8")
|