"""Diagrama din `gen/strategii/mbp_exemplu.spec.txt` (TIP: PRET).
Trade-ul short complet din material (SPY, 15 min, 25.11.2025): ~12 candele
de urcare violentă spre vârful ~661, candela M (verde, peste bandă + TICUS
≥ +1000), trigger Sell Momentum → intrare short 660.00, SL 662.71
(risc 0.41 %), TP1 657.29 (1:1) atins în prima candelă, apoi trail pe 7
candele de 15 min până la ieșirea la 646.80 = 2 % = ~4.9R.
Serie continuă de 32 de candele (`from_closes`, open = close anterior), cu
context înainte de urcare, panou TICUS dedesubt (bar ilustrativ la candela M)
și legendă. Toate cifrele din desen vin din specificație; singurele valori
construite de mine sunt structura barelor dintre niveluri și bara TICUS
(marcată explicit „ilustrativ", pentru că valoarea exactă nu e în material).
Rulare: python3 gen/strategii/mbp_exemplu.py
"""
import re
import sys
from pathlib import Path
sys.path.insert(0, "/workspace/atm-curs-dl")
from svg_candles import Chart, Level, Mark, MA, from_closes # noqa: E402
OUT = Path(__file__).with_suffix(".svg")
# ── cifrele din material ────────────────────────────────────────────────────
ENTRY = 660.00 # 09_QA/11_2025-11-25.md:565, 632-636
SL = 662.71 # risc 0.41 % → (sl-entry)/entry*100
TP1 = 657.29 # entry - (sl - entry) = 1:1
EXIT = 646.80 # ieșire pe trail = 2 % = ~4.9R
BAND = 660.90 # bandă superioară schematică (peste close-ul lui M)
W, GAP, BODY_W, LEFT = 660, 15, 9, 40
SCALE = 20.0
M_I, E_I, TP1_I, EXIT_I = 23, 24, 25, 31 # M, trigger, TP1, ieșire
N_RISE = M_I - 11 # 12 candele de urcare (idx 12..23)
# ── seria: context → urcare violentă → M → trigger → TP1 → trail ────────────
closes = [
# idx 0-11: context în jurul lui 648, înainte de urcarea violentă
649.00, 648.60, 649.20, 648.40, 648.90, 648.10,
648.70, 647.90, 648.50, 648.00, 648.60, 648.30,
# idx 12-23: 12 candele de urcare de la ~648 la vârful ~661; idx 23 = M
648.60, 649.40, 650.30, 651.20, 652.10, 653.10,
654.10, 655.20, 656.30, 657.50, 658.80, 660.50,
# idx 24: trigger Sell Momentum → close = intrarea short
660.00,
# idx 25: prima candelă după intrare atinge TP1 657.29
657.80,
# idx 26-31: trail până la ieșirea la 646.80 (6 candele)
656.40, 655.00, 653.40, 651.60, 649.40, 646.80,
]
assert len(closes) == 32, len(closes)
wicks = [(0.35, 0.35)] * 12 # context
wicks += [(0.30, 0.25)] * (M_I - 12) # urcarea (idx 12..22)
wicks += [(0.50, 0.20)] # M: high = 661.00 (vârful)
wicks += [(0.20, 0.40), # trigger
(0.25, 0.51), # TP1: low = 657.80-0.51 = 657.29
(0.30, 0.30), (0.30, 0.30), (0.30, 0.30),
(0.30, 0.30), (0.30, 0.30), (0.30, 0.30)] # trail
assert len(wicks) == len(closes)
cds = from_closes(closes, wicks=wicks, start=648.90)
# ── assert-urile din specificație ───────────────────────────────────────────
entry = cds[E_I].c
sl = SL
tp1 = TP1
exit_ = cds[EXIT_I].c
assert abs(entry - 660.00) < 0.01, entry
assert entry == cds[E_I].c, "intrarea nu e close-ul candelei trigger"
assert abs((sl - entry) / entry * 100 - 0.41) < 0.01, (sl - entry) / entry * 100
assert abs((sl - entry) - 2.71) < 0.01, sl - entry
assert abs(tp1 - (entry - (sl - entry))) < 0.01, (entry - (sl - entry), tp1)
assert cds[TP1_I].l <= tp1 + 1e-9, "prima candelă după intrare nu atinge TP1"
assert abs(cds[TP1_I].l - tp1) < 0.01, cds[TP1_I].l
assert abs((entry - exit_) / entry * 100 - 2.0) < 0.05, (entry - exit_) / entry * 100
assert abs((entry - exit_) / (sl - entry) - 4.9) < 0.1, (entry - exit_) / (sl - entry)
assert EXIT_I - E_I == 7, EXIT_I - E_I
assert sl > entry > tp1, "short: SL deasupra intrării, TP1 sub intrare"
assert BAND > cds[M_I].c, "banda schematică nu e deasupra close-ului lui M"
# structura care susține afirmațiile din text
assert cds[M_I].c > cds[M_I].o, "candela M trebuie să fie verde"
assert cds[E_I].c < cds[E_I].o, "candela trigger trebuie să fie roșie"
assert M_I == E_I - 1, "trigger-ul nu e imediat după M"
assert N_RISE == 12, N_RISE
assert 30 <= len(cds) <= 34, len(cds)
assert max(cds[i].c for i in range(12, M_I + 1)) == cds[M_I].c, "M nu e vârful urcării"
assert max(c.h for c in cds) == cds[M_I].h, "vârful seriei nu e la M"
assert all(cds[i].c > cds[i - 1].c for i in range(13, M_I + 1)), "urcarea nu e monotonă"
# ── panoul de preț ──────────────────────────────────────────────────────────
levels = [
Level(SL, "SL 662.71 (risc 0.41 %)", "#c00", label_at="right"),
Level(ENTRY, "intrare short 660.00", "#188a3e", label_at="left"),
Level(BAND, "bandă superioară (schematic)", "#777", dash="2 4", label_at="left"),
Level(TP1, "TP1 657.29 (1:1)", "#1a5fb4", label_at="left"),
Level(EXIT, "ieșire trail 646.80 = 2 % = ~4.9R", "#1a5fb4", label_at="left"),
]
marks = [
Mark(M_I, "M: candelă verde peste bandă + TICUS ≥ +1000", "#188a3e", "above"),
]
cds[M_I].note = ("M: valori exacte NEACOPERIT în material (bandă + TICUS); "
"bara ~+1000 din panou e ilustrativă")
cds[E_I].note = "trigger Sell Momentum → intrare short 660.00 (risc 0.41 % = 2.71)"
cds[TP1_I].note = "TP1 657.29 (1:1) atins în prima candelă după intrare"
cds[EXIT_I].note = ("ieșire pe trail 646.80 = 2 % = ~4.9R, după 7 candele "
"de 15 min de la intrare")
ch = Chart(
candles=cds,
levels=levels,
marks=marks,
mas=[MA(20, "#b06000", dash="4 3", text="SMA-20")],
scale=SCALE, width=W, gap=GAP, body_w=BODY_W, left=LEFT,
title="SPY — 15 min: MBAP short 25.11.2025 (risc 0.41 % → ieșire 4.9R)",
caption="",
)
price_svg = ch.render()
# corpul graficului de preț și înălțimea lui (pentru lipirea panoului TICUS)
m = re.search(r'viewBox="0 0 (\d+) ([\d.]+)"', price_svg)
H1 = float(m.group(2))
body1 = price_svg[price_svg.index(">") + 1:price_svg.rindex("")].strip("\n")
# ── panoul TICUS dedesubt (bar ilustrativ la candela M) ─────────────────────
VGAP = 26
Y0 = H1 + VGAP
TICUS_H = 100
zero_y = Y0 + 84 # linia 0
plus_y = Y0 + 32 # linia +1000 (punctată)
bar_top = plus_y - 18 # bara depășește +1000
m_x = LEFT + M_I * GAP
assert bar_top < plus_y, "bara TICUS nu depășește linia +1000"
assert plus_y < zero_y, "linia +1000 nu e deasupra liniei 0"
ticus = [
f''
f'Panou TICUS — linia 0 și pragul +1000 (ilustrativ)',
f'',
f''
f'+1000 (prag)',
f'',
f'0',
f'',
f''
f'ilustrativ',
f''
f'candela M',
]
# ── legenda (cifrele/sursele) ───────────────────────────────────────────────
legend = [
"Legendă: procentele (risc 0.41 %, ieșire 2 %) sunt ale instructorului.",
"Prețurile absolute (660.00 / 662.71 / 657.29 / 646.80) sunt construite de sumarizator —",
"09_QA/11_2025-11-25.md:716-719.",
"Structura de reținut: risc mic → TP1 (1:1) atins imediat → trail lung (7 candele de 15 min).",
"Valoarea exactă a benzii și a TICUS la candela M nu e dată în sesiune; bara ~+1000 e ilustrativă.",
"„Taurii urcă pe scări, urșii sar de la etaj\" — de ce short-ul se consumă în ore.",
"(09_QA/11_2025-11-25.md:643-648).",
]
cap_top = Y0 + TICUS_H + 20
H = cap_top + 16 * (len(legend) - 1) + 14
parts = [
f'",
]
svg = "\n ".join(parts)
assert "\n\n" not in svg, "linie goală în SVG — rupe markdown-ul"
assert svg.count("