"""Funnel-ul de selecție din `summaries/08_ANALIZA_ZILNICA/11_selectia_simbolului.md`.
Fluxul din scanner, de sus în jos, așa cum îl spune documentul:
scanner (multe semnale) -> sectorul care domină -> doar suport/pivot, după
downtrend -> RS cadranul 1-2 -> relative volume peste 100% -> stele (Trinity)
-> listă scurtă.
SVG scris de mână (nu e grafic de preț, deci nu se folosește svg_candles).
Fără dependențe: doar stdlib. Rulare:
.venv/bin/python gen/selectia/funnel.py
Scrie `gen/selectia/funnel.svg`.
"""
from pathlib import Path
from xml.sax.saxutils import escape
OUT = Path(__file__).resolve().parent / "funnel.svg"
BLACK = "#000000"
GRAY = "#777777"
BLUE = "#1a5fb4"
W = 700
TITLE_Y = 34
SUB_Y = 56
BAND_H = 58
GAP = 24
STEP = BAND_H + GAP
TOP0 = 84
WIDTHS = [620, 566, 512, 458, 404, 350, 296]
STAGES = [
("Scanner — multe semnale", "zeci sau sute de semnale"),
("Sectorul care domină", "multe simboluri din același sector = rotație de bani"),
("Doar suport / pivot, după downtrend",
"suport monthly / quarterly / anual sau pivot"),
("Relative strength: cadran 1-2", "doar cadranele 1 și 2, RS pozitiv"),
("Relative volume peste 100%",
"candela de volum / media ultimelor 10 candele"),
("Stele (Trinity)", "3 stele: RS + power + volum, cadran 1"),
("Listă scurtă", "taie lista la câteva idei"),
]
N_BANDS = len(STAGES)
BANDS_BOTTOM = TOP0 + (N_BANDS - 1) * STEP + BAND_H
FOOT_Y = BANDS_BOTTOM + 34
H = FOOT_Y + 96
def est_width(text, size, bold=False):
"""Lățime aproximativă pentru system-ui, folosită la verificarea suprapunerilor."""
factor = 0.62 if bold else 0.56
return len(text) * size * factor
def txt(x, y, s, size=13, bold=False, fill=BLACK, anchor="middle"):
weight = "700" if bold else "400"
return (
f'{escape(s)}'
)
def band(i):
name, detail = STAGES[i]
w = WIDTHS[i]
x = (W - w) / 2
y = TOP0 + i * STEP
parts = [
f'',
txt(W / 2, y + 25, name, size=15, bold=True),
txt(W / 2, y + 45, detail, size=13, fill=GRAY),
]
return parts, (y, detail)
def arrow(y):
"""Chevron în jos, în golul dintre două benzi."""
return (
f''
)
svg = [
f'")
# ------------------------------------------------------------------ verificare
for i, (name, detail) in enumerate(STAGES):
w = WIDTHS[i]
for s, size, bold in ((name, 15, True), (detail, 13, False)):
ew = est_width(s, size, bold)
assert ew <= w - 24, f"eticheta nu incape in banda {i}: {s!r} ({ew:.0f} > {w - 24})"
assert ew <= W - 20, f"eticheta iese din panza: {s!r}"
for k in range(N_BANDS):
y0 = TOP0 + k * STEP
assert 0 < y0 and y0 + BAND_H < H, "banda iese din viewBox"
seen = []
for l, r, y, s in labels:
assert 0 <= l and r <= W, f"eticheta iese din panza pe orizontala: {s!r} ({l:.0f},{r:.0f})"
assert 0 <= y <= H, f"eticheta iese din panza pe verticala: {s!r}"
for l2, r2, y2, s2 in seen:
if abs(y - y2) < 14 and not (r < l2 or r2 < l):
raise AssertionError(f"posibile etichete suprapuse: {s!r} / {s2!r}")
seen.append((l, r, y, s))
OUT.write_text("\n".join(svg) + "\n", encoding="utf-8")
print(f"scris {OUT} ({W}x{H}), {N_BANDS} trepte, verificare OK")