diff --git a/gen/strategii/hammer_mbt10_bh_forma.py b/gen/strategii/hammer_mbt10_bh_forma.py
new file mode 100644
index 0000000..4f76ab5
--- /dev/null
+++ b/gen/strategii/hammer_mbt10_bh_forma.py
@@ -0,0 +1,154 @@
+#!/usr/bin/env python3
+"""Ce e un hammer și ce nu — patru candele cu același high 63.30 și low 62.10.
+
+Prima imagine a capitolului: cine o vede nu știe ce e un hammer, deci candela
+A primește săgeți pe corp și pe fiecare fitil, iar C și D sunt contraexemple
+cu aceeași înălțime. Zonă verde peste A și B, roșie peste C și D.
+
+Desenat direct, nu cu `Chart`: fiecare etichetă trebuie să stea lipită sub
+candela ei, pe 2-3 rânduri, plus săgeți cu text pe lateral.
+
+Rulare: python3 gen/strategii/hammer_mbt10_bh_forma.py
+"""
+from pathlib import Path
+
+OUT = Path(__file__).with_suffix(".svg")
+GREEN, RED, GREY, BLUE = "#188a3e", "#c00", "#555", "#1a5fb4"
+
+H, L = 63.30, 62.10
+RNG = H - L # 1.20
+PRAG_SUS = L + 2 / 3 * RNG # 62.90 — corpul trebuie să stea peste el
+
+# (nume, o, h, l, c, verdict, rânduri de etichetă sub candelă)
+CANDLES = [
+ ("A", 63.05, H, L, 63.30, "DA", ["DA: corp 0.25,", "fitil jos 0.95 = 3,8x corpul"]),
+ ("B", 62.90, H, L, 63.30, "DA", ["DA, la limită: corp 0.40,", "fitil jos 0.80 = exact 2x"]),
+ ("C", 62.60, H, L, 63.30, "NU", ["NU: corp 0.70,", "fitil jos 0.50 = 0,7x corpul"]),
+ ("D", 62.25, H, L, 62.45, "NU", ["NU: corpul e jos, fitilul", "lung e sus — alt pattern"]),
+]
+
+
+def masuri(o, h, l, c):
+ return abs(c - o), min(o, c) - l, h - max(o, c) # corp, fitil jos, fitil sus
+
+
+# --- verificările -----------------------------------------------------------
+M = {n: masuri(o, h, l, c) for n, o, h, l, c, *_ in CANDLES}
+for n, o, h, l, c, *_ in CANDLES:
+ assert (h, l) == (H, L), (n, h, l) # 1 aceeași bară
+assert [tuple(round(v, 2) for v in M[n]) for n in "ABCD"] == [ # 2 cifrele din etichete
+ (0.25, 0.95, 0.00), (0.40, 0.80, 0.00), (0.70, 0.50, 0.00), (0.20, 0.15, 0.85)], M
+for n in "AB": # 3 A și B trec ambele reguli
+ corp, jos, _ = M[n]
+ o, c = [(o, c) for nn, o, _, _, c, *_ in CANDLES if nn == n][0]
+ assert jos >= 2 * corp - 1e-9, (n, jos, corp)
+ assert min(o, c) >= PRAG_SUS - 1e-9, (n, min(o, c), PRAG_SUS)
+assert M["C"][1] < 2 * M["C"][0], "C ar trebui să pice pe regula fitilului" # 4
+oD, cD = CANDLES[3][1], CANDLES[3][4]
+assert max(oD, cD) < PRAG_SUS, "D ar trebui să pice pe regula treimii de sus" # 5
+assert round(M["A"][1] / M["A"][0], 1) == 3.8 and round(M["B"][1] / M["B"][0], 1) == 2.0
+assert round(M["C"][1] / M["C"][0], 1) == 0.7
+
+# --- geometrie --------------------------------------------------------------
+SCALE, TOP, W = 260, 210, 1000
+XS = [350, 490, 690, 860]
+BODY_W = 26
+
+
+def y(p):
+ return TOP + (H - p) * SCALE
+
+
+assert y(CANDLES[0][1]) > y(CANDLES[0][4]), "open A trebuie sub close A pe ecran"
+
+out = []
+t = out.append
+
+
+def text(x, yy, s, color=GREY, anchor="start", bold=False, size=12):
+ style = f' style="font-weight:bold;font-size:{size}px"' if bold else (
+ f' style="font-size:{size}px"' if size != 12 else "")
+ t(f'{s}')
+
+
+def zone(x0, x1, y0, y1, label, color):
+ t(f'')
+ text(x0 + 6, y0 - 7, label, color, bold=True)
+
+
+def arrow(x1, y1, x2, y2, color, lines, anchor="start"):
+ d = max(((x2 - x1) ** 2 + (y2 - y1) ** 2) ** 0.5, 1e-9)
+ ux, uy = (x2 - x1) / d, (y2 - y1) / d
+ bx, by = x2 - 8 * ux, y2 - 8 * uy
+ t(f'')
+ t(f'')
+ dx = -6 if anchor == "end" else (0 if anchor == "middle" else 6)
+ y0 = (y1 - 10 - 14 * (len(lines) - 1) if anchor == "middle"
+ else y1 + 4 - 14 * (len(lines) - 1) / 2)
+ for k, s in enumerate(lines):
+ text(x1 + dx, y0 + 14 * k, s, color, anchor, bold=(k == 0))
+
+
+text(24, 30, "Ce e un hammer și ce nu — aceeași bară, 63.30 sus și 62.10 jos", "#333",
+ bold=True, size=14)
+
+label_y0 = y(L) + 28
+z_bot = label_y0 + 16 * 2 - 4
+zone(XS[0] - 58, XS[1] + 66, y(H) - 30, z_bot, "hammer", GREEN)
+zone(XS[2] - 60, XS[3] + 74, y(H) - 30, z_bot, "nu e hammer", RED)
+
+for p in (H, L):
+ t(f'')
+text(24, y(H) - 6, "cât de sus a urcat prețul: 63.30")
+text(24, y(L) + 14, "cât de jos a coborât: 62.10")
+# pragul treimii de sus: linia pe care corpul trebuie s-o depășească
+t(f'')
+text(24, y(PRAG_SUS) - 4, f"treimea de sus începe la {PRAG_SUS:.2f}", BLUE)
+
+for (n, o, h, l, c, verdict, lines), x in zip(CANDLES, XS):
+ color = GREEN if c >= o else RED
+ vcol = GREEN if verdict == "DA" else RED
+ t(f'')
+ top, bot = y(max(o, c)), y(min(o, c))
+ t(f'')
+ for k, s in enumerate(lines):
+ text(x, label_y0 + 16 * k, s, vcol, "middle", bold=(k == 0))
+
+xA = XS[0]
+arrow(xA - 60, y(63.10), xA - 15, y(63.17), BLUE, anchor="end",
+ lines=["corpul: mic și sus în bară",
+ "ziua s-a închis aproape de maxim —",
+ "cumpărătorii au ținut până la final"])
+arrow(xA - 60, y(62.55), xA - 3, y(62.60), BLUE, anchor="end",
+ lines=["fitilul de jos, lung",
+ "vânzătorii au dus prețul până la 62.10,",
+ "dar cumpărătorii l-au adus înapoi sus"])
+text(xA + 20, y(63.05) + 12, "open 63.05")
+text(xA + 20, y(63.30) - 4, "close = high 63.30")
+
+xC, xD = XS[2], XS[3]
+arrow(xC + 34, y(H) - 112, xC + 14, y(62.95), RED,
+ lines=["corp prea gros", "fitilul de jos nu e nici cât corpul —", "nimeni n-a fost respins jos"])
+arrow(xD, y(H) - 34, xD, y(63.24), RED, anchor="middle",
+ lines=["fitilul lung e sus, nu jos", "aici cumpărătorii au fost respinși;",
+ "e inverted hammer, alt pattern"])
+
+cap_y = z_bot + 30
+text(24, cap_y, "Hammer = fitilul de jos e cel puțin dublul corpului ȘI corpul stă în treimea "
+ "de sus a barei (aici peste 62.90).")
+text(24, cap_y + 16, "Pragul „fitil ≥ 2x corpul\" e al cursului; „corpul în treimea de sus\" e "
+ "traducerea mea a lui „corp mic sus, fitil de sus aproape inexistent\".", GREY)
+height = cap_y + 40
+
+svg = (f'\n")
+assert "\n\n" not in svg
+OUT.write_text(svg, encoding="utf-8")
+print(f"scris {OUT.name} ({W}x{height:.0f})")
diff --git a/gen/strategii/hammer_mbt10_bh_forma.svg b/gen/strategii/hammer_mbt10_bh_forma.svg
new file mode 100644
index 0000000..4ea4eed
--- /dev/null
+++ b/gen/strategii/hammer_mbt10_bh_forma.svg
@@ -0,0 +1,53 @@
+
diff --git a/gen/strategii/hammer_mbt10_bh_iesire.py b/gen/strategii/hammer_mbt10_bh_iesire.py
new file mode 100644
index 0000000..081cbe2
--- /dev/null
+++ b/gen/strategii/hammer_mbt10_bh_iesire.py
@@ -0,0 +1,135 @@
+#!/usr/bin/env python3
+"""Buy hammer — ieșirea: jumătate la 1:1, restul pe trail sub swing low-uri.
+
+Pornește din aceeași serie ca diagrama de ansamblu (barele 0-33, importate)
+și îi adaugă o continuare construită (barele 34-40), pentru că seria de
+ansamblu se oprește înainte ca trail-ul să apuce să urce de două ori.
+
+Regula de trail desenată: stopul se mută sub ultimul swing low — cel mai jos
+low al unui pullback, confirmat de două bare care nu-l rup. Ieși la prima
+închidere sub linie.
+
+Rulare: python3 gen/strategii/hammer_mbt10_bh_iesire.py
+"""
+import re
+import sys; sys.path.insert(0, "/workspace/atm-curs-dl")
+sys.path.insert(0, "/workspace/atm-curs-dl/gen/strategii")
+from pathlib import Path
+from hammer_mbt10_bh_ansamblu import serie
+from svg_candles import Arrow, Candle, Chart, Level, Zone
+
+OUT = Path(__file__).with_suffix(".svg")
+GREEN, RED, BLUE, ORANGE, GREY = "#188a3e", "#c00", "#1a5fb4", "#b06000", "#555"
+
+INTRARE, SL, R = 63.35, 62.05, 1.30
+TP1 = round(INTRARE + R, 2) # 64.65 — ținta 1:1 a strategiei MBP
+TP2R = round(INTRARE + 2 * R, 2) # 65.95 — ținta din exemplul lecției
+
+cds_all, hammer, conf = serie()
+conf.note = "" # nota e a diagramei de ansamblu; aici ar sta peste legendă
+# continuarea construită: un pullback, o urcare, al doilea pullback, ieșirea
+CONT = [Candle(*b) for b in [
+ (65.60, 66.10, 65.45, 65.95), # 34
+ (65.95, 66.80, 65.85, 66.60), # 35
+ (66.60, 66.90, 66.20, 66.35), # 36
+ (66.35, 66.55, 66.00, 66.45), # 37 -> swing low 66.00
+ (66.45, 67.30, 66.35, 67.10), # 38
+ (67.10, 67.60, 66.90, 67.30), # 39
+ (67.30, 67.40, 65.80, 65.90), # 40 -> close sub trail: ieși
+]]
+full = cds_all + CONT
+assert abs(CONT[0].o - cds_all[-1].c) < 1e-9, "continuarea nu pornește din close-ul barei 33"
+for i, c in enumerate(full):
+ assert c.l <= min(c.o, c.c) and c.h >= max(c.o, c.c), f"bara {i}: h/l nu cuprind o/c"
+
+
+def swing_lows(cds, start):
+ """Cel mai jos low al unui pullback, confirmat de două bare care nu-l rup."""
+ out = []
+ for i in range(start, len(cds) - 2):
+ if (cds[i].l < cds[i - 1].l and cds[i + 1].l >= cds[i].l and cds[i + 2].l >= cds[i].l):
+ out.append((i, round(cds[i].l, 2)))
+ return out
+
+
+CONF_I = 20
+sw = swing_lows(full, CONF_I + 1)
+trepte, iesire = [], None
+t = None
+for i in range(CONF_I + 1, len(full)):
+ if t is not None and full[i].c < t:
+ iesire = i
+ break
+ nou = [p for j, p in sw if j + 2 <= i]
+ if nou and (t is None or nou[-1] > t):
+ t = nou[-1]
+ trepte.append((i, t))
+
+# --- verificări -------------------------------------------------------------
+assert round(full[CONF_I].c, 2) == INTRARE, "intrarea nu e close-ul barei 20"
+prim_tp1 = next(i for i in range(CONF_I + 1, len(full)) if full[i].h >= TP1)
+prim_2r = next(i for i in range(CONF_I + 1, len(full)) if full[i].h >= TP2R)
+assert (prim_tp1, prim_2r) == (23, 28), (prim_tp1, prim_2r)
+assert round(TP1 - INTRARE, 2) == R and round(TP2R - INTRARE, 2) == 2 * R
+assert trepte == [(23, 63.05), (34, 65.10), (39, 66.00)], trepte
+assert all(b[1] > a[1] for a, b in zip(trepte, trepte[1:])), "trail-ul trebuie doar să urce"
+assert iesire == 40 and abs(full[40].c - 65.90) < 1e-9, iesire
+assert trepte[0][1] > SL, "prima treaptă nu îmbunătățește stopul inițial"
+assert trepte[-1][1] > INTRARE, "ultima treaptă de trail e sub intrare: n-ar proteja profitul"
+R_IES = round((full[iesire].c - INTRARE) / R, 2)
+assert R_IES == 1.96, R_IES
+assert min(c.l for c in full[CONF_I:]) > SL, "stopul inițial n-ar fi fost atins în fereastră"
+
+I0 = 16
+w = full[I0:]
+sh = lambda i: i - I0 # noqa: E731 index în fereastră
+ch = Chart(
+ candles=w,
+ levels=[Level(TP2R, "65.95 = 2R — ținta din exemplul lecției", BLUE),
+ Level(TP1, "64.65 = 1:1 — prima jumătate", GREEN),
+ Level(INTRARE, "intrare 63.35", GREY, label_at="left"),
+ Level(SL, "SL inițial 62.05", RED, label_at="left")],
+ zones=[Zone(sh(30), sh(34), "pullback", ORANGE), Zone(sh(36), sh(38), "", ORANGE)],
+ arrows=[
+ Arrow(sh(23), "prima jumătate iese la 64.65:\nexact cât ai riscat (1R)",
+ price=TP1, where="above", color=GREEN, dx=-150, dy=-40),
+ Arrow(sh(40), "a doua jumătate iese la 65.90 (+1,96R):\nprima închidere sub linia de trail",
+ where="below", color=RED, dx=-250, dy=54),
+ ],
+ scale=34.0, gap=16, body_w=9, left=48, width=700, vol_h=0, top=58,
+ title="Buy hammer — ieșirea (daily, ticker ilustrativ XYZ; barele de după 33 sunt construite)",
+)
+svg = ch.render()
+m = re.search(r'viewBox="0 0 (\d+) (\d+)"', svg)
+W, H = int(m.group(1)), int(m.group(2))
+OFF = (float(re.search(r'linia de trail')
+extra_svg = (f'' + "".join(etichete))
+
+linii = [
+ ("Trail-ul urcă doar sub un swing low nou — 63.05, apoi 65.10, apoi 66.00 — și nu coboară "
+ "niciodată; ieși la prima închidere sub el.", "#333"),
+ ("Lecția de pattern nu dă o țintă proprie — 1:1 vine de la strategia MBP, iar 2R e ținta "
+ "exemplului din lecție. Continuarea barelor e construită.", GREY),
+]
+add = 16 * len(linii) + 10
+svg = svg.replace(f'viewBox="0 0 {W} {H}"', f'viewBox="0 0 {W} {H + add}"')
+svg = svg.replace("", extra_svg + "".join(
+ f'{t}'
+ for i, (t, c) in enumerate(linii)) + "")
+assert "\n\n" not in svg, "linie goală în SVG"
+OUT.write_text(svg, encoding="utf-8")
+print(f"scris {OUT.name} ({W}x{H + add}); TP1 la bara {prim_tp1}, 2R la {prim_2r}, "
+ f"trail {trepte}, ieșire bara {iesire} @ {full[iesire].c:.2f} = +{R_IES}R")
diff --git a/gen/strategii/hammer_mbt10_bh_iesire.svg b/gen/strategii/hammer_mbt10_bh_iesire.svg
new file mode 100644
index 0000000..2e6d324
--- /dev/null
+++ b/gen/strategii/hammer_mbt10_bh_iesire.svg
@@ -0,0 +1,72 @@
+
\ No newline at end of file
diff --git a/gen/strategii/hammer_mbt10_bh_locatie.py b/gen/strategii/hammer_mbt10_bh_locatie.py
new file mode 100644
index 0000000..5e290ff
--- /dev/null
+++ b/gen/strategii/hammer_mbt10_bh_locatie.py
@@ -0,0 +1,124 @@
+#!/usr/bin/env python3
+"""Buy hammer — locația: hammer la un nivel major vs. hammer în mijlocul range-ului.
+
+Două panouri alăturate cu ACEEAȘI serie de 12 bare, pe aceeași scară și
+aceeași axă de preț (candelele stau la aceleași y). Stânga: suport monthly
+62.00, la 0,16 % sub low-ul hammer-ului. Dreapta: cel mai apropiat nivel de
+sub el e la 58.00, adică un gol de 7,07 %. Nivelurile se desenează de mână,
+cu coordonate absolute (liniile din Chart pornesc de la x=24 și ar trece
+peste panoul vecin).
+
+Rulare: python3 gen/strategii/hammer_mbt10_bh_locatie.py
+"""
+import re
+import sys; sys.path.insert(0, "/workspace/atm-curs-dl")
+from pathlib import Path
+from svg_candles import Arrow, Chart, Mark, from_closes
+
+OUT = Path(__file__).with_suffix(".svg")
+GREEN, RED, BLUE, ORANGE, GREY = "#188a3e", "#c00", "#1a5fb4", "#b06000", "#555"
+
+CLOSES = [65.40, 64.90, 64.40, 64.70, 64.00, 63.50, 63.05,
+ 63.30, # 7 = hammer: o 63.05, h = c 63.30, l 62.10
+ 63.35, # 8 = confirmare, close peste high-ul hammer-ului
+ 63.80, 64.20, 64.60]
+wicks = [(0.15, 0.15)] * len(CLOSES)
+wicks[7] = (0.00, 0.95)
+cds = from_closes(CLOSES, wicks=wicks, start=65.90)
+ham = cds[7]
+SUP_DA, SUP_NU, REZ_NU = 62.00, 58.00, 67.00
+
+# --- verificări -------------------------------------------------------------
+for i in range(1, len(cds)):
+ assert abs(cds[i].o - cds[i - 1].c) < 1e-9, f"open[{i}] != close[{i-1}]"
+assert (round(ham.h, 2), round(ham.l, 2), round(ham.c, 2)) == (63.30, 62.10, 63.30)
+corp, jos = ham.c - ham.o, ham.o - ham.l
+assert jos >= 2 * corp, "bara 7 nu e hammer"
+assert cds[8].c > ham.h, "bara 8 nu confirmă"
+d_da = round((ham.l - SUP_DA) / SUP_DA * 100, 2)
+assert d_da == 0.16 and d_da <= 2, d_da
+d_nu = round((ham.l - SUP_NU) / SUP_NU * 100, 2)
+assert d_nu == 7.07 and d_nu > 2, d_nu
+d_rez = round((REZ_NU - ham.h) / ham.h * 100, 2)
+assert d_rez == 5.85 and d_rez > 2, d_rez
+assert not any(SUP_NU < p < REZ_NU for p in (SUP_NU, REZ_NU) if False)
+
+# --- layout -----------------------------------------------------------------
+W, PW = 760, 350
+PX = [24, 24 + PW + 28]
+SCALE, TOP = 34.0, 96
+
+ARR = {
+ PX[0]: Arrow(4, "suport monthly: nivel urmărit de mulți —\naici au cumpărători motiv să apară",
+ price=SUP_DA, where="below", color=BLUE, dx=30, dy=22),
+ PX[1]: Arrow(7, "aceeași formă, dar nimic sub ea:\nfitilul n-a fost oprit de un nivel,\nci s-a oprit singur",
+ where="below", color=RED, dx=70, dy=30),
+}
+charts = []
+for px in PX:
+ ch = Chart(candles=cds,
+ marks=[Mark(7, "hammer 63.30 / 62.10", ORANGE, "above", dy=-16)],
+ arrows=[ARR[px]],
+ scale=SCALE, gap=17, body_w=10, left=px + 34, width=px + PW, top=TOP)
+ body = ch._render()
+ assert f'viewBox="0 0 {px + PW} ' in body, f"panoul {px}: textul unei săgeți iese"
+ assert (ch.top, ch.left) == (TOP, px + 34), "Chart a mutat graficul: panourile s-ar dezalinia"
+ charts.append((ch, body.split(">", 1)[1].rsplit("", 1)[0].strip()))
+(ch_da, body_da), (ch_nu, body_nu) = charts
+assert ch_da.scale == ch_nu.scale and ch_da._pmax == ch_nu._pmax, "scări diferite"
+y = ch_da._y
+
+
+def level(px, price, text, color, below=False):
+ yy = y(price)
+ ty, anchor, tx = (yy + 14, "end", px + PW - 4) if below else (yy - 6, "end", px + PW - 4)
+ return [f'',
+ f'{text}']
+
+
+def zone(px, p_top, p_bot, label, color, label_at_bottom=False):
+ y0, y1 = y(p_top), y(p_bot)
+ ly, anchor, lx = ((y1 - 12, "start", px + 8) if label_at_bottom
+ else (y0 - 6, "end", px + PW - 4))
+ return [f'',
+ f'{label}']
+
+
+PCT_DA, PCT_NU = f"{d_da:.2f}".replace(".", ","), f"{d_nu:.2f}".replace(".", ",")
+y_bot = y(SUP_NU)
+parts = [
+ f'",
+]
+svg = "\n ".join(parts).replace("\n ", "\n")
+assert "\n\n" not in svg, "linie goală în SVG"
+for m in re.finditer(r']*>(.*?)', body_da):
+ assert float(m.group(1)) < PX[1] - 14, f"text din DA trece în NU: {m.group(2)}"
+OUT.write_text(svg, encoding="utf-8")
+print(f"scris {OUT.name} ({W}x{y_bot + 66:.0f}); DA {d_da}% NU {d_nu}% (rezistență {d_rez}%)")
diff --git a/gen/strategii/hammer_mbt10_bh_locatie.svg b/gen/strategii/hammer_mbt10_bh_locatie.svg
new file mode 100644
index 0000000..1ed3e2c
--- /dev/null
+++ b/gen/strategii/hammer_mbt10_bh_locatie.svg
@@ -0,0 +1,79 @@
+
\ No newline at end of file
diff --git a/gen/strategii/hammer_mbt10_bh_sell.py b/gen/strategii/hammer_mbt10_bh_sell.py
new file mode 100644
index 0000000..6e568bb
--- /dev/null
+++ b/gen/strategii/hammer_mbt10_bh_sell.py
@@ -0,0 +1,86 @@
+#!/usr/bin/env python3
+"""Sell hammer — oglinda pe short a trade-ului din Partea A.
+
+Seria e chiar trade-ul long, oglindit în jurul lui K = 162.30: fiecare preț
+p devine K - p, iar fitilele se inversează. Așa, hammer-ul 63.30 / 62.10
+devine 99.00 / 100.20, intrarea 63.35 devine 98.95, stopul 62.05 devine
+100.25 și ținta 65.95 devine 96.35 — riscul rămâne exact 1.30, deci cele
+două diagrame nu se pot contrazice la cifre. Oglindirea e verificată bară
+cu bară prin assert.
+
+Rulare: python3 gen/strategii/hammer_mbt10_bh_sell.py
+"""
+import re
+import sys; sys.path.insert(0, "/workspace/atm-curs-dl")
+sys.path.insert(0, "/workspace/atm-curs-dl/gen/strategii")
+from pathlib import Path
+from hammer_mbt10_bh_ansamblu import serie
+from svg_candles import Arrow, Candle, Chart, Level
+
+OUT = Path(__file__).with_suffix(".svg")
+GREEN, RED, BLUE, ORANGE, GREY = "#188a3e", "#c00", "#1a5fb4", "#b06000", "#555"
+
+K = 162.30
+lung, ham_l, conf_l = serie()
+conf_l.note = ""
+cds = [Candle(K - c.o, K - c.l, K - c.h, K - c.c, vol=c.vol) for c in lung]
+ham, conf = cds[19], cds[20]
+INTRARE, SL, TP = round(K - 63.35, 2), round(K - 62.05, 2), round(K - 65.95, 2)
+R = round(SL - INTRARE, 2)
+
+# --- verificări -------------------------------------------------------------
+for i, (a, b) in enumerate(zip(lung, cds)):
+ assert abs(b.o - (K - a.o)) < 1e-9 and abs(b.c - (K - a.c)) < 1e-9, i
+ assert abs(b.h - (K - a.l)) < 1e-9 and abs(b.l - (K - a.h)) < 1e-9, f"bara {i}: fitil neoglindit"
+ assert b.l <= min(b.o, b.c) and b.h >= max(b.o, b.c), f"bara {i}: h/l nu cuprind o/c"
+for i in range(1, len(cds)):
+ assert abs(cds[i].o - cds[i - 1].c) < 1e-9, f"open[{i}] != close[{i-1}]"
+assert (round(ham.l, 2), round(ham.h, 2)) == (99.00, 100.20), ham
+corp, sus = ham.o - ham.c, ham.h - ham.o
+assert sus >= 2 * corp, "bara 19 nu e sell hammer (fitilul de sus sub dublul corpului)"
+assert abs(ham.l - ham.c) < 1e-9, "corpul nu se sprijină pe low: fitil de jos inexistent"
+assert cds[18].c > cds[0].c + 8, "urcarea dinainte nu se vede"
+assert conf.c < ham.l and round(conf.c, 2) == INTRARE, "confirmarea nu închide sub low-ul lui"
+assert round(SL, 2) == round(ham.h + 0.05, 2), "SL nu e la 5 cenți peste high-ul sell hammer-ului"
+assert R == 1.30 and round(INTRARE - TP, 2) == 2 * R, (R, TP)
+assert TP < INTRARE < SL, "ordinea nivelurilor e greșită pe short"
+prim_tp = next(i for i in range(21, len(cds)) if cds[i].l <= TP)
+assert prim_tp == 28, prim_tp
+assert max(c.h for c in cds[21:prim_tp + 1]) < SL, "stopul ar fi fost atins înainte de țintă"
+
+ch = Chart(
+ candles=cds,
+ levels=[Level(SL, "SL 100.25 — peste high-ul sell hammer-ului", RED),
+ Level(INTRARE, "intrare short 98.95", GREEN, label_at="left"),
+ Level(TP, "TP 96.35 = 2R", BLUE)],
+ arrows=[
+ Arrow(19, "sell hammer 99.00 / 100.20:\ncorp mic jos, fitil lung în sus",
+ where="left", color=ORANGE, dx=-8, dy=-30),
+ Arrow(20, "close 98.95, sub low-ul lui 99.00:\naici vinzi în lipsă",
+ where="below", color=GREEN, dx=-120, dy=40),
+ Arrow(prim_tp, "ținta 96.35: aceeași distanță\nca la long, dar în jos",
+ price=TP, where="below", color=BLUE, dx=-60, dy=30),
+ ],
+ scale=32.0, gap=15, body_w=9, left=44, width=720, top=56, vol_h=70, vol_label="volum",
+ title="Sell hammer — oglinda pe short (daily, exemplu construit)",
+)
+svg = ch.render()
+m = re.search(r'viewBox="0 0 (\d+) (\d+)"', svg)
+W, H = int(m.group(1)), int(m.group(2))
+linii = [
+ ("Totul e întors: urcare înainte, corp mic jos și fitil lung în sus, intrarea la o "
+ "închidere SUB", "#333"),
+ ("low-ul candelei, stopul PESTE high-ul ei, ținta la aceeași distanță în jos.", "#333"),
+ ("Completare proprie: cursul numește Sell Hammer ca simetric al lui Buy Hammer, dar "
+ "nu-i scrie", GREY),
+ ("regulile separat. Seria e trade-ul long oglindit, deci riscul e tot 1.30.", GREY),
+]
+add = 16 * len(linii) + 10
+svg = svg.replace(f'viewBox="0 0 {W} {H}"', f'viewBox="0 0 {W} {H + add}"')
+svg = svg.replace("", "".join(
+ f'{t}'
+ for i, (t, c) in enumerate(linii)) + "")
+assert "\n\n" not in svg, "linie goală în SVG"
+OUT.write_text(svg, encoding="utf-8")
+print(f"scris {OUT.name} ({W}x{H + add}); intrare {INTRARE} SL {SL} TP {TP} R {R}, "
+ f"țintă atinsă la bara {prim_tp}")
diff --git a/gen/strategii/hammer_mbt10_bh_sell.svg b/gen/strategii/hammer_mbt10_bh_sell.svg
new file mode 100644
index 0000000..5d015c9
--- /dev/null
+++ b/gen/strategii/hammer_mbt10_bh_sell.svg
@@ -0,0 +1,125 @@
+
\ No newline at end of file
diff --git a/gen/strategii/hammer_mbt10_bh_stop.py b/gen/strategii/hammer_mbt10_bh_stop.py
new file mode 100644
index 0000000..a578a56
--- /dev/null
+++ b/gen/strategii/hammer_mbt10_bh_stop.py
@@ -0,0 +1,85 @@
+#!/usr/bin/env python3
+"""Buy hammer — unde stă stopul și câte acțiuni cumperi (același trade XYZ).
+
+Zoom pe barele 14-27 din seria diagramei de ansamblu (importată, ca să nu
+existe două variante ale aceluiași trade). Arată: SL 62.05, cu 5 cenți sub
+low-ul hammer-ului, paranteza de risc 1.30 $/acțiune și, sub grafic, calculul
+de mărime al lecției: 2 % dintr-un cont de 10.000 $ = 200 $; 200 / 1.30 = 153
+de acțiuni.
+
+Rulare: python3 gen/strategii/hammer_mbt10_bh_stop.py
+"""
+import re
+import sys; sys.path.insert(0, "/workspace/atm-curs-dl")
+sys.path.insert(0, "/workspace/atm-curs-dl/gen/strategii")
+from pathlib import Path
+from hammer_mbt10_bh_ansamblu import serie
+from svg_candles import Arrow, Chart, Level, Zone
+
+OUT = Path(__file__).with_suffix(".svg")
+GREEN, RED, BLUE, ORANGE, GREY = "#188a3e", "#c00", "#1a5fb4", "#b06000", "#555"
+
+I0, I1 = 14, 27 # fereastra desenată din seria completă
+cds_all, hammer, conf = serie()
+cds = cds_all[I0:I1 + 1]
+HAM, CONF = 19 - I0, 20 - I0 # indicii hammer / confirmare în fereastră
+
+INTRARE, SL = 63.35, 62.05
+RISC = round(INTRARE - SL, 2)
+CONT, PROCENT = 10_000, 0.02
+RISC_LEI = CONT * PROCENT
+ACTIUNI = int(RISC_LEI // RISC)
+
+# --- verificări -------------------------------------------------------------
+assert cds[HAM] is hammer and cds[CONF] is conf, "fereastra nu prinde hammer-ul și confirmarea"
+assert round(SL, 2) == round(hammer.l - 0.05, 2), "SL nu e la 5 cenți sub low-ul hammer-ului"
+assert round(conf.c, 2) == INTRARE and conf.c > hammer.h, "intrarea nu e close-ul confirmării"
+assert RISC == 1.30, RISC
+assert RISC_LEI == 200 and ACTIUNI == 153, (RISC_LEI, ACTIUNI)
+assert min(c.l for c in cds) == hammer.l, "în fereastră, cel mai jos low nu e al hammer-ului"
+assert SL < min(c.l for c in cds), "linia stopului trebuie să stea sub toate fitilele"
+# pierderea maximă planificată încape în regula de 2 %
+assert ACTIUNI * RISC <= RISC_LEI, "pierderea planificată depășește 2 % din cont"
+
+ch = Chart(
+ candles=cds,
+ levels=[Level(INTRARE, "intrare 63.35 (close-ul confirmării)", GREEN, label_at="left"),
+ Level(SL, "SL 62.05", RED, label_at="left")],
+ zones=[Zone(HAM, CONF, "cele două bare care dau stopul", ORANGE)],
+ arrows=[
+ Arrow(HAM, "low-ul hammer-ului: 62.10\nsub el, cererea care a făcut\nfitilul nu mai există",
+ where="below", price=hammer.l, color=BLUE, dx=-176, dy=26),
+ Arrow(CONF, "riscul e distanța intrare - SL:\n63.35 - 62.05 = 1.30 $ pe acțiune",
+ where="above", color=GREY, dx=186, dy=-4),
+ ],
+ scale=44.0, gap=26, body_w=13, left=64, width=680, vol_h=0,
+ title="Buy hammer — stopul și mărimea poziției (daily, ticker ilustrativ XYZ)",
+)
+svg = ch.render()
+m = re.search(r'viewBox="0 0 (\d+) (\d+)"', svg)
+W, H = int(m.group(1)), int(m.group(2))
+
+# paranteza de risc, desenată de mână: stop -> intrare, cu textul în dreapta
+OFF = (float(re.search(r''
+ f'1R = 1.30 $')
+
+linii = [
+ ("Mărimea poziției, cu cifrele lecției: 2 % dintr-un cont de 10.000 $ = 200 $ de risc.", "#333"),
+ (f"200 $ / 1.30 $ pe acțiune = {200 / RISC:.1f} → {ACTIUNI} de acțiuni (rotunjit în jos); "
+ f"pierderea maximă {ACTIUNI * RISC:.2f} $.", "#333"),
+ ("Cei 5 cenți de sub low sunt marja folosită în tot capitolul pe exemplele construite de "
+ "mine; unde cifra vine din material, e spusă acolo.", GREY),
+]
+extra = 16 * len(linii) + 10
+svg = svg.replace(f'viewBox="0 0 {W} {H}"', f'viewBox="0 0 {W} {H + extra}"')
+svg = svg.replace("", brk + "".join(
+ f'{t}'
+ for i, (t, c) in enumerate(linii)) + "")
+assert "\n\n" not in svg, "linie goală în SVG"
+OUT.write_text(svg, encoding="utf-8")
+print(f"scris {OUT.name} ({W}x{H + extra}); risc {RISC}, {ACTIUNI} acțiuni")
diff --git a/gen/strategii/hammer_mbt10_bh_stop.svg b/gen/strategii/hammer_mbt10_bh_stop.svg
new file mode 100644
index 0000000..a033351
--- /dev/null
+++ b/gen/strategii/hammer_mbt10_bh_stop.svg
@@ -0,0 +1,47 @@
+
\ No newline at end of file
diff --git a/gen/strategii/hammer_mbt10_bh_trend.py b/gen/strategii/hammer_mbt10_bh_trend.py
new file mode 100644
index 0000000..4543365
--- /dev/null
+++ b/gen/strategii/hammer_mbt10_bh_trend.py
@@ -0,0 +1,99 @@
+#!/usr/bin/env python3
+"""Aceeași candelă, două contexte: hammer (după scădere) vs. hanging man (după urcare).
+
+Două panouri alăturate, aceeași scară px/unitate. Bara 9 e IDENTICĂ în ambele
+(o 63.05, h = c 63.30, l 62.10) — verificat prin assert; ce diferă e ce s-a
+întâmplat înaintea ei. Panourile nu împart axa de preț: seriile trăiesc în
+zone de preț diferite, iar comparația cerută aici e de context, nu de nivel.
+
+Rulare: python3 gen/strategii/hammer_mbt10_bh_trend.py
+"""
+import re
+import sys; sys.path.insert(0, "/workspace/atm-curs-dl")
+from pathlib import Path
+from svg_candles import Arrow, Chart, Zone, from_closes
+
+OUT = Path(__file__).with_suffix(".svg")
+GREEN, RED, BLUE, ORANGE, GREY = "#188a3e", "#c00", "#1a5fb4", "#b06000", "#555"
+
+HAM = (63.05, 63.30, 62.10, 63.30) # o, h, l, c — bara 9, în ambele panouri
+
+JOS = [68.00, 67.20, 66.50, 66.80, 65.60, 64.90, 65.10, 64.20, 63.05,
+ 63.30, 63.35, 63.80]
+SUS = [58.00, 58.70, 59.40, 59.20, 60.30, 61.00, 61.80, 62.40, 63.05,
+ 63.30, 62.00, 61.40]
+
+
+def serie(closes):
+ w = [(0.15, 0.15)] * len(closes)
+ w[9] = (0.00, 0.95) # bara-hammer: high = close, fitil jos 0.95
+ return from_closes(closes, wicks=w, start=closes[0] + (0.4 if closes[0] > 60 else -0.4))
+
+
+L, R = serie(JOS), serie(SUS)
+
+# --- verificări -------------------------------------------------------------
+for s, nume in ((L, "stânga"), (R, "dreapta")):
+ for i in range(1, len(s)):
+ assert abs(s[i].o - s[i - 1].c) < 1e-9, f"{nume}: open[{i}] != close[{i-1}]"
+ b = s[9]
+ assert (round(b.o, 2), round(b.h, 2), round(b.l, 2), round(b.c, 2)) == HAM, (nume, b)
+assert (L[9].o, L[9].h, L[9].l, L[9].c) == (R[9].o, R[9].h, R[9].l, R[9].c), \
+ "bara 9 trebuie să fie identică în ambele panouri"
+corp, jos = HAM[3] - HAM[0], HAM[0] - HAM[2]
+assert jos >= 2 * corp, "bara 9 nu trece regula de formă"
+assert L[8].c < L[0].c - 4, "stânga: scăderea nu se vede"
+assert R[8].c > R[0].c + 4, "dreapta: urcarea nu se vede"
+assert L[10].c > L[9].h, "stânga: bara 10 nu confirmă peste high-ul hammer-ului"
+assert R[10].c < R[9].l, "dreapta: bara 10 nu închide sub low-ul candelei"
+
+# --- desen ------------------------------------------------------------------
+W, PW = 780, 358
+PX = [24, 24 + PW + 20]
+SCALE, TOP, SHIFT = 26.0, 104, 34
+
+panels, inalt = [], []
+for px, cds, (zt, zc), arr in (
+ (PX[0], L, ("scădere de 9 candele", RED),
+ [Arrow(9, "hammer 63.30 / 62.10: prețul a fost dus la 62.10\nși readus sus, la 63.30",
+ where="below", color=ORANGE, dx=-70, dy=34),
+ Arrow(10, "close 63.35, peste high-ul hammer-ului:\nsemnalul e valid, aici intri",
+ where="above", color=GREEN, dx=-96, dy=-24)]),
+ (PX[1], R, ("urcare de 9 candele", GREEN),
+ [Arrow(9, "aceeași candelă 63.30 / 62.10, dar după o urcare:\nse numește hanging man și avertizează de scădere",
+ where="below", color=RED, dx=-250, dy=98),
+ Arrow(10, "close 62.00, sub low-ul ei: scăderea\nîncepe — nu era semnal de cumpărare",
+ where="above", color=RED, dx=-70, dy=-18)]),
+):
+ ch = Chart(candles=cds, arrows=arr,
+ zones=[Zone(0, 8, zt, zc)],
+ scale=SCALE, gap=24, body_w=12, left=px + 46, width=px + PW, top=TOP)
+ body = ch._render()
+ assert f'viewBox="0 0 {px + PW} ' in body, f"panoul {px}: ceva iese din cadru"
+ assert ch.scale == SCALE
+ corp_panou = body.split(">", 1)[1].rsplit("", 1)[0].strip()
+ panels.append(f'{corp_panou}')
+ inalt.append(float(re.search(r'viewBox="0 0 [\d.]+ ([\d.]+)"', body).group(1)))
+
+H = int(max(inalt)) + SHIFT + 46
+parts = [
+ f'",
+]
+svg = "\n ".join(parts).replace("\n ", "\n")
+assert "\n\n" not in svg, "linie goală în SVG"
+OUT.write_text(svg, encoding="utf-8")
+print(f"scris {OUT.name} ({W}x{H})")
diff --git a/gen/strategii/hammer_mbt10_bh_trend.svg b/gen/strategii/hammer_mbt10_bh_trend.svg
new file mode 100644
index 0000000..4acc22d
--- /dev/null
+++ b/gen/strategii/hammer_mbt10_bh_trend.svg
@@ -0,0 +1,76 @@
+
\ No newline at end of file
diff --git a/gen/strategii/hammer_mbt10_bh_trigger.py b/gen/strategii/hammer_mbt10_bh_trigger.py
new file mode 100644
index 0000000..f72efa2
--- /dev/null
+++ b/gen/strategii/hammer_mbt10_bh_trigger.py
@@ -0,0 +1,104 @@
+#!/usr/bin/env python3
+"""Trigger-ul buy hammer: un singur DA și două feluri de NU.
+
+Trei panouri cu ACELEAȘI șase bare (scădere + hammer 63.30 / 62.10) și cu o
+singură bară diferită la final — candela următoare. Axa de preț e comună:
+toate trei folosesc același `pmax`, verificat prin assert, deci liniile
+63.30 și 62.10 cad la același y pe toate panourile.
+
+Rulare: python3 gen/strategii/hammer_mbt10_bh_trigger.py
+"""
+import re
+import sys; sys.path.insert(0, "/workspace/atm-curs-dl")
+from pathlib import Path
+from svg_candles import Arrow, Chart, from_closes
+
+OUT = Path(__file__).with_suffix(".svg")
+GREEN, RED, BLUE, ORANGE, GREY = "#188a3e", "#c00", "#1a5fb4", "#b06000", "#555"
+
+BAZA = [65.20, 64.60, 64.10, 63.60, 63.05, 63.30] # 5 = hammer
+H_HAM, L_HAM = 63.30, 62.10
+ULTIMA = {"DA": 63.35, "DEVREME": 63.20, "MORT": 62.00}
+
+
+def serie(ultim):
+ closes = BAZA + [ultim]
+ w = [(0.15, 0.15)] * len(closes)
+ w[5] = (0.00, 0.95)
+ return from_closes(closes, wicks=w, start=65.70)
+
+
+S = {k: serie(v) for k, v in ULTIMA.items()}
+
+# --- verificări -------------------------------------------------------------
+for k, cds in S.items():
+ for i in range(1, len(cds)):
+ assert abs(cds[i].o - cds[i - 1].c) < 1e-9, f"{k}: open[{i}] != close[{i-1}]"
+ h = cds[5]
+ assert (round(h.h, 2), round(h.l, 2)) == (H_HAM, L_HAM), (k, h)
+ assert (h.o - h.l) >= 2 * (h.c - h.o), f"{k}: bara 5 nu e hammer"
+ assert [round(c.c, 2) for c in cds[:6]] == BAZA, k
+assert S["DA"][6].c > H_HAM, "DA: candela următoare nu închide peste high"
+assert L_HAM <= S["DEVREME"][6].c <= H_HAM, "DEVREME: închiderea trebuie între low și high"
+assert S["MORT"][6].c < L_HAM, "MORT: candela următoare nu închide sub low"
+
+# --- layout -----------------------------------------------------------------
+W, PW = 760, 240
+PX = [12, 12 + PW + 14, 12 + 2 * (PW + 14)]
+SCALE, TOP, SHIFT = 46.0, 96, 30
+
+CFG = [
+ ("DA", PX[0], GREEN, "DA — închide peste high",
+ Arrow(6, "close 63.35, peste 63.30:\naici intri", where="above", color=GREEN, dx=-70, dy=-18)),
+ ("DEVREME", PX[1], RED, "NU — încă sub high",
+ Arrow(6, "close 63.20, sub 63.30:\nnu s-a întâmplat nimic,\naștepți mai departe",
+ where="above", color=RED, dx=-78, dy=-18)),
+ ("MORT", PX[2], RED, "NU — închide sub low",
+ Arrow(6, "close 62.00, sub 62.10:\nvânzătorii au dus prețul\nsub fitil — setup mort",
+ where="below", color=RED, dx=-84, dy=26)),
+]
+
+bodies, inalt, charts = [], [], {}
+for k, px, col, _, arr in CFG:
+ ch = Chart(candles=S[k], arrows=[arr], scale=SCALE, gap=20, body_w=11,
+ left=px + 46, width=px + PW, top=TOP)
+ body = ch._render()
+ assert f'viewBox="0 0 {px + PW} ' in body, f"panoul {k}: textul iese din cadru"
+ assert (ch.top, ch.left) == (TOP, px + 46), f"panoul {k}: Chart a mutat graficul"
+ charts[k] = ch
+ inalt.append(float(re.search(r'viewBox="0 0 [\d.]+ ([\d.]+)"', body).group(1)))
+ bodies.append(f''
+ + body.split(">", 1)[1].rsplit("", 1)[0].strip() + '')
+pmax = {k: c._pmax for k, c in charts.items()}
+assert len(set(pmax.values())) == 1, f"panourile n-au aceeași axă: {pmax}"
+y = lambda p: charts["DA"]._y(p) + SHIFT # noqa: E731
+
+H = int(max(inalt)) + SHIFT + 44
+parts = [
+ f'",
+]
+svg = "\n ".join(parts).replace("\n ", "\n")
+assert "\n\n" not in svg, "linie goală în SVG"
+OUT.write_text(svg, encoding="utf-8")
+print(f"scris {OUT.name} ({W}x{H})")
diff --git a/gen/strategii/hammer_mbt10_bh_trigger.svg b/gen/strategii/hammer_mbt10_bh_trigger.svg
new file mode 100644
index 0000000..9d8f636
--- /dev/null
+++ b/gen/strategii/hammer_mbt10_bh_trigger.svg
@@ -0,0 +1,78 @@
+
\ No newline at end of file
diff --git a/gen/strategii/hammer_mbt10_mbt_confluente.py b/gen/strategii/hammer_mbt10_mbt_confluente.py
new file mode 100644
index 0000000..2e9f5bb
--- /dev/null
+++ b/gen/strategii/hammer_mbt10_mbt_confluente.py
@@ -0,0 +1,114 @@
+#!/usr/bin/env python3
+"""MBT-10, pasul „unde": ideea de bottom cu confluențe (exemplu construit).
+
+Seria de aici e SURSA UNICĂ a trade-ului MBT-10 din Nivelul 2:
+`hammer_mbt10_mbt_ppd`, `_mbt_stop` și `_mbt_iesire` importă `serie()` și
+pornesc din aceleași bare. Nivelurile 89.90 (intrare la close), 87.30 (SL) și
+95.20 (ținta, vârful de unde a pornit coborârea) sunt cifrele exemplului
+construit din fișierul principal; suportul weekly 87.50 și pivotul daily
+89.60 sunt puse de mine, ca acele cifre să aibă de unde ieși.
+
+Diagrama asta arată doar contextul: double bottom pe suportul weekly, cu
+volum mai mic pe al doilea picior. Nicio intrare desenată — intrarea vine la
+pasul următor.
+
+Rulare: python3 gen/strategii/hammer_mbt10_mbt_confluente.py
+"""
+import re
+import sys; sys.path.insert(0, "/workspace/atm-curs-dl")
+from pathlib import Path
+from svg_candles import Arrow, Chart, Level, Zone, from_closes
+
+OUT = Path(__file__).with_suffix(".svg")
+GREEN, RED, BLUE, ORANGE, GREY = "#188a3e", "#c00", "#1a5fb4", "#b06000", "#555"
+
+SUP_W, PPD = 87.50, 89.60 # suport weekly, pivotul daily
+INTRARE, SL, TP = 89.90, 87.30, 95.20
+R = round(INTRARE - SL, 2)
+FUND1, FUND2 = 10, 15 # cele două picioare ale double bottom-ului
+CLOSE_I = 19 # bara care se închide peste pivotul daily
+
+CLOSES = [96.00, 95.00, 94.20, 94.60, 93.10, 92.00, 91.20, 90.10, 89.00, # 0-8
+ 88.20, 87.70, 88.40, 89.10, # 9-12
+ 88.60, 88.00, 87.65, 88.30, # 13-16
+ 88.90, 89.40, 89.90, # 17-19
+ 90.60, 91.40, 92.20, 91.80, 93.00, 94.10, 95.40] # 20-26
+VOLS = [60, 58, 55, 50, 62, 66, 70, 74, 80,
+ 86, 94, 62, 58,
+ 55, 60, 66, 54, # piciorul 2: volum vizibil mai mic
+ 58, 62, 96,
+ 70, 66, 64, 58, 68, 72, 80]
+
+
+def serie():
+ w = [(0.20, 0.25)] * len(CLOSES)
+ w[FUND1] = (0.20, 0.10) # low 87.60
+ w[FUND2] = (0.20, 0.10) # low 87.55
+ # barele imediat de după picioare pornesc din același preț: fără fitil lung
+ # în jos, altfel ele ar face minimul, nu picioarele
+ w[FUND1 + 1] = w[FUND2 + 1] = (0.20, 0.05)
+ # bara 18 atinge pivotul intraday, dar se închide sub el: contraexemplul
+ # de la pasul cu condiția de preț
+ w[CLOSE_I - 1] = (0.30, 0.25)
+ cds = from_closes(CLOSES, wicks=w, vols=VOLS, start=96.60)
+ assert len(cds) == len(VOLS) == 27
+ for i in range(1, len(cds)):
+ assert abs(cds[i].o - cds[i - 1].c) < 1e-9, f"open[{i}] != close[{i-1}]"
+ l1, l2 = round(cds[FUND1].l, 2), round(cds[FUND2].l, 2)
+ assert (l1, l2) == (87.60, 87.55), (l1, l2)
+ assert abs(l1 - l2) <= 0.05 and l2 < l1, "double bottom: al doilea picior, puțin mai jos"
+ assert round(min(c.l for c in cds), 2) == l2, "cel mai jos low nu e al piciorului 2"
+ assert l2 > SUP_W > SL, "picioarele trebuie să stea peste suport, iar stopul sub el"
+ assert VOLS[FUND2] < VOLS[FUND1], "fără divergență de volum pe piciorul 2"
+ # de la al doilea picior încoace, nicio bară nu închide peste pivotul zilei
+ # intrării — altfel trigger-ul ar fi venit mai devreme
+ assert max(c.c for c in cds[FUND2:CLOSE_I]) < PPD < cds[CLOSE_I].c, \
+ "o bară dinaintea trigger-ului închide deja peste pivotul daily"
+ assert round(cds[CLOSE_I].c, 2) == INTRARE
+ assert cds[CLOSE_I - 1].h > PPD > cds[CLOSE_I - 1].c, \
+ "bara 18 trebuie să atingă pivotul intraday, dar să închidă sub el"
+ assert min(c.l for c in cds[CLOSE_I:]) > SL, "stopul ar fi fost atins după intrare"
+ prim_tp = next(i for i in range(CLOSE_I + 1, len(cds)) if cds[i].h >= TP)
+ assert prim_tp == 26, prim_tp
+ return cds
+
+
+if __name__ == "__main__":
+ cds = serie()
+ pct1 = round((cds[FUND1].l - SUP_W) / SUP_W * 100, 2)
+ pct2 = round((cds[FUND2].l - SUP_W) / SUP_W * 100, 2)
+ assert pct1 < 0.2 and pct2 < 0.2, (pct1, pct2)
+ vis = cds[:CLOSE_I] # contextul, fără bara de trigger
+
+ ch = Chart(
+ candles=vis,
+ levels=[Level(SUP_W, "suport weekly 87.50", BLUE, label_at="left")],
+ zones=[Zone(FUND1, FUND2, "double bottom: 87.60 și 87.55", ORANGE)],
+ arrows=[
+ Arrow(FUND1, "piciorul 1: low 87.60,\nla 0,11 % peste suport",
+ where="below", color=GREY, dx=-150, dy=26),
+ Arrow(FUND2, "piciorul 2: low 87.55, cu 5 cenți mai jos,\ndar cu volum mai mic decât "
+ "la piciorul 1 —\nvânzarea a slăbit (divergență de volum)",
+ where="below", color=RED, dx=-40, dy=64),
+ ],
+ scale=30.0, gap=22, body_w=12, left=54, width=700, vol_h=70, vol_label="volum",
+ title="MBT-10, pasul „unde\" — double bottom pe suportul weekly (exemplu construit)",
+ )
+ svg = ch.render()
+ m = re.search(r'viewBox="0 0 (\d+) (\d+)"', svg)
+ W, H = int(m.group(1)), int(m.group(2))
+ linii = [
+ ("Aici nu intri încă: diagrama arată doar ideea de bottom. MBT-10 e ultima piesă, și "
+ "vine abia la close-ul unei bare.", "#333"),
+ ("Cifrele 89.90 / 87.30 / 95.20 ale trade-ului sunt din exemplul construit al "
+ "materialului;", GREY),
+ ("suportul weekly 87.50, pivotul daily 89.60 și structura barelor sunt ale mele.", GREY),
+ ]
+ add = 16 * len(linii) + 10
+ svg = svg.replace(f'viewBox="0 0 {W} {H}"', f'viewBox="0 0 {W} {H + add}"')
+ svg = svg.replace("", "".join(
+ f'{t}'
+ for i, (t, c) in enumerate(linii)) + "")
+ assert "\n\n" not in svg, "linie goală în SVG"
+ OUT.write_text(svg, encoding="utf-8")
+ print(f"scris {OUT.name} ({W}x{H + add}); picioare {pct1}% / {pct2}% peste suport, R={R}")
diff --git a/gen/strategii/hammer_mbt10_mbt_confluente.svg b/gen/strategii/hammer_mbt10_mbt_confluente.svg
new file mode 100644
index 0000000..a935ec1
--- /dev/null
+++ b/gen/strategii/hammer_mbt10_mbt_confluente.svg
@@ -0,0 +1,75 @@
+
\ No newline at end of file
diff --git a/gen/strategii/hammer_mbt10_mbt_ppd.py b/gen/strategii/hammer_mbt10_mbt_ppd.py
new file mode 100644
index 0000000..20116a6
--- /dev/null
+++ b/gen/strategii/hammer_mbt10_mbt_ppd.py
@@ -0,0 +1,64 @@
+#!/usr/bin/env python3
+"""MBT-10 — singura bucată reconstituibilă: close peste pivotul daily.
+
+Aceleași bare ca la pasul „unde" (importate din `hammer_mbt10_mbt_confluente`),
+tăiate în jurul trigger-ului. Bara 18 atinge pivotul 89.60 în timpul zilei,
+dar se închide sub el: nu e semnal. Bara 19 se închide la 89.90, peste pivot:
+aici intri, la close.
+
+Rulare: python3 gen/strategii/hammer_mbt10_mbt_ppd.py
+"""
+import re
+import sys; sys.path.insert(0, "/workspace/atm-curs-dl")
+sys.path.insert(0, "/workspace/atm-curs-dl/gen/strategii")
+from pathlib import Path
+from hammer_mbt10_mbt_confluente import CLOSE_I, INTRARE, PPD, SUP_W, serie
+from svg_candles import Arrow, Chart, Level, Zone
+
+OUT = Path(__file__).with_suffix(".svg")
+GREEN, RED, BLUE, ORANGE, GREY = "#188a3e", "#c00", "#1a5fb4", "#b06000", "#555"
+
+cds = serie()
+I0, I1 = 9, 22
+w = cds[I0:I1 + 1]
+sh = lambda i: i - I0 # noqa: E731
+NU, DA = CLOSE_I - 1, CLOSE_I
+
+assert cds[NU].h > PPD > cds[NU].c, "bara 18 nu mai e contraexemplul așteptat"
+assert cds[DA].c > PPD and round(cds[DA].c, 2) == INTRARE
+assert 0 <= sh(NU) < len(w) and sh(DA) < len(w), "fereastra nu prinde cele două bare"
+assert max(c.c for c in cds[I0:DA]) < PPD, "o bară din fereastră închide deja peste pivot"
+
+ch = Chart(
+ candles=w,
+ levels=[Level(PPD, "pivotul daily 89.60", BLUE),
+ Level(SUP_W, "suport weekly 87.50", GREY, label_at="left")],
+ zones=[Zone(sh(NU), sh(DA), "cele două zile care se compară", ORANGE)],
+ arrows=[
+ Arrow(sh(NU), "a atins 89.70 în timpul zilei,\ndar s-a închis la 89.40, sub pivot:\nnu e semnal",
+ where="below", color=RED, dx=-190, dy=40),
+ Arrow(sh(DA), "s-a închis la 89.90, peste pivot:\naici intri, la close-ul zilei",
+ where="above", color=GREEN, dx=-70, dy=-20),
+ ],
+ scale=44.0, gap=26, body_w=13, left=64, width=680, vol_h=60, vol_label="volum",
+ title="MBT-10 — condiția de preț: close peste pivotul daily (exemplu construit)",
+)
+svg = ch.render()
+m = re.search(r'viewBox="0 0 (\d+) (\d+)"', svg)
+W, H = int(m.group(1)), int(m.group(2))
+linii = [
+ ("Pivotul daily se recalculează în fiecare zi din maximul, minimul și închiderea zilei "
+ "precedente;", "#333"),
+ ("linia desenată e valoarea din ziua intrării, 89.60, nu un nivel fix.", "#333"),
+ ("Asta e tot ce se poate verifica din MBT-10 fără indicatorul proprietar — „prima "
+ "componentă\" din el,", GREY),
+ ("cum îi spune materialul; restul formulei nu e publicat nicăieri.", GREY),
+]
+add = 16 * len(linii) + 10
+svg = svg.replace(f'viewBox="0 0 {W} {H}"', f'viewBox="0 0 {W} {H + add}"')
+svg = svg.replace("", "".join(
+ f'{t}'
+ for i, (t, c) in enumerate(linii)) + "")
+assert "\n\n" not in svg, "linie goală în SVG"
+OUT.write_text(svg, encoding="utf-8")
+print(f"scris {OUT.name} ({W}x{H + add})")
diff --git a/gen/strategii/hammer_mbt10_mbt_ppd.svg b/gen/strategii/hammer_mbt10_mbt_ppd.svg
new file mode 100644
index 0000000..d1c1388
--- /dev/null
+++ b/gen/strategii/hammer_mbt10_mbt_ppd.svg
@@ -0,0 +1,62 @@
+
\ No newline at end of file