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
This commit is contained in:
Claude Agent
2026-09-20 12:46:38 +00:00
parent 0ed607134d
commit c08c857274
25 changed files with 2133 additions and 623 deletions

View File

@@ -1,166 +1,203 @@
"""Diagrama de ansamblu MDSRT long (strategii, marcajul `ansamblu`).
"""MDSRT — diagrama de ansamblu (Nivelul 1) și seria canonică a capitolului.
„SPY — 5 min: al doilea picior mai jos la 589.80, sub suportul 590.00 →
divergență bull (M) preț-vs-volum → candela-trigger Buy Hammer/Doji →
intrare 590.20 → SL 589.40 (risc 0.80) → TP1 591.00 (1:1) → poziția rămasă
pe trail, ieșire la close 592.40 (≈ 2.75R)."
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).
Toate close-urile, wick-urile speciale și nivelurile vin din
`gen/strategii/mdsrt_ansamblu.spec.txt`. Structura M→A→P, suportul 590.00,
minimul 589.80 și riscul 0.80 sunt din exemplul cursului
(`05_MODUL_4/02_strategia_5_mdsrt.md:159-177, 246, 308-311`). Intrare/SL/TP
sunt exemplul construit al autorului sumarului, marcat explicit ca NU citat
din curs (`...:166-177, 439-445`); transcrierea se întrerupe înainte de
planul numeric. Volumele sunt relative, alese de generator (materialul nu dă
volum pentru acest exemplu).
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.
Rulează:
python3 gen/strategii/mdsrt_ansamblu.py
Scrie lângă el `mdsrt_ansamblu.svg`.
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, from_closes # noqa: E402
from svg_candles import Chart, Level, Mark, MA, Zone, Arrow, from_closes # noqa: E402
OUT = Path(__file__).resolve().parent / "mdsrt_ansamblu.svg"
OUT = Path(__file__).with_suffix(".svg")
GRAY, RED, GREEN, BLUE, ORANGE = "#777", "#c00", "#188a3e", "#1a5fb4", "#b06000"
# ---------------------------------------------------------------- seria (30 bare)
# ------------------------------------------------- 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, 591.90, 591.30, 590.80, 590.30, 590.55, 590.10, # 8-15
589.85, 590.05, # 16-17
589.80, # 18 = al doilea picior mai jos + candela-trigger (hammer)
590.20, # 19 = intrare (close = entry; sparge high-ul trigger-ului)
590.60, 590.90, 591.00, # 20-22 (22 = TP1 1:1)
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.40, # 29 = ieșire pe trail (close)
592.80, 593.10, 592.90, # 29-31 = ultimul impuls
592.40, # 32 = ieșire pe trail
]
assert len(CLOSES) == 30, len(CLOSES)
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
# fitile (sus, jos): (0.25, 0.30) peste tot, cu excepțiile din spec.
WICKS = [(0.25, 0.30)] * 30
WICKS[18] = (0.10, 0.40) # hammer: high 590.15, low 589.40 (= SL)
WICKS[19] = (0.10, 0.05) # intrare: high 590.30, low 589.75
# bara 21 coborâtă sub TP1 (high 590.95 < 591.00), ca TP1 să fie atins exact
# prima dată la bara 22 (altfel assert-ul 5 ar pica pe „primul high").
WICKS[21] = (0.05, 0.30)
# volum relativ (ales de generator): 16-19 crescător, 18-19 peste medie,
# vol[18] > vol[10] (higher low pe volum la lower low pe preț).
VOLS = [
48, 46, 44, 45, 43, 41, 40, 42, 44, 46, # 0-9
52, 38, 40, 42, 39, 44, # 10-15
50, 58, 72, 85, # 16-19 absorbție la minim
70, 64, 58, 60, 55, 52, 50, 48, 46, 44, # 20-29
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(VOLS) == 30, len(VOLS)
assert len(CLOSES) == len(WICKS) == len(VOLS) == 33
cds = from_closes(CLOSES, wicks=WICKS, vols=VOLS, start=596.60)
# ---------------------------------------------------------------- assert-urile din spec
entry = cds[19].c
sl = cds[18].l
tp1 = cds[22].c
EXIT_I = 32 # bara pe care se închide poziția, sub linia de trail
# 1. minimul și SL-ul pe bara 18
assert cds[18].c == 589.80, cds[18].c
assert cds[18].l == 589.40, cds[18].l
# 2. al doilea picior rupe suportul/pivotul 590.00
assert cds[18].l < 590.00, cds[18].l
# 3. intrarea = close-ul barei 19 și sparge high-ul trigger-ului
assert cds[19].c == 590.20, cds[19].c
assert cds[19].c == entry
assert cds[19].c > cds[18].h, (cds[19].c, cds[18].h)
# 4. TP1 exact 1:1
assert abs((entry - sl) - (tp1 - entry)) < 1e-9, (entry - sl, tp1 - entry)
assert abs((entry - sl) - 0.80) < 1e-9
# 5. TP1 atins la bara 22 (și nu mai devreme)
assert cds[22].c == 591.00, cds[22].c
assert cds[22].c == tp1
assert max(c.h for c in cds[20:22]) < tp1, [c.h for c in cds[20:22]]
# 6. ieșirea pe trail e pe profit
assert cds[29].c == 592.40, cds[29].c
assert cds[29].c > entry, (cds[29].c, entry)
# 7. hammer: corp mic, coadă lungă în jos
body = abs(cds[18].o - cds[18].c)
rng = cds[18].h - cds[18].l
tail = min(cds[18].o, cds[18].c) - cds[18].l
assert body == 0.25, body
assert body <= 0.5 * rng, (body, rng)
assert tail > body, (tail, body)
# 8. downtrend vizibil
assert cds[13].c < cds[0].c - 5, (cds[13].c, cds[0].c)
# 9. divergență pe volum: lower low pe preț, higher low pe volum
assert cds[18].l < cds[10].l, (cds[18].l, cds[10].l)
assert VOLS[18] > VOLS[10], (VOLS[18], VOLS[10])
# barele 16-19 au volum crescător; 18-19 peste media seriei
assert VOLS[16] < VOLS[17] < VOLS[18] < VOLS[19], VOLS[16:20]
avg_vol = sum(VOLS) / len(VOLS)
assert VOLS[18] > avg_vol and VOLS[19] > avg_vol, (VOLS[18], VOLS[19], avg_vol)
# 10. fiecare open == close-ul barei anterioare
for a, b in zip(cds, cds[1:]):
assert a.c == b.o, (a.c, b.o)
r_mult = (cds[29].c - entry) / (entry - sl)
assert abs(r_mult - 2.75) < 1e-9, r_mult
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
# ---------------------------------------------------------------- niveluri, marks
levels = [
Level(590.00, "suport / pivot 590.00", BLUE, label_at="left"),
Level(sl, f"SL {sl:.2f} — risc 0.80", RED, label_at="left"),
# stânga: altfel eticheta din dreapta se calcă pe textul „SMA-20" al mediei
Level(tp1, f"TP1 {tp1:.2f} = 1:1", GREEN, label_at="left"),
]
marks = [
Mark(18, f"minim 589.80 — divergență (M)", RED, "below"),
# ridicat deasupra vârfurilor candelelor de urcare (altfel textul cade peste
# fitilele barelor 20-22) — bara 19 e totuși exact sub el
Mark(19, f"intrare {entry:.2f}", GREEN, "above", dy=-46),
Mark(29, f"ieșire trail {cds[29].c:.2f}", GREEN, "above"),
]
def serie():
"""Seria canonică a capitolului, 33 de candele de 5 minute."""
return from_closes(CLOSES, wicks=WICKS, vols=VOLS, start=596.60)
# titlul e injectat pe două linii: textul din spec (82 caractere) nu încape
# pe un singur rând în 640 px fără să fie tăiat la margine
TITLE = ["SPY — 5 min (exemplu construit pe scara lecției;",
"cifrele planului nu vin din curs)"]
ch = Chart(
candles=cds, levels=levels, marks=marks,
mas=[MA(20, ORANGE, dash="4 3", text="SMA-20")],
scale=48.0, gap=15, body_w=9, left=44, width=640, top=64, vol_h=70,
vol_label="volum",
title="", caption="",
)
svg = ch.render()
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:]
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)
# ---------------------------------------------------------------- legenda (onestitate)
m = re.search(r'viewBox="0 0 (\d+) ([0-9.]+)"', svg)
W, H0 = int(m.group(1)), float(m.group(2))
legend = [
"Legendă: entry/SL/TP/trail sunt exemplul construit al autorului sumarului,",
"nu citate din curs (transcrierea se întrerupe înainte de planul numeric).",
]
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-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}]"
assert svg.count("\n\n") == 0, "linie goală în SVG rupe blocul din markdown"
assert f"TP1 {tp1:.2f}" in svg and f"intrare {entry:.2f}" in svg
OUT.write_text(svg)
print(f"scris {OUT.name}: {len(cds)} candele, suport 590.00, minim {cds[18].l:.2f}, "
f"intrare {entry:.2f}, SL {sl:.2f} (risc 0.80), TP1 {tp1:.2f} (1:1), "
f"ieșire {cds[29].c:.2f} = {r_mult:.2f}R, vol[18]={VOLS[18]} > vol[10]={VOLS[10]}")
# 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")