strategii: diagramele puse in capitole
36 din 37 de diagrame substituite in .md. substituie.py accepta acum si cheia cu slug inclus (unii agenti au scris mp2_exemplu, altii exemplu) si marcajul indentat. A ramas mdsrt_aplus: agentul a raportat OK fara sa scrie niciun fisier — exact capcana din CLAUDE.md, un raport de agent nu e dovada. Relansat separat. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ThN7ixZrowca8aTGrK1MUJ
This commit is contained in:
327
gen/strategii/selectia_cadrane.py
Normal file
327
gen/strategii/selectia_cadrane.py
Normal file
@@ -0,0 +1,327 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Relative strength — cele 4 cadrane (matrice 2x2).
|
||||
|
||||
Diagramă conceptuală (NU grafic de preț): pentru `strategii/04_selectia.md`,
|
||||
Nivel 1, §1.2 F4 „Relative strength în cadranul 1-2". Citește specificația
|
||||
`gen/strategii/selectia_cadrane.spec.txt` și scrie
|
||||
`gen/strategii/selectia_cadrane.svg`.
|
||||
|
||||
Nu folosește `svg_candles` / `pchart`: SVG-ul e construit ca string, doar
|
||||
stdlib.
|
||||
|
||||
Axele (așa cum le dă spec-ul):
|
||||
- vertical: histograma RS față de linia de 0 (peste / sub 0);
|
||||
- orizontal: histograma RS față de MA (peste / sub MA).
|
||||
|
||||
Cadranele (definițiile din spec, preluate din
|
||||
03_MODUL_2/06_indicatorul_de_performanta.md:31-39):
|
||||
1 supraperformanță peste 0 ȘI peste MA -> LONG
|
||||
2 îmbunătățire sub 0, dar peste MA -> LONG (zona de cumpărat)
|
||||
3 înrăutățire peste 0, dar sub MA -> SHORT
|
||||
4 subperformanță sub 0 ȘI sub MA -> SHORT / stai pe margine
|
||||
|
||||
Banda de jos e avertismentul despre cadranul 4 (08_ANALIZA_ZILNICA/
|
||||
11_selectia_simbolului.md:493-508), plus nota mică despre eroarea de
|
||||
transcriere (03_MODUL_2/06 ...:41-48) — marcată explicit ca NOTĂ, nu ca
|
||||
definiție a cursului.
|
||||
|
||||
Fără cifre de RS inventate: desenul e pur calitativ.
|
||||
|
||||
Rulare: .venv/bin/python gen/strategii/selectia_cadrane.py
|
||||
"""
|
||||
import html
|
||||
from pathlib import Path
|
||||
|
||||
W, H = 700, 720
|
||||
|
||||
BLACK = "#000000"
|
||||
GRAY = "#777777"
|
||||
RED = "#c00"
|
||||
GREEN = "#188a3e"
|
||||
BLUE = "#1a5fb4"
|
||||
ORANGE = "#b06000"
|
||||
|
||||
GREEN_BG = "#eef7f0"
|
||||
RED_BG = "#fdeeee"
|
||||
|
||||
# ---------------------------------------------------------------- geometrie
|
||||
PLOT_X0, PLOT_X1 = 130.0, 570.0
|
||||
PLOT_Y0, PLOT_Y1 = 134.0, 414.0
|
||||
CX = (PLOT_X0 + PLOT_X1) / 2.0 # 350
|
||||
CY = (PLOT_Y0 + PLOT_Y1) / 2.0 # 266
|
||||
|
||||
GUT = 4.0
|
||||
CW = (PLOT_X1 - PLOT_X0 - GUT) / 2.0 # 218
|
||||
CH = (PLOT_Y1 - PLOT_Y0 - GUT) / 2.0 # 138
|
||||
|
||||
Q3 = {"x": PLOT_X0, "y": PLOT_Y0} # peste 0, sub MA
|
||||
Q1 = {"x": CX + GUT / 2, "y": PLOT_Y0} # peste 0, peste MA
|
||||
Q4 = {"x": PLOT_X0, "y": CY + GUT / 2} # sub 0, sub MA
|
||||
Q2 = {"x": CX + GUT / 2, "y": CY + GUT / 2} # sub 0, peste MA
|
||||
for _q in (Q1, Q2, Q3, Q4):
|
||||
_q["w"], _q["h"] = CW, CH
|
||||
|
||||
BAND = {"x": 40.0, "y": 460.0, "w": 620.0, "h": 196.0}
|
||||
|
||||
# ---------------------------------------------------------------- collect
|
||||
TEXTS = [] # (x, y, s, size, weight, fill, anchor)
|
||||
RECTS = [] # (x, y, w, h, tag)
|
||||
|
||||
|
||||
def add(x, y, s, size=13, weight="normal", fill=BLACK, anchor="middle"):
|
||||
TEXTS.append((x, y, s, size, weight, fill, anchor))
|
||||
return s
|
||||
|
||||
|
||||
def est_w(s, size, weight):
|
||||
factor = 0.62 if weight == "bold" else 0.56
|
||||
return len(s) * size * factor
|
||||
|
||||
|
||||
def txt(x, y, s, size=13, weight="normal", fill=BLACK, anchor="middle"):
|
||||
return (f'<text x="{x:.1f}" y="{y:.1f}" font-size="{size}" '
|
||||
f'font-weight="{weight}" fill="{fill}" text-anchor="{anchor}">'
|
||||
f'{html.escape(s, quote=False)}</text>')
|
||||
|
||||
|
||||
def rect(x, y, w, h, tag, fill="#ffffff", stroke=GRAY, sw=1.5, dash=None):
|
||||
RECTS.append((x, y, w, h, tag))
|
||||
d = f' stroke-dasharray="{dash}"' if dash else ""
|
||||
return (f'<rect x="{x:.1f}" y="{y:.1f}" width="{w:.1f}" height="{h:.1f}" '
|
||||
f'fill="{fill}" stroke="{stroke}" stroke-width="{sw}"{d}/>')
|
||||
|
||||
|
||||
def line(x1, y1, x2, y2, stroke=GRAY, width=1.0, dash=None):
|
||||
d = f' stroke-dasharray="{dash}"' if dash else ""
|
||||
return (f'<line x1="{x1:.1f}" y1="{y1:.1f}" x2="{x2:.1f}" y2="{y2:.1f}" '
|
||||
f'stroke="{stroke}" stroke-width="{width}"{d}/>')
|
||||
|
||||
|
||||
def cell_text(q, dy, s, *a, **k):
|
||||
add(q["x"] + q["w"] / 2.0, q["y"] + dy, s, *a, **k)
|
||||
|
||||
|
||||
# =============================================================== CONȚINUT
|
||||
p = []
|
||||
|
||||
# ---- titlu + subtitlu + legenda axelor
|
||||
add(W / 2, 34, "Relative strength — cele 4 cadrane", 20, "bold")
|
||||
add(W / 2, 58, "RS = cât de bine se mișcă simbolul față de benchmark (ex. SPY)",
|
||||
13, "normal", GRAY)
|
||||
add(W / 2, 80,
|
||||
"Axa verticală: histograma RS față de 0 (peste / sub 0)",
|
||||
13, "normal", GRAY)
|
||||
add(W / 2, 98,
|
||||
"Axa orizontală: histograma RS față de MA (peste / sub MA)",
|
||||
13, "normal", GRAY)
|
||||
|
||||
# ---- etichetele coloanelor / rândurilor (axe)
|
||||
add(PLOT_X0 + CW / 2 + GUT / 2, 122, "sub MA", 14, "bold", ORANGE)
|
||||
add(CX + GUT / 2 + CW / 2, 122, "peste MA", 14, "bold", BLUE)
|
||||
add(PLOT_X0 - 8, Q3["y"] + CH / 2, "peste 0", 14, "bold", GREEN, "end")
|
||||
add(PLOT_X0 - 8, Q4["y"] + CH / 2, "sub 0", 14, "bold", RED, "end")
|
||||
|
||||
# ---- liniile de 0 (orizontală) și de MA (verticală), în spatele cadranelor
|
||||
p.append(rect(PLOT_X0, PLOT_Y0, PLOT_X1 - PLOT_X0, PLOT_Y1 - PLOT_Y0,
|
||||
"plot", fill="#ffffff", stroke=GRAY, sw=1.0))
|
||||
p.append(line(PLOT_X0, CY, PLOT_X1, CY, GRAY, 1.0, "4 4"))
|
||||
p.append(line(CX, PLOT_Y0, CX, PLOT_Y1, GRAY, 1.0, "4 4"))
|
||||
|
||||
# ---- cadrane
|
||||
p.append(rect(Q3["x"], Q3["y"], CW, CH, "Q3", fill=RED_BG, stroke=RED, sw=1.6))
|
||||
p.append(rect(Q1["x"], Q1["y"], CW, CH, "Q1", fill=GREEN_BG, stroke=GREEN, sw=1.6))
|
||||
p.append(rect(Q4["x"], Q4["y"], CW, CH, "Q4", fill=RED_BG, stroke=RED, sw=1.6,
|
||||
dash="6 4"))
|
||||
p.append(rect(Q2["x"], Q2["y"], CW, CH, "Q2", fill=GREEN_BG, stroke=GREEN, sw=1.6))
|
||||
|
||||
# Cadran 3 — top-left: peste 0, sub MA -> SHORT
|
||||
cell_text(Q3, 30, "Cadran 3", 16, "bold", BLACK)
|
||||
cell_text(Q3, 52, "înrăutățire", 14, "bold", RED)
|
||||
cell_text(Q3, 80, "peste 0, dar sub MA", 13, "normal", BLACK)
|
||||
cell_text(Q3, 112, "SHORT", 16, "bold", RED)
|
||||
|
||||
# Cadran 1 — top-right: peste 0, peste MA -> LONG
|
||||
cell_text(Q1, 30, "Cadran 1", 16, "bold", BLACK)
|
||||
cell_text(Q1, 52, "supraperformanță", 14, "bold", GREEN)
|
||||
cell_text(Q1, 80, "peste 0 și peste MA", 13, "normal", BLACK)
|
||||
cell_text(Q1, 112, "LONG", 16, "bold", GREEN)
|
||||
|
||||
# Cadran 4 — bottom-left: sub 0, sub MA -> SHORT / stai pe margine
|
||||
cell_text(Q4, 26, "Cadran 4", 16, "bold", BLACK)
|
||||
cell_text(Q4, 48, "subperformanță", 14, "bold", RED)
|
||||
cell_text(Q4, 74, "sub 0 și sub MA", 13, "normal", BLACK)
|
||||
cell_text(Q4, 100, "SHORT", 15, "bold", RED)
|
||||
cell_text(Q4, 122, "stai pe margine", 13, "normal", RED)
|
||||
|
||||
# Cadran 2 — bottom-right: sub 0, peste MA -> LONG (zona de cumpărat)
|
||||
cell_text(Q2, 26, "Cadran 2", 16, "bold", BLACK)
|
||||
cell_text(Q2, 48, "îmbunătățire", 14, "bold", GREEN)
|
||||
cell_text(Q2, 74, "sub 0, dar peste MA", 13, "normal", BLACK)
|
||||
cell_text(Q2, 100, "LONG", 15, "bold", GREEN)
|
||||
cell_text(Q2, 122, "zona de cumpărat", 13, "normal", GREEN)
|
||||
|
||||
# ---- concluzia de sub matrice
|
||||
add(W / 2, 442, "Long = cadranele 1-2 · Short = cadranele 3-4",
|
||||
14, "bold", BLACK)
|
||||
|
||||
# ---- banda de avertisment (cadranul 4)
|
||||
p.append(rect(BAND["x"], BAND["y"], BAND["w"], BAND["h"], "band",
|
||||
fill="#fdf0e0", stroke=ORANGE, sw=1.6))
|
||||
add(BAND["x"] + 20, 488, "Avertisment — cadranul 4 nu se tranzacționează pe orb",
|
||||
15, "bold", RED, "start")
|
||||
add(BAND["x"] + 20, 516,
|
||||
"„Cadranul 4 nu se tranzacționează pe orb: aștepți trecerea în 2 sau 1,",
|
||||
13, "normal", BLACK, "start")
|
||||
add(BAND["x"] + 20, 536, "ori un MP4-1 / MBT-10 long ca semn de schimbare.”",
|
||||
13, "normal", BLACK, "start")
|
||||
add(BAND["x"] + 20, 566,
|
||||
"„Un setup de bottom cu RS fără îmbunătățire are șanse mai mici.”",
|
||||
13, "bold", BLACK, "start")
|
||||
add(BAND["x"] + 20, 596,
|
||||
"Notă: transcrierea descrierii cadranului 4 are o eroare (îl descrie ca pe",
|
||||
13, "normal", GRAY, "start")
|
||||
add(BAND["x"] + 20, 616,
|
||||
"cadranul 3); definiția de mai sus e completarea sumarului —",
|
||||
13, "normal", GRAY, "start")
|
||||
add(BAND["x"] + 20, 636,
|
||||
"03_MODUL_2/06_indicatorul_de_performanta.md:41-48.",
|
||||
13, "normal", GRAY, "start")
|
||||
|
||||
# ---- surse
|
||||
add(W / 2, 686,
|
||||
"Surse: 08_ANALIZA_ZILNICA/11_selectia_simbolului.md:486-499, :501-508;",
|
||||
13, "normal", GRAY)
|
||||
add(W / 2, 704,
|
||||
"03_MODUL_2/06_indicatorul_de_performanta.md:31-39.",
|
||||
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="Matricea 2x2 a relative strength: cadranele 1 si 2 sunt '
|
||||
f'pentru long, cadranele 3 si 4 pentru short; cadranul 4 nu se '
|
||||
f'tranzactioneaza pe orb">')
|
||||
body = [head, f'<rect x="0" y="0" width="{W}" height="{H}" fill="#ffffff"/>']
|
||||
body += p
|
||||
for x, y, s, size, weight, fill, anchor in TEXTS:
|
||||
body.append(txt(x, y, s, size, weight, fill, anchor))
|
||||
body.append("</svg>")
|
||||
svg = "\n".join(body) + "\n"
|
||||
|
||||
# =============================================================== VERIFICARE
|
||||
# 1. geometrie: 4 cadrane egale, fără suprapunere, în interiorul plot-ului
|
||||
assert len(RECTS) == 6, f"aștept 6 dreptunghiuri, am {len(RECTS)}"
|
||||
quads = [r for r in RECTS if r[4] in ("Q1", "Q2", "Q3", "Q4")]
|
||||
assert len(quads) == 4, f"aștept 4 cadrane, am {len(quads)}"
|
||||
for x, y, w, h, tag in quads:
|
||||
assert abs(w - CW) < 1e-9 and abs(h - CH) < 1e-9, f"{tag}: dimensiune greșită"
|
||||
assert PLOT_X0 - 0.5 <= x and x + w <= PLOT_X1 + 0.5, f"{tag}: iese orizontal"
|
||||
assert PLOT_Y0 - 0.5 <= y and y + h <= PLOT_Y1 + 0.5, f"{tag}: iese vertical"
|
||||
for i in range(len(quads)):
|
||||
for j in range(i + 1, len(quads)):
|
||||
x1, y1, w1, h1, t1 = quads[i]
|
||||
x2, y2, w2, h2, t2 = quads[j]
|
||||
assert not (x1 < x2 + w2 and x2 < x1 + w1 and
|
||||
y1 < y2 + h2 and y2 < y1 + h1), \
|
||||
f"cadrane suprapuse: {t1} / {t2}"
|
||||
|
||||
# 2. pozițiile relative: 1 sus-dreapta, 2 jos-dreapta, 3 sus-stânga, 4 jos-stânga
|
||||
by_tag = {r[4]: r for r in quads}
|
||||
assert by_tag["Q1"][0] > CX and by_tag["Q1"][1] < CY, "cadranul 1 nu e sus-dreapta"
|
||||
assert by_tag["Q2"][0] > CX and by_tag["Q2"][1] > CY, "cadranul 2 nu e jos-dreapta"
|
||||
assert by_tag["Q3"][0] < CX and by_tag["Q3"][1] < CY, "cadranul 3 nu e sus-stânga"
|
||||
assert by_tag["Q4"][0] < CX and by_tag["Q4"][1] > CY, "cadranul 4 nu e jos-stânga"
|
||||
|
||||
# 3. definițiile din spec sunt exact cele desenate
|
||||
for req in ("Cadran 1", "Cadran 2", "Cadran 3", "Cadran 4",
|
||||
"supraperformanță", "îmbunătățire", "înrăutățire",
|
||||
"subperformanță"):
|
||||
assert any(t[2] == req for t in TEXTS), f"lipsește din desen: {req!r}"
|
||||
|
||||
# condiția fiecărui cadran
|
||||
assert any(t[2] == "peste 0 și peste MA" for t in TEXTS)
|
||||
assert any(t[2] == "sub 0, dar peste MA" for t in TEXTS)
|
||||
assert any(t[2] == "peste 0, dar sub MA" for t in TEXTS)
|
||||
assert any(t[2] == "sub 0 și sub MA" for t in TEXTS)
|
||||
# direcția: LONG pe 1-2, SHORT pe 3-4
|
||||
assert svg.count(">LONG</text>") == 2, "LONG trebuie să apară exact de 2 ori"
|
||||
assert svg.count(">SHORT</text>") == 2, "SHORT trebuie să apară exact de 2 ori"
|
||||
assert "stai pe margine" in svg, "lipsește «stai pe margine» din cadranul 4"
|
||||
assert "zona de cumpărat" in svg, "lipsește «zona de cumpărat» din cadranul 2"
|
||||
assert "Long = cadranele 1-2" in svg and "Short = cadranele 3-4" in svg
|
||||
|
||||
# 4. titlu, subtitlu, legenda axelor, avertismentul, nota
|
||||
for req in ("Relative strength — cele 4 cadrane",
|
||||
"RS = cât de bine se mișcă simbolul față de benchmark (ex. SPY)",
|
||||
"Axa verticală: histograma RS față de 0 (peste / sub 0)",
|
||||
"Axa orizontală: histograma RS față de MA (peste / sub MA)",
|
||||
"Cadranul 4 nu se tranzacționează pe orb",
|
||||
"aștepți trecerea în 2 sau 1",
|
||||
"MP4-1 / MBT-10 long",
|
||||
"Un setup de bottom cu RS fără îmbunătățire are șanse mai mici",
|
||||
"transcrierea descrierii cadranului 4 are o eroare",
|
||||
"e completarea sumarului",
|
||||
"03_MODUL_2/06_indicatorul_de_performanta.md:41-48",
|
||||
"11_selectia_simbolului.md:486-499",
|
||||
"06_indicatorul_de_performanta.md:31-39"):
|
||||
assert req in svg, f"lipsește din desen: {req!r}"
|
||||
|
||||
# 5. etichetele cadranului 2 și 3 care se pot încurca: verifică asocierea
|
||||
t2 = [t for t in TEXTS if t[0] > CX and t[1] > CY]
|
||||
assert any("îmbunătățire" == t[2] for t in t2), "cadranul 2 nu e marcat corect"
|
||||
t3 = [t for t in TEXTS if t[0] < CX and t[1] < CY]
|
||||
assert any("înrăutățire" == t[2] for t in t3), "cadranul 3 nu e marcat corect"
|
||||
|
||||
# 6. textul fiecărui cadran încape în cadranul lui
|
||||
for tag, q in (("Q1", Q1), ("Q2", Q2), ("Q3", Q3), ("Q4", Q4)):
|
||||
for x, y, s, size, weight, fill, anchor in TEXTS:
|
||||
if q["x"] <= x <= q["x"] + q["w"] and q["y"] <= y <= q["y"] + q["h"]:
|
||||
w = est_w(s, size, weight)
|
||||
assert w <= q["w"] - 16, f"{tag}: text prea lat: {s!r} ({w:.0f})"
|
||||
|
||||
# 7. toate etichetele în panză + fără suprapuneri
|
||||
boxes = []
|
||||
for x, y, s, size, weight, fill, anchor in TEXTS:
|
||||
assert size >= 13, f"font sub 13px: {s!r} ({size})"
|
||||
w = est_w(s, size, weight)
|
||||
if anchor == "end":
|
||||
left = x - w
|
||||
elif anchor == "start":
|
||||
left = x
|
||||
else:
|
||||
left = x - w / 2.0
|
||||
right = left + w
|
||||
top, bottom = y - size * 0.78, y + size * 0.22
|
||||
assert -2 <= left and right <= W + 2, f"text în afara panzei orizontal: {s!r}"
|
||||
assert 0 <= top and bottom <= H, f"text în afara panzei vertical: {s!r}"
|
||||
boxes.append((left, right, top, bottom, s))
|
||||
for i in range(len(boxes)):
|
||||
for j in range(i + 1, len(boxes)):
|
||||
l1, r1, t1, b1, s1 = boxes[i]
|
||||
l2, r2, t2_, b2, s2 = boxes[j]
|
||||
if r1 > l2 + 2 and r2 > l1 + 2 and b1 > t2_ + 2 and b2 > t1 + 2:
|
||||
raise AssertionError(f"etichete suprapuse: {s1!r} / {s2!r}")
|
||||
|
||||
# 8. stil: fără umbre / gradienți, fontul cerut
|
||||
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"
|
||||
|
||||
# 8b. SVG-ul e XML valid, cu o singură rădăcină <svg> (bug prins la render:
|
||||
# un rect scris înaintea rădăcinii => „extra content at the end”)
|
||||
import xml.etree.ElementTree as _ET
|
||||
assert svg.lstrip().startswith("<svg "), "rădăcina nu e <svg>"
|
||||
_root = _ET.fromstring(svg)
|
||||
assert _root.tag.endswith("svg"), f"rădăcină neașteptată: {_root.tag}"
|
||||
|
||||
# 9. fără cifre de RS inventate: niciun text nu conține numere zecimale sau
|
||||
# procente (axele folosesc doar „0”, ca prag, nu valori de RS)
|
||||
import re
|
||||
for _x, _y, _s, _size, _weight, _fill, _anchor in TEXTS:
|
||||
assert not re.search(r"\d+[.,]\d+", _s), f"cifră de RS inventată: {_s!r}"
|
||||
assert "%" not in _s, f"procent inventat: {_s!r}"
|
||||
assert "score" not in svg and "scor" not in svg
|
||||
|
||||
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(quads)} cadrane, {W}x{H}), verificare OK")
|
||||
Reference in New Issue
Block a user