02_mp2: capitol reconstruit dupa sablon

Nivelurile 1-2-3, cei 6 pasi cu diagrama fiecare, mini-glosar de 34 de
termeni, oglinda pe short cu diagrama ei proprie, checklist. Sursele in
fise/02_mp2_surse.md.

Toate diagramele procedurii ies acum dintr-o singura serie
(mp2_serie.py), deci capitolul nu mai poate avea doua reguli numerice.
Ansamblul folosea NVDA cu stop la swing low absolut (-23 %), adica alta
regula decat procedura celor 4 bare; NVDA a devenit "bottom", la nivelul
3, marcat explicit ca exceptie.

perechi_tf e diagrama cu doua timeframe-uri: panoul mic e descompunerea
reala a ultimelor 2 candele daily (1 zi = 7 bare de 60 min), cu pivotul
weekly pe ambele panouri si conector. Pivotii se calculeaza din serie, nu
sunt scrisi de mana.

Reparate: trail-ul din proza era 600.05, calculul din serie da 600.75;
doua SVG-uri aveau trimiteri fisier:linie desenate pe ele.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ThN7ixZrowca8aTGrK1MUJ
This commit is contained in:
Claude Agent
2026-09-20 12:51:58 +00:00
parent 9b047e10bb
commit 2ea599f265
25 changed files with 4449 additions and 1666 deletions

268
gen/strategii/mp2_serie.py Normal file
View File

