#!/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'') body = [head, f''] + p body += [txt(*t) for t in TEXTS] body.append("") 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")