#!/usr/bin/env python3
"""Harta celor patru cadrane relative strength, fata de benchmark.
Pentru `summaries/08_ANALIZA_ZILNICA/11_selectia_simbolului.md` (SVG:cadrane),
sectiunea 2.1. Nu e grafic de pret, deci nu se folosesc svg_candles / pchart:
SVG scris de mana, doar stdlib.
Ce arata: cadranele 1-4 asa cum le descrie documentul, grupate pe directie.
Randul de sus = conditia de intrare LONG (cadranele 1 si 2), randul de jos =
conditia de intrare SHORT (cadranele 3 si 4); sageti duble grupeaza perechile.
Fiecare cadran are numarul, sensul si directia.
Etichete si cifre, toate din text/glosar (fara axe inventate, fara procente):
- RS = cat de bine se misca simbolul fata de benchmark (ex. SPY) (.md:62)
- cadran 1 = supraperformanta, bate benchmark-ul, long (.md:63,310)
- cadran 2 = imbunatatire spre supraperformanta, zona de cumparat,
la long, de preferat (.md:64,311)
- cadran 3 = INRAUTATIRE (histograma inca peste linia 0, dar sub medie),
pentru short. Definitia din modul:
`03_MODUL_2/06_6_INDICATORUL_DE_PERFORMANTA.txt:6`. Documentul scria
„imbunatatire spre underperform" — gresit, corectat pe 19.09.2026.
- cadran 4 = underperform, pierde in fata benchmark-ului,
short / stai pe margine (.md:66,313)
- cadranul 4 nu se tranzactioneaza pe orb: astepti trecerea in 2 sau 1,
ori un MP4-1 / MBT-10 long ca semn de schimbare (.md:315-316)
- setup de bottom cu RS fara imbunatatire: sanse mai mici (.md:323-324)
Fara semafor rosu/verde (nu e in sursa); un singur accent albastru pe
chenare/sageti/numere, text negru pe fundal alb.
Ruleaza: .venv/bin/python gen/selectia/cadrane.py -> gen/selectia/cadrane.svg
"""
from pathlib import Path
from xml.sax.saxutils import escape
OUT = Path(__file__).with_suffix(".svg")
W, H = 700, 580
BLACK, GRAY, BLUE, WHITE = "#000000", "#777777", "#1a5fb4", "#ffffff"
COL1_X, COL_W, GAP = 40, 290, 40
COL2_X = COL1_X + COL_W + GAP # 370
COL1_R = COL1_X + COL_W # 330
COL2_R = COL2_X + COL_W # 660
CARD_H = 150
ROW1_Y, ROW2_Y = 86, 302
ROW1_B, ROW2_B = ROW1_Y + CARD_H, ROW2_Y + CARD_H # 236 / 452
BR1_Y, BR2_Y = ROW1_B + 14, ROW2_B + 14 # 250 / 466
# fiecare card: (x, y, linii [(dy, text, size, weight, fill)])
CARDS = [
(COL1_X, ROW1_Y, [
(34, "Cadran 1", 16, "700", BLACK),
(68, "supraperformanță", 14, "400", BLACK),
(96, "bate benchmark-ul", 13, "400", GRAY),
(124, "LONG", 13, "700", BLUE),
]),
(COL2_X, ROW1_Y, [
(34, "Cadran 2", 16, "700", BLACK),
(66, "îmbunătățire spre", 14, "400", BLACK),
(86, "supraperformanță", 14, "400", BLACK),
(110, "zona de cumpărat, la long", 13, "400", GRAY),
(132, "LONG", 13, "700", BLUE),
]),
(COL1_X, ROW2_Y, [
(34, "Cadran 3", 16, "700", BLACK),
(66, "înrăutățire", 14, "400", BLACK),
(86, "încă peste linia 0", 14, "400", BLACK),
(110, "pierde avans; pentru short", 13, "400", GRAY),
(132, "SHORT", 13, "700", BLUE),
]),
(COL2_X, ROW2_Y, [
(34, "Cadran 4", 16, "700", BLACK),
(68, "underperform", 14, "400", BLACK),
(96, "pierde în fața benchmark-ului", 13, "400", GRAY),
(124, "SHORT / stai pe margine", 13, "700", BLUE),
]),
]
TEXTS = [] # (x, y, text, size, weight, anchor) pentru verificare
def txt(x, y, s, size=13, weight="400", fill=BLACK, anchor="middle"):
TEXTS.append((x, y, s, size, weight, anchor))
return (
f'{escape(s)}'
)
def line(x1, y1, x2, y2, stroke=BLUE, width=1.5, dash=None):
d = f' stroke-dasharray="{dash}"' if dash else ""
return (f'')
def arrow(x, y, direction, color=BLUE):
"""Varf de sageata orizontal: direction=1 spre dreapta, -1 spre stanga."""
d = f"M {x} {y} L {x - 9 * direction} {y - 5} L {x - 9 * direction} {y + 5} Z"
return f''
def card(x, y, lines):
out = [f'']
for dy, s, size, weight, fill in lines:
out.append(txt(x + COL_W / 2, y + dy, s, size, weight, fill))
return out
def bracket(y, label):
"""Bara dubla cu varfuri, grupeaza cele doua carduri de pe rand."""
x0, x1 = COL1_X + 16, COL2_R - 16
return [
line(x0, y, x1, y, BLUE, 1.5),
arrow(x1, y, 1),
arrow(x0, y, -1),
txt(W / 2, y + 22, label, 14, "700", BLUE),
]
def build():
p = [
f'')
return "\n".join(p) + "\n"
# ---------------------------------------------------------------- verificare
def est_w(s, size, weight):
factor = 0.62 if weight == "700" else 0.56
return len(s) * size * factor
def verify():
for x, y, s, size, weight, anchor in TEXTS:
ew = est_w(s, size, weight)
left = x - ew if anchor == "end" else (x - ew / 2 if anchor == "middle" else x)
right = left + ew
assert -2 <= left and right <= W + 2, f"text in afara panzei: {s!r}"
assert 0 <= y <= H, f"text in afara inaltimii: {s!r} (y={y})"
for i in range(len(TEXTS)):
xi, yi, si, szi, wi, ai = TEXTS[i]
we = est_w(si, szi, wi)
li = xi - we if ai == "end" else (xi - we / 2 if ai == "middle" else xi)
ri = li + we
for j in range(i + 1, len(TEXTS)):
xj, yj, sj, szj, wj, aj = TEXTS[j]
wej = est_w(sj, szj, wj)
lj = xj - wej if aj == "end" else (xj - wej / 2 if aj == "middle" else xj)
rj = lj + wej
if abs(yi - yj) < 13 and li < rj - 4 and lj < ri - 4:
raise AssertionError(f"texte suprapuse: {si!r} / {sj!r}")
for x, y, lines in CARDS:
for dy, s, size, weight, fill in lines:
ew = est_w(s, size, weight)
assert ew <= COL_W - 24, f"eticheta nu incape in card: {s!r} ({ew:.0f})"
assert y + dy <= y + CARD_H - 8, f"eticheta iese din card: {s!r}"
assert "nan" not in OUT_TEXT[0]
return True
OUT_TEXT = [build()]
assert verify(), "verificare esuata"
OUT.write_text(OUT_TEXT[0], encoding="utf-8")
print(f"scris {OUT} ({W}x{H}), verificare OK: {len(TEXTS)} etichete")