"""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}" # etichetele goale (nivelurile-distanțier) nu au ce căuta în SVG: auditul le # citește ca text suprapus peste ce e sub ele svg = re.sub(r"\n\s*]*>\s*", "", svg) assert svg.count("\n\n") == 0 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}")