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
205 lines
10 KiB
Python
205 lines
10 KiB
Python
"""Diagrama „aceeași candelă" MBP / MBPA++ (marcajul `samecandle`).
|
|
|
|
Două panouri alături, fiecare cu un grafic de 10 candele de 15 min și un panou
|
|
TICUS dedesubt, legate printr-o linie punctată verticală de la candela M la
|
|
valoarea ei TICUS. Singura diferență dintre panouri: pe care candelă e TICUS
|
|
extrem.
|
|
|
|
- Panoul DA: M (roșie, index 5) închide 623.87 sub banda inferioară 623.90
|
|
ȘI TICUS(M) = -1150 (≤ -1000) — pe EXACT aceeași candelă. M îndeplinit.
|
|
- Panoul NU: aceeași structură de preț, dar TICUS(M) = -600 (peste prag),
|
|
iar extremul de -1200 e pe candela m-1, a cărei închidere e peste bandă.
|
|
M neîndeplinit, deși vizual „pare" același setup.
|
|
|
|
Regula și cifrele vin din `summaries/05_MODUL_4/03_strategia_6_mbp_mbappe.md`:
|
|
- regula (bandă și TICUS pe ACEEAȘI candelă) — liniile 179-221, 439-442;
|
|
- verificarea prin click pe candelă, nu din ochi — liniile 217-221, 436-438;
|
|
- pragurile ±1000, ±500 doar referință vizuală — liniile 56-64;
|
|
- M close 623.87 sub banda 623.90 și TICUS(M) -1150 (exemplul real) —
|
|
liniile 336-342.
|
|
Cifrele din panoul NU (TICUS(M) = -600, TICUS(m-1) = -1200) sunt construite de
|
|
mine ca ilustrare a cazului „aproape, dar nu" (spec, rândurile 27-31).
|
|
Structura candelelor e construită de mine, ca fiecare nivel afirmat să iasă
|
|
exact din serie (assert-urile de mai jos).
|
|
|
|
Rulează:
|
|
python3 gen/strategii/mbp_samecandle.py
|
|
Scrie lângă el `mbp_samecandle.svg`.
|
|
"""
|
|
import re
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
sys.path.insert(0, "/workspace/atm-curs-dl")
|
|
from svg_candles import Chart, Level, from_closes # noqa: E402
|
|
|
|
OUT = Path(__file__).resolve().parent / "mbp_samecandle.svg"
|
|
|
|
GRAY, RED, GREEN, BLUE, ORANGE = "#777", "#c00", "#188a3e", "#1a5fb4", "#b06000"
|
|
|
|
# ---------------------------------------------------------------- seria (10 bare)
|
|
# 5 bare de coborâre până la M (index 5), apoi 4 bare care își revin. Open-ul
|
|
# fiecărei bare = close-ul precedentei. Aceeași serie în ambele panouri.
|
|
CLOSES = [625.35, 625.10, 624.90, 624.65, 624.30, # 0-4 downtrend
|
|
623.87, # 5 = M, roșie, sub bandă
|
|
623.98, 624.12, 624.28, 624.45] # 6-9 revenire
|
|
WICKS = [(0.08, 0.08), (0.10, 0.10), (0.08, 0.10), (0.10, 0.10), (0.08, 0.12),
|
|
(0.06, 0.10), # M: high 624.36, low 623.77
|
|
(0.06, 0.06), (0.08, 0.06), (0.06, 0.06), (0.08, 0.05)]
|
|
assert len(CLOSES) == 10 and len(WICKS) == 10, (len(CLOSES), len(WICKS))
|
|
|
|
BAND = 623.90
|
|
M = 5
|
|
|
|
cds = from_closes(CLOSES, wicks=WICKS, start=625.55)
|
|
|
|
# ---------------------------------------------------------------- assert-urile din spec
|
|
assert abs(cds[M].c - 623.87) < 0.005, cds[M].c # exemplul real din curs
|
|
assert cds[M].c < BAND, cds[M].c # M închide sub banda inferioară
|
|
assert cds[M].c < cds[M].o, (cds[M].o, cds[M].c) # M e roșie
|
|
assert abs(cds[M].l - 623.77) < 0.005, cds[M].l
|
|
assert cds[M].l == min(c.l for c in cds) # M are minimul seriei
|
|
assert cds[M - 1].c > BAND, cds[M - 1].c # candela m-1 închide PESTE bandă
|
|
assert [round(c.c, 2) for c in cds] == CLOSES # close-urile sunt respectate
|
|
|
|
# TICUS: valoarea pe candelă. DA: doar M, sub prag. NU: extremul pe m-1.
|
|
TICUS_DA = {M: -1150.0}
|
|
TICUS_NU = {M - 1: -1200.0, M: -600.0}
|
|
|
|
assert TICUS_DA[M] <= -1000, TICUS_DA[M] # DA: ticus(M) under -1000
|
|
assert TICUS_NU[M] > -1000, TICUS_NU[M] # NU: ticus(M) peste prag
|
|
assert TICUS_NU[M - 1] <= -1000, TICUS_NU[M - 1] # NU: extremul e pe m-1
|
|
assert not any(i == M and v <= -1000 for i, v in TICUS_NU.items()), \
|
|
"NU: nu are voie să existe bar TICUS sub -1000 aliniat cu candela M"
|
|
|
|
# ---------------------------------------------------------------- construcția panourilor
|
|
SCALE, GAP, BODY, LEFT, W = 85.0, 16, 10, 44, 380
|
|
|
|
|
|
def _ref_line(Wv, y, color, dash, text, lab_dy):
|
|
dash_attr = f' stroke-dasharray="{dash}"' if dash else ""
|
|
return (f'<line x1="24" y1="{y:.1f}" x2="{Wv - 14}" y2="{y:.1f}" stroke="{color}" '
|
|
f'stroke-width="1"{dash_attr}/>'
|
|
f'<text x="26" y="{y + lab_dy:.1f}" fill="{color}">{text}</text>')
|
|
|
|
|
|
def build_panel(ticus, labels):
|
|
"""Un panou = grafic de preț (svg_candles) + panou TICUS dedesubt."""
|
|
ch = Chart(candles=cds,
|
|
levels=[Level(BAND, "bandă 623.90", BLUE, dash="4 3", label_at="right")],
|
|
scale=SCALE, gap=GAP, body_w=BODY, left=LEFT, width=W)
|
|
raw = ch.render()
|
|
mm = re.search(r'viewBox="0 0 (\d+) ([0-9.]+)"', raw)
|
|
Wv, H0 = int(mm.group(1)), float(mm.group(2))
|
|
inner = raw[raw.index(">") + 1: raw.rindex("</svg>")].strip("\n")
|
|
|
|
ticus_top = H0 + 30
|
|
y0 = H0 + 100 # linia 0 a TICUS
|
|
PX = 44.0 # 1000 unități TICUS = 44 px
|
|
bar_label_x = ch._cx(M) + 16 # etichetele barelor stau în coloană, la dreapta
|
|
|
|
def ty(v):
|
|
return y0 - v / 1000.0 * PX
|
|
|
|
total_h = y0 + 96
|
|
parts = []
|
|
|
|
# linia punctată verticală: aceeași candelă sus și jos (desenată prima, ca
|
|
# etichetele să vină peste ea)
|
|
for i, v in sorted(ticus.items()):
|
|
xb = ch._cx(i)
|
|
assert -1 <= xb <= Wv + 1, xb
|
|
parts.append(f'<line x1="{xb:.1f}" y1="{ch._y(cds[i].l):.1f}" x2="{xb:.1f}" '
|
|
f'y2="{ty(v):.1f}" stroke="#aaa" stroke-width="1" stroke-dasharray="3 3"/>')
|
|
|
|
parts.append(f'<text x="14" y="{ticus_top:.0f}" fill="#333" '
|
|
f'style="font-weight:bold">TICUS</text>')
|
|
# praguri: 0 (linie plină), ±1000 (punctate), ±500 (fine, doar referință)
|
|
parts.append(_ref_line(Wv, ty(1000), RED, "4 3", "+1000 (prag)", -5))
|
|
parts.append(_ref_line(Wv, ty(500), "#caa000", "2 2", "+500", -5))
|
|
parts.append(_ref_line(Wv, ty(0), "#888", "", "0", 4))
|
|
parts.append(_ref_line(Wv, ty(-500), "#caa000", "2 2", "-500", 13))
|
|
parts.append(_ref_line(Wv, ty(-1000), GREEN, "4 3", "-1000 (prag)", 13))
|
|
|
|
# barele TICUS (centrate pe x-ul candelei)
|
|
for i, v in sorted(ticus.items()):
|
|
xb = ch._cx(i)
|
|
yv = ty(v)
|
|
col = RED if v <= -1000 else ORANGE
|
|
parts.append(f'<rect x="{xb - BODY / 2:.1f}" y="{min(y0, yv):.1f}" '
|
|
f'width="{BODY}" height="{abs(yv - y0):.1f}" fill="{col}"/>')
|
|
|
|
# eticheta de valoare lângă fiecare bar (aceeași coloană x, rânduri diferite)
|
|
for i, (txt, dy) in sorted(labels.items()):
|
|
col = RED if ticus[i] <= -1000 else ORANGE
|
|
parts.append(f'<text x="{bar_label_x:.1f}" y="{ty(ticus[i]) + dy:.1f}" '
|
|
f'fill="{col}">{txt}</text>')
|
|
|
|
# adnotarea candelei M, în zona de preț, sub bară (zona e liberă)
|
|
parts.append(f'<text x="{ch._cx(M):.1f}" y="{ch._y(cds[M].l) + 18:.1f}" fill="{RED}" '
|
|
f'text-anchor="middle" style="font-weight:bold">'
|
|
f'M: close 623.87 < bandă 623.90</text>')
|
|
|
|
# alinierea TICUS(M) cu candela M: barul se centrează exact pe x-ul candelei
|
|
xm = ch._cx(M)
|
|
x_bar_m = ch._cx(M) # centrul barei TICUS a lui M
|
|
assert abs(x_bar_m - xm) < 1e-9
|
|
assert ticus[M] <= -1000 if ticus is TICUS_DA else ticus[M] > -1000
|
|
|
|
body = inner + "\n " + "\n ".join(parts)
|
|
# bara TICUS a lui M chiar a fost scrisă cu centrul pe x-ul candelei M
|
|
assert f'<rect x="{xm - BODY / 2:.1f}"' in body, "bara TICUS(M) nu e aliniată cu M"
|
|
assert f'<line x1="{xm:.1f}"' in body, "linia punctată verticală nu pleacă din M"
|
|
return ch, Wv, H0, total_h, body
|
|
|
|
|
|
labels_da = {M: ("M: TICUS -1150 (sub -1000)", 18)}
|
|
labels_nu = {M - 1: ("TICUS(m-1) = -1200 (extrem)", 20),
|
|
M: ("TICUS(M) = -600 (nu atinge -1000)", 20)}
|
|
|
|
ch_da, Wv, H0, PH, inner_da = build_panel(TICUS_DA, labels_da)
|
|
ch_nu, Wv2, H02, PH2, inner_nu = build_panel(TICUS_NU, labels_nu)
|
|
assert Wv == Wv2 and PH == PH2, (PH, PH2)
|
|
|
|
# aceeași structură de preț => aceleași x-uri ale candelelor în ambele panouri
|
|
assert all(abs(ch_da._cx(i) - ch_nu._cx(i)) < 1e-9 for i in range(len(cds)))
|
|
|
|
# ---------------------------------------------------------------- compunerea SVG-ului
|
|
TOP = 48
|
|
FOOTER = [
|
|
"Regula: banda (close sub 623.90) și TICUS ≤ -1000 trebuie pe ACEEAȘI candelă.",
|
|
"Pragurile TICUS sunt ±1000 (overbought / oversold); ±500 sunt doar referință vizuală.",
|
|
"Verificarea se face prin click pe candelă, nu din ochi — TICUS(M) se citește exact pe candela M.",
|
|
"DA: TICUS(M) = -1150 (exemplul real din curs).",
|
|
"NU: TICUS(M) = -600, TICUS(m-1) = -1200 — cifre construite de mine ca ilustrare.",
|
|
"Structura candelelor (10 bare de 15 min, aceeași în ambele panouri) e construită de mine.",
|
|
]
|
|
root_h = TOP + PH + 22 + 17 * len(FOOTER) + 8
|
|
|
|
out = [f'<svg viewBox="0 0 780 {root_h:.0f}" xmlns="http://www.w3.org/2000/svg" '
|
|
f'style="width:100%;height:auto;font-family:system-ui,sans-serif;font-size:13px">',
|
|
f'<svg x="8" y="{TOP}" width="{Wv}" height="{PH:.0f}" viewBox="0 0 {Wv} {PH:.0f}">{inner_da}</svg>',
|
|
f'<svg x="396" y="{TOP}" width="{Wv}" height="{PH:.0f}" viewBox="0 0 {Wv} {PH:.0f}">{inner_nu}</svg>',
|
|
f'<text x="8" y="22" fill="{GREEN}" style="font-weight:bold">DA — aceeași candelă, M îndeplinit</text>',
|
|
f'<text x="396" y="16" fill="{RED}" style="font-weight:bold">NU — TICUS extrem pe altă candelă,</text>',
|
|
f'<text x="396" y="32" fill="{RED}" style="font-weight:bold">M neîndeplinit</text>']
|
|
fy = TOP + PH + 22
|
|
for i, line in enumerate(FOOTER):
|
|
out.append(f'<text x="8" y="{fy + 17 * i:.0f}" fill="#555">{line}</text>')
|
|
out.append("</svg>")
|
|
svg = "\n ".join(out)
|
|
|
|
# ---------------------------------------------------------------- verificări pe SVG-ul final
|
|
assert svg.count("\n\n") == 0, "linie goală în SVG rupe blocul din markdown"
|
|
assert "DA — aceeași candelă, M îndeplinit" in svg
|
|
assert "NU — TICUS extrem pe altă candelă," in svg
|
|
assert "M neîndeplinit" in svg
|
|
assert "click pe candelă, nu din ochi" in svg
|
|
assert svg.count("<svg") == 3 and svg.count("</svg>") == 3, "cele două panouri + rădăcina"
|
|
|
|
OUT.write_text(svg)
|
|
print(f"scris {OUT.name}: {len(cds)} candele/panou, M close {cds[M].c:.2f} "
|
|
f"(< {BAND}), TICUS DA {TICUS_DA[M]:.0f} / NU {TICUS_NU[M]:.0f} "
|
|
f"(extrem {TICUS_NU[M - 1]:.0f} pe m-1), x(M)={ch_da._cx(M):.0f}, "
|
|
f"înălțime {root_h:.0f}px")
|