Files
atm-curs-dl/gen/strategii/hammer_mbt10_mbt_tesla.py
Claude Agent a7785740cc 06_hammer_mbt10: capitol reconstruit dupa sablon
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
2026-09-20 13:05:41 +00:00

152 lines
6.6 KiB
Python

"""Diagrama din `gen/strategii/hammer_mbt10_mbt_tesla.spec.txt` (TIP: PRET).
Exemplul real Tesla din Q&A (`03_2026-08-11.md:118-120`): trigger MBT-10 după
o cădere anterioară, intrare la închiderea zilei la 311.21, ieșire la 333.00
șase zile mai târziu = +7.0 % pe acțiune.
Reale: 311.21 / 333.00 / +7.0 % și contextul calitativ. Construită de mine:
structura barelor dintre cele două niveluri. SL-ul lipsește din material, deci
NU se trasează nicio linie de stop (spec, „LIPSĂ").
Serie continuă de 25 de candele daily din lista de close-uri din specificație,
open = close-ul precedentului, wick-uri implicite (0.50, 0.60), fără panou de
volum (materialul nu discută volumul exemplului). Legenda e spartă pe trei
rânduri și lipește sub grafic, cu viewBox extins.
"""
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__).resolve().parent / "hammer_mbt10_mbt_tesla.svg"
# ── cifrele reale din material (`03_2026-08-11.md:118-120`) ─────────────────
INTRARE = 311.21 # intrarea la închiderea zilei
REZULTAT = 333.00 # six zile mai târziu
ZILE = 6 # (21 - 15)
PROCENT = 7.0 # +7.0 % pe acțiune
I_INTRARE = 15 # candela de intrare (trigger MBT-10)
I_REZULTAT = 21
# ── seria: 25 de close-uri, exact ca în specificație ───────────────────────
closes = [
360.00, 356.00, 358.00, 352.00, 348.00, 349.00, 343.00, 338.00, 340.00,
334.00, 329.00, 331.00, 325.00, 320.00, 315.00,
311.21, # 15 = trigger MBT-10, intrare la închiderea zilei
314.00, 318.00, 322.00, 326.00, 330.00,
333.00, # 21 = 6 bare după intrare
331.00, 328.00, 330.00,
]
assert len(closes) == 25, len(closes)
# wick-uri implicite pentru toate barele
wicks = [(0.50, 0.60)] * len(closes)
cds = from_closes(closes, wicks=wicks, start=closes[0])
# ── assert-urile cerute de specificație ────────────────────────────────────
# 1. close[15] == 311.21 == intrare (la închiderea zilei)
assert abs(cds[I_INTRARE].c - 311.21) < 1e-9, cds[I_INTRARE].c
assert abs(cds[I_INTRARE].c - INTRARE) < 1e-9, (cds[I_INTRARE].c, INTRARE)
# 2. close[21] == 333.00 și (21 - 15) == 6
assert abs(cds[I_REZULTAT].c - 333.00) < 1e-9, cds[I_REZULTAT].c
assert (I_REZULTAT - I_INTRARE) == 6, I_REZULTAT - I_INTRARE
# 3. procentul REAL
pct = round((cds[I_REZULTAT].c / cds[I_INTRARE].c - 1) * 100, 1)
assert pct == 7.0, pct
# 4. cădere anterioară: close[15] < close[0] - 40
assert cds[I_INTRARE].c < cds[0].c - 40, (cds[I_INTRARE].c, cds[0].c)
# 5. nu se trasează linie de SL; în structură nu apare niciun nivel de stop
# (verificat mai jos, după construirea listelor de niveluri/marcaje)
# 6. entry < exit, iar exit e atins după exact 6 bare (nu mai devreme)
assert cds[I_INTRARE].c < cds[I_REZULTAT].c
for i in range(I_INTRARE + 1, I_REZULTAT):
assert cds[i].c < REZULTAT, f"rezultatul 333.00 e atins deja la bara {i}"
assert cds[I_REZULTAT].c == REZULTAT
# serie continuă: open-ul fiecărei bare = close-ul precedentului
for a, b in zip(cds, cds[1:]):
assert abs(a.c - b.o) < 1e-9, "serie cu gol artificial între bare"
# ── desenul ────────────────────────────────────────────────────────────────
levels = [
Level(INTRARE, "intrare 311.21 (close)", "#188a3e", label_at="left"),
Level(REZULTAT, "rezultat 333.00 = +7.0 % în 6 zile", "#1a5fb4"),
]
marks = [
Mark(I_INTRARE, "trigger MBT-10", "#188a3e", "below"),
Mark(I_REZULTAT, "exact 6 bare", "#1a5fb4", "above", dy=-16),
]
# 5. niciun nivel/marcaj de stop în structură
assert not any("stop" in t.lower() for t in
[lv.text for lv in levels] + [mk.text for mk in marks])
assert not any(t.strip().upper().startswith("SL") for t in
[lv.text for lv in levels] + [mk.text for mk in marks])
ch = Chart(
candles=cds,
levels=levels,
marks=marks,
scale=7.5, gap=15, body_w=9, left=44, width=620,
title="Tesla — daily: trigger MBT-10, intrare 311.21 → 333.00 = +7.0 % în 6 zile",
caption="", # legenda obligatorie e pusă manual, pe rânduri, cu viewBox extins
)
svg = ch.render()
assert "<svg" in svg and svg.count("\n\n") == 0, "linie goală în SVG"
# ── legenda obligatorie, spartă ca să nu iasă din pânză ────────────────────
m = re.search(r'viewBox="0 0 (\d+) (\d+)"', svg)
W, H = int(m.group(1)), int(m.group(2))
legenda = [
"Cifrele 311.21 / 333.00 / +7.0 % sunt reale;",
"structura barelor dintre ele e construită.",
"SL: materialul nu îl dă pentru acest exemplu.",
]
# fiecare rând încape în pânză (≈6 px/caracter la 12px)
for rand in legenda:
assert 24 + len(rand) * 6.0 <= W - 10, f"legenda iese din pânză: {rand!r}"
extra = 16 * len(legenda) + 6
svg = svg.replace(f'viewBox="0 0 {W} {H}"', f'viewBox="0 0 {W} {H + extra}"')
svg = svg.replace("</svg>", "".join(
f'<text x="24" y="{H + 12 + 16 * i}" fill="#555">{t}</text>'
for i, t in enumerate(legenda)) + "</svg>")
assert "\n\n" not in svg, "linie goală în SVG după legendă"
# legenda completă există, rând cu rând
for rand in legenda:
assert rand in svg, f"lipsește din legendă: {rand!r}"
# ── verificare: fiecare etichetă e în pânză și nu se suprapune alta ────────
texte = re.findall(r'<text x="([\d.]+)" y="([\d.]+)"([^>]*)>([^<]*)</text>', svg)
cutii = []
for xs, ys, attrs, continut in texte:
x, y = float(xs), float(ys)
w = len(continut) * (7.44 if "bold" in attrs else 6.72)
if 'text-anchor="end"' in attrs:
x0 = x - w
elif 'text-anchor="middle"' in attrs:
x0 = x - w / 2
else:
x0 = x
assert -2 <= x0 and x0 + w <= W + 2, f"etichetă în afara pânzei: {continut!r}"
assert 0 <= y <= H + extra, f"etichetă în afara înălțimii: {continut!r}"
cutii.append((x0, x0 + w, y, continut))
for i in range(len(cutii)):
for j in range(i + 1, len(cutii)):
a, b = cutii[i], cutii[j]
if abs(a[2] - b[2]) < 13 and a[0] < b[1] - 2 and b[0] < a[1] - 2:
raise AssertionError(f"texte suprapuse: {a[3]!r} / {b[3]!r}")
OUT.write_text(svg, encoding="utf-8")
print(f"serie {len(cds)} candele | intrare {INTRARE:.2f} @ bara {I_INTRARE} | "
f"rezultat {REZULTAT:.2f} @ bara {I_REZULTAT} (+{pct:.1f} % în "
f"{I_REZULTAT - I_INTRARE} zile, atins nu mai devreme)")
print(f"scris {OUT}")