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
This commit is contained in:
146
gen/selectia/funnel.py
Normal file
146
gen/selectia/funnel.py
Normal file
@@ -0,0 +1,146 @@
|
||||
"""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")
|
||||
Reference in New Issue
Block a user