Files
atm-curs-dl/gen/strategii/mdsrt_ansamblu.py
Claude Agent f8dd2f7bd0 07_mdsrt: capitol reconstruit dupa sablon
Nivelurile 1-2-3, cei 6 pasi cu diagrama fiecare, mini-glosar de 33 de
termeni, oglinda pe short cu diagrama ei, checklist. Sursele in
fise/07_mdsrt_surse.md.

Capitolul avea TREI reguli numerice diferite in acelasi loc: stop fix pe
low in ansamblu, 0.10 sub low in aplus si exemplu, plus doua reguli de
intrare. Acum mdsrt_ansamblu.py tine seria si regulile, iar picior,
trigger, stop, iesire si short le importa — nu mai pot diverge.

Noi: zona (daily -> 60 min ca descompunere reala, 2 candele daily = 14
bare, cu caseta, ghidaje si pivotul pe ambele panouri), picior, trigger,
stop, iesire, short (seria long rasturnata, deci fiecare cifra are
perechea ei).

Verificare numerica text-diagrama: trei nepotriviri reparate, inclusiv o
cifra din proza care nu exista pe niciun desen.

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

204 lines
10 KiB
Python
Raw Permalink 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.

"""MDSRT — diagrama de ansamblu (Nivelul 1) și seria canonică a capitolului.
Aici trăiește singurul exemplu construit al capitolului: SPY pe 5 minute,
suport/pivot daily 590.00, piciorul 1 la 590.10 (ține nivelul), piciorul 2 la
589.50 (îl rupe, cu divergență pe volum), candela-trigger = Buy Hammer,
intrare 590.20, SL 589.40, risc 0.80, TP1 591.00 (1:1), ieșire pe trail la
592.40 (2.75R).
Celelalte generatoare de preț ale capitolului (picior, trigger, stop, iesire)
IMPORTĂ seria de aici. Așa o regulă numerică — marja stopului, distanța până
la țintă — nu poate să difere de la o diagramă la alta.
Regulile numerice ale capitolului, definite o singură dată, aici:
MARJA = 0.10 — stopul stă cu atât sub low-ul piciorului 2
intrarea = close-ul candelei care sparge high-ul candelei-trigger
TP1 = intrare + (intrare − SL)
Ce e din curs și ce nu: structura M→A→P e a cursului; suportul 590.00 și
minimul piciorului 2 vin din exemplul construit al autorului sumarului
(`05_MODUL_4/02_strategia_5_mdsrt.md:166-177, 439-445`), nu din transcriere —
transcrierea se întrerupe înainte de planul numeric.
Rulează: python3 gen/strategii/mdsrt_ansamblu.py
"""
import re
import sys
from pathlib import Path
sys.path.insert(0, "/workspace/atm-curs-dl")
from svg_candles import Chart, Level, Mark, MA, Zone, Arrow, from_closes # noqa: E402
OUT = Path(__file__).with_suffix(".svg")
GRAY, RED, GREEN, BLUE, ORANGE = "#777", "#c00", "#188a3e", "#1a5fb4", "#b06000"
# ------------------------------------------------- regulile numerice ale capitolului
MARJA = 0.10 # stopul, sub low-ul piciorului 2
SUPORT = 590.00 # pivotul daily (nivelul de pe TF-ul mare)
LEG1, TRIG, CONF = 10, 18, 19 # indicii: piciorul 1, candela-trigger, candela de intrare
STOP_BARS = 4 # low-ul se caută pe ultimele 4 candele, ca la Buy Doji
CLOSES = [
596.00, 595.20, 595.60, 594.80, 594.10, 593.40, 593.70, 592.90, # 0-7
592.20, 591.60,
590.40, # 10 = piciorul 1
590.90, 591.40, 591.10, 591.30, 590.90, 590.50, 590.05, # 11-17
589.80, # 18 = piciorul 2 (Buy Hammer)
590.20, # 19 = intrarea
590.60, 590.90, 591.00, # 20-22 (22 = TP1)
591.30, 591.10, 591.45, 591.70, 591.95, 592.15, # 23-28
592.80, 593.10, 592.90, # 29-31 = ultimul impuls
592.40, # 32 = ieșire pe trail
]
WICKS = [(0.25, 0.30)] * 33
WICKS[LEG1] = (0.25, 0.30) # piciorul 1: low 590.10, deasupra suportului
WICKS[16] = (0.20, 0.15)
WICKS[17] = (0.15, 0.10) # low 589.95 — prețul intră în zonă, fără să facă minim nou
WICKS[TRIG] = (0.10, 0.30) # hammer: high 590.15, low 589.50
WICKS[CONF] = (0.10, 0.05)
WICKS[20] = (0.25, 0.15) # retestul nivelului ține: low 590.05, peste pivot
WICKS[21] = (0.05, 0.15) # high 590.95 < TP1: ținta e atinsă prima dată la bara 22
VOLS = [
48, 46, 44, 45, 43, 41, 40, 42, 44, 46, # 0-9
52, # 10 = volumul piciorului 1
38, 40, 42, 39, 44, 50, 58, # 11-17
88, 80, # 18-19 absorbție la minim
70, 64, 58, 60, 55, 52, 50, 48, 46, # 20-28
52, 48, 42, 46, # 29-32
]
assert len(CLOSES) == len(WICKS) == len(VOLS) == 33
EXIT_I = 32 # bara pe care se închide poziția, sub linia de trail
def bull_trail(cds, start):
"""Bull Trail, definiția cursului: low-ul fiecărei candele care se închide
peste high-ul candelei dinainte; linia urcă, nu coboară niciodată.
Întoarce lista [(index, nivel)] de la bara `start` încolo."""
out, cur = [], None
for i in range(start, len(cds)):
if cds[i].c > cds[i - 1].h:
cur = round(cds[i].l, 2) if cur is None else max(cur, round(cds[i].l, 2))
if cur is not None:
out.append((i, cur))
return out
def serie():
"""Seria canonică a capitolului, 33 de candele de 5 minute."""
return from_closes(CLOSES, wicks=WICKS, vols=VOLS, start=596.60)
cds = serie()
leg1_l = cds[LEG1].l
leg2_l = cds[TRIG].l
ENTRY = cds[CONF].c
SL = round(leg2_l - MARJA, 2)
R = round(ENTRY - SL, 2)
TP1 = round(ENTRY + R, 2)
# ------------------------------------------------------------------ assert-urile
for i, c in enumerate(cds):
assert c.h >= max(c.o, c.c) and c.l <= min(c.o, c.c), f"bara {i}: h/l incoerent"
if i:
assert abs(c.o - cds[i - 1].c) < 1e-9, f"open[{i}] != close[{i-1}]"
# 1. piciorul 1 ține nivelul, piciorul 2 îl rupe — asta e povestea capitolului
assert leg1_l == 590.10 and leg1_l > SUPORT, leg1_l
assert leg2_l == 589.50 and leg2_l < SUPORT, leg2_l
assert leg2_l < leg1_l, (leg2_l, leg1_l)
# 2. cele două picioare sunt minimele locale, nu doar două bare oarecare
assert leg1_l == min(c.l for c in cds[:LEG1 + 6]), "piciorul 1 nu e minimul zonei lui"
assert leg2_l == min(c.l for c in cds), "piciorul 2 nu e minimul seriei"
# 3. distanța dintre picioare depășește minimul de 5 candele cerut de curs
assert TRIG - LEG1 == 8, TRIG - LEG1
# 4. hammer: corp mic sus, coadă lungă în jos
body = abs(cds[TRIG].o - cds[TRIG].c)
rng = cds[TRIG].h - cds[TRIG].l
tail = min(cds[TRIG].o, cds[TRIG].c) - cds[TRIG].l
assert body <= 0.5 * rng and tail > body, (body, rng, tail)
# 5. intrarea sparge high-ul candelei-trigger (regula de intrare a capitolului)
assert ENTRY == 590.20 and ENTRY > cds[TRIG].h, (ENTRY, cds[TRIG].h)
# 6. stopul: marja sub cel mai jos low din ultimele 4 candele
low4 = min(c.l for c in cds[CONF - STOP_BARS + 1:CONF + 1])
assert low4 == leg2_l, (low4, leg2_l)
assert SL == 589.40 and R == 0.80, (SL, R)
# 7. TP1 e 1:1 exact și e atins prima dată la bara 22
assert abs((ENTRY - SL) - (TP1 - ENTRY)) < 1e-9
assert TP1 == 591.00
assert next(i for i in range(CONF + 1, len(cds)) if cds[i].h >= TP1 - 1e-9) == 22
# 8. downtrend vizibil înainte de piciorul 1
assert cds[LEG1].c < cds[0].c - 5, (cds[LEG1].c, cds[0].c)
# 9. divergența: lower low pe preț, higher low pe volum
assert cds[TRIG].l < cds[LEG1].l and VOLS[TRIG] > VOLS[LEG1]
assert VOLS[16] < VOLS[17] < VOLS[TRIG] == max(VOLS)
# 10. după intrare, retestul ține: nicio bară nu mai închide sub pivot
assert all(c.c > SUPORT for c in cds[CONF + 1:]), "prețul recade sub pivot după intrare"
assert min(c.l for c in cds[CONF + 1:]) > SUPORT, "o bară de după intrare rupe iar pivotul"
# 11. ieșirea pe trail: ultimul swing low înainte de întoarcere e linia de trail,
# iar bara de ieșire e prima care se închide sub ea
_trail = bull_trail(cds, CONF)
TRAIL = dict(_trail)[EXIT_I] # nivelul liniei de trail când vine bara de ieșire
assert TRAIL == 592.50, TRAIL
assert [p for _, p in _trail] == sorted(p for _, p in _trail), "linia de trail coboară"
assert all(cds[i].c > p for i, p in _trail if i < EXIT_I), "trail-ul era rupt mai devreme"
assert cds[EXIT_I].c == 592.40 < TRAIL, (cds[EXIT_I].c, TRAIL)
r_mult = round((cds[EXIT_I].c - ENTRY) / R, 2)
assert r_mult == 2.75, r_mult
if __name__ == "__main__":
levels = [
Level(SUPORT, f"pivot daily {SUPORT:.2f} — zona (M)", BLUE, label_at="left"),
Level(SL, f"SL {SL:.2f} = 0.10 sub piciorul 2 (risc {R:.2f})", RED),
Level(TP1, f"TP1 {TP1:.2f} = 1:1", GREEN, label_at="left"),
]
marks = [
Mark(LEG1, f"piciorul 1: low {leg1_l:.2f}", GRAY, "below"),
Mark(TRIG, f"piciorul 2: low {leg2_l:.2f}", RED, "below", dy=16),
]
arrows = [
Arrow(TRIG, f"Buy Hammer: close {cds[TRIG].c:.2f}, coadă lungă în jos\nvânzătorii au dus prețul sub nivel\nși nu l-au putut ține acolo",
where="below", price=leg2_l, color=RED, dx=210, dy=58),
Arrow(CONF, f"intrare {ENTRY:.2f}: intri abia aici,\nla close peste high-ul candelei-trigger",
where="above", color=GREEN, dx=40, dy=-44),
Arrow(EXIT_I, f"ieșire pe trail {cds[EXIT_I].c:.2f} = {r_mult:.2f}R\nprima închidere sub linia de trail",
where="right", color=GREEN, dy=-6),
]
ch = Chart(
candles=cds, levels=levels, marks=marks, zones=[
Zone(LEG1, CONF, "M (zonă + divergență) → A (trigger) → P (plan)", BLUE)],
arrows=arrows,
mas=[MA(20, ORANGE, dash="4 3", text="SMA-20")],
scale=48.0, gap=15, body_w=9, left=44, width=660, top=74, vol_h=70,
vol_label="volum: la piciorul 2, volum mai mare decât la piciorul 1 = divergență",
)
svg = ch.render()
TITLE = ["SPY — 5 min (exemplu construit pe scara lecției;",
"cifrele planului nu vin din curs)"]
title_svg = "\n ".join(
f'<text x="24" y="{26 + 18 * i}" fill="#333" style="font-weight:bold">{t}</text>'
for i, t in enumerate(TITLE))
i = svg.index(">") + 1
svg = svg[:i] + "\n " + title_svg + svg[i:]
m = re.search(r'viewBox="0 0 (\d+) ([0-9.]+)"', svg)
W, H0 = int(m.group(1)), float(m.group(2))
legend = [
"Legendă: structura M → A → P e a cursului; nivelurile (590.00, 589.50) și planul",
"(intrare, SL, TP) sunt exemplul construit al autorului sumarului, nu citate din curs.",
]
legend_svg = [f'<text x="24" y="{H0 + 16 + 17 * i:.1f}" fill="#555">{t}</text>'
for i, t in enumerate(legend)]
new_h = H0 + 16 + 17 * (len(legend) - 1) + 14
svg = re.sub(r'(viewBox="0 0 \d+ )[0-9.]+(")',
lambda mm: mm.group(1) + f"{new_h:.0f}" + mm.group(2), svg, count=1)
svg = svg.replace("\n</svg>", "\n " + "\n ".join(legend_svg) + "\n</svg>")
assert "\n\n" not in svg, "linie goală în SVG"
OUT.write_text(svg)
print(f"scris {OUT.name}: picior 1 {leg1_l:.2f} / picior 2 {leg2_l:.2f} sub {SUPORT:.2f}, "
f"intrare {ENTRY:.2f}, SL {SL:.2f} (risc {R:.2f}), TP1 {TP1:.2f}, ieșire {r_mult:.2f}R")