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
152 lines
6.6 KiB
Python
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}")
|