"""Trinity — 3 stele vs. trei divergențe simultane. Diagramă conceptuală pentru `summaries/08_ANALIZA_ZILNICA/11_selectia_simbolului.md` (marcaj ``), §3 „Trinity și power — stelele". Două panouri: 1. Trinity complet — relative strength, power și volum, toate în cadranul 1, aceeași direcție => „3 stele cu plus". 2. Trei divergențe simultane (volum, RS, power) => setup de bottom, dar fără trigger încă: „doar pe listă". Nu e grafic de preț, deci NU folosește `svg_candles` / `pchart`: SVG-ul e construit ca string, fără dependințe. Rulează: .venv/bin/python gen/selectia/trinity.py Scrie `gen/selectia/trinity.svg`. Fiecare cifră și etichetă de aici există în textul din jurul marcajului sau în citatele lui: - „3 stele cu plus" / „3 stele" / cadranul 1 / relativ strength, power, volum: `75_05052026.txt:101-106` și `82_24042026.txt:334-335`; - cele trei divergențe simultane și așteptarea trigger-ului: `98_30032026.txt:395-397`; - „3 stele cu roșu" pe indice = short: `75_05052026.txt:101-106` și tabelul Trinity DA/NU. Textul nu leagă DA de verde și nici divergențele de vreo culoare, așa că nu am folosit verde/semafor: roșu apare doar acolo unde sursa spune explicit „roșu". """ import html from pathlib import Path W, H = 700, 640 BLACK = "#000000" GRAY = "#777777" RED = "#c00" BLUE = "#1a5fb4" texts = [] # (x, y, text, size, weight, fill, w_est) def add(x, y, s, size=13, weight="normal", fill=BLACK): factor = 0.62 if weight == "bold" else 0.56 texts.append((x, y, s, size, weight, fill, len(s) * size * factor)) return x, y, s, size, weight, fill, len(s) * size * factor def box(x, y, w, h): return ( f'' ) parts = [ f'', f'', ] # ---------------------------------------------------------------- titlu add(W / 2, 32, "Trinity — 3 stele", 20, "bold") add(W / 2, 56, "Relative strength, power și volum aliniate în aceeași direcție, în cadranul 1;", 13, "normal", GRAY) add(W / 2, 74, "„3 stele cu plus” pe simbol.", 13, "normal", GRAY) # ---------------------------------------------------------------- panoul 1 P1_X, P1_Y, P1_W, P1_H = 40, 92, 620, 198 parts.append(box(P1_X, P1_Y, P1_W, P1_H)) add(W / 2, 118, "DA — Trinity complet: 3 stele cu plus", 16, "bold", BLUE) COL_W, COL_H, COL_Y = 180, 96, 136 COL_X = [60, 260, 460] CENTER = [x + COL_W / 2.0 for x in COL_X] COL_TITLES = ["Relative strength", "Power", "Volum"] for cx, title in zip(CENTER, COL_TITLES): parts.append(box(cx - COL_W / 2.0, COL_Y, COL_W, COL_H)) add(cx, COL_Y + 26, title, 15, "bold") add(cx, COL_Y + 56, "cadranul 1", 14, "bold", BLUE) add(W / 2, 258, "toate trei: cadranul 1, aceeași direcție", 14, "bold") add(W / 2, 280, "→ 3 stele cu plus", 15, "bold", BLUE) # ---------------------------------------------------------------- panoul 2 P2_X, P2_Y, P2_W, P2_H = 40, 306, 620, 214 parts.append(box(P2_X, P2_Y, P2_W, P2_H)) add(W / 2, 332, "NU — trei divergențe simultane: nu sunt 3 stele", 16, "bold") COL2_Y = 350 COL2_TITLES = ["Volum", "Relative strength", "Power"] for cx, title in zip(CENTER, COL2_TITLES): parts.append(box(cx - COL_W / 2.0, COL2_Y, COL_W, COL_H)) add(cx, COL2_Y + 26, title, 15, "bold") add(cx, COL2_Y + 56, "divergență", 14, "bold", GRAY) add(W / 2, 472, "setup de bottom: prețul e la fund, dar încă nu s-a întors", 14) add(W / 2, 496, "aștepți trigger-ul — deocamdată doar pe listă", 14, "bold", GRAY) # ---------------------------------------------------------------- note + sursă add(W / 2, 548, "Trinity — NU și: două din trei aliniate, a treia în roșu;", 13, "normal", GRAY) add(W / 2, 566, "„3 stele cu roșu” pe indice = short, nu cumperi contra.", 13, "normal", RED) add(W / 2, 596, "Sursa: 11_selectia_simbolului.md §3 — 75_05052026.txt:101-106;", 13, "normal", GRAY) add(W / 2, 614, "82_24042026.txt:334-335; 98_30032026.txt:395-397.", 13, "normal", GRAY) # ---------------------------------------------------------------- emitere for x, y, s, size, weight, fill, _ in texts: parts.append( f'' f'{html.escape(s, quote=False)}' ) parts.append("") svg = "\n".join(parts) + "\n" # ---------------------------------------------------------------- verificare assert "filter=" not in svg and "gradient" not in svg, "fără umbre/gradienti" boxes = [] for x, y, s, size, weight, fill, w in texts: assert size >= 13, f"font sub 13px: {s!r} ({size})" left, right = x - w / 2.0, x + w / 2.0 top, bottom = y - size * 0.78, y + size * 0.22 assert 0 <= left and right <= W, f"etichetă în afara panzei pe orizontală: {s!r}" assert 0 <= top and bottom <= H, f"etichetă în afara panzei pe verticală: {s!r}" boxes.append((left, right, top, bottom, 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 and r2 > l1 and b1 > t2 and b2 > t1: raise AssertionError(f"etichete suprapuse: {s1!r} / {s2!r}") for required in ("3 stele", "cadranul 1", "Relative strength", "Power", "Volum", "3 stele cu plus", "divergență", "trei divergențe simultane", "setup de bottom", "trigger", "doar pe listă", "3 stele cu roșu", "short"): assert required in svg, f"lipsește din desen: {required!r}" # fiecare din cele trei condiții apare o dată în panoul 1 și o dată în panoul 2 for word in ("Relative strength", "Power", "Volum"): assert svg.count(f">{word}") == 2, f"{word!r} nu apare de două ori" 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}), verificare OK")