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
204 lines
10 KiB
Python
204 lines
10 KiB
Python
"""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")
|