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
200 lines
8.4 KiB
Python
200 lines
8.4 KiB
Python
#!/usr/bin/env python3
|
|
"""MDSRT — minimul obligatoriu (M complet) vs. varianta Trinity.
|
|
|
|
Diagramă conceptuală (nu grafic de preț): pentru
|
|
`summaries/strategii/07_mdsrt.md`, Nivel 2, §2.1 „Ce dă edge", după tabel
|
|
(marcajul `<!-- DIAGRAMA: trinity -->`). Citește specificația
|
|
`gen/strategii/mdsrt_trinity.spec.txt` și scrie
|
|
`gen/strategii/mdsrt_trinity.svg`.
|
|
|
|
NU folosește `svg_candles`: SVG-ul e construit ca string, fără dependințe.
|
|
|
|
Conținutul, cu sursa fiecărei afirmații:
|
|
- Power obligatoriu; dacă nu e în divergență, setup-ul nu se califică:
|
|
`05_MODUL_4/02_strategia_5_mdsrt.md:59-61, 432-434`.
|
|
- Minim 2 indicatori: Power + (Volume SAU Relative Strength):
|
|
`05_MODUL_4/02_strategia_5_mdsrt.md:85-88`.
|
|
- Trinity = toți trei în divergență simultan, „setup de 5 stele":
|
|
`05_MODUL_4/02_strategia_5_mdsrt.md:6-8, 34-42, 87-88`.
|
|
- Sensul din selecția simbolului (aliniere în cadranul 1; trei divergențe
|
|
simultane NU sunt Trinity): `08_ANALIZA_ZILNICA/11_selectia_simbolului.md:612-627`.
|
|
|
|
Rulează: .venv/bin/python gen/strategii/mdsrt_trinity.py
|
|
"""
|
|
import html
|
|
from pathlib import Path
|
|
|
|
W, H = 700, 550
|
|
|
|
BLACK = "#000000"
|
|
GRAY = "#777777"
|
|
RED = "#c00"
|
|
GREEN = "#188a3e"
|
|
BLUE = "#1a5fb4"
|
|
|
|
texts = [] # (x, y, text, size, weight, fill, w_est)
|
|
boxes = [] # dict-uri: doar casetele, pentru numărătoarea din assert-uri
|
|
|
|
|
|
def add(x, y, s, size=13, weight="normal", fill=BLACK):
|
|
factor = 0.62 if weight == "bold" else 0.56
|
|
w = len(s) * size * factor
|
|
texts.append((x, y, s, size, weight, fill, w))
|
|
return (x, y, s, size, weight, fill, w)
|
|
|
|
|
|
def box(x, y, w, h, *, fill="#ffffff", stroke=GRAY, sw=1.5, dash=None, row=None):
|
|
b = {"x": x, "y": y, "w": w, "h": h, "row": row, "lines": []}
|
|
boxes.append(b)
|
|
return b
|
|
|
|
|
|
def box_line(b, dx, dy, s, size=14, weight="normal", fill=BLACK):
|
|
cx = b["x"] + b["w"] / 2.0 + dx if dx is not None else b["x"] + b["w"] / 2.0
|
|
add(cx, b["y"] + dy, s, size, weight, fill)
|
|
b["lines"].append(s)
|
|
|
|
|
|
parts = []
|
|
|
|
|
|
def rect(b, fill, stroke, sw=1.5, dash=None):
|
|
d = f' stroke-dasharray="{dash}"' if dash else ""
|
|
return (f'<rect x="{b["x"]}" y="{b["y"]}" width="{b["w"]}" '
|
|
f'height="{b["h"]}" fill="{fill}" stroke="{stroke}" '
|
|
f'stroke-width="{sw}"{d}/>')
|
|
|
|
|
|
# --------------------------------------------------------------- titlu
|
|
add(W / 2, 34, "MDSRT — minimul obligatoriu vs. Trinity", 20, "bold")
|
|
add(W / 2, 58,
|
|
"Minim Power + unul din ceilalți e obligatoriu; toți trei în divergență = Trinity.",
|
|
13, "normal", GRAY)
|
|
|
|
# ------------------------------------------------- rând 1 — minim obligatoriu
|
|
add(W / 2, 100, "Minimul obligatoriu (M complet)", 16, "bold")
|
|
|
|
B1 = box(40, 112, 250, 56, row=1)
|
|
B2 = box(310, 112, 350, 56, row=1)
|
|
parts.append(rect(B1, GREEN, GREEN))
|
|
parts.append(rect(B2, "#ffffff", GREEN, sw=2, dash="5 3"))
|
|
|
|
box_line(B1, None, 31, "Power (obligatoriu)", 15, "bold", "#ffffff")
|
|
box_line(B2, None, 22, "Volume SAU Relative Strength", 14, "normal", GREEN)
|
|
box_line(B2, None, 44, "— oricare una", 13, "normal", GREEN)
|
|
|
|
add(W / 2, 196, "Power lipsește → setup-ul NU se califică", 14, "bold", RED)
|
|
|
|
# ------------------------------------------------- rând 2 — Trinity (opțional)
|
|
add(W / 2, 250, "Trinity (opțional, semnal mai puternic)", 16, "bold")
|
|
|
|
TITLES = ["Power (obligatoriu)", "Volume", "Relative Strength"]
|
|
B3 = box(30, 262, 200, 76, row=2)
|
|
B4 = box(250, 262, 200, 76, row=2)
|
|
B5 = box(470, 262, 200, 76, row=2)
|
|
for b in (B3, B4, B5):
|
|
parts.append(rect(b, GREEN, GREEN))
|
|
for b, t in zip((B3, B4, B5), TITLES):
|
|
box_line(b, None, 34, t, 14, "bold", "#ffffff")
|
|
box_line(b, None, 56, "divergență", 14, "bold", "#ffffff")
|
|
|
|
add(W / 2, 360, "toți trei în divergență simultan, pe aceeași zonă de preț",
|
|
14, "normal", BLACK)
|
|
add(W / 2, 386, "comparat în curs cu un «setup de 5 stele»", 14, "bold", GREEN)
|
|
|
|
# --------------------------------------------------------------- subsol
|
|
parts.append(f'<line x1="40" y1="420" x2="660" y2="420" stroke="{GRAY}" '
|
|
f'stroke-width="1"/>')
|
|
add(W / 2, 448,
|
|
"La MDSRT, „Trinity” se folosește local cu sensul „toți trei în divergență”.",
|
|
13, "normal", GRAY)
|
|
add(W / 2, 468,
|
|
"Lecția de selecție dă aceluiași cuvânt alt sens:", 13, "normal", GRAY)
|
|
add(W / 2, 486,
|
|
"aliniere în cadranul 1, nu divergență (contradicția 2.4).",
|
|
13, "normal", GRAY)
|
|
add(W / 2, 512,
|
|
"Sursa: 05_MODUL_4/02_strategia_5_mdsrt.md:6-8, 34-42, 59-61, 85-88;",
|
|
13, "normal", GRAY)
|
|
add(W / 2, 530,
|
|
"08_ANALIZA_ZILNICA/11_selectia_simbolului.md:612-627.",
|
|
13, "normal", GRAY)
|
|
|
|
# --------------------------------------------------------------- emitere
|
|
head = (f'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 {W} {H}" '
|
|
f'width="{W}" height="{H}" font-family="system-ui, sans-serif" role="img" '
|
|
f'aria-label="MDSRT: minimul obligatoriu este Power (obligatoriu) plus '
|
|
f'Volume sau Relative Strength; varianta Trinity are toți trei '
|
|
f'indicatori în divergență simultan, pe aceeași zonă de preț">')
|
|
parts.insert(0, f'<rect x="0" y="0" width="{W}" height="{H}" fill="#ffffff"/>')
|
|
parts.insert(0, head)
|
|
|
|
body = []
|
|
for x, y, s, size, weight, fill, _ in texts:
|
|
body.append(
|
|
f'<text x="{x:.1f}" y="{y:.1f}" font-size="{size}" '
|
|
f'font-weight="{weight}" fill="{fill}" text-anchor="middle">'
|
|
f'{html.escape(s, quote=False)}</text>')
|
|
svg = "\n".join(parts + body + ["</svg>"]) + "\n"
|
|
|
|
# =============================================================== VERIFICARE
|
|
# 1. Rândul 1 conține exact 2 casete (Power + „Volume SAU Relative Strength"),
|
|
# nu 3.
|
|
row1 = [b for b in boxes if b["row"] == 1]
|
|
assert len(row1) == 2, f"rândul 1 are {len(row1)} casete, trebuie exact 2"
|
|
|
|
# 2. Rândul 2 conține exact 3 casete (Power, Volume, Relative Strength).
|
|
row2 = [b for b in boxes if b["row"] == 2]
|
|
assert len(row2) == 3, f"rândul 2 are {len(row2)} casete, trebuie exact 3"
|
|
assert [b["lines"][0] for b in row2] == TITLES, "titlurile din rândul 2 diferă"
|
|
|
|
# 3. Textul „Power (obligatoriu)" apare în ambele rânduri.
|
|
r1_labels = [s for b in row1 for s in b["lines"]]
|
|
r2_labels = [s for b in row2 for s in b["lines"]]
|
|
assert "Power (obligatoriu)" in r1_labels, "lipsește din rândul 1"
|
|
assert "Power (obligatoriu)" in r2_labels, "lipsește din rândul 2"
|
|
assert svg.count(">Power (obligatoriu)</text>") == 2, \
|
|
"„Power (obligatoriu)” nu apare exact de două ori"
|
|
|
|
# 4. Eticheta „Power lipsește → setup-ul NU se califică" e lângă rândul 1.
|
|
assert "Power lipsește → setup-ul NU se califică" in svg, "lipsește eticheta rând 1"
|
|
|
|
# 5. Nota de subsol despre cele două sensuri ale lui „Trinity" e prezentă.
|
|
assert "se folosește local cu sensul" in svg and "alt sens" in svg \
|
|
and "cadranul 1, nu divergență" in svg and "contradicția 2.4" in svg, \
|
|
"lipsește nota despre cele două sensuri ale lui Trinity"
|
|
|
|
# stil: fără umbre/gradienti, text >= 13px, totul în panză, fără suprapuneri
|
|
assert "filter=" not in svg and "gradient" not in svg, "fără umbre/gradienti"
|
|
assert 'font-family="system-ui, sans-serif"' in svg, "lipsește fontul cerut"
|
|
|
|
rects_collide = []
|
|
for x, y, s, size, weight, fill, w in texts:
|
|
assert size >= 13, f"font sub 13px: {s!r} ({size})"
|
|
left, right = x - w / 2.0, x + w / 2.0
|
|
top, bottom = y - size * 0.78, y + size * 0.22
|
|
assert 0 <= left and right <= W, f"etichetă în afara panzei (orizontal): {s!r}"
|
|
assert 0 <= top and bottom <= H, f"etichetă în afara panzei (vertical): {s!r}"
|
|
rects_collide.append((left, right, top, bottom, s))
|
|
|
|
for i in range(len(rects_collide)):
|
|
for j in range(i + 1, len(rects_collide)):
|
|
l1, r1, t1, b1, s1 = rects_collide[i]
|
|
l2, r2, t2, b2, s2 = rects_collide[j]
|
|
if r1 > l2 and r2 > l1 and b1 > t2 and b2 > t1:
|
|
raise AssertionError(f"etichete suprapuse: {s1!r} / {s2!r}")
|
|
|
|
for required in ("Minimul obligatoriu (M complet)",
|
|
"Volume SAU Relative Strength",
|
|
"oricare una",
|
|
"Trinity (opțional, semnal mai puternic)",
|
|
"divergență",
|
|
"pe aceeași zonă de preț",
|
|
"setup de 5 stele"):
|
|
assert required in svg, f"lipsește din desen: {required!r}"
|
|
|
|
OUT = Path(__file__).with_suffix(".svg")
|
|
OUT.write_text(svg, encoding="utf-8")
|
|
print(f"scris {OUT} ({len(svg)} bytes, {len(texts)} etichete, "
|
|
f"{len(boxes)} casete, {W}x{H}), verificare OK")
|