#!/usr/bin/env python3
"""Pasul 1 — universul și lichiditatea: cele două praguri, desenate ca o zonă.
Diagramă conceptuală (NU grafic de preț), pentru
`summaries/17_STRATEGII/04_selectia.md`, pasul 1. Pe orizontală prețul de
închidere, pe verticală volumul mediu zilnic. Pragurile cursului (close peste
30-40 $, volum mediu de cel puțin 500.000 de acțiuni, ideal peste un milion)
taie planul în patru; dreptunghiul verde e singurul colț din care ai voie să
iei simboluri.
Cele trei simboluri sunt anonime (A, B, C) și cu cifre construite de mine:
cursul dă pragurile, nu exemple numerice. Le pun ca să se vadă că un simbol
poate pica pe oricare din cele două praguri, separat.
Nu folosește `svg_candles`: SVG-ul e construit ca string, doar stdlib.
Rulare: python3 gen/strategii/selectia_univers.py
"""
import html
from pathlib import Path
W, H = 700, 604
BLACK, GRAY, RED, GREEN, BLUE, ORANGE = "#000000", "#777777", "#c00", "#188a3e", "#1a5fb4", "#b06000"
GREEN_BG = "#eef7f0"
# ---------------------------------------------------------------- praguri (cursul)
PRET_MIN, PRET_IDEAL = 30.0, 40.0 # close peste 30-40 $
VOL_MIN, VOL_IDEAL = 0.5, 1.0 # milioane de acțiuni pe zi
# ---------------------------------------------------------------- simboluri (construite)
# (nume, preț, volum mediu în milioane, verdict)
SIMB = [
("A", 58.0, 2.4, "trece"),
("B", 24.0, 3.1, "cade pe preț"),
("C", 72.0, 0.18, "cade pe volum"),
]
# ---------------------------------------------------------------- geometrie
X0, X1 = 110.0, 630.0
Y0, Y1 = 130.0, 420.0
PMAX, VMAX = 100.0, 3.5
def px(p):
return X0 + p / PMAX * (X1 - X0)
def py(v):
return Y1 - v / VMAX * (Y1 - Y0)
TEXTS = []
def add(x, y, s, size=13, weight="normal", fill=BLACK, anchor="middle"):
TEXTS.append((x, y, s, size, weight, fill, anchor))
def est_w(s, size, weight):
return len(s) * size * (0.62 if weight == "bold" else 0.56)
def txt(x, y, s, size, weight, fill, anchor):
return (f'{html.escape(s, quote=False)}')
def line(x1, y1, x2, y2, stroke=GRAY, w=1.0, dash=None):
d = f' stroke-dasharray="{dash}"' if dash else ""
return (f'')
p = []
add(W / 2, 34, "Pasul 1 — cine intră în universul de scanare", 20, "bold")
add(W / 2, 58, "pe orizontală prețul de închidere, pe verticală volumul mediu zilnic",
13, "normal", GRAY)
add(W / 2, 78, "de ce pragurile: pe un simbol subțire, ordinul tău mută singur prețul",
13, "normal", GRAY)
# zona verde: singurul colț bun
p.append(f'')
add(px(PRET_MIN) + 10, Y0 + 22, "zona din care iei simboluri", 14, "bold", GREEN, "start")
# cadrul și axele
p.append(line(X0, Y0, X0, Y1, GRAY, 1.2))
p.append(line(X0, Y1, X1, Y1, GRAY, 1.2))
# pragurile
p.append(line(px(PRET_MIN), Y0, px(PRET_MIN), Y1, GREEN, 1.8))
p.append(line(px(PRET_IDEAL), Y0, px(PRET_IDEAL), Y1, GREEN, 1.2, "5 3"))
p.append(line(X0, py(VOL_MIN), X1, py(VOL_MIN), GREEN, 1.8))
p.append(line(X0, py(VOL_IDEAL), X1, py(VOL_IDEAL), GREEN, 1.2, "5 3"))
add(px(PRET_MIN), Y1 + 38, "30 $", 13, "bold", GREEN)
add(px(PRET_IDEAL), Y1 + 56, "40 $ (ideal)", 13, "normal", GREEN)
add(X0 - 10, py(VOL_MIN) + 4, "500.000", 13, "bold", GREEN, "end")
add(X0 - 10, py(VOL_IDEAL) + 4, "1 milion", 13, "normal", GREEN, "end")
# repere de axă
for v in (0, 1, 2, 3):
p.append(line(X0 - 5, py(v), X0, py(v), GRAY, 1.0))
add(X0 - 62, py(0) + 4, "0", 13, "normal", GRAY, "end")
add(X0 - 62, py(2) + 4, "2 mil.", 13, "normal", GRAY, "end")
add(X0 - 62, py(3) + 4, "3 mil.", 13, "normal", GRAY, "end")
for pr in (0, 50, 100):
p.append(line(px(pr), Y1, px(pr), Y1 + 5, GRAY, 1.0))
add(px(pr), Y1 + 20, f"{pr} $", 13, "normal", GRAY)
add(X1, Y1 + 56, "prețul de închidere", 13, "normal", GRAY, "end")
add(X0 - 48, Y0 - 12, "volum mediu zilnic", 13, "normal", GRAY, "start")
# cele trei simboluri
for nume, pret, vol, verdict in SIMB:
x, y = px(pret), py(vol)
col = GREEN if verdict == "trece" else RED
p.append(f'')
add(x, y - 14, f"Simbol {nume}", 13, "bold", col)
add(x, y + 26, f"{pret:.0f} $ · {vol:.2f} mil.", 13, "normal", BLACK)
add(x, y + 44, verdict, 13, "bold", col)
# caseta cu de ce
NOTE_Y = 504.0
p.append(f'')
add(60, NOTE_Y + 26, "De ce contează amândouă, nu doar una", 15, "bold", ORANGE, "start")
add(60, NOTE_Y + 48,
"Simbolul B se tranzacționează mult, dar e prea ieftin; simbolul C e scump, dar",
13, "normal", BLACK, "start")
add(60, NOTE_Y + 66,
"aproape nimeni nu-l cumpără: acolo ordinul tău e cel care mișcă prețul.",
13, "normal", BLACK, "start")
head = (f'")
svg = "\n".join(body) + "\n"
# =============================================================== VERIFICARE
# 1. verdictul fiecărui simbol iese din cifrele lui, nu e scris de mână
for nume, pret, vol, verdict in SIMB:
trece = pret >= PRET_MIN and vol >= VOL_MIN
assert trece == (verdict == "trece"), f"Simbol {nume}: verdictul nu iese din cifre"
if verdict == "cade pe preț":
assert pret < PRET_MIN and vol >= VOL_MIN, f"Simbol {nume} nu cade doar pe preț"
if verdict == "cade pe volum":
assert vol < VOL_MIN and pret >= PRET_MIN, f"Simbol {nume} nu cade doar pe volum"
assert sum(1 for *_, v in SIMB if v == "trece") == 1, "aștept exact un simbol care trece"
# 2. zona verde e exact colțul „peste ambele praguri"
assert px(PRET_MIN) > X0 and py(VOL_MIN) < Y1, "zona verde nu e un colț al planului"
for nume, pret, vol, verdict in SIMB:
inauntru = px(PRET_MIN) <= px(pret) <= X1 and Y0 <= py(vol) <= py(VOL_MIN)
assert inauntru == (verdict == "trece"), f"Simbol {nume}: punctul nu e desenat în zona lui"
# 3. toate punctele încap în cadru
for nume, pret, vol, _ in SIMB:
assert X0 < px(pret) < X1, f"Simbol {nume}: prețul iese din cadru"
assert Y0 < py(vol) < Y1, f"Simbol {nume}: volumul iese din cadru"
# 4. liniile de prag nu cad pe marginea cadrului
for x in (px(PRET_MIN), px(PRET_IDEAL)):
assert x - X0 > 20 and X1 - x > 20, "linie de prag lipită de marginea cadrului"
for y in (py(VOL_MIN), py(VOL_IDEAL)):
assert y - Y0 > 20 and Y1 - y > 20, "linie de prag lipită de marginea cadrului"
# 5. etichete în pânză și fără suprapuneri
boxes = []
for x, y, s, size, weight, fill, anchor in TEXTS:
assert size >= 13, f"font sub 13px: {s!r}"
w = est_w(s, size, weight)
left = x - w if anchor == "end" else (x - w / 2 if anchor == "middle" else x)
assert -2 <= left and left + w <= W + 2, f"text în afara pânzei: {s!r}"
assert 0 <= y - size * 0.78 and y + size * 0.22 <= H, f"text în afara pânzei: {s!r}"
boxes.append((left, left + w, y - size * 0.78, y + size * 0.22, s))
for i in range(len(boxes)):
for j in range(i + 1, len(boxes)):
l1, r1, t1, b1, s1 = boxes[i]
l2, r2, t2, b2, s2 = boxes[j]
if r1 > l2 + 2 and r2 > l1 + 2 and b1 > t2 + 2 and b2 > t1 + 2:
raise AssertionError(f"etichete suprapuse: {s1!r} / {s2!r}")
assert "\n\n" not in svg, "linie goală în SVG"
OUT = Path(__file__).with_suffix(".svg")
OUT.write_text(svg, encoding="utf-8")
print(f"scris {OUT.name} ({len(svg)} bytes, {len(TEXTS)} etichete, "
f"{len(SIMB)} simboluri, {W}x{H}), verificare OK")