Files
atm-curs-dl/gen/strategii/mdsrt_ansamblu.py
Claude Agent c08c857274 atmi: diagramele refacute, cinci noi, si fisa de surse
Noi: pivoti (PPQ/SQ calculati din trimestrul precedent, cu formula pe
desen), perechi_tf (weekly -> daily ca descompunere reala, cu caseta,
ghidaje si PPQ pe ambele panouri), trigger (BIM apoi verde, fata de
verde inainte de BIM — cutitul in cadere), risc (ce inseamna "fara
stop": 24 de bare sub apa, -20 % pe pozitie), reinvestire (ciclul
banilor pe trei cicluri).

aplus devine corectie: diagrama arata un prag de corectie, nu un setup
A+, si numele vechi trimitea la altceva.

Trei bug-uri reale: exemplul era etichetat weekly cu 20 de bare, dar
textul spunea "un an mai tarziu" — trecut pe monthly cu assert pe numarul
de luni; ansamblul avea o trimitere fisier:linie desenata in SVG; si
benzile PPQ/SQ erau inversate ca sens, cu SQ fix pe intrare.

PPQ 95.80 si SQ 90.80 sunt completare proprie — cursul spune explicit ca
nu da valorile; derivate cu formula standard, cu assert.

Textul capitolului e neatins — agentul a atins pragul de context.

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

204 lines
9.9 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.

"""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, "Buy Hammer: 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")