Corectorul n-are voie la SVG, deci a semnalat si le-am reparat eu, in generatoare: - gen/selectia/cadrane.py: cadranul 3 scria "imbunatatire spre underperform" — chiar langa textul corectat. Acum "inrautatire, histograma inca peste linia 0". - gen/selectia/funnel.py: "0.96 si 0.74 au fost respinse" era fals. Pe YPF la 0.96 zice "din pacate" si il trece oricum pe lista. - gen/strategii/selectia_scor.py: "sub 7 se deduce ca excludere". Bonus, preexistenta: in selectia_exemplu.py eticheta rezistentei 84.50 cadea peste titlu. Mutata la dreapta. audit_diagrams: 0 pe toate. A1_risc: doua trimiteri la fise/selectia.md decalate cu 7 linii. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ThN7ixZrowca8aTGrK1MUJ
147 lines
5.2 KiB
Python
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 e un minus, nu un veto: 0.96 (YPF) „din păcate”, dar îl trece pe listă;",
|
|
size=13, fill=GRAY))
|
|
svg.append(txt(W / 2, FOOT_Y + 20,
|
|
"0.74 (AON) „hai să ne uităm așa un pic pe el”. Neconfirmat: dacă sub prag oprește o intrare pe trigger.",
|
|
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")
|