Files
atm-curs-dl/gen/strategii/mbp_iesire.py
Claude Agent 1f91e0b5a7 08_mbp: capitol reconstruit dupa sablon
Primul capitol terminat cap-coada dupa 01_buy_doji: nivelurile 1-2-3,
cei 5 pasi cu propozitie de actiune si "Treci mai departe doar daca",
mini-glosar de 38 de termeni, oglinda pe short cu diagrama ei, checklist.
Sursele au iesit in fise/08_mbp_surse.md.

Diagrame noi pentru pasii 1, 3, 4 si 5, care nu aveau: context, trigger,
stop, iesire. Benzile Bollinger din context sunt calculate din close-uri,
nu desenate, cu 20 de bare de incalzire nedesenate.

MBP e monotimeframe, deci cerinta de corespondenta intre timeframe-uri nu
se aplica aici — nu s-a inventat o diagrama pentru ea.

Stopul e fix pe cel mai jos minim din 4 candele, fara marja: asa spune
cursul pentru strategia asta, spre deosebire de 01_buy_doji. Diferenta e
spusa explicit in text.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ThN7ixZrowca8aTGrK1MUJ
2026-09-20 12:48:27 +00:00

196 lines
9.8 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), 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("</svg>")].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'<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</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")
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}, 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)")