diff --git a/gen/strategii/mbp_context.py b/gen/strategii/mbp_context.py
new file mode 100644
index 0000000..b74aa8b
--- /dev/null
+++ b/gen/strategii/mbp_context.py
@@ -0,0 +1,248 @@
+"""Diagrama de context MBP (marcajul `context`), Pasul 1.
+
+Două panouri de 15 minute, cu benzile Bollinger calculate din close-uri
+(SMA20 ± 2 deviații standard), nu desenate de mână:
+
+ - DA: mișcare rapidă în jos, opt bare, care ajunge într-un nivel major
+ (minimul zilei precedente) și împinge o candelă să închidă SUB banda
+ inferioară. Ăsta e terenul strategiei.
+ - NU: consolidare strânsă, aceeași durată; benzile se strâng odată cu
+ prețul, niciun close nu iese din ele, deci condiția de bandă nu se poate
+ îndeplini niciodată.
+
+Cifrele desenate ies din serie prin calcul (assert-uri mai jos). Nivelul
+„suport major" e construit de mine: exemplul din curs nu dă un preț pentru
+el, spune doar că semnalul are sens la capătul unei mișcări rapide care
+ajunge într-un nivel major.
+
+Rulează: python3 gen/strategii/mbp_context.py
+"""
+import re
+import statistics
+import sys
+from pathlib import Path
+
+sys.path.insert(0, "/workspace/atm-curs-dl")
+from svg_candles import Chart, Level, from_closes # noqa: E402
+
+OUT = Path(__file__).with_suffix(".svg")
+RED, GREEN, BLUE, GRAY = "#c00", "#188a3e", "#1a5fb4", "#777"
+N, K = 20, 2.0
+
+
+def bb(closes: list[float]) -> list[tuple[float, float, float] | None]:
+ """SMA20 ± 2 deviații standard, per bară; None până se adună 20 de close-uri."""
+ out: list[tuple[float, float, float] | None] = []
+ for i in range(len(closes)):
+ if i + 1 < N:
+ out.append(None)
+ continue
+ w = closes[i + 1 - N:i + 1]
+ mid = sum(w) / N
+ sd = statistics.pstdev(w)
+ out.append((mid - K * sd, mid, mid + K * sd))
+ return out
+
+
+# Benzile au nevoie de 20 de close-uri înainte să existe. Ca să se vadă pe tot
+# graficul, seriile au un prefix de 20 de bare care se calculează, dar nu se
+# desenează; se afișează doar cele 28 de bare de după el.
+PRE_DA = [624.55, 624.40, 624.52, 624.35, 624.20, 624.32, 624.15, 624.28,
+ 624.10, 624.22, 624.05, 624.18, 624.00, 624.12, 623.95, 624.08,
+ 623.90, 624.02, 623.85, 623.80]
+# ── panoul DA: dip în suport, urcare, apoi 8 bare de cădere rapidă înapoi în el
+CTX_DA = [623.72, 623.65, 623.88, 624.10, 624.35, 624.60, 624.80, 625.00,
+ 625.15, 625.30, 625.20, 625.35, 625.25, 625.42, 625.30, 625.48,
+ 625.36, 625.52, 625.44, 625.58]
+FALL = [625.20, 624.85, 624.60, 624.40, 624.25, 624.10, 624.02, 623.87]
+SHOW_DA = CTX_DA + FALL
+# Calibrare: banda inferioară se calculează din close-uri, iar în exemplul din
+# curs ea e 623.90. Ridic cu 1,2 cenți close-urile din fereastra de 20 de bare
+# (fără candela M, al cărei close 623.87 e cifra cursului), ca banda calculată
+# să iasă exact 623.90 și capitolul să nu aibă două valori pentru același nivel.
+CAL = 0.012
+CLOSES_DA = [c + (CAL if 28 <= i <= 46 else 0.0)
+ for i, c in enumerate(PRE_DA + SHOW_DA)]
+W_DA = ([(0.08, 0.08)] * 20
+ + [(0.06, 0.06), (0.06, 0.05), (0.10, 0.05)] + [(0.10, 0.10)] * 17
+ + [(0.06, 0.14)] * 7 + [(0.06, 0.27)])
+assert len(CLOSES_DA) == len(W_DA) == 48
+
+# ── panoul NU: aceeași lungime afișată, dar consolidare strânsă până la capăt
+PRE_NU = [625.30, 625.52, 625.60, 625.44, 625.20, 625.05, 625.12, 625.31,
+ 625.53, 625.62, 625.45, 625.22, 625.07, 625.14, 625.34, 625.55,
+ 625.59, 625.47, 625.26, 625.08]
+SHOW_NU = [625.35, 625.57, 625.62, 625.45, 625.23, 625.08, 625.15, 625.33,
+ 625.55, 625.64, 625.47, 625.21, 625.10, 625.13, 625.37, 625.56,
+ 625.60, 625.49, 625.25, 625.06, 625.14, 625.36, 625.58, 625.61,
+ 625.46, 625.24, 625.09, 625.16]
+CLOSES_NU = PRE_NU + SHOW_NU
+W_NU = [(0.07, 0.07)] * 48
+assert len(CLOSES_NU) == len(W_NU) == 48
+assert len(SHOW_DA) == len(SHOW_NU) == 28
+
+SKIP = 20 # bare de încălzire, nedesenate
+
+da_full = from_closes(CLOSES_DA, wicks=W_DA, start=624.70)
+nu_full = from_closes(CLOSES_NU, wicks=W_NU, start=625.20)
+da, nu = da_full[SKIP:], nu_full[SKIP:]
+
+bb_da, bb_nu = bb(CLOSES_DA)[SKIP:], bb(CLOSES_NU)[SKIP:]
+M = len(SHOW_DA) - 1 # ultima bară a panoului DA = candela M
+
+# ── ce afirmă textul, verificat pe serie ───────────────────────────────────
+b_m = bb_da[M]
+assert b_m is not None
+lo_da, mid_da, up_da = b_m
+assert abs(lo_da - 623.90) < 0.005, lo_da # aceeași bandă ca în exemplul din curs
+assert da[M].c < lo_da, (da[M].c, lo_da) # DA: close sub banda inferioară
+assert da[M].c < da[M].o # candela M e roșie
+# într-o cădere rapidă ies din bandă mai multe bare; le numărăm, ca eticheta
+# să spună o cifră care chiar iese din serie
+sub = [i for i, v in enumerate(bb_da) if v and da[i].c < v[0]]
+assert M in sub and len(sub) >= 2, sub
+n_sub = len(sub)
+
+nivel = min(c.l for c in da[:6]) # suport major = minimul din stânga graficului
+assert abs(nivel - 623.60) < 1e-9, nivel
+assert abs(da[M].l - nivel) < 1e-9, da[M].l # căderea se oprește exact în el
+assert min(c.l for c in da[6:M]) > nivel + 0.2 # între timp prețul n-a mai fost acolo
+
+cadere = da[len(CTX_DA) - 1].c - da[M].c
+assert cadere > 1.50, cadere # mișcare rapidă, nu derivă
+n_fall = len(FALL)
+assert n_fall == 8
+
+lat_da = up_da - lo_da
+b_nu = bb_nu[-1]
+assert b_nu is not None
+lat_nu = b_nu[2] - b_nu[0]
+assert lat_nu < lat_da / 2.5, (lat_nu, lat_da) # benzile se strâng în consolidare
+for i in range(len(SHOW_NU)):
+ v = bb_nu[i]
+ assert v is not None and v[0] < nu[i].c < v[2], (i, nu[i].c, v)
+
+SCALE, GAP, BODY, LEFT, PW = 78.0, 12, 8, 40, 372
+
+
+def bands_svg(ch: Chart, vals, color=BLUE) -> str:
+ """Cele trei linii Bollinger, desenate din valorile calculate mai sus."""
+ parts = []
+ for j, dash in ((0, "4 3"), (1, ""), (2, "4 3")):
+ pts = [f"{ch._cx(i):.1f},{ch._y(v[j]):.1f}" for i, v in enumerate(vals) if v]
+ d = f' stroke-dasharray="{dash}"' if dash else ""
+ parts.append(f'')
+ return "\n ".join(parts)
+
+
+# aceeași fereastră de preț în ambele panouri (altfel lățimile de bandă nu se
+# pot compara cu ochiul) și 6 % aer sus și jos, ca nicio linie să nu cadă pe
+# marginea cadrului
+pts_all = [p for c in (da + nu) for p in (c.h, c.l)]
+pts_all += [v[j] for vv in (bb_da, bb_nu) for v in vv if v for j in (0, 2)]
+P_LO, P_HI = min(pts_all), max(pts_all)
+AER = 0.06 * (P_HI - P_LO)
+SPACER = [Level(P_HI + AER, "", "none", dash=""), Level(P_LO - AER, "", "none", dash="")]
+
+
+def panel(cds, vals, levels):
+ ch = Chart(candles=cds, levels=levels + SPACER, scale=SCALE, gap=GAP, body_w=BODY,
+ left=LEFT, width=PW)
+ raw = ch.render()
+ m = re.search(r'viewBox="0 0 (\d+) ([0-9.]+)"', raw)
+ w, h = int(m.group(1)), float(m.group(2))
+ inner = raw[raw.index(">") + 1:raw.rindex("")].strip("\n")
+ return ch, w, h, bands_svg(ch, vals) + "\n " + inner
+
+
+ch_da, w_da, h_da, in_da = panel(
+ da, bb_da,
+ [Level(nivel, "", GRAY, dash="6 3", label_at="left")])
+ch_nu, w_nu, h_nu, in_nu = panel(nu, bb_nu, [])
+
+# liniile nu au voie să cadă pe marginea cadrului (6 % aer sus și jos)
+for ch, cds, vals in ((ch_da, da, bb_da), (ch_nu, nu, bb_nu)):
+ ys = [ch._y(p) for c in cds for p in (c.h, c.l)]
+ ys += [ch._y(v[j]) for v in vals if v for j in (0, 2)]
+ assert min(ys) > 8.0, min(ys) # aer față de marginea de sus
+ assert max(ys) < min(h_da, h_nu) - 8, (max(ys), h_da)
+ assert max(ys) - min(ys) > 40
+
+PH = max(h_da, h_nu)
+TOP = 52
+add_da = [
+ # eticheta nivelului, legată de linia lui, pusă în zona liberă din mijloc
+ f'suport major {nivel:.2f}',
+ f'candela M: close {da[M].c:.2f},',
+ f'sub banda inferioară {lo_da:.2f}',
+ f'cădere de {cadere:.2f} $ în {n_fall} bare',
+ f'',
+]
+add_nu = [
+ f''
+ f'benzile se strâng la {lat_nu:.2f} $ lățime',
+ f'niciun close în afara lor — semnalul nu apare',
+]
+
+FOOTER = [
+ f"DA: opt bare duc prețul cu {cadere:.2f} $ în jos, în suportul major {nivel:.2f} "
+ f"(minimul din stânga graficului),",
+ f"iar {n_sub} bare închid sub banda inferioară; M e ultima dintre ele, la {da[M].c:.2f} "
+ f"față de bandă {lo_da:.2f}.",
+ "Care dintre ele e candela M o alege TICUS, la pasul 2 — banda singură nu decide.",
+ f"NU: aceeași durată, dar prețul stă pe loc; banda are {lat_nu:.2f} $ lățime față de "
+ f"{lat_da:.2f} $ în stânga,",
+ "iar niciun close nu iese din ea. Fără atingerea benzii nu există condiția M, deci nu există trade.",
+ "Benzile sunt calculate din close-uri (SMA20 ± 2 deviații standard), nu desenate de mână.",
+ "Nivelul de suport major e construit de mine: exemplul din curs nu dă un preț pentru el.",
+]
+root_h = TOP + PH + 24 + 17 * len(FOOTER) + 8
+out = [f'")
+svg = "\n ".join(out)
+
+assert svg.count("\n\n") == 0, "linie goală în SVG rupe blocul din markdown"
+
+
+def _fits(body: str, w: float, h: float, unde: str) -> None:
+ """Nicio etichetă nu are voie să iasă din cadrul panoului (text tăiat)."""
+ for mm in re.finditer(r']*)>(.*?)', body):
+ x, y, attrs, txt = float(mm.group(1)), float(mm.group(2)), mm.group(3), mm.group(4)
+ if not txt.strip():
+ continue
+ tw = len(re.sub(r"<[^>]+>", "", txt)) * (6.4 if "bold" in attrs else 5.9)
+ x0 = x - tw if 'anchor="end"' in attrs else (x - tw / 2 if 'anchor="middle"' in attrs else x)
+ assert x0 >= -2 and x0 + tw <= w + 2, f"{unde}: text tăiat: {txt!r} ({x0:.0f}..{x0 + tw:.0f} / {w})"
+ assert 0 <= y <= h + 2, f"{unde}: text în afara înălțimii: {txt!r}"
+
+
+_fits(in_da + "\n" + "\n".join(add_da), w_da, PH, "panoul DA")
+_fits(in_nu + "\n" + "\n".join(add_nu), w_nu, PH, "panoul NU")
+assert svg.count("") == 3
+assert f"{n_fall} bare" in svg and len(FALL) == n_fall
+OUT.write_text(svg, encoding="utf-8")
+print(f"scris {OUT.name}: DA close {da[M].c:.2f} < bandă {lo_da:.2f}, cădere {cadere:.2f} $ "
+ f"în {n_fall} bare, suport {nivel:.2f}; NU lățime bandă {lat_nu:.2f} vs {lat_da:.2f}")
diff --git a/gen/strategii/mbp_context.svg b/gen/strategii/mbp_context.svg
new file mode 100644
index 0000000..62eb82e
--- /dev/null
+++ b/gen/strategii/mbp_context.svg
@@ -0,0 +1,147 @@
+
+ Pasul 1 — unde are voie strategia să caute: mișcare rapidă într-un nivel major
+ DA — cădere rapidă în suport, banda atinsă
+ NU — consolidare strânsă, banda nu se atinge
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ suport major 623.60
+candela M: close 623.87,
+sub banda inferioară 623.90
+cădere de 1.72 $ în 8 bare
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ benzile se strâng la 0.79 $ lățime
+niciun close în afara lor — semnalul nu apare
+ DA: opt bare duc prețul cu 1.72 $ în jos, în suportul major 623.60 (minimul din stânga graficului),
+ iar 4 bare închid sub banda inferioară; M e ultima dintre ele, la 623.87 față de bandă 623.90.
+ Care dintre ele e candela M o alege TICUS, la pasul 2 — banda singură nu decide.
+ NU: aceeași durată, dar prețul stă pe loc; banda are 0.79 $ lățime față de 2.19 $ în stânga,
+ iar niciun close nu iese din ea. Fără atingerea benzii nu există condiția M, deci nu există trade.
+ Benzile sunt calculate din close-uri (SMA20 ± 2 deviații standard), nu desenate de mână.
+ Nivelul de suport major e construit de mine: exemplul din curs nu dă un preț pentru el.
+
\ No newline at end of file
diff --git a/gen/strategii/mbp_iesire.py b/gen/strategii/mbp_iesire.py
new file mode 100644
index 0000000..2ac855f
--- /dev/null
+++ b/gen/strategii/mbp_iesire.py
@@ -0,0 +1,190 @@
+"""Diagrama de ieșire MBP (marcajul `iesire`), Pasul 5.
+
+Două panouri de 15 minute care pornesc din aceeași intrare (623.95, stop
+623.60, deci 1R = 0.35 — cifrele exemplului din curs) și se deosebesc prin ce
+faci cu stopul după ce prima tranșă a ieșit la 1:1:
+
+ - DA: prima tranșă iese la TP1 624.30 (1:1), stopul rămas se mută la break
+ even (623.95, prețul de intrare), restul merge pe trail — stopul urcă sub
+ minimul fiecărei candele închise — și iese la prima închidere sub el.
+ - NU: stopul e mutat la jumătatea drumului („50 %", 624.125). Fitilul unei
+ bare lungi coboară sub el și te scoate, deși prețul urcă apoi mai sus
+ decât în panoul din stânga.
+
+Toate nivelurile ies din serie prin calcul: TP1 = intrare + (intrare − stop),
+break even = intrarea, nivelul „50 %" = intrare + 1R/2, iar ieșirea pe trail e
+prima închidere sub minimul candelei precedente, căutată în serie.
+
+Rulează: python3 gen/strategii/mbp_iesire.py
+"""
+import re
+import sys
+from pathlib import Path
+
+sys.path.insert(0, "/workspace/atm-curs-dl")
+from svg_candles import Arrow, Chart, Level, from_closes # noqa: E402
+
+OUT = Path(__file__).with_suffix(".svg")
+RED, GREEN, BLUE, GRAY, ORANGE = "#c00", "#188a3e", "#1a5fb4", "#777", "#b06000"
+
+ENTRY, SL = 623.95, 623.60
+RISC = ENTRY - SL
+TP1 = ENTRY + RISC
+assert abs(RISC - 0.35) < 1e-9 and abs(TP1 - 624.30) < 1e-9
+
+E = 3 # indexul candelei de trigger (intrarea = close-ul ei)
+PRE = [624.15, 623.98, 623.87, ENTRY] # ...M la index 2, trigger la 3
+W_PRE = [(0.06, 0.10), (0.05, 0.16), (0.06, 0.27), (0.05, 0.12)]
+
+# DA: urcare la TP1, încă două bare, apoi întoarcere care rupe trail-ul
+DA = PRE + [624.12, 624.24, 624.34, 624.46, 624.52, 624.40, 624.28]
+W_DA = W_PRE + [(0.05, 0.06), (0.06, 0.05), (0.04, 0.06), (0.05, 0.06),
+ (0.07, 0.05), (0.04, 0.08), (0.03, 0.06)]
+# NU: aceeași urcare, dar a treia bară are un fitil lung care taie nivelul 50 %
+NU = PRE + [624.12, 624.24, 624.20, 624.38, 624.52, 624.60, 624.55]
+W_NU = W_PRE + [(0.05, 0.06), (0.06, 0.05), (0.04, 0.22), (0.05, 0.06),
+ (0.06, 0.05), (0.06, 0.05), (0.04, 0.07)]
+
+da = from_closes(DA, wicks=W_DA, start=624.30)
+nu = from_closes(NU, wicks=W_NU, start=624.30)
+assert len(da) == len(nu) == 11
+
+# ── ce afirmă textul, verificat pe serie ───────────────────────────────────
+assert abs(da[E].c - ENTRY) < 1e-9 and abs(nu[E].c - ENTRY) < 1e-9
+assert da[E].c > da[E].o and nu[E].c > nu[E].o # candela de trigger e verde
+assert min(c.l for c in da[E - 3:E + 1]) == SL, "stopul: cel mai jos minim din 4 candele"
+assert min(c.l for c in nu[E - 3:E + 1]) == SL
+assert all(abs(a.c - b.c) < 1e-9 for a, b in zip(da[:E + 2], nu[:E + 2]))
+
+tp_i = next(i for i in range(E + 1, len(da)) if da[i].h >= TP1)
+assert abs(da[tp_i].h - 624.34) < 0.05, da[tp_i].h # TP1 atins de fitilul barei
+assert tp_i == E + 3, tp_i
+
+# trail: stopul urcă sub minimul fiecărei candele închise; ieși la prima
+# închidere sub el. Nivelul și bara de ieșire se caută în serie, nu se scriu.
+iesire_i = next(i for i in range(tp_i + 1, len(da)) if da[i].c < da[i - 1].l)
+trail = da[iesire_i - 1].l
+iesire = da[iesire_i].c
+assert iesire > ENTRY, iesire
+r_iesire = (iesire - ENTRY) / RISC
+assert 0.5 < r_iesire < 2.5, r_iesire
+assert tp_i < iesire_i < len(da), iesire_i # ieșirea vine după TP1, în serie
+
+# panoul NU: nivelul „50 %" și fitilul care îl taie
+half = ENTRY + RISC / 2
+assert abs(half - 624.125) < 1e-9, half
+sc_i = E + 3
+assert nu[sc_i].l < half, (nu[sc_i].l, half) # fitilul coboară sub 50 %
+assert nu[sc_i].c > half, nu[sc_i].c # dar candela închide peste el
+assert nu[sc_i].h < TP1, "în panoul NU ieșirea vine înainte de TP1"
+assert max(c.h for c in nu[sc_i + 1:]) > max(c.h for c in da), \
+ "NU: prețul urcă după aceea mai sus decât în panoul DA"
+ratat = max(c.h for c in nu[sc_i + 1:])
+r_scos = (half - ENTRY) / RISC
+r_ratat = (ratat - ENTRY) / RISC
+
+SCALE, GAP, BODY, LEFT, PW = 300.0, 30, 18, 52, 372
+pts = [p for c in (da + nu) for p in (c.h, c.l)] + [TP1, SL, half]
+AER = 0.07 * (max(pts) - min(pts))
+SPACER = [Level(max(pts) + AER, "", "none", dash=""), Level(min(pts) - AER, "", "none", dash="")]
+
+
+def panel(cds, levels, arrows):
+ ch = Chart(candles=cds, levels=levels + SPACER, arrows=arrows, scale=SCALE,
+ gap=GAP, body_w=BODY, left=LEFT, width=PW)
+ raw = ch.render()
+ m = re.search(r'viewBox="0 0 (\d+) ([0-9.]+)"', raw)
+ assert m
+ return ch, int(m.group(1)), float(m.group(2)), raw[raw.index(">") + 1:raw.rindex("")].strip("\n")
+
+
+ch_da, w_da, h_da, in_da = panel(
+ da,
+ [Level(TP1, f"TP1 {TP1:.2f} (1:1)", BLUE, dash="4 3", label_at="left"),
+ Level(ENTRY, f"intrare {ENTRY:.2f} = break even", GREEN, dash="2 3", label_at="left"),
+ Level(SL, f"SL inițial {SL:.2f}", RED, dash="4 3", label_at="left"),
+ Level(trail, f"trail {trail:.2f}", ORANGE, dash="1 3", label_at="right")],
+ [Arrow(tp_i, f"prima tranșă iese la {TP1:.2f}", where="above", color=BLUE, dy=-6),
+ Arrow(iesire_i, f"închidere sub trail:\nieși cu +{r_iesire:.2f}R", where="below", color=ORANGE)])
+ch_nu, w_nu, h_nu, in_nu = panel(
+ nu,
+ [Level(TP1, f"TP1 {TP1:.2f} (1:1) — nu mai ajungi la el", BLUE, dash="4 3", label_at="left"),
+ Level(half, f"stop mutat la 50 %: {half:.3f}", RED, dash="4 3", label_at="right"),
+ Level(ENTRY, f"intrare {ENTRY:.2f}", GREEN, dash="2 3", label_at="left"),
+ Level(SL, f"SL inițial {SL:.2f}", RED, dash="4 3", label_at="left")],
+ [Arrow(sc_i, f"fitilul taie 50 %:\nieși cu +{r_scos:.1f}R", where="below", color=RED),
+ Arrow(len(nu) - 2, f"prețul urcă la {ratat:.2f}\n(+{r_ratat:.1f}R) fără tine",
+ where="above", color=GRAY)])
+
+PH = max(h_da, h_nu)
+TOP = 52
+X_DA, X_NU = 8, 8 + w_da + 16
+RW = max(780, X_NU + w_nu + 8)
+FOOTER = [
+ f"Ambele panouri pornesc din aceeași intrare {ENTRY:.2f}, cu stopul la {SL:.2f}, "
+ f"deci 1R = {RISC:.2f} $ și TP1 = {TP1:.2f}.",
+ f"DA: prima tranșă iese la TP1. Stopul rămas se duce la break even {ENTRY:.2f} — de acolo "
+ f"trade-ul nu mai poate ieși pe minus —",
+ f"iar restul merge pe trail: stopul urcă sub minimul fiecărei candele închise "
+ f"({trail:.2f} aici)",
+ f"și ieși la prima închidere sub el, {iesire:.2f} = +{r_iesire:.2f}R.",
+ f"NU: stopul mutat la jumătatea drumului, {half:.3f}. Fitilul unei singure bare coboară sub "
+ f"el și te scoate cu +{r_scos:.1f}R,",
+ f"deși bara închide peste nivel, iar prețul urcă după aceea la {ratat:.2f} "
+ f"(+{r_ratat:.1f}R).",
+ "Break even nu are cum să te scoată în pierdere: stă fix la prețul de intrare.",
+ "Ce nu a ieșit nici la țintă, nici pe trail se închide oricum la finalul sesiunii.",
+ "Cifrele intrării, stopului și ale TP1 sunt din curs; barele de după intrare sunt "
+ "construite de mine.",
+]
+root_h = TOP + PH + 24 + 17 * len(FOOTER) + 8
+out = [f'',
+ 'Pasul 5 — unde muți stopul '
+ 'după ce prima tranșă a ieșit',
+ f'DA — break even, apoi '
+ f'trail',
+ f'NU — stopul mutat la '
+ f'„50 %"',
+ f''
+ f'{in_da}',
+ f''
+ f'{in_nu}']
+fy = TOP + PH + 24
+out += [f'{t}' for i, t in enumerate(FOOTER)]
+out.append("")
+svg = "\n ".join(out)
+
+
+def fits(body: str, w: float, h: float, unde: str) -> None:
+ box: list[tuple[float, float, float, str]] = []
+ for mm in re.finditer(r']*)>(.*?)', body):
+ x, y, attrs, txt = float(mm.group(1)), float(mm.group(2)), mm.group(3), mm.group(4)
+ if not txt.strip():
+ continue
+ tw = len(txt) * (6.4 if "bold" in attrs else 5.9)
+ x0 = x - tw if 'anchor="end"' in attrs else (x - tw / 2 if 'anchor="middle"' in attrs else x)
+ assert x0 >= -2 and x0 + tw <= w + 2, f"{unde}: text tăiat: {txt!r}"
+ assert 0 <= y <= h + 2, f"{unde}: text în afara înălțimii: {txt!r}"
+ box.append((x0, x0 + tw, y, txt))
+ for i in range(len(box)):
+ for j in range(i + 1, len(box)):
+ (a0, a1, ay, at), (b0, b1, by, bt) = box[i], box[j]
+ assert not (abs(ay - by) < 13 and a0 < b1 - 4 and b0 < a1 - 4), \
+ f"{unde}: texte suprapuse: {at!r} / {bt!r}"
+
+
+fits(in_da, w_da, PH, "panoul DA")
+fits(in_nu, w_nu, PH, "panoul NU")
+fits("\n".join(out[-len(FOOTER) - 1:]), RW, root_h, "subsolul")
+assert svg.count("\n\n") == 0, "linie goală în SVG rupe blocul din markdown"
+assert svg.count("") == 3
+# aceeași scară în ambele panouri: 1R desenat identic
+assert abs((ch_da._y(ENTRY) - ch_da._y(TP1)) - (ch_nu._y(ENTRY) - ch_nu._y(TP1))) < 1e-9
+# 1:1 și vizual: distanța intrare-TP1 == distanța SL-intrare
+assert abs((ch_da._y(ENTRY) - ch_da._y(TP1)) - (ch_da._y(SL) - ch_da._y(ENTRY))) < 1e-9
+
+OUT.write_text(svg, encoding="utf-8")
+print(f"scris {OUT.name}: TP1 {TP1:.2f} la bara {tp_i}, trail {trail:.2f}, ieșire {iesire:.2f} "
+ f"(+{r_iesire:.2f}R); NU: 50 % la {half:.3f}, scos cu +{r_scos:.1f}R, ratat {ratat:.2f} "
+ f"(+{r_ratat:.1f}R)")
diff --git a/gen/strategii/mbp_iesire.svg b/gen/strategii/mbp_iesire.svg
new file mode 100644
index 0000000..156644e
--- /dev/null
+++ b/gen/strategii/mbp_iesire.svg
@@ -0,0 +1,97 @@
+
+ Pasul 5 — unde muți stopul după ce prima tranșă a ieșit
+ DA — break even, apoi trail
+ NU — stopul mutat la „50 %"
+
+ TP1 624.30 (1:1)
+
+ intrare 623.95 = break even
+
+ SL inițial 623.60
+
+ trail 624.41
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ prima tranșă iese la 624.30
+
+
+ închidere sub trail:
+ ieși cu +1.29R
+
+ TP1 624.30 (1:1) — nu mai ajungi la el
+
+ stop mutat la 50 %: 624.125
+
+ intrare 623.95
+
+ SL inițial 623.60
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ fitilul taie 50 %:
+ ieși cu +0.5R
+
+
+ prețul urcă la 624.66
+ (+2.0R) fără tine
+ Ambele panouri pornesc din aceeași intrare 623.95, cu stopul la 623.60, deci 1R = 0.35 $ și TP1 = 624.30.
+ DA: prima tranșă iese la TP1. Stopul rămas se duce la break even 623.95 — de acolo trade-ul nu mai poate ieși pe minus —
+ iar restul merge pe trail: stopul urcă sub minimul fiecărei candele închise (624.41 aici)
+ și ieși la prima închidere sub el, 624.40 = +1.29R.
+ NU: stopul mutat la jumătatea drumului, 624.125. Fitilul unei singure bare coboară sub el și te scoate cu +0.5R,
+ deși bara închide peste nivel, iar prețul urcă după aceea la 624.66 (+2.0R).
+ Break even nu are cum să te scoată în pierdere: stă fix la prețul de intrare.
+ Ce nu a ieșit nici la țintă, nici pe trail se închide oricum la finalul sesiunii.
+ Cifrele intrării, stopului și ale TP1 sunt din curs; barele de după intrare sunt construite de mine.
+
\ No newline at end of file
diff --git a/gen/strategii/mbp_stop.py b/gen/strategii/mbp_stop.py
new file mode 100644
index 0000000..a2a7e39
--- /dev/null
+++ b/gen/strategii/mbp_stop.py
@@ -0,0 +1,154 @@
+"""Diagrama de stop MBP (marcajul `stop`), Pasul 4.
+
+Fereastra de 4 candele din care iese stopul, mărită: candela de trigger și
+cele 3 dinaintea ei (deci candela M intră în fereastră). Cel mai jos minim
+din ele e 623.60 — low-ul lui M — iar stopul stă fix acolo, ca în exemplul
+din curs (fără marjă adăugată; e aceeași regulă în toate diagramele
+capitolului).
+
+Pe desen: zona punctată peste exact cele 4 candele (numărul din etichetă e
+numărat, nu scris de mână), linia de intrare la 623.95, linia de stop la
+623.60 și măsura 1R = 0.35 $ între ele, cu care se calculează mărimea
+poziției.
+
+Cifrele din curs (`05_MODUL_4/03_strategia_6_mbp_mbappe.md`): M close 623.87
+sub banda 623.90, trigger → intrare 623.95, SL 623.60 = lowest low din 4
+candele, trigger inclus. Structura barelor e construită de mine.
+
+Rulează: python3 gen/strategii/mbp_stop.py
+"""
+import re
+import sys
+from pathlib import Path
+
+sys.path.insert(0, "/workspace/atm-curs-dl")
+from svg_candles import Arrow, Chart, Level, Zone, from_closes # noqa: E402
+
+OUT = Path(__file__).with_suffix(".svg")
+RED, GREEN, BLUE, GRAY = "#c00", "#188a3e", "#1a5fb4", "#777"
+
+BAND = 623.90
+M = 11
+CLOSES = [624.90, 624.75, 624.60, 624.68, 624.50, 624.38, 624.22, 624.30,
+ 624.12, 624.05, 623.98, 623.87, 623.95, 624.08, 624.02, 624.15]
+WICKS = ([(0.06, 0.10)] * 9
+ + [(0.05, 0.20), # 9: low 623.85
+ (0.05, 0.16), # 10: low 623.82
+ (0.06, 0.27), # 11 = M: low 623.60
+ (0.05, 0.12), # 12 = trigger: low 623.75
+ (0.06, 0.08), (0.06, 0.08), (0.06, 0.08)])
+assert len(CLOSES) == len(WICKS) == 16
+
+cds = from_closes(CLOSES, wicks=WICKS, start=625.02)
+TRIG = M + 1
+I0 = TRIG - 3 # prima candelă din fereastră
+
+# ── ce afirmă textul, verificat pe serie ───────────────────────────────────
+entry = cds[TRIG].c
+assert abs(entry - 623.95) < 1e-9, entry
+assert cds[TRIG].c > cds[TRIG].o # trigger verde
+assert abs(cds[M].c - 623.87) < 1e-9 and cds[M].c < BAND
+assert cds[M].c < cds[M].o # M roșie
+
+fereastra = cds[I0:TRIG + 1]
+N_F = len(fereastra)
+assert N_F == 4, N_F
+sl = min(c.l for c in fereastra)
+assert abs(sl - 623.60) < 1e-9, sl
+assert sl == cds[M].l, "cel mai jos minim din fereastră e low-ul lui M"
+assert all(c.l > sl for c in fereastra if c is not cds[M]), \
+ "nicio altă candelă din fereastră nu coboară sub low-ul lui M"
+
+risc = entry - sl
+assert abs(risc - 0.35) < 1e-9, risc
+# stopul nu are marjă adăugată: e fix pe minim, ca în cifra cursului
+assert sl == min(c.l for c in fereastra)
+# candela dinaintea ferestrei are minim peste stop — altfel fereastra de 4 n-ar conta
+assert cds[I0 - 1].l > sl, cds[I0 - 1].l
+
+# mărimea poziției, calculată din risc (cifrele contului sunt alegerea mea)
+CONT, RISC_PCT = 50_000.0, 0.5
+risc_bani = CONT * RISC_PCT / 100
+unitati = int(risc_bani / risc)
+notional = unitati * entry
+assert unitati == 714, unitati
+assert abs(notional / CONT - 8.9) < 0.1, notional / CONT
+
+SCALE, GAP, BODY, LEFT, W = 300.0, 30, 18, 64, 756
+AER = 0.07 * (max(c.h for c in cds) - min(c.l for c in cds))
+
+ch = Chart(
+ candles=cds,
+ levels=[Level(BAND, f"banda inferioară {BAND:.2f}", BLUE, dash="4 3", label_at="left"),
+ Level(entry, f"intrare {entry:.2f} = close-ul trigger-ului", GREEN, dash="2 3",
+ label_at="right"),
+ Level(sl, f"SL {sl:.2f}", RED, dash="4 3", label_at="left"),
+ Level(max(c.h for c in cds) + AER, "", "none", dash=""),
+ Level(sl - AER, "", "none", dash="")],
+ zones=[Zone(I0, TRIG, f"{N_F} candele: trigger-ul și cele 3 dinaintea lui", RED)],
+ arrows=[Arrow(M, f"cel mai jos minim din fereastră: {sl:.2f}\nsub el, ideea de epuizare "
+ f"e moartă — acolo stă stopul", where="below", color=RED),
+ Arrow(TRIG, "trigger: aici intri, la close", where="above", color=GREEN, dx=52, dy=-24)],
+ scale=SCALE, gap=GAP, body_w=BODY, left=LEFT, width=W,
+ title="SPY — 15 min: de unde iese stopul (fereastra de 4 candele)")
+svg = ch.render()
+m = re.search(r'viewBox="0 0 (\d+) ([0-9.]+)"', svg)
+assert m
+W2, H = int(m.group(1)), float(m.group(2))
+
+# ── măsura 1R între intrare și stop, lipită de liniile ei ──────────────────
+xr = ch._cx(len(cds) - 1) + 26
+y_e, y_s = ch._y(entry), ch._y(sl)
+assert y_s > y_e
+masura = [
+ f'',
+ f'',
+ f'',
+ f'1R = {risc:.2f} $',
+]
+W3 = max(W2, xr + 8 + len(f"1R = {risc:.2f} $") * 6.4 + 8)
+
+LEG = [
+ f"Numeri {N_F} candele înapoi începând cu cea de trigger (trigger, M și cele două "
+ f"dinaintea lui M) și iei cel mai jos minim:",
+ f"{sl:.2f}. Acolo pui stopul, fix pe minim, fără marjă adăugată — asta e cifra cursului.",
+ f"Riscul pe unitate iese din desen: {entry:.2f} − {sl:.2f} = {risc:.2f} $ = 1R. Din el "
+ f"calculezi câte unități cumperi.",
+ f"Completare proprie (cifrele contului sunt ale mele): cont {CONT:,.0f} $, risc "
+ f"{RISC_PCT} % = {risc_bani:.0f} $ → {risc_bani:.0f} / {risc:.2f} = {unitati} unități,".replace(",", "."),
+ f"adică un notional de {notional:,.0f} $ ≈ {notional / CONT:.1f}× contul. "
+ f"Un stop strâns nu înseamnă un trade mic.".replace(",", "."),
+]
+W3 = max(W3, max(len(t) for t in LEG) * 5.9 + 32)
+ly = H + 6
+svg = svg.replace("\n", "\n " + "\n ".join(
+ masura + [f'{t}'
+ for i, t in enumerate(LEG)]) + "\n")
+H2 = ly + 17 * len(LEG) + 6
+svg = re.sub(r'(viewBox="0 0 )\d+ [0-9.]+(")', lambda mm: mm.group(1) + f"{W3:.0f} {H2:.0f}" + mm.group(2),
+ svg, count=1)
+
+# ── verificări pe SVG-ul final ─────────────────────────────────────────────
+assert svg.count("\n\n") == 0, "linie goală în SVG rupe blocul din markdown"
+assert f"{N_F} candele" in svg and N_F == TRIG - I0 + 1
+# liniile nu cad pe marginea cadrului
+assert ch._y(sl) < H2 - 20 and ch._y(max(c.h for c in cds)) > 14
+box = []
+for mm in re.finditer(r']*)>(.*?)', svg):
+ x, y, attrs, txt = float(mm.group(1)), float(mm.group(2)), mm.group(3), mm.group(4)
+ tw = len(txt) * (6.4 if "bold" in attrs else 5.9)
+ x0 = x - tw if 'anchor="end"' in attrs else (x - tw / 2 if 'anchor="middle"' in attrs else x)
+ assert x0 >= -2 and x0 + tw <= W3 + 2, f"text tăiat: {txt!r}"
+ assert 0 <= y <= H2 + 2, f"text în afara înălțimii: {txt!r}"
+ box.append((x0, x0 + tw, y, txt))
+for i in range(len(box)):
+ for j in range(i + 1, len(box)):
+ (a0, a1, ay, at), (b0, b1, by, bt) = box[i], box[j]
+ assert not (abs(ay - by) < 13 and a0 < b1 - 4 and b0 < a1 - 4), \
+ f"texte suprapuse: {at!r} / {bt!r}"
+
+OUT.write_text(svg, encoding="utf-8")
+print(f"scris {OUT.name}: fereastră {N_F} candele [{I0}..{TRIG}], SL {sl:.2f} (= low M), "
+ f"intrare {entry:.2f}, 1R {risc:.2f}, {unitati} unități, notional {notional:,.0f}")
diff --git a/gen/strategii/mbp_stop.svg b/gen/strategii/mbp_stop.svg
new file mode 100644
index 0000000..7790a00
--- /dev/null
+++ b/gen/strategii/mbp_stop.svg
@@ -0,0 +1,63 @@
+
+ SPY — 15 min: de unde iese stopul (fereastra de 4 candele)
+
+ 4 candele: trigger-ul și cele 3 dinaintea lui
+
+ banda inferioară 623.90
+
+ intrare 623.95 = close-ul trigger-ului
+
+ SL 623.60
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ cel mai jos minim din fereastră: 623.60
+ sub el, ideea de epuizare e moartă — acolo stă stopul
+
+
+ trigger: aici intri, la close
+
+
+
+ 1R = 0.35 $
+ Numeri 4 candele înapoi începând cu cea de trigger (trigger, M și cele două dinaintea lui M) și iei cel mai jos minim:
+ 623.60. Acolo pui stopul, fix pe minim, fără marjă adăugată — asta e cifra cursului.
+ Riscul pe unitate iese din desen: 623.95 − 623.60 = 0.35 $ = 1R. Din el calculezi câte unități cumperi.
+ Completare proprie (cifrele contului sunt ale mele): cont 50.000 $. risc 0.5 % = 250 $ → 250 / 0.35 = 714 unități.
+ adică un notional de 445.500 $ ≈ 8.9× contul. Un stop strâns nu înseamnă un trade mic.
+
\ No newline at end of file
diff --git a/gen/strategii/mbp_trigger.py b/gen/strategii/mbp_trigger.py
new file mode 100644
index 0000000..a263a73
--- /dev/null
+++ b/gen/strategii/mbp_trigger.py
@@ -0,0 +1,165 @@
+"""Diagrama de trigger MBP (marcajul `trigger`), Pasul 3.
+
+Două panouri de 15 minute care pornesc din exact aceeași candelă M (close
+623.87 sub banda 623.90, low 623.60 — cifrele exemplului din curs), și se
+despart după ea:
+
+ - DA: prima candelă de după M e verde și închide la 623.95 (Buy Momentum
+ din curs). Acolo intri, la close-ul ei.
+ - NU: cele trei candele de după M rămân roșii, fără nicio candelă de
+ moment. Nu intri. Cine ar fi cumpărat „că oricum era oversold" ar fi
+ avut stopul la 623.60 și l-ar fi văzut rupt două bare mai târziu.
+
+Zona punctată acoperă exact ferestrele în care aștepți trigger-ul, iar
+numărul din eticheta ei e numărul de bare pe care le acoperă, calculat.
+
+Rulează: python3 gen/strategii/mbp_trigger.py
+"""
+import re
+import sys
+from pathlib import Path
+
+sys.path.insert(0, "/workspace/atm-curs-dl")
+from svg_candles import Arrow, Chart, Level, Zone, from_closes # noqa: E402
+
+OUT = Path(__file__).with_suffix(".svg")
+RED, GREEN, BLUE, GRAY = "#c00", "#188a3e", "#1a5fb4", "#777"
+
+BAND = 623.90 # banda inferioară din exemplul cursului
+M = 10 # indexul candelei M, în ambele panouri
+COMUN = [624.95, 624.80, 624.62, 624.70, 624.50, 624.36, 624.20, 624.28,
+ 624.10, 624.02, 623.87] # 0..10, ultima = M
+W_COMUN = [(0.06, 0.10)] * 10 + [(0.06, 0.27)] # M: low 623.60
+
+DA = COMUN + [623.95, 624.12, 624.24] # 11 = trigger verde
+W_DA = W_COMUN + [(0.05, 0.12), (0.06, 0.08), (0.06, 0.08)]
+NU = COMUN + [623.80, 623.62, 623.45] # trei bare roșii, fără trigger
+W_NU = W_COMUN + [(0.04, 0.10), (0.04, 0.10), (0.04, 0.10)]
+
+da = from_closes(DA, wicks=W_DA, start=625.10)
+nu = from_closes(NU, wicks=W_NU, start=625.10)
+
+# ── ce afirmă textul, verificat pe serie ───────────────────────────────────
+assert len(da) == len(nu) == 14
+assert all(abs(a.c - b.c) < 1e-9 for a, b in zip(da[:M + 1], nu[:M + 1])), \
+ "panourile trebuie să pornească din aceeași candelă M"
+assert abs(da[M].c - 623.87) < 1e-9 and da[M].c < BAND # M închide sub bandă
+assert da[M].c < da[M].o # M e roșie
+assert abs(da[M].l - 623.60) < 1e-9, da[M].l # low-ul lui M = viitorul stop
+
+TRIG = M + 1
+entry = da[TRIG].c
+assert abs(entry - 623.95) < 1e-9, entry
+assert da[TRIG].c > da[TRIG].o, "candela de trigger trebuie să fie verde"
+assert entry > da[M].c, "trigger-ul închide peste close-ul lui M"
+
+sl = min(c.l for c in da[TRIG - 3:TRIG + 1]) # 4 candele, trigger inclus
+assert abs(sl - 623.60) < 1e-9, sl
+assert sl == da[M].l
+
+# panoul NU: nicio candelă verde după M, iar prețul rupe nivelul stopului
+fereastra_nu = nu[M + 1:]
+assert all(c.c < c.o for c in fereastra_nu), "în panoul NU nu are voie să apară o candelă verde"
+assert min(c.l for c in fereastra_nu) < sl, "NU: prețul trebuie să rupă 623.60"
+rupere = min(c.l for c in fereastra_nu)
+n_nu = len(fereastra_nu)
+assert n_nu == 3
+pierdere = sl - rupere
+
+SCALE, GAP, BODY, LEFT, PW = 190.0, 22, 14, 44, 372
+
+pts_all = [p for c in (da + nu) for p in (c.h, c.l)] + [BAND]
+P_LO, P_HI = min(pts_all), max(pts_all)
+AER = 0.07 * (P_HI - P_LO)
+SPACER = [Level(P_HI + AER, "", "none", dash=""), Level(P_LO - AER, "", "none", dash="")]
+
+
+def panel(cds, levels, zones, arrows):
+ ch = Chart(candles=cds, levels=levels + SPACER, zones=zones, arrows=arrows,
+ scale=SCALE, gap=GAP, body_w=BODY, left=LEFT, width=PW)
+ raw = ch.render()
+ m = re.search(r'viewBox="0 0 (\d+) ([0-9.]+)"', raw)
+ assert m
+ w, h = int(m.group(1)), float(m.group(2))
+ return ch, w, h, raw[raw.index(">") + 1:raw.rindex("")].strip("\n")
+
+
+ch_da, w_da, h_da, in_da = panel(
+ da,
+ [Level(BAND, f"bandă inferioară {BAND:.2f}", BLUE, dash="4 3", label_at="left"),
+ Level(sl, f"SL {sl:.2f}", RED, dash="4 3", label_at="left")],
+ [Zone(TRIG, TRIG, "", GREEN)],
+ [Arrow(TRIG, f"trigger: candelă verde,\nintri la close {entry:.2f}", where="above", color=GREEN),
+ Arrow(M, f"M: close {da[M].c:.2f},\nsub bandă", where="below", color=RED)])
+ch_nu, w_nu, h_nu, in_nu = panel(
+ nu,
+ [Level(BAND, f"bandă inferioară {BAND:.2f}", BLUE, dash="4 3", label_at="left"),
+ Level(sl, f"stopul ar fi fost {sl:.2f}", RED, dash="4 3", label_at="left")],
+ [Zone(M + 1, M + n_nu, f"{n_nu} candele fără trigger", RED)],
+ [Arrow(len(nu) - 1, f"aici s-ar fi rupt stopul,\ncu {pierdere:.2f} $ sub {sl:.2f}",
+ where="below", color=RED)])
+
+PH = max(h_da, h_nu)
+add_da: list[str] = []
+add_nu: list[str] = []
+
+FOOTER = [
+ f"Aceeași candelă M în ambele panouri: close {da[M].c:.2f} sub banda {BAND:.2f}, "
+ f"low {sl:.2f}. Ce urmează după ea decide.",
+ f"DA: candela {TRIG - M} de după M e verde și închide la {entry:.2f} — ăsta e trigger-ul, "
+ f"iar close-ul ei e prețul de intrare.",
+ f"NU: {n_nu} candele roșii la rând, niciun trigger. Nu intri. Prețul coboară până la "
+ f"{rupere:.2f}, cu {pierdere:.2f} $ sub {sl:.2f},",
+ "adică exact sub stopul pe care l-ai fi avut dacă intrai „pentru că oricum era oversold\".",
+ "Condiția M (bandă + TICUS) nu e un ordin de cumpărare: e doar permisiunea de a aștepta trigger-ul.",
+ "Cifrele lui M și ale trigger-ului (623.87 / 623.90 / 623.60 / 623.95) sunt din curs; "
+ "restul barelor sunt construite de mine.",
+]
+TOP = 52
+X_DA, X_NU = 8, 8 + w_da + 16
+RW = max(780, X_NU + w_nu + 8)
+root_h = TOP + PH + 24 + 17 * len(FOOTER) + 8
+out = [f'',
+ 'Pasul 3 — condiția M deschide '
+ 'fereastra; trigger-ul e cel care te bagă în trade',
+ f'DA — candelă de moment '
+ f'după M',
+ f'NU — M valid, dar fără '
+ f'trigger',
+ f''
+ f'{in_da}\n {chr(10).join(add_da)}',
+ f''
+ f'{in_nu}\n {chr(10).join(add_nu)}']
+fy = TOP + PH + 24
+out += [f'{t}' for i, t in enumerate(FOOTER)]
+out.append("")
+svg = "\n ".join(out)
+
+
+def fits(body: str, w: float, h: float, unde: str) -> None:
+ box: list[tuple[float, float, float, str]] = []
+ for mm in re.finditer(r']*)>(.*?)', body):
+ x, y, attrs, txt = float(mm.group(1)), float(mm.group(2)), mm.group(3), mm.group(4)
+ if not txt.strip():
+ continue
+ tw = len(re.sub(r"<[^>]+>", "", txt)) * (6.4 if "bold" in attrs else 5.9)
+ x0 = x - tw if 'anchor="end"' in attrs else (x - tw / 2 if 'anchor="middle"' in attrs else x)
+ assert x0 >= -2 and x0 + tw <= w + 2, f"{unde}: text tăiat: {txt!r}"
+ assert 0 <= y <= h + 2, f"{unde}: text în afara înălțimii: {txt!r}"
+ box.append((x0, x0 + tw, y, txt))
+ for i in range(len(box)):
+ for j in range(i + 1, len(box)):
+ (a0, a1, ay, at), (b0, b1, by, bt) = box[i], box[j]
+ assert not (abs(ay - by) < 13 and a0 < b1 - 4 and b0 < a1 - 4), \
+ f"{unde}: texte suprapuse: {at!r} / {bt!r}"
+
+
+fits(in_da + "\n" + "\n".join(add_da), w_da, PH, "panoul DA")
+fits(in_nu + "\n" + "\n".join(add_nu), w_nu, PH, "panoul NU")
+assert svg.count("\n\n") == 0, "linie goală în SVG rupe blocul din markdown"
+assert svg.count("") == 3
+assert f"{n_nu} candele fără trigger" in svg and n_nu == len(fereastra_nu)
+OUT.write_text(svg, encoding="utf-8")
+print(f"scris {OUT.name}: M {da[M].c:.2f} (low {sl:.2f}), trigger {entry:.2f}; "
+ f"NU {n_nu} bare roșii, rupere {rupere:.2f} ({pierdere:.2f} $ sub stop)")
diff --git a/gen/strategii/mbp_trigger.svg b/gen/strategii/mbp_trigger.svg
new file mode 100644
index 0000000..e40cba1
--- /dev/null
+++ b/gen/strategii/mbp_trigger.svg
@@ -0,0 +1,100 @@
+
+ Pasul 3 — condiția M deschide fereastra; trigger-ul e cel care te bagă în trade
+ DA — candelă de moment după M
+ NU — M valid, dar fără trigger
+
+
+ bandă inferioară 623.90
+
+ SL 623.60
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ trigger: candelă verde,
+ intri la close 623.95
+
+
+ M: close 623.87,
+ sub bandă
+
+
+ 3 candele fără trigger
+
+ bandă inferioară 623.90
+
+ stopul ar fi fost 623.60
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ aici s-ar fi rupt stopul,
+ cu 0.25 $ sub 623.60
+
+ Aceeași candelă M în ambele panouri: close 623.87 sub banda 623.90, low 623.60. Ce urmează după ea decide.
+ DA: candela 1 de după M e verde și închide la 623.95 — ăsta e trigger-ul, iar close-ul ei e prețul de intrare.
+ NU: 3 candele roșii la rând, niciun trigger. Nu intri. Prețul coboară până la 623.35, cu 0.25 $ sub 623.60,
+ adică exact sub stopul pe care l-ai fi avut dacă intrai „pentru că oricum era oversold".
+ Condiția M (bandă + TICUS) nu e un ordin de cumpărare: e doar permisiunea de a aștepta trigger-ul.
+ Cifrele lui M și ale trigger-ului (623.87 / 623.90 / 623.60 / 623.95) sunt din curs; restul barelor sunt construite de mine.
+
\ No newline at end of file