strategii: primele 30 de diagrame, cu generatoarele lor
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
This commit is contained in:
199
gen/strategii/mdsrt_trinity.py
Normal file
199
gen/strategii/mdsrt_trinity.py
Normal file
@@ -0,0 +1,199 @@
|
||||
#!/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")
|
||||
Reference in New Issue
Block a user