"""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)")