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
159 lines
8.4 KiB
Python
159 lines
8.4 KiB
Python
"""ATMI — aceeași corecție: 4.0 % (DA) vs. 3.9 % (NU; cursul zice „3.93 %").
|
|
|
|
Citește specificația `gen/strategii/atmi_aplus.spec.txt`.
|
|
|
|
O singură serie de 32 de candele daily (open-ul fiecărei bare = close-ul
|
|
precedentei, deci seria e continuă), care urmează calea DA: urcare la swing
|
|
high 100.00, corecție exact 4.0 % până la 96.00 (BIM + candelă verde =
|
|
intrare), apoi urcare la TP +10 % = 105.60. Peste ea, un traseu punctat
|
|
(calea NU) care se oprește la 96.10 = 3.9 % și apoi coboară ilustrativ — fără
|
|
intrare, pentru că nu atinge pragul de 4 %.
|
|
|
|
Traseele punctate / benzile se inserează în SVG-ul scos de `svg_candles`
|
|
folosind coordonatele lui (`ch._cx`, `ch._y`), la fel ca în `az_structura.py`
|
|
— candelele rămân desenate de `svg_candles`, nu de mână.
|
|
"""
|
|
import sys
|
|
|
|
sys.path.insert(0, "/workspace/atm-curs-dl")
|
|
from svg_candles import Chart, Level, Mark, from_closes # noqa: E402
|
|
|
|
GRAY, RED, GREEN, BLUE, ORANGE, INK = "#777", "#c00", "#188a3e", "#1a5fb4", "#b06000", "#333"
|
|
|
|
# --- seria: 32 de close-uri, structura din specificație ----------------------
|
|
# 0-11 urcare 90.00 -> 100.00 (swing high la bara 11)
|
|
# 12-17 corecție (cădere)
|
|
# 18 BIM (hammer de reversie, codiță lungă în jos)
|
|
# 19 prima candelă verde daily => intrare, close 96.00
|
|
# 20-31 urcare 96.00 -> 105.60 (TP +10 %)
|
|
closes = [
|
|
90.00, 90.90, 91.70, 92.40, 93.20, 94.00, 94.70, 95.40, 96.10, 97.00, 98.30, 100.00, # 0-11
|
|
99.30, 98.50, 97.70, 97.10, 96.60, 96.20, # 12-17
|
|
95.98, # 18 BIM
|
|
96.00, # 19 intrare (verde)
|
|
97.00, 97.90, 98.80, 99.70, 100.50, 101.30, 102.10, 102.90, 103.70, 104.50, 105.10, 105.60, # 20-31
|
|
]
|
|
assert len(closes) == 32
|
|
|
|
# wick-uri (sus, jos): implicite (0.25, 0.30), cu excepțiile care fixează nivelurile
|
|
wicks = [(0.25, 0.30)] * 32
|
|
wicks[11] = (0.00, 0.30) # swing high: high = close = 100.00
|
|
wicks[18] = (0.20, 0.45) # BIM: codiță lungă în jos (hammer)
|
|
wicks[19] = (0.20, 0.15) # candela de intrare
|
|
wicks[31] = (0.00, 0.30) # TP: high = close = 105.60
|
|
|
|
# volum relativ ales de generator (materialul nu dă volum pe exemplu):
|
|
# crește pe corecție (vânzătorii), vârf pe candela de intrare, scade pe urcare.
|
|
vols = [48, 50, 46, 52, 54, 50, 56, 52, 58, 60, 64, 72,
|
|
70, 74, 78, 82, 86, 80, 84,
|
|
88,
|
|
72, 66, 62, 58, 54, 50, 46, 62, 68, 72, 78, 86]
|
|
assert len(vols) == 32
|
|
|
|
cds = from_closes(closes, wicks=wicks, vols=vols, start=89.40)
|
|
|
|
# --- indicii-cheie și nivelurile ---------------------------------------------
|
|
SWING_I = 11 # swing high
|
|
BIM_I = 18 # primul BIM
|
|
ENTRY_I = 19 # candela verde de intrare
|
|
SWING = 100.00
|
|
PRAG4 = 96.00 # prag corecție 4.0 % => DA
|
|
NU_STOP = 96.10 # cazul real 3.9 % => NU
|
|
TP = 105.60 # +10 % față de 96.00
|
|
entry = cds[ENTRY_I].c
|
|
|
|
# --- cele 8 assert-uri cerute de specificație --------------------------------
|
|
# 1. pragul de 4 % corespunde lui 96.00
|
|
assert abs((SWING - PRAG4) / SWING * 100 - 4.00) < 0.05, "96.00 nu e 4.0 %"
|
|
# 2. cazul NU: 96.10 = 3.9 % (graficul cursului scrie 3.9 %; textul „3.93 %")
|
|
assert abs((SWING - NU_STOP) / SWING * 100 - 3.90) < 0.02, "96.10 nu e 3.9 %"
|
|
# 3. calea DA coboară mai jos decât calea NU
|
|
assert PRAG4 < NU_STOP, "pragul DA nu e sub oprirea NU"
|
|
# 4. intrarea = close-ul candelei verzi = 96.00
|
|
assert abs(entry - 96.00) < 1e-9, f"intrarea {entry} != 96.00"
|
|
assert cds[ENTRY_I].c >= cds[ENTRY_I].o, "candela de intrare nu e verde"
|
|
# 5. TP = +10 %
|
|
assert abs(TP / entry - 1.10) < 0.005, f"TP/entry = {TP / entry:.4f}, nu 1.10"
|
|
# 6. BIM înainte de candela de intrare
|
|
assert BIM_I < ENTRY_I, "BIM nu e înainte de candela de intrare"
|
|
# 7. traseul NU nu atinge pragul de trigger: se oprește la 96.10 > 96.00, deci
|
|
# rămâne fără intrare; pe traseul punctat nu există nicio candelă / BIM
|
|
nu_route = [(SWING_I, 100.00), (12, 99.40), (13, 98.65), (14, 97.95),
|
|
(15, 97.35), (16, 96.80), (17, NU_STOP)]
|
|
assert min(p for _, p in nu_route) == NU_STOP, "oprirea traseului NU nu e 96.10"
|
|
assert min(p for _, p in nu_route) > PRAG4, "traseul NU atinge pragul de 4 %"
|
|
assert not any(i >= BIM_I for i, _ in nu_route), "traseul NU ajunge la candela BIM"
|
|
# 8. ambele trasee pleacă din același swing high 100.00
|
|
assert abs(cds[SWING_I].h - SWING) < 1e-9, f"swing high {cds[SWING_I].h} != 100.00"
|
|
assert abs(nu_route[0][1] - cds[SWING_I].h) < 1e-9, "traseul NU nu pleacă din swing high"
|
|
|
|
# verificări de context cerute de STYLE.md (structura susține eticheta)
|
|
assert abs(TP - max(c.h for c in cds[-10:])) < 1e-9, "TP nu e maximul de la final"
|
|
assert min(closes[12:20]) < 96.10 and min(closes[12:20]) >= 95.90, \
|
|
"corecția DA nu coboară în jurul lui 96.00"
|
|
assert all(b > a for a, b in zip(closes[20:31], closes[21:32])), "urcarea spre TP nu e monotonă"
|
|
|
|
print(f"swing {SWING:.2f} prag DA {PRAG4:.2f} (4.0 %) stop NU {NU_STOP:.2f} (3.9 %) "
|
|
f"intrare {entry:.2f} TP {TP:.2f} (+{TP / entry - 1:.1%}) BIM bara {BIM_I}")
|
|
|
|
# --- desenul -----------------------------------------------------------------
|
|
cds[ENTRY_I].note = ("Regula e pe procent, nu pe cât de dramatică arată linia: "
|
|
"calea NU pare o cădere mare, dar e 3.9 %.")
|
|
|
|
# traseul NU: decizia (până la 96.10) + continuarea ilustrativă în jos
|
|
nu_cont = [(17, NU_STOP), (18, 95.70), (19, 95.35), (21, 94.95), (23, 94.65), (25, 94.45)]
|
|
nu_pts = nu_route + nu_cont[1:]
|
|
|
|
ch = Chart(
|
|
candles=cds,
|
|
levels=[
|
|
Level(SWING, "100.00 — swing high", INK),
|
|
Level(PRAG4, "96.00 = 4.0 % — DA, intri", GREEN),
|
|
Level(TP, "TP +10 % = 105.60", BLUE),
|
|
],
|
|
marks=[
|
|
Mark(BIM_I, "BIM", GREEN, "above", dy=-2),
|
|
Mark(ENTRY_I, "candelă verde → trigger", GREEN, "below", dy=20),
|
|
],
|
|
scale=18.0, gap=16, body_w=9, left=44, width=680, vol_h=70,
|
|
vol_label="volum", title="SPY — daily: aceeași corecție, 4.0 % (DA) vs. 3.9 % (NU)",
|
|
caption="Calea DA (candele): 4.0 % la 96.00. Calea NU (punctat): 3.9 % („3.93 %” în curs) — fără intrare.",
|
|
)
|
|
svg = ch.render()
|
|
|
|
# --- elemente suplimentare, pe coordonatele graficului -----------------------
|
|
x0, x1 = 24, ch.width - 14
|
|
band_top, band_bot = ch._y(96.40), ch._y(95.40)
|
|
band = (f'<rect x="{x0}" y="{band_top:.1f}" width="{x1 - x0}" height="{band_bot - band_top:.1f}" '
|
|
f'fill="{GRAY}" opacity="0.10"/>')
|
|
|
|
nu_line = ('<polyline points="' + " ".join(f"{ch._cx(i):.1f},{ch._y(p):.1f}" for i, p in nu_pts) + '" '
|
|
f'fill="none" stroke="{RED}" stroke-width="1.8" stroke-dasharray="5 4"/>')
|
|
# linia pragului NU (96.10) se desenează manual, ca Level-ul gol să nu lase text vid
|
|
nu_hline = (f'<line x1="24" y1="{ch._y(NU_STOP):.1f}" x2="{ch.width - 14}" y2="{ch._y(NU_STOP):.1f}" '
|
|
f'stroke="{RED}" stroke-width="1.5" stroke-dasharray="4 3"/>')
|
|
# punctul de oprire al traseului NU (96.10, strict deasupra pragului de 4 %)
|
|
stop_dot = (f'<circle cx="{ch._cx(17):.1f}" cy="{ch._y(NU_STOP):.1f}" r="3.5" fill="{RED}"/>')
|
|
# eticheta traseului NU, sub linia 96.10 (nu se suprapune cu eticheta verde de 96.00)
|
|
nu_lab = (f'<text x="{ch.width - 28}" y="{ch._y(NU_STOP) + 12:.1f}" fill="{RED}" text-anchor="end">'
|
|
f'96.10 = 3.9 % — NU, sub prag</text>')
|
|
# eticheta zonei quarterly, sub banda (fără cifre: cursul nu le dă)
|
|
zone_lab = (f'<text x="{ch.width - 28}" y="{ch._y(95.40) + 13:.1f}" fill="{GRAY}" text-anchor="end">'
|
|
f'zonă quarterly</text>')
|
|
# legende scurte ale celor două trasee
|
|
da_lab = (f'<text x="24" y="{ch._y(98.50):.1f}" fill="{GREEN}" '
|
|
f'style="font-weight:bold">calea DA (candelele)</text>')
|
|
nu_leg = (f'<text x="{ch._cx(25):.1f}" y="{ch._y(94.45) + 16:.1f}" fill="{RED}" '
|
|
f'style="font-weight:bold">calea NU (continuă în jos)</text>')
|
|
|
|
overlay = [nu_line, nu_hline, stop_dot, nu_lab, zone_lab, da_lab, nu_leg]
|
|
svg = svg.replace(">", ">\n " + band, 1) # banda, sub candele
|
|
svg = svg.replace("\n</svg>", "\n " + "\n ".join(overlay) + "\n</svg>") # traseele, peste
|
|
|
|
assert "\n\n" not in svg, "linie goală în SVG"
|
|
assert "</svg>" in svg and "stroke-dasharray=\"5 4\"" in svg, "traseul NU nu a fost inserat"
|
|
|
|
with open("/workspace/atm-curs-dl/gen/strategii/atmi_aplus.svg", "w") as f:
|
|
f.write(svg)
|
|
print("scris gen/strategii/atmi_aplus.svg")
|