Cate un agent grafician per diagrama, fiecare cu generatorul si SVG-ul lui. Graficele de pret trec prin svg_candles, cu assert-urile din specificatie scrise in generator — verifica structura, nu doar ca ruleaza: ca al doilea picior e mai jos, ca intrarea e close-ul barei de confirmare, ca 1:1 e exact. Verificare prin sondaj pe buy_doji_ansamblu, cu screenshot citit: serie continua de 32 de candele, panou de volum, MA(20), doji-ul si ieșirea pe trail etichetate, cifrele identice cu specificatia. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ThN7ixZrowca8aTGrK1MUJ
137 lines
6.1 KiB
Python
137 lines
6.1 KiB
Python
"""Diagrama de ansamblu M2D (strategii/03_m2d.md, marcajul `ansamblu`).
|
||
|
||
„Setup M2D LONG complet pe TF mic (3 min), cu TF mare (15 min) în Buy":
|
||
urcare -> pullback (M = Sell Momentum) -> trigger (A = primul Buy) -> intrare
|
||
-> SL de 4 candele -> TP1 la 1:1 -> continuare -> vârf -> ieșire pe bull trail.
|
||
|
||
Toate close-urile, wick-urile, volumele și nivelurile vin din
|
||
`gen/strategii/m2d_ansamblu.spec.txt`. Seria e construită (exemplu generic,
|
||
fără ticker), la scara 1.00 $ risc / 2.2R randament — nu e un trade real.
|
||
|
||
Rulează:
|
||
python3 gen/strategii/m2d_ansamblu.py
|
||
Scrie lângă el `m2d_ansamblu.svg`.
|
||
"""
|
||
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
|
||
|
||
OUT = Path(__file__).resolve().parent / "m2d_ansamblu.svg"
|
||
|
||
GRAY, RED, GREEN, BLUE, ORANGE = "#777", "#c00", "#188a3e", "#1a5fb4", "#b06000"
|
||
|
||
# ---------------------------------------------------------------- seria (32 bare)
|
||
CLOSES = [
|
||
96.8, 97.2, 97.7, 98.1, 98.6, 99.1, 99.5, 100.0, # 0-7 urcare
|
||
99.8, 99.4, 99.0, 99.2, 98.9, 99.15, 99.3, # 8-14 pullback (M)
|
||
99.8, 100.2, # 15-16 trigger (A)
|
||
100.7, 101.2, 101.6, 102.0, 102.4, 102.7, 103.0, # 17-23 continuare
|
||
103.3, 103.1, 102.8, 102.7, 102.9, 102.5, 102.2, 102.0, # 24-31 vârf + violare
|
||
]
|
||
assert len(CLOSES) == 32, len(CLOSES)
|
||
|
||
# fitile (sus, jos); jos mai mari pe 10-12, ca minimul de 4 candele (11:15) să
|
||
# fie clar sub intrare, la exact 98.80 (candela 12).
|
||
WICKS = (
|
||
[(0.15, 0.10)] * 8 # 0-7
|
||
+ [(0.10, 0.15), (0.10, 0.20), (0.10, 0.15), # 8-10
|
||
(0.10, 0.15), (0.10, 0.10), (0.10, 0.05), (0.10, 0.10)] # 11-14
|
||
+ [(0.15, 0.10), (0.15, 0.10)] # 15-16
|
||
+ [(0.15, 0.10)] * 7 # 17-23
|
||
+ [(0.20, 0.10), (0.10, 0.15), (0.10, 0.15), (0.12, 0.15), # 24-27
|
||
(0.10, 0.15), (0.10, 0.15), (0.10, 0.15), (0.10, 0.20)] # 28-31
|
||
)
|
||
assert len(WICKS) == 32, len(WICKS)
|
||
|
||
VOLS = (
|
||
[62, 66, 60, 68, 64, 70, 66, 74] # 0-7 trend
|
||
+ [44, 40, 38, 36, 34, 33, 35] # 8-14 pullback mic
|
||
+ [82, 88] # 15-16 trigger
|
||
+ [72, 70, 74, 68, 72, 66, 70] # 17-23 trend
|
||
+ [76, 64, 60, 58, 62, 56, 54, 66] # 24-31 vârf + ieșire
|
||
)
|
||
assert len(VOLS) == 32, len(VOLS)
|
||
|
||
cds = from_closes(CLOSES, wicks=WICKS, vols=VOLS, start=96.4)
|
||
|
||
# ---------------------------------------------------------------- assert-urile din spec
|
||
entry = cds[15].c # 1. intrarea = close-ul trigger-ului
|
||
SL = min(c.l for c in cds[11:15]) # 2. minimul ultimelor 4 dinainte
|
||
TP1 = entry + (entry - SL) # 4. TP1 exact 1:1
|
||
|
||
assert entry == round(cds[15].c, 2), (entry, cds[15].c)
|
||
assert SL == min(c.l for c in cds[11:15]), SL
|
||
assert SL < entry, (SL, entry)
|
||
assert abs(TP1 - (entry + (entry - SL))) < 1e-6
|
||
for a, b in zip(cds, cds[1:]): # 5. serie continuă, fără goluri
|
||
assert a.c == b.o, (a.c, b.o)
|
||
assert cds[15].c > cds[14].c # 6. trigger în sus după pullback
|
||
assert any(c.c < c.o for c in cds[8:15]) # 7. pullback = Sell
|
||
assert any(c.c > c.o for c in cds[0:8]) # context în sus
|
||
exit_price = cds[31].c
|
||
assert exit_price > entry # 8. ieșirea e peste intrare
|
||
|
||
# bull trail = low-ul ultimei candele care a închis peste high-ul precedentei
|
||
qual = [i for i in range(1, 31) if cds[i].c > cds[i - 1].h]
|
||
assert qual and qual[-1] == 24, qual
|
||
bull = cds[24].l
|
||
assert cds[31].c < bull, (cds[31].c, bull) # finala închide sub linie = ieșire aici
|
||
|
||
r_mult = (exit_price - entry) / (entry - SL) # 9. R-ul afișat
|
||
assert round(r_mult, 1) == 2.2, r_mult
|
||
|
||
# ---------------------------------------------------------------- niveluri, etichete, marks
|
||
levels = [
|
||
Level(entry, f"intrare {entry:.2f}", GREEN, label_at="left"),
|
||
Level(SL, f"SL {SL:.2f} (4 candele)", RED, label_at="right"),
|
||
Level(TP1, f"TP1 {TP1:.2f} = 1:1", BLUE, label_at="right"),
|
||
]
|
||
assert [round(lv.price, 2) for lv in levels] == [99.80, 98.80, 100.80]
|
||
|
||
exit_text = f"ieșire {exit_price:.2f} pe bull trail ({r_mult:.1f}R)"
|
||
assert f"{r_mult:.1f}R" in exit_text
|
||
|
||
marks = [
|
||
Mark(0, "TF mare (15 min) în Buy", BLUE, "above", dy=-227),
|
||
Mark(11, "M: Sell Momentum (pullback)", RED, "below"),
|
||
Mark(15, "A: primul Buy = trigger", GREEN, "above", dy=-122),
|
||
# „vârf" stă sub candela 24: deasupra ei, orice text de la y-ul vârfului se
|
||
# calcă pe titlul din stânga-sus (ambele pornesc din `top`).
|
||
Mark(24, "vârf 103.30", GRAY, "below", dy=25),
|
||
Mark(31, "close sub bull trail = ieși", RED, "below", dy=55),
|
||
Mark(31, exit_text, RED, "below", dy=73),
|
||
]
|
||
|
||
caption = (f"Serie construită (32 bare, 3 min). R = ({exit_price:.2f} − {entry:.2f}) / "
|
||
f"{entry - SL:.2f} = {r_mult:.1f}R.")
|
||
assert f"{r_mult:.1f}R" in caption
|
||
|
||
ch = Chart(
|
||
candles=cds, levels=levels, marks=marks,
|
||
mas=[MA(20, ORANGE, dash="4 3", text="SMA-20")],
|
||
scale=52.0, gap=16, body_w=9, left=44, width=720, vol_h=70,
|
||
vol_label="volum",
|
||
title="Simbol construit — 3 min (TF mare 15 min în Buy)",
|
||
caption=caption,
|
||
)
|
||
svg = ch.render()
|
||
|
||
# bull trail: nu e un `Level` (spec-ul cere exact trei niveluri), deci se
|
||
# injectează ca linie separată, la aceeași scară (`ch._y`).
|
||
yb = ch._y(bull)
|
||
bull_layer = (
|
||
f'<line x1="24" y1="{yb:.1f}" x2="{ch.width - 14}" y2="{yb:.1f}" '
|
||
f'stroke="{ORANGE}" stroke-width="1.5" stroke-dasharray="2 3"/>'
|
||
f'<text x="{ch.width - 28}" y="{yb - 6:.1f}" fill="{ORANGE}" '
|
||
f'text-anchor="end">bull trail {bull:.2f}</text>'
|
||
)
|
||
i = svg.index(">") + 1
|
||
svg = svg[:i] + "\n " + bull_layer + svg[i:]
|
||
|
||
assert svg.count("\n\n") == 0, "linie goală în SVG rupe blocul din markdown"
|
||
OUT.write_text(svg)
|
||
print(f"scris {OUT.name}: {len(cds)} candele, intrare {entry:.2f}, SL {SL:.2f}, "
|
||
f"TP1 {TP1:.2f}, ieșire {exit_price:.2f} = {r_mult:.1f}R, bull trail {bull:.2f}")
|