"""Clasamentul celor 11 sectoare S&P: sortare după cadranul 1 + valoarea RS,
cu rotația banilor între sectoare.
Diagramă conceptuală pentru `summaries/08_ANALIZA_ZILNICA/11_selectia_simbolului.md`
(marcaj ``), §4.1 „Sectorul care domină".
Ce susține textul din jurul marcajului:
- „cele 11 sectoare S&P" (rândul 439) și „clasamentul sectoarelor | 11 rânduri,
sortate săptămânal" (rândul 88);
- sortarea după „cadranul 1 + valoarea relativ strength"
(`54_08062026.txt:14`, citat la rândul 441-443);
- top 4-ul din citatul sursă: XLV primul, apoi energia, financiarul, real
estate (`54_08062026.txt:9-10`);
- rotația sectorială: „banii migrează dintr-un sector în altul" (rândul 71),
„se mută bani [...] rotație a banilor în acel sector"
(`32_15072026.txt:43-49`), „uită-te ce predomină, în ce sectoare"
(`148_11122025.txt:725-727`).
CE NU AM PUS, pentru că sursa nu-l susține:
- valorile numerice de RS: textul/citatul dă doar regula de sortare
(„cu cât este mai mare histograma de relativ strength și cadranul 1"),
nu și cifre concrete. Desenul redă deci ordinea, nu valori inventate;
- săgeți cu schimbări concrete „săptămâna trecută X, săptămâna asta Y":
nu există asemenea date în sursă. Săgețile arată doar conceptul de rotație
ciclică (semnale în același sector -> bani acolo -> sortare refăcută
săptămânal);
- ordinea celor 7 sectoare din afara top-ului: sursa enumeră doar primele 4.
Nu e grafic de preț, deci NU folosește `svg_candles` / `pchart`: SVG-ul e
construit ca string, fără dependințe. Rulează:
.venv/bin/python gen/selectia/sectoare.py
Scrie `gen/selectia/sectoare.svg`.
Fără semafor: textul nu leagă cadranul 1 de verde sau restul de roșu. Accentele
sunt albastru (informativ) și gri (secundar).
"""
import html
from pathlib import Path
W, H = 700, 640
BLACK = "#000000"
GRAY = "#777777"
BLUE = "#1a5fb4"
texts = [] # (x, y, text, size, weight, fill, w_est, anchor)
def add(x, y, s, size=13, weight="normal", fill=BLACK, anchor="middle"):
factor = 0.62 if weight == "bold" else 0.56
w = len(s) * size * factor
texts.append((x, y, s, size, weight, fill, w, anchor))
return texts[-1]
def box(x, y, w, h, stroke=GRAY):
return (
f''
)
def arrow_h(x0, x1, y, color=BLUE):
"""Săgeată orizontală de la x0 la x1, vârful la x1."""
return (
f''
f''
)
parts = [
f'")
svg = "\n".join(parts) + "\n"
# ---------------------------------------------------------------- verificare
assert "filter=" not in svg and "gradient" not in svg, "fără umbre/gradienti"
boxes = []
for x, y, s, size, weight, fill, w, anchor in texts:
assert size >= 13, f"font sub 13px: {s!r} ({size})"
if anchor == "start":
left, right = x, x + w
elif anchor == "end":
left, right = x - w, x
else:
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 pe orizontală: {s!r} ({left:.0f}..{right:.0f})"
)
assert 0 <= top and bottom <= H, (
f"etichetă în afara panzei pe verticală: {s!r} ({top:.0f}..{bottom:.0f})"
)
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 and r2 > l1 and b1 > t2 and b2 > t1:
raise AssertionError(f"etichete suprapuse: {s1!r} / {s2!r}")
for required in ("Sectoare S&P 500", "cadranul 1", "valoarea RS",
"Healthcare (XLV)", "Energie (XLE)", "Financiar (XLF)",
"Imobiliare (XLRE)", "Tehnologie (XLK)",
"Consum discreționar (XLY)", "Comunicații (XLC)",
"Industrii (XLI)", "Consum de bază (XLP)",
"Utilități (XLU)", "Materiale (XLB)",
"rotația banilor", "săptămânal", "11 sectoare"):
assert html.escape(required, quote=False) in svg, \
f"lipsește din desen: {required!r}"
# cele 11 sectoare apar exact o dată fiecare
for _, name, _ in TOP4:
assert svg.count(name) == 1, f"sector duplicat sau lipsă: {name!r}"
for name in REST:
assert svg.count(name) == 1, f"sector duplicat sau lipsă: {name!r}"
OUT = Path(__file__).with_suffix(".svg")
OUT.write_text(svg, encoding="utf-8")
print(f"scris {OUT} ({len(svg)} bytes, {len(texts)} etichete, {W}x{H}), verificare OK")