#!/usr/bin/env python3
"""Orizontul de ținere al ATMI — două descrieri incompatibile, pe aceeași axă.
Pentru `strategii/03_atmi.md`, marcajul ``.
Nu e grafic de preț, e o linie de timp comparativă: SVG scris de mână, doar
stdlib — nu se folosește svg_candles / pchart.
Ce arată (toate cifrele / citatele vin din spec, nu se inventează prețuri):
- bara de sus, albastră: „ATMI clasică — fișierul principal", orizont luni
până la ~1 an, fără stop-loss, TP1 atins „aproximativ un an mai târziu";
- bara de jos, portocalie: „ATMI ca adaptare pentru prop — Q&A 11.06.2025",
orizont 2 săptămâni;
- cutia de anotare: pe prop, limita zilnică se calculează și pe pozițiile
deschise, iar la atingere pozițiile se pot închide; și nota că descrierile nu
se rezolvă aici, de clarificat cu autorul.
Barele sunt pe o axă comună 0 → 1 an, ca diferența să fie vizibilă; bara de
2 săptămâni e scurtă exact pentru că atât ține, nu din greșeală de scară.
Rulare: python3 gen/strategii/atmi_orizont.py -> gen/strategii/atmi_orizont.svg
"""
from pathlib import Path
from xml.sax.saxutils import escape
OUT = Path(__file__).with_suffix(".svg")
W, H = 700, 670
BLACK, GRAY, BLUE, ORANGE, WHITE = "#000000", "#777777", "#1a5fb4", "#b06000", "#ffffff"
AX_X0, AX_X1 = 60.0, 650.0
AX_W = AX_X1 - AX_X0 # 590 px = 1 an
AX_Y = 112.0
TICKS = [0, 3, 6, 9, 12] # luni
TICK_LABELS = {0: "0", 3: "3 luni", 6: "6 luni", 9: "9 luni", 12: "~1 an"}
WEEKS_YEAR = 52.0
LEN_PROP = AX_W * 2 / WEEKS_YEAR # ~22.7 px = 2 săptămâni
BAR_TOP_Y = 172.0
BAR_PROP_Y = 298.0
BAR_H = 42.0
CARD_X, CARD_Y, CARD_W, CARD_H = 160.0, 292.0, 500.0, 112.0
BOX_X, BOX_Y, BOX_W, BOX_H = 40.0, 440.0, 620.0, 150.0
TEXTS = [] # (x, y, text, size, weight, anchor) pentru verificare
def txt(x, y, s, size=13, weight="400", fill=BLACK, anchor="middle"):
TEXTS.append((x, y, s, size, weight, anchor))
return (
f'{escape(s)}'
)
def line(x1, y1, x2, y2, stroke=GRAY, width=1.0, dash=None):
d = f' stroke-dasharray="{dash}"' if dash else ""
return (f'')
def tick_x(months):
return AX_X0 + AX_W * months / 12.0
def build():
p = [
f'')
return "\n".join(p) + "\n"
# ---------------------------------------------------------------- verificare
def est_w(s, size, weight):
factor = 0.62 if weight == "700" else 0.56
return len(s) * size * factor
def verify(svg):
# 1. geometria barelor, față de axa comună
assert abs(AX_W - (AX_X1 - AX_X0)) < 1e-9
assert abs(LEN_PROP - AX_W * 2 / WEEKS_YEAR) < 1e-9
assert LEN_PROP < AX_W / 20, "bara de 2 săptămâni ar trebui să fie vizibil scurtă"
assert AX_X0 + LEN_PROP < CARD_X, "bara prop intră în card"
assert BAR_TOP_Y + BAR_H < BAR_PROP_Y
assert CARD_X + CARD_W <= AX_X1 + 10
# 2. toate etichetele în panză
for x, y, s, size, weight, anchor in TEXTS:
ew = est_w(s, size, weight)
left = x - ew if anchor == "end" else (x - ew / 2 if anchor == "middle" else x)
right = left + ew
assert -2 <= left and right <= W + 2, f"text în afara panzei: {s!r}"
assert 0 <= y <= H, f"text în afara înălțimii: {s!r} (y={y})"
# 3. etichete care nu se suprapun
for i in range(len(TEXTS)):
xi, yi, si, szi, wi, ai = TEXTS[i]
we = est_w(si, szi, wi)
li = xi - we if ai == "end" else (xi - we / 2 if ai == "middle" else xi)
ri = li + we
for j in range(i + 1, len(TEXTS)):
xj, yj, sj, szj, wj, aj = TEXTS[j]
wej = est_w(sj, szj, wj)
lj = xj - wej if aj == "end" else (xj - wej / 2 if aj == "middle" else xj)
rj = lj + wej
if abs(yi - yj) < 13 and li < rj - 4 and lj < ri - 4:
raise AssertionError(f"texte suprapuse: {si!r} / {sj!r}")
# 4. textul cardului încape în card
for x, y, s, size, weight, anchor in TEXTS:
if CARD_Y <= y <= CARD_Y + CARD_H and CARD_X <= x <= CARD_X + CARD_W:
ew = est_w(s, size, weight)
assert ew <= CARD_W - 24, f"eticheta nu încape în card: {s!r} ({ew:.0f})"
# 5. cutia de anotare: textul nu iese
for x, y, s, size, weight, anchor in TEXTS:
if BOX_Y <= y <= BOX_Y + BOX_H and x >= BOX_X:
ew = est_w(s, size, weight)
assert BOX_X + 20 + ew <= BOX_X + BOX_W - 12, f"text iese din cutie: {s!r}"
# 6. citatele și sursele din spec sunt prezente
for cheie in ("fără stop-loss, poziția ținută luni/ani",
"aproximativ un an mai târziu",
"suport quarterly",
"2 săptămâni în poziție",
"3 transacții să califici propul",
"limita zilnică se calculează",
"pozițiile se pot închide",
"nu se rezolvă aici",
"03_strategia_2_atmi.md:26-29",
"09_QA/26_2025-06-11.md:225-227",
"09_QA/26_2025-06-11.md:216-222"):
assert cheie in svg, f"lipsește din desen: {cheie!r}"
# 7. fără cifre de preț inventate
for interzis in ("$", "USD", "SL ", "intrare "):
assert interzis not in svg, f"nu ar trebui să apară preț: {interzis!r}"
return True
SVG = build()
assert verify(SVG), "verificare eșuată"
OUT.write_text(SVG, encoding="utf-8")
print(f"scris {OUT} ({W}x{H}), verificare OK: {len(TEXTS)} etichete, "
f"bara prop {LEN_PROP:.1f}px din {AX_W:g}px")