Files
atm-curs-dl/gen/strategii/mbp_iesire.py
Claude Agent bd24084c29 17_STRATEGII/08_mbp: trail-ul pus la locul lui, si contradictia care venea din el
Capitolul luase regula de trail a variantei set-and-forget (sub minimul
fiecarei candele inchise) si o pusese la varianta cu 3 pozitii. Regula
pozitiei 3 e Bull Trail, apoi ultimul swing low. Varianta candela-cu-candela
ramane unde ii e locul, cu avertisment sa nu se amestece.

De aici venea si contradictia: acelasi trade iesea la +4R in diagrama de
ansamblu si la +1.29R la pasul 5. Diagrama de iesire e refacuta cu trail pe
doua trepte de swing low, fiecare pornind din bara care o ridica (Level.i0),
si da +4.00R, ca ansamblul. Contrastul cu trail-ul fin e calculat in generator
pe aceeasi serie, nu inventat.

Restul: motivul inventat pentru TICUS (in TradeStation e gratuit, pe
TradingView costa), universul de instrumente marcat ca judecata proprie,
"nu a adaugat nimic" -> "1-2%, nu cat sa merite", nivelul 623.60 marcat ca
al meu, si trimiterile din fisa.

eticheta_libera a prins trei suprapuneri reale in generatorul refacut.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ThN7ixZrowca8aTGrK1MUJ
2026-10-02 05:01:32 +00:00

