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
182 lines
7.8 KiB
Python
182 lines
7.8 KiB
Python
"""După gap down — primul MBT-10 te scoate, al doilea merge (exemplu construit).
|
||
|
||
Citește `gen/strategii/hammer_mbt10_mbt_gapdown.spec.txt`
|
||
(marcajul `<!-- DIAGRAMA: mbt_gapdown -->`, Partea B §2.3 Gotchas).
|
||
|
||
Serie continuă de 33 candele daily, construită din close-urile date în
|
||
specificație, cu open-ul fiecărei bare = close-ul precedentei (STYLE.md),
|
||
cu două excepții impuse de exemplu:
|
||
|
||
- bara 13 (ultima dinainte de gap): ca să iasă ȘI ``low[13] = 70.60``
|
||
(nivelul de închidere a gap-ului, cerut de assert 1 și 6) ȘI
|
||
``high[13] = 71.00`` (structura seriei, „de la ~71"), bara trebuie să
|
||
fie un doji ``o = c = 70.60``. De aceea am ridicat ``close[12]`` de la
|
||
69.80 (cât dă lista) la 70.60: altfel ``min(open,close)=69.80`` și
|
||
low-ul ar ieși 69.80, nu 70.60. Wu=0.40 => high 71.00, wd=0.00 => low
|
||
70.60. (Linia „Mecioni" cere wu=0.20 => high 70.80; am ales 71.00,
|
||
cifra din structura seriei și din textul „~71".)
|
||
- bara 14 (gap down): ``open[14] = 66.40``, NU close[13]=70.60 — singura
|
||
bară cu open diferit de close-ul precedent. O construim manual:
|
||
o=c=66.40, wu=0.30 => high 66.70, wd=0.30 => low 66.10.
|
||
|
||
Wick-urile speciale: bara 16 wd=0.20 (low 64.60), bara 18 wd=0.20 (low
|
||
63.20), bara 21 wd=0.20 (low 62.00); restul (0.30, 0.40).
|
||
|
||
Rulează: python3 gen/strategii/hammer_mbt10_mbt_gapdown.py
|
||
Scrie: gen/strategii/hammer_mbt10_mbt_gapdown.svg
|
||
"""
|
||
import re
|
||
import sys
|
||
from pathlib import Path
|
||
|
||
sys.path.insert(0, "/workspace/atm-curs-dl")
|
||
from svg_candles import Candle, Chart, Level, Mark, from_closes # noqa: E402
|
||
|
||
OUT = Path(__file__).resolve().parent / "hammer_mbt10_mbt_gapdown.svg"
|
||
|
||
GRAY, RED, GREEN, BLUE, ORANGE = "#777", "#c00", "#188a3e", "#1a5fb4", "#b06000"
|
||
|
||
# --- seria: 33 de close-uri, exact ca în specificație ------------------------
|
||
# Singura abatere: closes[12] 69.80 -> 70.60, ca bara 13 să poată avea
|
||
# low 70.60 (vezi docstring-ul).
|
||
closes = [
|
||
60.00, 60.80, 61.60, 62.50, 63.30, 64.20, 65.00, 65.80, 66.60, 67.40, # 0-9
|
||
68.20, 69.00, 70.60, 70.60, # 10-13
|
||
66.40, # 14 = bara de GAP DOWN (open impus 66.40, NU close[13])
|
||
65.50, # 15
|
||
64.80, # 16 = MBT-10 #1, închidere => intrare #1
|
||
64.20, # 17
|
||
63.40, # 18 = low 63.20 => stop #1
|
||
62.90, # 19
|
||
62.50, # 20
|
||
62.20, # 21 = MBT-10 #2, închidere => intrare #2
|
||
62.80, 63.60, 64.50, 65.50, 66.50, 67.50, 68.50, 69.50, # 22-29
|
||
70.60, # 30 = gap închis (TP)
|
||
70.90, # 31
|
||
70.50, # 32
|
||
]
|
||
assert len(closes) == 33, len(closes)
|
||
|
||
WICKS = [(0.30, 0.40)] * 33 # restul barelor
|
||
WICKS[13] = (0.40, 0.00) # high 71.00, low 70.60
|
||
WICKS[14] = (0.30, 0.30) # high 66.70, low 66.10 (bara de gap)
|
||
WICKS[16] = (0.30, 0.20) # low 64.60
|
||
WICKS[18] = (0.30, 0.20) # low 63.20
|
||
WICKS[21] = (0.30, 0.20) # low 62.00
|
||
|
||
# Volume relative, alese de generator (materialul nu dă volum aici):
|
||
# în scădere pe piciorul coborârii după gap (14 -> 21), apoi recuperare.
|
||
vols = [
|
||
48, 46, 44, 45, 43, 42, 44, 41, 40, 42, # 0-9
|
||
43, 41, 46, 58, # 10-13
|
||
90, # 14 = gap down
|
||
76, 66, 57, 50, 45, 42, # 15-20
|
||
40, # 21 = MBT-10 #2 (minim)
|
||
46, 52, 58, 63, 68, 73, 78, 82, 86, 88, 80, # 22-32
|
||
]
|
||
assert len(vols) == 33, len(vols)
|
||
coborare = vols[14:22]
|
||
assert all(coborare[i] >= coborare[i + 1] for i in range(len(coborare) - 1)), \
|
||
f"volumul nu scade pe piciorul coborârii: {coborare}"
|
||
assert vols[-1] > vols[21], "volumul nu recuperează după bara 21"
|
||
|
||
cds = from_closes(closes, wicks=WICKS, vols=vols, start=60.00)
|
||
# bara de gap: open = 66.40, nu close[13] = 70.60
|
||
cds[14] = Candle(66.40, 66.70, 66.10, 66.40, vol=vols[14], color=RED)
|
||
|
||
# --- assert-urile din specificație -----------------------------------------
|
||
# 1. gap down real
|
||
assert abs(cds[14].o - 66.40) < 1e-9, cds[14].o
|
||
assert abs(cds[13].l - 70.60) < 1e-9, f"low[13]={cds[13].l} != 70.60"
|
||
assert abs(cds[13].c - 70.60) < 1e-9, cds[13].c
|
||
assert cds[14].o < cds[13].l and cds[14].h < cds[13].l, "gapul nu e real"
|
||
assert abs(cds[14].o - cds[13].c) > 1e-9, "open[14] nu e excepția de la serie"
|
||
|
||
# 2. #1
|
||
intrare1, sl1 = 64.80, 63.20
|
||
assert abs(cds[16].c - intrare1) < 1e-9, f"close[16]={cds[16].c} != 64.80"
|
||
assert abs(sl1 - 63.20) < 1e-9
|
||
assert min(c.l for c in cds[16:19]) <= sl1, "stopul #1 nu e atins"
|
||
assert cds[16].c > sl1, "intrarea #1 e sub stopul ei"
|
||
|
||
# 3. #2
|
||
intrare2, sl2 = 62.20, 60.40
|
||
assert abs(cds[21].c - intrare2) < 1e-9, f"close[21]={cds[21].c} != 62.20"
|
||
assert abs(sl2 - 60.40) < 1e-9
|
||
assert not any(c.l <= sl2 for c in cds[22:]), "un low atinge SL2 după bara 21"
|
||
|
||
# 4. riscuri
|
||
risc1 = intrare1 - sl1 # 1.60
|
||
risc2 = intrare2 - sl2 # 1.80
|
||
assert abs(risc1 - 1.60) < 1e-9 and abs(risc2 - 1.80) < 1e-9, (risc1, risc2)
|
||
|
||
# 5. TP = închiderea gap-ului
|
||
TP = 70.60
|
||
reward2 = TP - intrare2 # 8.40
|
||
assert abs(reward2 - 8.40) < 1e-9, reward2
|
||
assert round(reward2 / risc2, 1) == 4.7, round(reward2 / risc2, 1)
|
||
|
||
# 6. low[13] e nivelul de închidere a gap-ului și TP-ul e atins în urcare
|
||
assert abs(cds[13].l - TP) < 1e-9, "low[13] != 70.60 (nivelul gap-ului)"
|
||
assert max(c.h for c in cds[22:33]) >= TP, "TP 70.60 nu e atins după #2"
|
||
primul_tp = next(i for i in range(22, 33) if cds[i].h >= TP)
|
||
|
||
# 7. același risc în bani: mărimea #2 = mărimea #1 × (risc1 / risc2)
|
||
marime1 = 1.0
|
||
marime2 = marime1 * (risc1 / risc2)
|
||
assert abs(marime2 - 1.60 / 1.80) < 1e-9, marime2
|
||
|
||
print(f"gap {cds[13].l:.2f} -> {cds[14].o:.2f} "
|
||
f"#1 intrare {intrare1:.2f} SL {sl1:.2f} (risc {risc1:.2f}) "
|
||
f"#2 intrare {intrare2:.2f} SL {sl2:.2f} (risc {risc2:.2f}) "
|
||
f"TP {TP:.2f} = {reward2 / risc2:.1f}R "
|
||
f"atins la bara {primul_tp} (high {cds[primul_tp].h:.2f}) "
|
||
f"mărime #2 = {marime2:.3f} × mărime #1")
|
||
|
||
# --- desenul ----------------------------------------------------------------
|
||
ch = Chart(
|
||
candles=cds,
|
||
levels=[
|
||
# toate etichetele stau în dreapta (x > ultima candelă), fără suprapuneri
|
||
Level(intrare1, "#1 intrare 64.80", GRAY, label_at="right"),
|
||
Level(sl1, "#1 SL 63.20", GRAY, label_at="right"),
|
||
Level(intrare2, "#2 intrare 62.20", GRAY, label_at="right"),
|
||
Level(sl2, "#2 SL 60.40", GRAY, label_at="right"),
|
||
Level(TP, "TP 70.60 = 4.7R (gap închis)", BLUE, label_at="right"),
|
||
],
|
||
marks=[
|
||
# decalate pe verticală cât să nu atingă candelele din coborâre
|
||
Mark(14, "gap down", RED, "above", dy=-22),
|
||
Mark(16, "MBT-10 #1", GRAY, "above", dy=-25),
|
||
Mark(18, "stop #1 atins", RED, "below", dy=30),
|
||
Mark(21, "MBT-10 #2", GREEN, "below", dy=25),
|
||
],
|
||
scale=28.0, gap=15, body_w=9, left=44, width=820, vol_h=70,
|
||
vol_label="volum",
|
||
title="După gap down — primul MBT-10 te scoate, al doilea merge (exemplu construit)",
|
||
caption="",
|
||
)
|
||
|
||
svg = ch.render()
|
||
assert "<svg" in svg and "\n\n" not in svg, "linie goală în SVG"
|
||
|
||
# --- blocul de text de jos: cine are același risc + legenda -----------------
|
||
m = re.search(r'viewBox="0 0 (\d+) (\d+)"', svg)
|
||
W, H = int(m.group(1)), int(m.group(2))
|
||
linii = [
|
||
"MBT-10 #1: intrare 64.80 / SL 63.20 — risc 1R în bani",
|
||
"MBT-10 #2: intrare 62.20 / SL 60.40 — risc 1R în bani (mărime ajustată)",
|
||
"Exemplu construit (02_mbt10.md:728-734): #1 și #2 au același risc în bani; "
|
||
"#2 singur dă 4.7R.",
|
||
"De aceea mărimea se împarte pe 2-3 încercări.",
|
||
]
|
||
extra = 16 * len(linii) + 6
|
||
svg = svg.replace(f'viewBox="0 0 {W} {H}"', f'viewBox="0 0 {W} {H + extra}"')
|
||
svg = svg.replace("</svg>", "".join(
|
||
f'<text x="24" y="{H + 12 + 16 * i}" fill="#555">{t}</text>'
|
||
for i, t in enumerate(linii)) + "</svg>")
|
||
assert "\n\n" not in svg, "linie goală în SVG după legendă"
|
||
|
||
OUT.write_text(svg)
|
||
print(f"scris {OUT.relative_to('/workspace/atm-curs-dl')}")
|