@@ -0,0 +1,268 @@
"""Seria comună a capitolului `17_STRATEGII/02_mp2.md` — un singur trade.
Toate diagramele din Nivelul 1 și Nivelul 2 ale capitolului desenează ACELAȘI
trade, din aceeași serie. Așa nu se poate întâmpla ca două diagrame să
folosească două reguli de stop diferite, fiecare etichetată corect local.
Ce e din material și ce e construit
-----------------------------------
Din material (rezumatul Q&A, exemplul ilustrativ al mecanicii celor două
poziții): intrare 592.00, SL 586.00, 1R = 6.00, TP1 598.00, mutarea SL-ului
la break-even la 60 % din drum, mărimea totală multiplu de 2.
Construit de mine: traseul barelor de 60 de minute care produce exact
cifrele alea, și pivoții — care NU sunt scriși de mână, ci calculați din
serie cu formula cursului, PP = (High + Low + Close) / 3 al perioadei
încheiate. Fără ticker: e un exemplu construit, nu date reale de piață.
Structura: 5 zile × 7 bare de 60 de minute (ședința 9:30-16:00 are 6 ore și
jumătate, deci ultima bară a zilei e de 30 de minute).
ziua 1 bare 0-6 coborâre
ziua 2 bare 7-13 minimul și întoarcerea
ziua 3 bare 14-20 ziua verde care ridică pivotul zilnic peste cel weekly
ziua 4 bare 21-27 crossing-ul e pe tablă: dip, trigger la 592.00, TP1
ziua 5 bare 28-34 poziția 2 pe trail, ieșire la 599.80
Rulat direct, verifică toate afirmațiile capitolului:
python3 gen/strategii/mp2_serie.py
"""
import re
import sys
sys.path.insert(0, "/workspace/atm-curs-dl")
from svg_candles import Candle, from_closes # noqa: E402
BARE_ZI = 7 # bare de 60 min într-o ședință
ZILE = 5
# pivotul săptămânal (MARE): al săptămânii încheiate, H 588.00 / L 580.00 /
# C 584.00. E un dat din afara ferestrei desenate — săptămâna trecută nu
# încape pe grafic — deci îl declar aici, cu OHLC-ul din care iese.
PPW_OHLC = (588.00, 580.00, 584.00)
# pivotul zilnic al PRIMEI zile: al ședinței de vineri, dinaintea ferestrei.
PPD0_OHLC = (586.00, 581.00, 582.60)
START = 585.00 # open-ul primei bare
CLOSES = [
# ziua 1 — coborâre
584.20, 583.40, 583.90, 582.60, 581.80, 582.20, 581.20,
# ziua 2 — minimul (580.00) și întoarcerea
580.60, 581.40, 580.90, 582.30, 583.10, 583.80, 584.60,
# ziua 3 — ziua verde; închiderile ei sunt peste pivotul zilnic, dar
# pivotul zilnic e încă sub cel weekly, deci NU e trigger
585.10, 584.70, 585.60, 586.40, 587.20, 587.80, 588.40,
# ziua 4 — dip sub pivotul zilnic nou, apoi trigger-ul
586.90, 586.30, 586.80, 592.00, 593.40, 595.90, 598.20,
# ziua 5 — poziția 2 pe trail
599.60, 601.20, 600.40, 602.30, 601.10, 599.80, 598.90,
]
W = [(0.25, 0.25)] * 35 # fitile (sus, jos); excepțiile fixează extremele zilei
W[0] = (0.40, 0.25) # H zi 1 = 585.40
W[6] = (0.25, 0.60) # L zi 1 = 580.60
W[7] = (0.25, 0.60) # L zi 2 = 580.00
W[13] = (0.40, 0.25) # H zi 2 = 585.00
W[14] = (0.25, 0.40) # L zi 3 = 584.20
W[20] = (0.40, 0.25) # H zi 3 = 588.80
W[21] = (0.30, 0.30)
W[22] = (0.30, 0.30) # L = 586.00 — minimul celor 4 bare de la trigger
W[23] = (0.30, 0.20)
W[24] = (0.40, 0.30) # bara de trigger
W[25] = (0.35, 0.40)
W[26] = (0.35, 0.40)
W[27] = (0.35, 0.40)
for i in range(28, 35):
W[i] = (0.35, 0.35)
VOLS = [46, 40, 38, 42, 39, 36, 44,
58, 52, 44, 41, 38, 36, 40,
44, 38, 41, 45, 48, 44, 52,
50, 46, 42, 118, 86, 78, 82,
64, 58, 46, 62, 44, 56, 48]
ZI3, ZI4 = 2, 3 # indexul zilei verzi și al zilei de trigger
TRIGGER = 24 # bara de trigger (ziua 4, a 4-a oră)
PREA_DEVREME = 16 # închidere peste pivotul mic cu M neîndeplinit
NU_INCHIDE = 22 # trece peste pivot în timpul barei, dar nu închide
BARE_STOP = 4 # stopul: extrema ultimelor 4 bare, de la trigger
PRAG_BE = 0.60 # la 60 % din drumul spre TP1, SL-ul merge la BE
PRAG_90 = 0.90
def pp(h, l, c):
"""Pivotul clasic al unei perioade încheiate (formula cursului)."""
return round((h + l + c) / 3, 2)
def serie(closes=None, wicks=None, start=None):
return from_closes(closes or CLOSES, wicks=wicks or W, vols=VOLS,
start=START if start is None else start)
def zilnice(cds):
"""Agregă barele de 60 min în candele zilnice: 7 bare = o ședință."""
out = []
for d in range(ZILE):
p = cds[d * BARE_ZI:(d + 1) * BARE_ZI]
out.append(Candle(p[0].o, max(c.h for c in p), min(c.l for c in p), p[-1].c))
return out
def pivoti(cds, ppw_ohlc=PPW_OHLC, ppd0_ohlc=PPD0_OHLC):
"""(PPW, PPD pe zi, PPD pe bară). PPD al zilei N = pivotul zilei N-1."""
zi = zilnice(cds)
ppw = pp(*ppw_ohlc)
ppd = [pp(*ppd0_ohlc)] + [pp(z.h, z.l, z.c) for z in zi[:-1]]
return ppw, ppd, [ppd[i // BARE_ZI] for i in range(len(cds))]
def trail(cds, de_la):
"""Bull trail: low-ul fiecărei bare care face un maxim nou, de la `de_la`.
Întoarce (linia pe bară, indexul barei de ieșire) — ieșirea e prima
închidere sub linie, nu primul fitil sub ea.
"""
linie, nivel, hmax, iesire = [], None, cds[de_la].h, None
for i, c in enumerate(cds):
if i >= de_la:
if c.h >= hmax:
hmax, nivel = c.h, c.l
if iesire is None and nivel is not None and i > de_la and c.c < nivel:
iesire = i
linie.append(nivel)
return linie, iesire
def oglinda(a=246.40, f=0.2):
"""Aceeași structură, întoarsă: p' = a - f*p. Transformarea e afină, deci
agregarea zilnică și pivoții se păstrează; short-ul nu e „e invers", are
desenul lui complet. Cu f = 0.2 toate prețurile rămân exacte la 2 zecimale."""
m = lambda p: round(a - f * p, 2) # noqa: E731
cds = [Candle(m(c.o), m(c.l), m(c.h), m(c.c), vol=c.vol) for c in serie()]
ppw = m(pp(*PPW_OHLC))
zi = zilnice(cds)
ppd = [m(pp(*PPD0_OHLC))] + [pp(z.l, z.h, z.c) for z in zi[:-1]]
return cds, ppw, ppd, [ppd[i // BARE_ZI] for i in range(len(cds))]
# ---------------------------------------------------------------- cifrele
CDS = serie()
PPW, PPD, PPD_BARA = pivoti(CDS)
ENTRY = CDS[TRIGGER].c
SL = min(c.l for c in CDS[TRIGGER - BARE_STOP + 1:TRIGGER + 1])
R = round(ENTRY - SL, 2)
TP1 = round(ENTRY + R, 2)
BE = round(ENTRY + PRAG_BE * R, 2)
P90 = round(ENTRY + PRAG_90 * R, 2)
SWING_LOW = min(c.l for c in CDS) # varianta de bottom major
TRAIL, IESIRE = trail(CDS, TRIGGER)
IES_PRET = CDS[IESIRE].c
I_TP1 = next(i for i in range(TRIGGER + 1, len(CDS)) if CDS[i].h >= TP1)
I_BE = next(i for i in range(TRIGGER + 1, len(CDS)) if CDS[i].h >= BE)
# mărimea poziției: regula de risc a cursului (0,5 % pe prop), contul e al meu
CONT, RISC_PCT = 50_000, 0.005
BUCATI = int(CONT * RISC_PCT / R) // 2 * 2 # multiplu de 2: două ordine egale
RISC_REAL = round(BUCATI * R, 2)
def _verifica():
for i, c in enumerate(CDS):
assert c.l <= min(c.o, c.c) and c.h >= max(c.o, c.c), f"bara {i}: h/l nu cuprind o/c"
if i:
assert abs(c.o - CDS[i - 1].c) < 1e-9, f"bara {i}: open != close precedent"
assert len(CDS) == BARE_ZI * ZILE == 35
# 1. cifrele din material ies din serie, nu sunt scrise de mână
assert (ENTRY, SL, R, TP1) == (592.00, 586.00, 6.00, 598.00), (ENTRY, SL, R, TP1)
assert BE == 595.60 and P90 == 597.40
# 2. M: pivotul mic e sub cel mare până în ziua 4, și peste din ziua 4
assert all(PPD[d] < PPW for d in range(ZI4)), PPD
assert PPD[ZI4] > PPW, (PPD[ZI4], PPW)
assert PPD[ZI4] - PPW > 2.5, "crossing-ul e prea strâns ca să se vadă pe desen"
# 3. crossing PROASPĂT, nu continuare: în ziua dinainte era de partea cealaltă
assert PPD[ZI4 - 1] < PPW < PPD[ZI4]
assert PPD[ZI4 + 1] > PPW, "ziua 5 e continuare (+1), nu crossing"
# 4. trigger-ul: PRIMA închidere peste pivotul mic din ziua în care M e îndeplinit
zi4 = range(ZI4 * BARE_ZI, (ZI4 + 1) * BARE_ZI)
assert TRIGGER == next(i for i in zi4 if CDS[i].c > PPD_BARA[i]), "nu e prima închidere"
assert CDS[TRIGGER].c > CDS[TRIGGER].o, "bara de trigger nu e verde"
# 5. NU — prea devreme: închidere peste pivotul mic cu M neîndeplinit
assert CDS[PREA_DEVREME].c > PPD_BARA[PREA_DEVREME] > 0
assert PPD_BARA[PREA_DEVREME] < PPW, "în ziua 3 M ar fi îndeplinit"
assert CDS[PREA_DEVREME].c > CDS[PREA_DEVREME].o, "bara „prea devreme” nu e verde"
# 6. NU — trece peste pivot în timpul barei, dar nu închide peste
assert CDS[NU_INCHIDE].h > PPD_BARA[NU_INCHIDE] > CDS[NU_INCHIDE].c
# 7. stopul: minimul celor 4 bare, și e minimul lor strict
assert SL == CDS[22].l and SL < min(CDS[i].l for i in (21, 23, 24))
assert SWING_LOW < SL, "varianta de bottom major ar coincide cu stopul de 4 bare"
assert round(SL - SWING_LOW, 2) == 6.00, round(SL - SWING_LOW, 2)
# 8. ieșirile, în ordine: BE, TP1, apoi trail-ul
assert I_BE == 26 and I_TP1 == 27 and IESIRE == 33, (I_BE, I_TP1, IESIRE)
assert CDS[IESIRE].c < TRAIL[IESIRE] and all(
CDS[i].c >= TRAIL[i] for i in range(TRIGGER + 1, IESIRE)), "ieșire greșită pe trail"
assert round((IES_PRET - ENTRY) / R, 2) == 1.30, (IES_PRET - ENTRY) / R
# 9. mărimea poziției
assert BUCATI == 40 and RISC_REAL == 240.00 and RISC_REAL <= CONT * RISC_PCT
# 10. oglinda păstrează structura
o_cds, o_ppw, o_ppd, _ = oglinda()
assert all(o_ppd[d] > o_ppw for d in range(ZI4)) and o_ppd[ZI4] < o_ppw
o_entry = o_cds[TRIGGER].c
o_sl = max(c.h for c in o_cds[TRIGGER - BARE_STOP + 1:TRIGGER + 1])
assert (o_entry, o_sl) == (128.00, 129.20), (o_entry, o_sl)
assert round(o_sl - o_entry, 2) == 1.20
_verifica()
# -------------------------------------------------- ajutoare pentru desen
def offset(svg, ch):
"""Câți px a coborât `render()` graficul față de ce întoarce acum ch._y().
`render()` mută `top` ca să facă loc titlului/săgeților și îl restaurează
la final, deci ch._y() de după randare e deplasat. Citim deplasarea din
prima linie de Level desenată, ca ce injectăm pe urmă să cadă exact peste
candele (bug real: liniile de pivot cu 22 px mai sus decât candelele).
"""
y = float(re.search(r'<line x1="24" y1="([\d.]+)"', svg).group(1))
return y - ch._y(ch.levels[0].price)
def trepte(ch, valori, off):
"""Polilinie în trepte pentru un pivot care se recalculează periodic."""
pts = []
for i, v in enumerate(valori):
if v is None:
continue
x, y = ch._cx(i), ch._y(v) + off
if pts and v != valori[i - 1]:
pts.append((x, ch._y(valori[i - 1]) + off))
pts.append((x, y))
return " ".join(f"{x:.1f},{y:.1f}" for x, y in pts)
def injecteaza(svg, bucati):
"""Pune `bucati` imediat după tag-ul de deschidere: desenate în spate."""
i = svg.index(">") + 1
return svg[:i] + "\n " + "\n ".join(bucati) + svg[i:]
def scrie(out, svg, w, h):
assert "\n\n" not in svg, "linie goală în SVG rupe blocul din markdown"
assert ".md:" not in svg, "trimitere fișier:linie în SVG"
for x, y in re.findall(r'<text x="([-\d.]+)" y="([-\d.]+)"', svg):
assert -1 <= float(x) <= w + 1 and 0 <= float(y) <= h + 1, f"text în afara pânzei ({x}, {y})"
out.write_text(svg, encoding="utf-8")
print(f"scris {out.name} ({w}x{h:.0f})")
if __name__ == "__main__":
print(f"PPW (mare) = {PPW:.2f} PPD (mic), pe zile = {PPD}")
print(f"crossing în ziua {ZI4 + 1}: {PPD[ZI4 - 1]:.2f} -> {PPD[ZI4]:.2f}, peste {PPW:.2f}")
print(f"trigger bara {TRIGGER}: close {ENTRY:.2f} peste pivotul mic {PPD[ZI4]:.2f}")
print(f"SL {SL:.2f} (minimul barelor {TRIGGER - 3}-{TRIGGER}), 1R = {R:.2f} "
f"({R / ENTRY * 100:.2f} %), TP1 {TP1:.2f} la bara {I_TP1}")
print(f"BE {BE:.2f} la bara {I_BE}; trail: ieșire la bara {IESIRE}, "
f"{IES_PRET:.2f} = {(IES_PRET - ENTRY) / R:.2f}R")
print(f"swing low absolut {SWING_LOW:.2f} (varianta de bottom major)")
print(f"mărime: {BUCATI} bucăți (2 x {BUCATI // 2}), risc real {RISC_REAL:.2f} $")
print("toate verificările trec")