From 3143e35ed9354e962111d67dc3f1b7312a86c90b Mon Sep 17 00:00:00 2001 From: Claude Agent Date: Sun, 20 Sep 2026 12:43:53 +0000 Subject: [PATCH] mbp: patru diagrame noi, pentru pasii care nu aveau MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Context (DA/NU: cadere in suport major vs. consolidare stransa, cu benzile Bollinger calculate din close-uri), trigger (aceeasi candela M in ambele panouri), stop (fereastra de 4 candele, 1R si sizing derivate) si iesire (TP1 -> break even -> trail, fata de capcana iesirii la 50 %, unde prețul urca apoi la +2R). Aceeasi regula de stop in toate cinci diagramele ale capitolului, si banda inferioara calibrata explicit ca sa nu existe doua valori pentru acelasi nivel. Textul capitolului e neatins — agentul a atins pragul de context. SVG-urile sunt deocamdata fisiere in plus, fara marcaje care sa le ceara. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01ThN7ixZrowca8aTGrK1MUJ --- gen/strategii/mbp_context.py | 248 ++++++++++++++++++++++++++++++++++ gen/strategii/mbp_context.svg | 147 ++++++++++++++++++++ gen/strategii/mbp_iesire.py | 190 ++++++++++++++++++++++++++ gen/strategii/mbp_iesire.svg | 97 +++++++++++++ gen/strategii/mbp_stop.py | 154 +++++++++++++++++++++ gen/strategii/mbp_stop.svg | 63 +++++++++ gen/strategii/mbp_trigger.py | 165 ++++++++++++++++++++++ gen/strategii/mbp_trigger.svg | 100 ++++++++++++++ 8 files changed, 1164 insertions(+) create mode 100644 gen/strategii/mbp_context.py create mode 100644 gen/strategii/mbp_context.svg create mode 100644 gen/strategii/mbp_iesire.py create mode 100644 gen/strategii/mbp_iesire.svg create mode 100644 gen/strategii/mbp_stop.py create mode 100644 gen/strategii/mbp_stop.svg create mode 100644 gen/strategii/mbp_trigger.py create mode 100644 gen/strategii/mbp_trigger.svg 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'', + f'Pasul 1 — unde are voie ' + f'strategia să caute: mișcare rapidă într-un nivel major', + f'DA — cădere rapidă în ' + f'suport, banda atinsă', + f'NU — consolidare strânsă, ' + f'banda nu se atinge', + 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) + +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