Files
atm-curs-dl/gen/selectia/funnel.py
Claude Agent 3a6da7a916 cele 7 diagrame din 11_selectia
Sunt diagrame conceptuale - funnel, bara de scor, matrice, cele patru
cadrane, Trinity, sectoare - nu grafice de pret, deci nu trec prin
svg_candles / pchart, care raman obligatorii doar pentru candele si
entry/SL/TP. Agent nou, diagramist, cate unul per diagrama, fiecare cu
generatorul lui in gen/selectia/; substitutia in .md o face
gen/az_selectia.py, ca sa nu scrie sapte agenti in acelasi fisier.

Regula de continut a agentului: fiecare cifra si eticheta trebuie sa
existe in document sau in citate. Ce nu reiese din sursa e scris pe
desen ca atare - ordinea celor 7 sectoare, ce se intampla sub 100%
relative volume. Fara semafor rosu/verde, care nu e in sursa.

Verificat cu ochii pe doua diagrame randate in browser.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ThN7ixZrowca8aTGrK1MUJ
2026-09-18 16:52:01 +00:00

147 lines
5.2 KiB
Python

"""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'<text x="{x}" y="{y}" font-family="system-ui, sans-serif" '
f'font-size="{size}" font-weight="{weight}" fill="{fill}" '
f'text-anchor="{anchor}">{escape(s)}</text>'
)
def band(i):
name, detail = STAGES[i]
w = WIDTHS[i]
x = (W - w) / 2
y = TOP0 + i * STEP
parts = [
f'<rect x="{x:g}" y="{y}" width="{w}" height="{BAND_H}" rx="8" '
f'fill="#ffffff" stroke="{BLUE}" stroke-width="1.5"/>',
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'<path d="M 341 {y} L 359 {y} L 350 {y + 12} Z" fill="{BLUE}"/>'
)
svg = [
f'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 {W} {H}" '
f'width="{W}" height="{H}" role="img" '
f'aria-label="Funnel de selecție: scanner, sector, suport/pivot, RS 1-2, '
f'relative volume peste 100%, stele, listă scurtă">',
f'<rect x="0" y="0" width="{W}" height="{H}" fill="#ffffff"/>',
txt(W / 2, TITLE_Y, "Funnel de selecție — fluxul din scanner", size=19, bold=True),
txt(W / 2, SUB_Y, "selecția se face înainte de semnalul tehnic", size=13, fill=GRAY),
]
labels = [] # (x_left, x_right, y_baseline, text) pentru verificarea suprapunerilor
for i in range(N_BANDS):
parts, (y, detail) = band(i)
svg.extend(parts)
w = WIDTHS[i]
half = est_width(detail, 13) / 2
labels.append((W / 2 - half, W / 2 + half, y + 45, detail))
name = STAGES[i][0]
halfn = est_width(name, 15, bold=True) / 2
labels.append((W / 2 - halfn, W / 2 + halfn, y + 25, name))
if i < N_BANDS - 1:
svg.append(arrow(y + BAND_H + 6))
svg.append(txt(W / 2, FOOT_Y,
"Sub 100% relative volume: 0.96 și 0.74 au fost respinse; el vrea peste 100%.",
size=13, fill=GRAY))
svg.append(txt(W / 2, FOOT_Y + 20,
"Neconfirmat: nu reiese din sesiuni dacă sub prag oprește orice intrare.",
size=13, fill=GRAY))
svg.append(txt(W / 2, H - 52,
"Cifre din text: peste 100%, 10 candele, 0.96 / 0.74, cadranele 1-2.",
size=13, fill=GRAY))
svg.append(txt(W / 2, H - 32,
"Sursa: 11_selectia_simbolului.md — fluxul din scanner.",
size=13, fill=GRAY))
svg.append("</svg>")
# ------------------------------------------------------------------ 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")