239 lines
12 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""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), iar restul merge pe trail **pe ultimul
swing low** — regula poziției 3 din lecția principală (Bull Trail, apoi
swing low dacă se formează unul mai sus). Fiecare treaptă pornește de la
swing low-ul care o declanșează, nu din marginea graficului.
- 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, treptele de trail sunt
swing low-urile găsite în serie (minim local confirmat de o bară cu low mai
sus), iar ieșirea e prima închidere sub ultima treaptă.
Rulează: python3 gen/strategii/mbp_iesire.py
"""
import re
import sys
from pathlib import Path
sys.path.insert(0, "/workspace/atm-curs-dl")
sys.path.insert(0, "/workspace/atm-curs-dl/gen")
from svg_candles import Arrow, Chart, Level, Mark, from_closes # noqa: E402
from eticheta_libera import eticheta_libera # 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: TP1, apoi două pullback-uri care lasă fiecare un swing low mai sus;
# trail-ul urcă pe ele și te scoate abia la a treia coborâre, pe la +4R.
DA = PRE + [624.70, 624.78, 624.60, 625.05, 625.45, 625.70, 625.52, 625.92, 625.35]
W_DA = W_PRE + [(0.04, 0.04), (0.04, 0.03), (0.03, 0.09), (0.05, 0.04),
(0.05, 0.03), (0.08, 0.02), (0.03, 0.14), (0.05, 0.03),
(0.02, 0.04)]
# 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.20)
nu = from_closes(NU, wicks=W_NU, start=624.20)
assert len(da) == 13 and 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 + 1], nu[:E + 1]))
tp_i = next(i for i in range(E + 1, len(da)) if da[i].h >= TP1)
assert tp_i == E + 1, tp_i # TP1 atins de fitilul barei
# ── trail-ul pe swing low ──────────────────────────────────────────────────
# Un swing low = minim local; devine treaptă de trail din momentul în care o
# bară de după el închide un low mai sus. Treptele se caută în serie, nu se
# scriu de mână; stopul nu coboară niciodată.
swings = [i for i in range(1, len(da) - 1)
if da[i].l < da[i - 1].l and da[i].l < da[i + 1].l]
trepte = [i for i in swings if i > tp_i]
assert trepte == [6, 10], trepte
assert all(da[j].l > da[i].l for i, j in zip(trepte, trepte[1:])), "treptele urcă"
iesire_i = next(i for i in range(trepte[-1] + 1, len(da)) if da[i].c < da[trepte[-1]].l)
trail = da[trepte[-1]].l
iesire = da[iesire_i].c
assert iesire_i == len(da) - 1, iesire_i
r_iesire = (iesire - ENTRY) / RISC
assert abs(r_iesire - 4.0) < 0.02, r_iesire # ~1:4, cât spune materialul
# nicio treaptă de trail nu are voie să treacă peste low-ul unei bare din
# dreapta ei — altfel linia ar fi desenată deasupra prețului. Bara care rupe
# trail-ul (ieșirea) e singura excepție, prin definiție.
for i in trepte:
for j in range(i + 1, iesire_i):
assert da[j].l > da[i].l, (i, j, da[i].l, da[j].l)
assert da[iesire_i].l < trail < da[iesire_i - 1].l + 1
# contrastul cu varianta „set and forget": trail sub minimul fiecărei candele
# închise, pe exact aceeași structură — prima corecție te scoate.
cc_i = next(i for i in range(tp_i + 1, len(da)) if da[i].c < da[i - 1].l)
cc, r_cc = da[cc_i].c, (da[cc_i].c - ENTRY) / RISC
assert cc_i < iesire_i and r_cc < r_iesire, (cc_i, r_cc)
# 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"
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 = 158.0, 28, 16, 46
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, marks, pw):
ch = Chart(candles=cds, levels=levels + SPACER, arrows=arrows, marks=marks, 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("</svg>")].strip("\n")
ET_DA = {f"swing low {da[i].l:.2f}" for i in trepte}
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="right"),
Level(SL, f"SL inițial {SL:.2f}", RED, dash="4 3", label_at="left")]
+ [Level(da[i].l, "", ORANGE, dash="1 3", i0=i) for i in trepte],
[Arrow(tp_i, f"prima tranșă iese la {TP1:.2f}", where="above", color=BLUE, dy=-4),
Arrow(iesire_i, f"închidere sub trail:\nieși cu +{r_iesire:.2f}R", where="below",
color=ORANGE)],
[Mark(i, f"swing low {da[i].l:.2f}", color=ORANGE, where="below", dy=400) for i in trepte],
404)
ch_nu, w_nu, h_nu, in_nu = panel(
nu,
[Level(TP1, f"TP1 {TP1:.2f} (1:1)", BLUE, dash="4 3", label_at="left"),
Level(half, f"stop la 50 %: {half:.3f}", RED, dash="4 3", label_at="right"),
Level(ENTRY, f"intrare {ENTRY:.2f}", GREEN, dash="2 3", label_at="right"),
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)],
[], 420)
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 —",
"iar a treia tranșă merge pe trail pe ultimul swing low — liniile portocalii punctate.",
f"Treapta 1 = {da[trepte[0]].l:.2f}, treapta 2 = {trail:.2f}; fiecare pornește de la swing "
f"low-ul care o ridică, nu din marginea graficului.",
f"Ieși la prima închidere sub ultima treaptă, {iesire:.2f} = +{r_iesire:.1f}R — de aici vine "
f"„realizat ~1:4\" din material; prețul atinsese {max(c.h for c in da):.2f}.",
"Trail-ul sub minimul fiecărei candele închise e pasul 4 al variantei „set and forget\", "
"nu regula poziției 3:",
f"pe aceeași structură te-ar fi scos la prima corecție, {cc:.2f} = +{r_cc:.1f}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'<svg viewBox="0 0 {RW:.0f} {root_h:.0f}" xmlns="http://www.w3.org/2000/svg" '
f'style="width:100%;height:auto;font:12px sans-serif">',
'<text x="8" y="20" fill="#333" style="font-weight:bold">Pasul 5 — unde muți stopul '
'după ce prima tranșă a ieșit</text>',
f'<text x="8" y="40" fill="{GREEN}" style="font-weight:bold">DA — break even, apoi '
f'trail pe ultimul swing low</text>',
f'<text x="{X_NU}" y="40" fill="{RED}" style="font-weight:bold">NU — stopul mutat la '
f'„50 %"</text>',
f'<svg x="{X_DA}" y="{TOP}" width="{w_da}" height="{PH:.0f}" viewBox="0 0 {w_da} {PH:.0f}">'
f'{in_da}</svg>',
f'<svg x="{X_NU}" y="{TOP}" width="{w_nu}" height="{PH:.0f}" viewBox="0 0 {w_nu} {PH:.0f}">'
f'{in_nu}</svg>']
fy = TOP + PH + 24
out += [f'<text x="8" y="{fy + 17 * i:.0f}" fill="#555">{t}</text>' for i, t in enumerate(FOOTER)]
out.append("</svg>")
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'<text x="([-\d.]+)" y="([-\d.]+)"([^>]*)>(.*?)</text>', 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")
# etichetele de trail și marcajele de swing low nu au voie să stea pe desen
eticheta_libera(in_da, "panoul DA", ET_DA, rects=True)
# etichetele de nivel stau lipite de linia lor prin construcție; aici contează
# doar să nu cadă peste corpurile candelelor
NIV = {f"TP1 {TP1:.2f} (1:1)", f"intrare {ENTRY:.2f} = break even", f"intrare {ENTRY:.2f}",
f"SL inițial {SL:.2f}", f"stop la 50 %: {half:.3f}"}
eticheta_libera(in_da, "nivelurile DA", NIV, rects=True, segmente=False)
eticheta_libera(in_nu, "nivelurile NU", NIV, rects=True, segmente=False)
assert svg.count("\n\n") == 0, "linie goală în SVG rupe blocul din markdown"
assert svg.count("<svg") == 3 and svg.count("</svg>") == 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
# 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*<text[^>]*>\s*</text>", "", svg)
assert svg.count("\n\n") == 0
OUT.write_text(svg, encoding="utf-8")
print(f"scris {OUT.name}: TP1 {TP1:.2f} la bara {tp_i}, trepte de trail "
f"{[f'{da[i].l:.2f}@{i}' for i in trepte]}, ieșire {iesire:.2f} (+{r_iesire:.2f}R); "
f"NU: 50 % la {half:.3f}, scos cu +{r_scos:.1f}R, ratat {ratat:.2f} (+{r_ratat:.1f}R)")