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
123 lines
5.0 KiB
Python
123 lines
5.0 KiB
Python
"""Diagrama forward P/E x venit pentru 11_selectia_simbolului.md (marcaj pe_venit).
|
||
|
||
Matrice 2x2: coloane = forward P/E (mic / mare), randuri = venit (creste / scade).
|
||
Fiecare cifra si eticheta vine din textul din jurul marcajului sau din citatele
|
||
lui (vezi caption-ul din josul SVG-ului). Nu e grafic de pret, deci nu foloseste
|
||
svg_candles / pchart.
|
||
|
||
Ruleaza:
|
||
.venv/bin/python gen/selectia/pe_venit.py
|
||
Scrie gen/selectia/pe_venit.svg (fara dependinte, SVG construit ca string).
|
||
"""
|
||
import html
|
||
import re
|
||
from pathlib import Path
|
||
|
||
W, H = 700, 620
|
||
X0, X1 = 120, 660
|
||
Y0, Y1 = 110, 520
|
||
COL_MID = (X0 + X1) / 2.0
|
||
ROW_MID = (Y0 + Y1) / 2.0
|
||
C1 = (X0 + COL_MID) / 2.0
|
||
C2 = (COL_MID + X1) / 2.0
|
||
R1 = (Y0 + ROW_MID) / 2.0
|
||
R2 = (ROW_MID + Y1) / 2.0
|
||
|
||
GRAY, BLUE = "#777", "#1a5fb4"
|
||
BLACK = "#000"
|
||
TINT = "#eaf1fb"
|
||
|
||
texts = []
|
||
|
||
|
||
def text(x, y, s, size=14, weight="normal", fill=BLACK, anchor="middle", transform=""):
|
||
texts.append((x, y, s, size, weight, fill, anchor, transform))
|
||
|
||
|
||
def cell_outline(x, y, w, h, accent=False):
|
||
fill = TINT if accent else "#fff"
|
||
stroke = BLUE if accent else "#999"
|
||
return (
|
||
f'<rect x="{x:.0f}" y="{y:.0f}" width="{w:.0f}" height="{h:.0f}" '
|
||
f'fill="{fill}" stroke="{stroke}" stroke-width="{2 if accent else 1}"/>'
|
||
)
|
||
|
||
|
||
parts = [
|
||
f'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 {W} {H}" width="{W}" height="{H}" '
|
||
f'font-family="system-ui, -apple-system, Segoe UI, Roboto, sans-serif">',
|
||
f'<rect x="0" y="0" width="{W}" height="{H}" fill="#fff"/>',
|
||
]
|
||
|
||
# ---------------------------------------------------------------- titluri
|
||
text(W / 2, 34, "Forward P/E × venit: candidat sau capcană", 20, "bold")
|
||
text(W / 2, 58, "Forward P/E mic contează doar dacă veniturile cresc.", 13, "normal", GRAY)
|
||
|
||
# ---------------------------------------------------------------- capete de coloana / rand
|
||
text(C1, 96, "Forward P/E mic", 15, "bold")
|
||
text(C2, 96, "Forward P/E mare", 15, "bold")
|
||
text(110, R1 + 5, "venit crește", 14, "bold", BLACK, "end")
|
||
text(110, R2 + 5, "venit scade", 14, "bold", BLACK, "end")
|
||
text(30, (Y0 + Y1) / 2.0, "venit", 14, "bold", BLACK, "middle",
|
||
f"rotate(-90 30 {(Y0 + Y1) / 2.0:.0f})")
|
||
|
||
# ---------------------------------------------------------------- celule
|
||
parts.append(cell_outline(X0, Y0, COL_MID - X0, ROW_MID - Y0, accent=True)) # candidat
|
||
parts.append(cell_outline(COL_MID, Y0, X1 - COL_MID, ROW_MID - Y0)) # bun dar scump
|
||
parts.append(cell_outline(X0, ROW_MID, COL_MID - X0, Y1 - ROW_MID)) # probleme
|
||
parts.append(cell_outline(COL_MID, ROW_MID, X1 - COL_MID, Y1 - ROW_MID)) # scump + scadere
|
||
|
||
# celula A: P/E mic + venit crește
|
||
text(C1, 192, "Candidat", 17, "bold", BLUE)
|
||
text(C1, 220, "TWLO", 15, "bold")
|
||
text(C1, 244, "forward P/E ~23", 14)
|
||
text(C1, 264, "venit în creștere", 14)
|
||
|
||
# celula B: P/E mare + venit crește
|
||
text(C2, 192, "Bun, dar scump", 17, "bold")
|
||
text(C2, 226, "scor fundamental bun,", 14)
|
||
text(C2, 246, "dar „nu aș cumpăra aici sus”", 14)
|
||
|
||
# celula C: P/E mic + venit scade
|
||
text(C1, 398, "Probleme, nu chilipir", 17, "bold")
|
||
text(C1, 426, "CRM", 15, "bold")
|
||
text(C1, 450, "forward P/E ~15", 14)
|
||
text(C1, 470, "veniturile sunt în scădere", 14)
|
||
|
||
# celula D: P/E mare + venit scade
|
||
text(C2, 400, "Scump + venit în scădere", 16, "bold")
|
||
text(C2, 432, "business care se micșorează", 14)
|
||
text(C2, 452, "scepticism", 14)
|
||
|
||
# ---------------------------------------------------------------- surse
|
||
text(110, 540, "Sursă: TWLO forward P/E ~23, venit în creștere", 13, "normal", GRAY, "start")
|
||
text(110, 558, "și CRM forward P/E ~15, venituri în scădere", 13, "normal", GRAY, "start")
|
||
text(110, 576, "(125_18022026.txt:20-35). P/E mare = scump,", 13, "normal", GRAY, "start")
|
||
text(110, 594, "„nu aș cumpăra aici sus” (44_22062026.txt:167).", 13, "normal", GRAY, "start")
|
||
|
||
# ---------------------------------------------------------------- emitere
|
||
for x, y, s, size, weight, fill, anchor, transform in texts:
|
||
t = f' transform="{transform}"' if transform else ""
|
||
parts.append(
|
||
f'<text x="{x:.1f}" y="{y:.1f}" font-size="{size}" font-weight="{weight}" '
|
||
f'fill="{fill}" text-anchor="{anchor}"{t}>{html.escape(s, quote=True)}</text>'
|
||
)
|
||
parts.append("</svg>")
|
||
svg = "\n".join(parts) + "\n"
|
||
|
||
# ---------------------------------------------------------------- verificare in-script
|
||
assert 'filter=' not in svg and 'gradient' not in svg, "fara umbre/gradienti"
|
||
seen = set()
|
||
for m in re.finditer(r'<text x="([\d.]+)" y="([\d.]+)"[^>]*font-size="(\d+)"', svg):
|
||
x, y, size = float(m.group(1)), float(m.group(2)), int(m.group(3))
|
||
assert 0 <= x <= W and 0 <= y <= H, f"eticheta in afara panzei: {x},{y}"
|
||
assert size >= 13, f"font sub 13: {size}"
|
||
key = (round(x), round(y))
|
||
assert key not in seen, f"doua texte pe aceeasi pozitie: {key}"
|
||
seen.add(key)
|
||
assert "~23" in svg and "~15" in svg, "cifrele sursa lipsesc"
|
||
|
||
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})")
|