"""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 "", "".join( f'{t}' for i, t in enumerate(legenda)) + "") 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']*)>([^<]*)', 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}")