#!/usr/bin/env python3
"""Pasul 3 — sectorul care domină: numeri semnalele pe sectoare.
Diagramă conceptuală (NU grafic de preț), pentru
`summaries/17_STRATEGII/04_selectia.md`, pasul 3. Cele 11 sectoare S&P, cu
câte semnale a dat scanerul în fiecare într-o singură zi; sectorul cu cele mai
multe e evidențiat, pentru că acolo intră banii.
Ce vine din material: cele 11 sectoare S&P și ETF-urile lor, regula „uită-te
ce predomină, în ce sectoare" și sortarea sectoarelor după cadranul 1 plus
valoarea relative strength. Ce e al meu: **numărul de semnale din fiecare
sector**. Cursul nu dă o zi cu cifre pe sectoare, așa că le-am construit; ele
ilustrează operațiunea (numărarea), nu o zi reală de piață. Scrie asta și în
desen, ca să nu fie luate drept date.
Nu folosește `svg_candles`: SVG-ul e construit ca string, doar stdlib.
Rulare: python3 gen/strategii/selectia_sectoare.py
"""
import html
from pathlib import Path
W, H = 700, 620
BLACK, GRAY, RED, GREEN, BLUE, ORANGE = "#000000", "#777777", "#c00", "#188a3e", "#1a5fb4", "#b06000"
BLUE_BG = "#eef3fb"
# (sector, ETF, semnale — numărul e construit de mine)
SECTOARE = [
("Sănătate", "XLV", 9),
("Energie", "XLE", 6),
("Financiar", "XLF", 5),
("Real estate", "XLRE", 4),
("Tehnologie", "XLK", 3),
("Industrial", "XLI", 3),
("Consum de bază", "XLP", 2),
("Consum discreționar", "XLY", 2),
("Utilități", "XLU", 1),
("Materiale", "XLB", 1),
("Comunicații", "XLC", 1),
]
X_NUME = 40.0
X0, X1 = 250.0, 600.0 # zona barelor
Y0, ROW = 132.0, 30.0
TEXTS = []
def add(x, y, s, size=13, weight="normal", fill=BLACK, anchor="middle"):
TEXTS.append((x, y, s, size, weight, fill, anchor))
def est_w(s, size, weight):
return len(s) * size * (0.62 if weight == "bold" else 0.56)
def txt(x, y, s, size, weight, fill, anchor):
return (f'{html.escape(s, quote=False)}')
p = []
MAXS = max(s for *_, s in SECTOARE)
assert SECTOARE[0][2] == MAXS, "prima linie trebuie să fie sectorul dominant"
assert sum(1 for *_, s in SECTOARE if s == MAXS) == 1, "dominantul nu e unic"
assert len(SECTOARE) == 11, "S&P are 11 sectoare"
assert all(a[2] >= b[2] for a, b in zip(SECTOARE, SECTOARE[1:])), "lista nu e sortată descrescător"
add(W / 2, 34, "Pasul 3 — din ce sector vin cele mai multe semnale", 20, "bold")
add(W / 2, 58, "cele 11 sectoare S&P, cu semnalele scanerului dintr-o singură zi",
13, "normal", GRAY)
add(W / 2, 78, "de ce: mai multe semnale în același sector = cineva mută bani acolo",
13, "normal", GRAY)
add(X_NUME, 110, "sector (ETF)", 13, "bold", GRAY, "start")
add(X0, 110, "semnale în ziua asta", 13, "bold", GRAY, "start")
# dreptunghiul de evidențiere peste sectorul dominant
p.append(f'')
for i, (nume, etf, n) in enumerate(SECTOARE):
y = Y0 + i * ROW
dominant = n == MAXS
col = BLUE if dominant else GRAY
add(X_NUME, y, f"{nume} ({etf})", 13, "bold" if dominant else "normal", col, "start")
w = n / MAXS * (X1 - X0)
p.append(f'')
add(X0 + w + 8, y, str(n), 13, "bold" if dominant else "normal", col, "start")
# săgeata care spune de ce
Y_ARR = Y0 - 6
p.append(f'')
add(X1 + 80, Y_ARR - 30, "aici te uiți", 13, "bold", BLUE, "end")
Y_NOTE = Y0 + len(SECTOARE) * ROW + 26
p.append(f'')
add(60, Y_NOTE + 26, "Ce faci cu asta", 15, "bold", ORANGE, "start")
add(60, Y_NOTE + 48, "Păstrezi simbolurile din sectorul dominant și le duci mai departe la pasul 4;",
13, "normal", BLACK, "start")
add(60, Y_NOTE + 66, "restul le lași pentru azi. Sectoarele se mai sortează și după cadranul 1 plus",
13, "normal", BLACK, "start")
add(60, Y_NOTE + 84, "valoarea relative strength — aceleași cadrane ca la pasul 5.",
13, "normal", BLACK, "start")
add(W / 2, H - 14, "Numărul de semnale pe sector e construit de mine: cursul dă regula, nu o zi cu cifre.",
13, "normal", GRAY)
head = (f'")
svg = "\n".join(body) + "\n"
# =============================================================== VERIFICARE
# lungimea barei e proporțională cu numărul din etichetă
for i, (nume, etf, n) in enumerate(SECTOARE):
w = n / MAXS * (X1 - X0)
assert abs(w / (X1 - X0) - n / MAXS) < 1e-9, f"{nume}: bara nu e la scară"
assert X0 + w <= X1 + 1e-9, f"{nume}: bara iese din zona ei"
# etichete în pânză, fără suprapuneri
boxes = []
for x, y, s, size, weight, fill, anchor in TEXTS:
assert size >= 13, f"font sub 13px: {s!r}"
w = est_w(s, size, weight)
left = x - w if anchor == "end" else (x - w / 2 if anchor == "middle" else x)
assert -2 <= left and left + w <= W + 2, f"text în afara pânzei: {s!r}"
assert 0 <= y - size * 0.78 and y + size * 0.22 <= H, f"text în afara pânzei: {s!r}"
boxes.append((left, left + w, y - size * 0.78, y + size * 0.22, 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}")
# numele de sector nu intră peste zona barelor
for x, y, s, size, weight, fill, anchor in TEXTS:
if anchor == "start" and x == X_NUME and y >= Y0:
assert x + est_w(s, size, weight) < X0 - 6, f"numele iese peste bare: {s!r}"
assert "\n\n" not in svg, "linie goală în SVG"
OUT = Path(__file__).with_suffix(".svg")
OUT.write_text(svg, encoding="utf-8")
print(f"scris {OUT.name} ({len(svg)} bytes, {len(TEXTS)} etichete, "
f"{len(SECTOARE)} sectoare, dominant {SECTOARE[0][0]} cu {MAXS}, {W}x{H}), verificare OK")