#!/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'', f'', f'', txt(W / 2, 34, "Relative strength — cele patru cadrane", 19, "700", BLACK), txt(W / 2, 58, "RS = cât de bine se mișcă simbolul față de benchmark " "(ex. SPY)", 13, "400", GRAY), ] for x, y, lines in CARDS: p.extend(card(x, y, lines)) p.extend(bracket(BR1_Y, "Condiție de intrare LONG — cadranele 1 și 2")) p.extend(bracket(BR2_Y, "Condiție de intrare SHORT — cadranele 3 și 4")) p.append(txt(W / 2, BR2_Y + 48, "Cadranul 4 nu se tranzacționează pe orb, chiar cu scor bun:", 13, "400", GRAY)) p.append(txt(W / 2, BR2_Y + 68, "aștepți trecerea în 2 sau 1, ori un MP4-1 / MBT-10 long ca semn " "de schimbare.", 13, "400", GRAY)) p.append(txt(W / 2, BR2_Y + 90, "Un setup de bottom cu RS fără îmbunătățire are șanse mai mici.", 13, "400", GRAY)) p.append('') 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")