La mdsrt_aplus eticheta spunea "primul low 94.60" pentru o bara al carei low era 94.50. Reparatia a mutat low-ul, nu eticheta: regula capitolului numara piciorul dupa low, iar specul spune tot 94.60 - doar umbra de 0.10 o contrazicea. Nicio cifra tiparita nu se schimba, iar nivelul isi ia acum eticheta din date, cu assert, ca sa nu mai poata minti. Cele trei de la mp2 aveau toate aceeasi cauza fizica: text colorat peste un corp de candela de aceeasi culoare. Acolo caracterele nu se incaleca, dispar - "intrare 577.00" se citea "intrare 577. 0", "crossing" se citea "crossi g". VERDICT pe restul de ~94: nu merita un lot. Din 20 de suprapuneri vazute efectiv pe ecran, doar 3 schimbau ce citesti, si toate trei din cauza aia. Celelalte 17 - text peste fitil, peste linie de nivel, peste chenar, peste coada de sageata - raman lizibile, fiindca fundalul e o linie subtire, nu o suprafata plina. Filtrul util nu e tiparul geometric, ci: eticheta cu cifra, peste un <rect> de aceeasi culoare. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ThN7ixZrowca8aTGrK1MUJ
154 lines
7.1 KiB
Python
154 lines
7.1 KiB
Python
"""Diagrama din `gen/strategii/mp2_exemplu.spec.txt` (TIP: PRET).
|
|
|
|
Exemplul complet din curs: SPY long la bottom-ul de aprilie 2020 — double
|
|
bottom + Inverted Head & Shoulders la suport quarterly, trigger MP² long
|
|
(pivotul mic închide peste cel mare), prima candelă verde după umărul drept.
|
|
Intrare 577.00, SL 554.40 (1R = 22.60), TP1 599.60, ținta 4R era 667.00, iar
|
|
mișcarea reală s-a oprit la 646.00 (~3R) într-o lună.
|
|
|
|
Toate cifrele vin din `summaries/09_QA/01_2026-08-25.md:384-394,485-494`
|
|
(liniile citate în spec). Serie continuă de 38 de candele daily
|
|
(`from_closes`), panou de volum, SMA-20, niveluri reale și marcaje ancorate.
|
|
"""
|
|
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
|
|
sys.path.insert(0, "/workspace/atm-curs-dl/gen")
|
|
from eticheta_libera import eticheta_libera # noqa: E402
|
|
|
|
OUT = Path(__file__).resolve().parent / "mp2_exemplu.svg"
|
|
|
|
# ── cifrele din material (09_QA/01_2026-08-25.md:384-394,485-494) ───────────
|
|
ENTRY = 577.00 # candela de intrare (prima verde după umărul drept)
|
|
SL = 554.40 # sub umărul drept, chiar peste suportul quarterly
|
|
TP1 = 599.60 # 577.00 + 22.60 (1R)
|
|
TARGET_4R = 667.00 # ținta declarată; 4R exact = 667.40
|
|
REAL = 646.00 # mișcarea reală (~3R, +12% efectiv)
|
|
QUARTERLY = 554.00 # suport quarterly
|
|
SHOULDER_L = 566.0 # umăr stâng
|
|
HEAD = 555.0 # head (cel mai jos)
|
|
SHOULDER_R = 568.0 # umăr drept
|
|
NECKLINE = 578.00
|
|
|
|
# ── structura seriei (spec): ~14 coborâre → ~10 bază → intrare → ~12 urcare ─
|
|
closes = [
|
|
# 0-13: ~14 candele de coborâre de la ~600 spre umărul stâng 566
|
|
600.0, 597.0, 594.0, 596.5, 591.0, 587.5, 590.0,
|
|
584.0, 579.5, 581.5, 576.0, 571.0, 567.0,
|
|
566.0, # 13 umăr stâng
|
|
# 14-24: baza — double bottom + Inverted Head & Shoulders
|
|
570.5, 578.0, 572.0, 567.0, 560.0,
|
|
555.0, # 19 head (cel mai jos)
|
|
561.5, 568.0, 578.0, 573.0,
|
|
568.0, # 24 umăr drept
|
|
# 25: candela de intrare, prima verde după umărul drept
|
|
577.00,
|
|
# 26-37: ~12 candele de urcare spre 646.00 (marcajul „real")
|
|
581.0, 585.5, 589.0, 584.0, 590.5, 596.0, 602.0,
|
|
609.0, 616.0, 624.0, 635.0, 646.0,
|
|
]
|
|
assert len(closes) == 38, len(closes)
|
|
|
|
ENTRY_I = 25
|
|
HEAD_I = 19
|
|
SHL_I = 13
|
|
SHR_I = 24
|
|
|
|
wicks = [(0.4, 0.4)] * len(closes)
|
|
wicks[SHL_I] = (0.4, 0.5) # low umăr stâng 565.50
|
|
wicks[HEAD_I] = (0.4, 0.6) # low head = 555.0 - 0.6 = 554.40 (suportul atins)
|
|
wicks[SHR_I] = (0.4, 0.5)
|
|
wicks[15] = (0.4, 0.4) # vârf 578.0 = neckline
|
|
wicks[22] = (0.4, 0.4) # vârf 578.0 = neckline
|
|
wicks[ENTRY_I] = (0.7, 0.4) # intrare verde, wick mic
|
|
wicks[37] = (0.0, 0.3) # high = 646.00 exact (mișcarea reală se oprește aici)
|
|
|
|
vols = [
|
|
# coborâre
|
|
48, 46, 50, 44, 52, 49, 45, 53, 55, 47, 50, 54, 58, 62,
|
|
# bază (head-ul are volum mare — capitulare)
|
|
60, 72, 65, 58, 66, 88, 70, 64, 74, 60, 56,
|
|
# intrare
|
|
92,
|
|
# urcare
|
|
78, 72, 68, 60, 66, 74, 82, 88, 80, 84, 90, 95,
|
|
]
|
|
assert len(vols) == len(closes)
|
|
|
|
cds = from_closes(closes, wicks=wicks, vols=vols, start=602.0)
|
|
|
|
# ── assert-urile din specificație ───────────────────────────────────────────
|
|
entry = cds[ENTRY_I].c
|
|
assert abs(entry - 577.00) < 0.05, entry
|
|
assert abs(SL - 554.40) < 1e-9, SL
|
|
assert SL > QUARTERLY, (SL, QUARTERLY) # peste suportul quarterly
|
|
assert abs((ENTRY - SL) - 22.60) < 0.05, ENTRY - SL # 1R
|
|
assert abs(TP1 - (ENTRY + 22.60)) < 0.05, TP1 # TP1 = intrare + 1R
|
|
target_4r = ENTRY + 4 * (ENTRY - SL)
|
|
assert abs(target_4r - 667.40) < 0.05, target_4r # 4R exact
|
|
assert abs(target_4r - TARGET_4R) < 0.5, target_4r # ≈ 667.00, rotunjit în curs
|
|
assert abs((REAL - ENTRY) / (ENTRY - SL) - 3.05) < 0.02, (REAL - ENTRY) / (ENTRY - SL)
|
|
assert abs((REAL - ENTRY) / (ENTRY - SL) - 4.0) > 0.5, "mișcarea reală NU e 4R"
|
|
|
|
# structura care susține afirmațiile din text
|
|
assert abs(cds[SHL_I].c - SHOULDER_L) < 0.05, cds[SHL_I].c
|
|
assert abs(cds[HEAD_I].c - HEAD) < 0.05, cds[HEAD_I].c
|
|
assert abs(cds[SHR_I].c - SHOULDER_R) < 0.05, cds[SHR_I].c
|
|
assert HEAD < SHOULDER_L and HEAD < SHOULDER_R, (HEAD, SHOULDER_L, SHOULDER_R)
|
|
assert cds[SHL_I].c == min(c.c for c in cds[SHL_I - 1:SHL_I + 2]), "umărul stâng nu e minim local"
|
|
assert cds[HEAD_I].c == min(c.c for c in cds[HEAD_I - 1:HEAD_I + 2]), "head-ul nu e minim local"
|
|
assert cds[SHR_I].c == min(c.c for c in cds[SHR_I - 1:SHR_I + 2]), "umărul drept nu e minim local"
|
|
min_pre = min(c.l for c in cds[:ENTRY_I])
|
|
assert abs(min_pre - 554.0) < 1.0, min_pre # suportul quarterly atins
|
|
assert abs(min_pre - SL) < 0.05, (min_pre, SL) # SL chiar la minimul head-ului
|
|
assert cds[ENTRY_I].c > cds[ENTRY_I].o, "candela de intrare trebuie verde"
|
|
assert cds[ENTRY_I - 1].c < cds[ENTRY_I - 1].o, "umărul drept e roșu"
|
|
assert abs(cds[37].c - REAL) < 0.05, cds[37].c
|
|
assert all(c.vol is not None for c in cds), "lipsește panoul de volum"
|
|
assert len(cds) == 38
|
|
|
|
# ── desenul ─────────────────────────────────────────────────────────────────
|
|
levels = [
|
|
Level(TARGET_4R, "ținta 4R era 667.00 (+16%)", "#555"),
|
|
Level(REAL, "646.00 — +12% efectiv (~3R)", "#188a3e", label_at="left"),
|
|
Level(TP1, "TP1 599.60", "#1a5fb4"),
|
|
Level(NECKLINE, "neckline 578.00", "#1a5fb4"),
|
|
Level(SL, "SL 554.40", "#c00"),
|
|
Level(QUARTERLY, "suport quarterly 554.00", "#777", label_at="left"),
|
|
]
|
|
|
|
marks = [
|
|
Mark(SHL_I, "umăr stâng 566", "#1a1a1a", "below"),
|
|
Mark(HEAD_I, "head 555", "#1a1a1a", "below"),
|
|
Mark(SHR_I, "umăr drept 568", "#1a1a1a", "below"),
|
|
# dy/dx: la dy=-10 corpul verde de peste două candele mânca primul
|
|
# zecimal — pe ecran scria „intrare 577. 0" (02.10.2026)
|
|
Mark(ENTRY_I, "intrare 577.00", "#188a3e", "above", dy=-32, dx=-10),
|
|
]
|
|
|
|
ch = Chart(
|
|
candles=cds,
|
|
levels=levels,
|
|
marks=marks,
|
|
mas=[MA(20, "#b06000", dash="4 3", text="SMA-20")],
|
|
scale=3.8, width=720, gap=15, body_w=9, left=44, vol_h=70,
|
|
vol_label="volum", title="SPY — daily",
|
|
caption=("Intrare 577.00 = prima verde după umărul drept (MP² long). "
|
|
"1R = 22.60; 646.00 ~3R, într-o lună."),
|
|
)
|
|
|
|
svg = ch.render()
|
|
assert svg.count("\n\n") == 0, "linie goală în SVG — rupe markdown-ul"
|
|
# eticheta intrării poartă cifra: să nu mai ajungă peste un corp de candelă
|
|
eticheta_libera(svg, "exemplu", {"intrare 577.00"}, rects=True)
|
|
OUT.write_text(svg)
|
|
|
|
print(f"serie {len(cds)} candele | intrare {entry:.2f} @ {ENTRY_I} | "
|
|
f"SL {SL:.2f} | 1R {ENTRY - SL:.2f} | TP1 {TP1:.2f} | "
|
|
f"4R {target_4r:.2f} (ținta declarată {TARGET_4R:.2f}) | "
|
|
f"real {REAL:.2f} = {(REAL - ENTRY) / (ENTRY - SL):.2f}R | "
|
|
f"min pre-intrare {min_pre:.2f}")
|
|
print(f"scris {OUT}")
|