cele 7 diagrame din 11_selectia
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
28
gen/az_selectia.py
Normal file
@@ -0,0 +1,28 @@
|
||||
"""Bagă cele 7 diagrame din gen/selectia/*.svg în 11_selectia_simbolului.md.
|
||||
|
||||
Diagramele sunt conceptuale (funnel, bară de scor, matrice, cadrane, Trinity,
|
||||
sectoare), nu grafice de preț — de aceea nu trec prin svg_candles/pchart.
|
||||
Fiecare are generatorul ei în gen/selectia/<cheie>.py; ăsta doar le rulează
|
||||
și le substituie între markeri, ca să nu scrie mai mulți agenți în .md.
|
||||
|
||||
Rulează: .venv/bin/python gen/az_selectia.py
|
||||
"""
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parent.parent
|
||||
MD = ROOT / "summaries/08_ANALIZA_ZILNICA/11_selectia_simbolului.md"
|
||||
GEN = ROOT / "gen/selectia"
|
||||
CHEI = ["funnel", "scor", "pe_venit", "cash_flow", "cadrane", "trinity", "sectoare"]
|
||||
|
||||
md = MD.read_text(encoding="utf-8")
|
||||
for cheie in CHEI:
|
||||
subprocess.run([sys.executable, str(GEN / f"{cheie}.py")], check=True, cwd=ROOT)
|
||||
svg = (GEN / f"{cheie}.svg").read_text(encoding="utf-8").strip()
|
||||
pat = re.compile(rf"<!--SVG:{cheie}-->.*?<!--/SVG-->", re.S)
|
||||
assert pat.search(md), f"marcatorul {cheie} lipsește din .md"
|
||||
md = pat.sub(lambda _m: f"<!--SVG:{cheie}-->\n{svg}\n<!--/SVG-->", md)
|
||||
MD.write_text(md, encoding="utf-8")
|
||||
print("scris", MD)
|
||||
187
gen/selectia/cadrane.py
Normal file
@@ -0,0 +1,187 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Harta celor patru cadrane relative strength, fata de benchmark.
|
||||
|
||||
Pentru `summaries/08_ANALIZA_ZILNICA/11_selectia_simbolului.md` (SVG:cadrane),
|
||||
sectiunea 2.1. Nu e grafic de pret, deci nu se folosesc svg_candles / pchart:
|
||||
SVG scris de mana, doar stdlib.
|
||||
|
||||
Ce arata: cadranele 1-4 asa cum le descrie documentul, grupate pe directie.
|
||||
Randul de sus = conditia de intrare LONG (cadranele 1 si 2), randul de jos =
|
||||
conditia de intrare SHORT (cadranele 3 si 4); sageti duble grupeaza perechile.
|
||||
Fiecare cadran are numarul, sensul si directia.
|
||||
|
||||
Etichete si cifre, toate din text/glosar (fara axe inventate, fara procente):
|
||||
- RS = cat de bine se misca simbolul fata de benchmark (ex. SPY) (.md:62)
|
||||
- cadran 1 = supraperformanta, bate benchmark-ul, long (.md:63,310)
|
||||
- cadran 2 = imbunatatire spre supraperformanta, zona de cumparat,
|
||||
la long, de preferat (.md:64,311)
|
||||
- cadran 3 = imbunatatire spre underperform, pentru short (.md:65,312)
|
||||
- cadran 4 = underperform, pierde in fata benchmark-ului,
|
||||
short / stai pe margine (.md:66,313)
|
||||
- cadranul 4 nu se tranzactioneaza pe orb: astepti trecerea in 2 sau 1,
|
||||
ori un MP4-1 / MBT-10 long ca semn de schimbare (.md:315-316)
|
||||
- setup de bottom cu RS fara imbunatatire: sanse mai mici (.md:323-324)
|
||||
|
||||
Fara semafor rosu/verde (nu e in sursa); un singur accent albastru pe
|
||||
chenare/sageti/numere, text negru pe fundal alb.
|
||||
Ruleaza: .venv/bin/python gen/selectia/cadrane.py -> gen/selectia/cadrane.svg
|
||||
"""
|
||||
from pathlib import Path
|
||||
from xml.sax.saxutils import escape
|
||||
|
||||
OUT = Path(__file__).with_suffix(".svg")
|
||||
|
||||
W, H = 700, 580
|
||||
BLACK, GRAY, BLUE, WHITE = "#000000", "#777777", "#1a5fb4", "#ffffff"
|
||||
|
||||
COL1_X, COL_W, GAP = 40, 290, 40
|
||||
COL2_X = COL1_X + COL_W + GAP # 370
|
||||
COL1_R = COL1_X + COL_W # 330
|
||||
COL2_R = COL2_X + COL_W # 660
|
||||
|
||||
CARD_H = 150
|
||||
ROW1_Y, ROW2_Y = 86, 302
|
||||
ROW1_B, ROW2_B = ROW1_Y + CARD_H, ROW2_Y + CARD_H # 236 / 452
|
||||
BR1_Y, BR2_Y = ROW1_B + 14, ROW2_B + 14 # 250 / 466
|
||||
|
||||
# fiecare card: (x, y, linii [(dy, text, size, weight, fill)])
|
||||
CARDS = [
|
||||
(COL1_X, ROW1_Y, [
|
||||
(34, "Cadran 1", 16, "700", BLACK),
|
||||
(68, "supraperformanță", 14, "400", BLACK),
|
||||
(96, "bate benchmark-ul", 13, "400", GRAY),
|
||||
(124, "LONG", 13, "700", BLUE),
|
||||
]),
|
||||
(COL2_X, ROW1_Y, [
|
||||
(34, "Cadran 2", 16, "700", BLACK),
|
||||
(66, "îmbunătățire spre", 14, "400", BLACK),
|
||||
(86, "supraperformanță", 14, "400", BLACK),
|
||||
(110, "zona de cumpărat, la long", 13, "400", GRAY),
|
||||
(132, "LONG", 13, "700", BLUE),
|
||||
]),
|
||||
(COL1_X, ROW2_Y, [
|
||||
(34, "Cadran 3", 16, "700", BLACK),
|
||||
(66, "îmbunătățire spre", 14, "400", BLACK),
|
||||
(86, "underperform", 14, "400", BLACK),
|
||||
(110, "pentru short", 13, "400", GRAY),
|
||||
(132, "SHORT", 13, "700", BLUE),
|
||||
]),
|
||||
(COL2_X, ROW2_Y, [
|
||||
(34, "Cadran 4", 16, "700", BLACK),
|
||||
(68, "underperform", 14, "400", BLACK),
|
||||
(96, "pierde în fața benchmark-ului", 13, "400", GRAY),
|
||||
(124, "SHORT / stai pe margine", 13, "700", BLUE),
|
||||
]),
|
||||
]
|
||||
|
||||
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'<text x="{x:.1f}" y="{y}" font-size="{size}" font-weight="{weight}" '
|
||||
f'fill="{fill}" text-anchor="{anchor}">{escape(s)}</text>'
|
||||
)
|
||||
|
||||
|
||||
def line(x1, y1, x2, y2, stroke=BLUE, width=1.5, dash=None):
|
||||
d = f' stroke-dasharray="{dash}"' if dash else ""
|
||||
return (f'<line x1="{x1}" y1="{y1}" x2="{x2}" y2="{y2}" '
|
||||
f'stroke="{stroke}" stroke-width="{width}"{d}/>')
|
||||
|
||||
|
||||
def arrow(x, y, direction, color=BLUE):
|
||||
"""Varf de sageata orizontal: direction=1 spre dreapta, -1 spre stanga."""
|
||||
d = f"M {x} {y} L {x - 9 * direction} {y - 5} L {x - 9 * direction} {y + 5} Z"
|
||||
return f'<path d="{d}" fill="{color}"/>'
|
||||
|
||||
|
||||
def card(x, y, lines):
|
||||
out = [f'<rect x="{x}" y="{y}" width="{COL_W}" height="{CARD_H}" rx="10" '
|
||||
f'fill="{WHITE}" stroke="{BLUE}" stroke-width="1.5"/>']
|
||||
for dy, s, size, weight, fill in lines:
|
||||
out.append(txt(x + COL_W / 2, y + dy, s, size, weight, fill))
|
||||
return out
|
||||
|
||||
|
||||
def bracket(y, label):
|
||||
"""Bara dubla cu varfuri, grupeaza cele doua carduri de pe rand."""
|
||||
x0, x1 = COL1_X + 16, COL2_R - 16
|
||||
return [
|
||||
line(x0, y, x1, y, BLUE, 1.5),
|
||||
arrow(x1, y, 1),
|
||||
arrow(x0, y, -1),
|
||||
txt(W / 2, y + 22, label, 14, "700", BLUE),
|
||||
]
|
||||
|
||||
|
||||
def build():
|
||||
p = [
|
||||
f'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 {W} {H}" '
|
||||
f'width="{W}" height="{H}" role="img" '
|
||||
f'aria-label="Cele patru cadrane relative strength fata de benchmark: '
|
||||
f'cadranele 1 si 2 = conditie de intrare long, cadranele 3 si 4 = short">',
|
||||
f'<g font-family="system-ui, sans-serif">',
|
||||
f'<rect x="0" y="0" width="{W}" height="{H}" fill="{WHITE}"/>',
|
||||
txt(W / 2, 34, "Relative strength — cele patru cadrane", 19, "700", BLACK),
|
||||
txt(W / 2, 58, "RS = cât de bine se mișcă simbolul față de benchmark "
|
||||
"(ex. SPY)", 13, "400", GRAY),
|
||||
]
|
||||
for x, y, lines in CARDS:
|
||||
p.extend(card(x, y, lines))
|
||||
p.extend(bracket(BR1_Y, "Condiție de intrare LONG — cadranele 1 și 2"))
|
||||
p.extend(bracket(BR2_Y, "Condiție de intrare SHORT — cadranele 3 și 4"))
|
||||
p.append(txt(W / 2, BR2_Y + 48,
|
||||
"Cadranul 4 nu se tranzacționează pe orb, chiar cu scor bun:",
|
||||
13, "400", GRAY))
|
||||
p.append(txt(W / 2, BR2_Y + 68,
|
||||
"aștepți trecerea în 2 sau 1, ori un MP4-1 / MBT-10 long ca semn "
|
||||
"de schimbare.", 13, "400", GRAY))
|
||||
p.append(txt(W / 2, BR2_Y + 90,
|
||||
"Un setup de bottom cu RS fără îmbunătățire are șanse mai mici.",
|
||||
13, "400", GRAY))
|
||||
p.append('</g></svg>')
|
||||
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():
|
||||
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 in afara panzei: {s!r}"
|
||||
assert 0 <= y <= H, f"text in afara inaltimii: {s!r} (y={y})"
|
||||
|
||||
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}")
|
||||
|
||||
for x, y, lines in CARDS:
|
||||
for dy, s, size, weight, fill in lines:
|
||||
ew = est_w(s, size, weight)
|
||||
assert ew <= COL_W - 24, f"eticheta nu incape in card: {s!r} ({ew:.0f})"
|
||||
assert y + dy <= y + CARD_H - 8, f"eticheta iese din card: {s!r}"
|
||||
assert "nan" not in OUT_TEXT[0]
|
||||
return True
|
||||
|
||||
|
||||
OUT_TEXT = [build()]
|
||||
assert verify(), "verificare esuata"
|
||||
OUT.write_text(OUT_TEXT[0], encoding="utf-8")
|
||||
print(f"scris {OUT} ({W}x{H}), verificare OK: {len(TEXTS)} etichete")
|
||||
1
gen/selectia/cadrane.spec.txt
Normal file
@@ -0,0 +1 @@
|
||||
cele 4 cadrane relative strength fata de benchmark: cadran 1 supraperformanta, cadran 2 imbunatatire (long), cadran 3 imbunatatire pe minus, cadran 4 underperform; sageti pentru conditiile de intrare long (1-2) si short (3-4)
|
||||
39
gen/selectia/cadrane.svg
Normal file
@@ -0,0 +1,39 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 700 580" width="700" height="580" role="img" aria-label="Cele patru cadrane relative strength fata de benchmark: cadranele 1 si 2 = conditie de intrare long, cadranele 3 si 4 = short">
|
||||
<g font-family="system-ui, sans-serif">
|
||||
<rect x="0" y="0" width="700" height="580" fill="#ffffff"/>
|
||||
<text x="350.0" y="34" font-size="19" font-weight="700" fill="#000000" text-anchor="middle">Relative strength — cele patru cadrane</text>
|
||||
<text x="350.0" y="58" font-size="13" font-weight="400" fill="#777777" text-anchor="middle">RS = cât de bine se mișcă simbolul față de benchmark (ex. SPY)</text>
|
||||
<rect x="40" y="86" width="290" height="150" rx="10" fill="#ffffff" stroke="#1a5fb4" stroke-width="1.5"/>
|
||||
<text x="185.0" y="120" font-size="16" font-weight="700" fill="#000000" text-anchor="middle">Cadran 1</text>
|
||||
<text x="185.0" y="154" font-size="14" font-weight="400" fill="#000000" text-anchor="middle">supraperformanță</text>
|
||||
<text x="185.0" y="182" font-size="13" font-weight="400" fill="#777777" text-anchor="middle">bate benchmark-ul</text>
|
||||
<text x="185.0" y="210" font-size="13" font-weight="700" fill="#1a5fb4" text-anchor="middle">LONG</text>
|
||||
<rect x="370" y="86" width="290" height="150" rx="10" fill="#ffffff" stroke="#1a5fb4" stroke-width="1.5"/>
|
||||
<text x="515.0" y="120" font-size="16" font-weight="700" fill="#000000" text-anchor="middle">Cadran 2</text>
|
||||
<text x="515.0" y="152" font-size="14" font-weight="400" fill="#000000" text-anchor="middle">îmbunătățire spre</text>
|
||||
<text x="515.0" y="172" font-size="14" font-weight="400" fill="#000000" text-anchor="middle">supraperformanță</text>
|
||||
<text x="515.0" y="196" font-size="13" font-weight="400" fill="#777777" text-anchor="middle">zona de cumpărat, la long</text>
|
||||
<text x="515.0" y="218" font-size="13" font-weight="700" fill="#1a5fb4" text-anchor="middle">LONG</text>
|
||||
<rect x="40" y="302" width="290" height="150" rx="10" fill="#ffffff" stroke="#1a5fb4" stroke-width="1.5"/>
|
||||
<text x="185.0" y="336" font-size="16" font-weight="700" fill="#000000" text-anchor="middle">Cadran 3</text>
|
||||
<text x="185.0" y="368" font-size="14" font-weight="400" fill="#000000" text-anchor="middle">îmbunătățire spre</text>
|
||||
<text x="185.0" y="388" font-size="14" font-weight="400" fill="#000000" text-anchor="middle">underperform</text>
|
||||
<text x="185.0" y="412" font-size="13" font-weight="400" fill="#777777" text-anchor="middle">pentru short</text>
|
||||
<text x="185.0" y="434" font-size="13" font-weight="700" fill="#1a5fb4" text-anchor="middle">SHORT</text>
|
||||
<rect x="370" y="302" width="290" height="150" rx="10" fill="#ffffff" stroke="#1a5fb4" stroke-width="1.5"/>
|
||||
<text x="515.0" y="336" font-size="16" font-weight="700" fill="#000000" text-anchor="middle">Cadran 4</text>
|
||||
<text x="515.0" y="370" font-size="14" font-weight="400" fill="#000000" text-anchor="middle">underperform</text>
|
||||
<text x="515.0" y="398" font-size="13" font-weight="400" fill="#777777" text-anchor="middle">pierde în fața benchmark-ului</text>
|
||||
<text x="515.0" y="426" font-size="13" font-weight="700" fill="#1a5fb4" text-anchor="middle">SHORT / stai pe margine</text>
|
||||
<line x1="56" y1="250" x2="644" y2="250" stroke="#1a5fb4" stroke-width="1.5"/>
|
||||
<path d="M 644 250 L 635 245 L 635 255 Z" fill="#1a5fb4"/>
|
||||
<path d="M 56 250 L 65 245 L 65 255 Z" fill="#1a5fb4"/>
|
||||
<text x="350.0" y="272" font-size="14" font-weight="700" fill="#1a5fb4" text-anchor="middle">Condiție de intrare LONG — cadranele 1 și 2</text>
|
||||
<line x1="56" y1="466" x2="644" y2="466" stroke="#1a5fb4" stroke-width="1.5"/>
|
||||
<path d="M 644 466 L 635 461 L 635 471 Z" fill="#1a5fb4"/>
|
||||
<path d="M 56 466 L 65 461 L 65 471 Z" fill="#1a5fb4"/>
|
||||
<text x="350.0" y="488" font-size="14" font-weight="700" fill="#1a5fb4" text-anchor="middle">Condiție de intrare SHORT — cadranele 3 și 4</text>
|
||||
<text x="350.0" y="514" font-size="13" font-weight="400" fill="#777777" text-anchor="middle">Cadranul 4 nu se tranzacționează pe orb, chiar cu scor bun:</text>
|
||||
<text x="350.0" y="534" font-size="13" font-weight="400" fill="#777777" text-anchor="middle">aștepți trecerea în 2 sau 1, ori un MP4-1 / MBT-10 long ca semn de schimbare.</text>
|
||||
<text x="350.0" y="556" font-size="13" font-weight="400" fill="#777777" text-anchor="middle">Un setup de bottom cu RS fără îmbunătățire are șanse mai mici.</text>
|
||||
</g></svg>
|
||||
|
After Width: | Height: | Size: 4.3 KiB |
195
gen/selectia/cash_flow.py
Normal file
@@ -0,0 +1,195 @@
|
||||
"""Trei stări de cash flow, din `summaries/08_ANALIZA_ZILNICA/11_selectia_simbolului.md`
|
||||
(secțiunea 1.5 Cash flow):
|
||||
|
||||
- pozitiv și în creștere = se finanțează, stă în picioare -> îl cauți
|
||||
- pozitiv, dar în scădere = deteriorare -> nu-l pui pe lista de investiție
|
||||
- negativ = speculă pe momentum, nu investiție
|
||||
|
||||
La ETF-uri, ETF-urile nu au scor, dar nu intră în această diagramă.
|
||||
|
||||
SVG scris de mână (nu e grafic de preț, deci NU se folosesc svg_candles / pchart).
|
||||
Fără dependințe: doar stdlib. Rulare:
|
||||
.venv/bin/python gen/selectia/cash_flow.py
|
||||
Scrie `gen/selectia/cash_flow.svg`.
|
||||
|
||||
Notă de conținut: textul nu dă praguri, procente sau cod de culoare
|
||||
(verde/roșu ca semafor), deci diagrama nu inventează niciunul. Toate cifrele
|
||||
care apar în desen sunt referințe de citat (fișier:linii) existente în text.
|
||||
"""
|
||||
from pathlib import Path
|
||||
from xml.sax.saxutils import escape
|
||||
|
||||
OUT = Path(__file__).resolve().parent / "cash_flow.svg"
|
||||
|
||||
BLACK = "#000000"
|
||||
GRAY = "#777777"
|
||||
BLUE = "#1a5fb4"
|
||||
|
||||
W = 700
|
||||
MARGIN = 30
|
||||
GAP = 20
|
||||
CARD_W = 200
|
||||
CARD_TOP = 80
|
||||
CARD_H = 216
|
||||
XS = [MARGIN + i * (CARD_W + GAP) for i in range(3)] # 30, 250, 470
|
||||
|
||||
TITLE_Y = 34
|
||||
SUB_Y = 56
|
||||
|
||||
# Fiecare card: header (poate fi pe două rânduri), "ce înseamnă" (2 rânduri),
|
||||
# acțiunea (1-2 rânduri, în caseta de jos) și sursa.
|
||||
CARDS = [
|
||||
{
|
||||
"header": ["Pozitiv", "și în creștere"],
|
||||
"meaning": ["se finanțează,", "stă în picioare"],
|
||||
"action": ["Îl cauți chiar și la", "companiile pe", "pierdere"],
|
||||
"source": "66_18052026.txt:120-121",
|
||||
},
|
||||
{
|
||||
"header": ["Pozitiv, dar", "în scădere"],
|
||||
"meaning": ["deși e pozitiv,", "s-a deteriorat"],
|
||||
"action": ["Nu-l pui pe lista", "de investiție"],
|
||||
"source": "72_08052026.txt:241-243",
|
||||
},
|
||||
{
|
||||
"header": ["Negativ"],
|
||||
"meaning": ["speculă pe momentum"],
|
||||
"action": ["Nu investiție"],
|
||||
"source": "„Ce faci”, secțiunea 1.5",
|
||||
},
|
||||
]
|
||||
|
||||
FOOTNOTES = [
|
||||
("Cash flow-ul pozitiv e ce ține compania în picioare — îl cauți chiar și la",
|
||||
BLACK),
|
||||
("companiile încă pe pierdere.", BLACK),
|
||||
("Pozitiv dar în scădere = deteriorare (53_09062026.txt:122-124).", GRAY),
|
||||
("Surse: 66_18052026.txt:120-121; 72_08052026.txt:241-243.", GRAY),
|
||||
("Secțiunea 1.5 Cash flow — summaries/08_ANALIZA_ZILNICA/11_selectia_simbolului.md.",
|
||||
GRAY),
|
||||
]
|
||||
|
||||
FOOT_TOP = CARD_TOP + CARD_H + 30
|
||||
H = FOOT_TOP + (len(FOOTNOTES) - 1) * 20 + 30
|
||||
|
||||
labels = [] # (x_left, x_right, y_top, y_bottom, text) pentru verificare
|
||||
|
||||
|
||||
def est_width(text, size, bold=False):
|
||||
"""Lățime aproximativă pentru system-ui, pentru verificarea suprapunerilor."""
|
||||
factor = 0.62 if bold else 0.56
|
||||
return len(text) * size * factor
|
||||
|
||||
|
||||
def txt(x, y, s, size=13, bold=False, fill=BLACK, anchor="middle"):
|
||||
weight = "700" if bold else "400"
|
||||
w = est_width(s, size, bold)
|
||||
if anchor == "middle":
|
||||
x0, x1 = x - w / 2, x + w / 2
|
||||
elif anchor == "start":
|
||||
x0, x1 = x, x + w
|
||||
else: # end
|
||||
x0, x1 = x - w, x
|
||||
labels.append((x0, x1, y - size, y + 3, s))
|
||||
return (
|
||||
f'<text x="{x:g}" y="{y}" font-family="system-ui, sans-serif" '
|
||||
f'font-size="{size}" font-weight="{weight}" fill="{fill}" '
|
||||
f'text-anchor="{anchor}">{escape(s)}</text>'
|
||||
)
|
||||
|
||||
|
||||
def card(idx, spec):
|
||||
x = XS[idx]
|
||||
parts = [
|
||||
f'<rect x="{x}" y="{CARD_TOP}" width="{CARD_W}" height="{CARD_H}" '
|
||||
f'rx="10" fill="#ffffff" stroke="{BLUE}" stroke-width="1.5"/>',
|
||||
]
|
||||
# header
|
||||
hy = CARD_TOP + 30
|
||||
for line in spec["header"]:
|
||||
parts.append(txt(x + CARD_W / 2, hy, line, size=16, bold=True))
|
||||
hy += 21
|
||||
sep_y = CARD_TOP + 64
|
||||
parts.append(
|
||||
f'<line x1="{x + 14}" y1="{sep_y}" x2="{x + CARD_W - 14}" y2="{sep_y}" '
|
||||
f'stroke="{BLUE}" stroke-width="1"/>'
|
||||
)
|
||||
# ce înseamnă
|
||||
parts.append(txt(x + 14, CARD_TOP + 88, "Ce înseamnă", size=13, fill=GRAY,
|
||||
anchor="start"))
|
||||
my = CARD_TOP + 108
|
||||
for line in spec["meaning"]:
|
||||
parts.append(txt(x + 14, my, line, size=13, anchor="start"))
|
||||
my += 19
|
||||
# caseta de acțiune
|
||||
box_y = CARD_TOP + CARD_H - 74
|
||||
parts.append(
|
||||
f'<rect x="{x + 14}" y="{box_y}" width="{CARD_W - 28}" height="58" '
|
||||
f'rx="6" fill="#ffffff" stroke="{BLUE}" stroke-width="1"/>'
|
||||
)
|
||||
ay = box_y + 19
|
||||
for line in spec["action"]:
|
||||
parts.append(txt(x + CARD_W / 2, ay, line, size=13, bold=True))
|
||||
ay += 17
|
||||
# sursa
|
||||
parts.append(txt(x + CARD_W / 2, CARD_TOP + CARD_H + 15, spec["source"],
|
||||
size=13, fill=GRAY))
|
||||
return parts
|
||||
|
||||
|
||||
svg = [
|
||||
f'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 {W} {H}" '
|
||||
f'width="{W}" height="{H}" role="img" '
|
||||
f'aria-label="Trei stări de cash flow: pozitiv în creștere, pozitiv dar '
|
||||
f'în scădere, negativ">',
|
||||
f'<rect x="0" y="0" width="{W}" height="{H}" fill="#ffffff"/>',
|
||||
txt(W / 2, TITLE_Y, "Cash flow — trei stări", size=19, bold=True),
|
||||
txt(W / 2, SUB_Y, "un cash flow pozitiv care scade e deteriorare, "
|
||||
"nu semnal de cumpărare", size=13, fill=GRAY),
|
||||
]
|
||||
|
||||
for i, spec in enumerate(CARDS):
|
||||
svg.extend(card(i, spec))
|
||||
|
||||
fy = FOOT_TOP + 10
|
||||
for line, color in FOOTNOTES:
|
||||
svg.append(txt(W / 2, fy, line, size=13, fill=color))
|
||||
fy += 20
|
||||
|
||||
svg.append("</svg>")
|
||||
|
||||
# ------------------------------------------------------------------ verificare
|
||||
# 1. fiecare text încape în card / în panză
|
||||
for i, spec in enumerate(CARDS):
|
||||
inner = CARD_W - 28
|
||||
for line in spec["header"]:
|
||||
assert est_width(line, 16, bold=True) <= inner, \
|
||||
f"header nu incape in card {i}: {line!r}"
|
||||
for line in spec["meaning"] + spec["action"]:
|
||||
assert est_width(line, 13, line in spec["action"]) <= inner, \
|
||||
f"text nu incape in card {i}: {line!r}"
|
||||
|
||||
# 2. toate etichetele au coordonate în interiorul panzei
|
||||
for x0, x1, y0, y1, s in labels:
|
||||
assert 0 <= x0 and x1 <= W, f"eticheta iese din panza pe orizontala: {s!r}"
|
||||
assert 0 <= y0 and y1 <= H, f"eticheta iese din panza pe verticala: {s!r}"
|
||||
|
||||
# 3. pe aceeași poziție / suprapuse
|
||||
for i in range(len(labels)):
|
||||
for j in range(i + 1, len(labels)):
|
||||
ax0, ax1, ay0, ay1, a = labels[i]
|
||||
bx0, bx1, by0, by1, b = labels[j]
|
||||
inter_w = min(ax1, bx1) - max(ax0, bx0)
|
||||
inter_h = min(ay1, by1) - max(ay0, by0)
|
||||
if inter_w > 1.5 and inter_h > 1.5:
|
||||
raise AssertionError(f"texte suprapuse: {a!r} / {b!r}")
|
||||
|
||||
# 4. nu se calcă pe verticală doua rânduri de text (baseline prea apropiat)
|
||||
ys = sorted((round((y0 + y1) / 2), s) for _, _, y0, y1, s in labels)
|
||||
for k in range(len(ys) - 1):
|
||||
assert ys[k + 1][0] - ys[k][0] >= 14 or ys[k][0] == ys[k + 1][0], \
|
||||
f"rânduri prea apropiate: {ys[k][1]!r} / {ys[k + 1][1]!r}"
|
||||
|
||||
OUT.write_text("\n".join(svg) + "\n", encoding="utf-8")
|
||||
print(f"scris {OUT} ({W}x{H}), {len(CARDS)} stări, "
|
||||
f"{len(labels)} etichete, verificare OK")
|
||||
1
gen/selectia/cash_flow.spec.txt
Normal file
@@ -0,0 +1 @@
|
||||
trei stari de cash flow: pozitiv si in crestere = sanatos; pozitiv dar in scadere = deteriorare, scoti de pe lista; negativ = specula pe momentum, nu investitie
|
||||
41
gen/selectia/cash_flow.svg
Normal file
@@ -0,0 +1,41 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 700 436" width="700" height="436" role="img" aria-label="Trei stări de cash flow: pozitiv în creștere, pozitiv dar în scădere, negativ">
|
||||
<rect x="0" y="0" width="700" height="436" fill="#ffffff"/>
|
||||
<text x="350" y="34" font-family="system-ui, sans-serif" font-size="19" font-weight="700" fill="#000000" text-anchor="middle">Cash flow — trei stări</text>
|
||||
<text x="350" y="56" font-family="system-ui, sans-serif" font-size="13" font-weight="400" fill="#777777" text-anchor="middle">un cash flow pozitiv care scade e deteriorare, nu semnal de cumpărare</text>
|
||||
<rect x="30" y="80" width="200" height="216" rx="10" fill="#ffffff" stroke="#1a5fb4" stroke-width="1.5"/>
|
||||
<text x="130" y="110" font-family="system-ui, sans-serif" font-size="16" font-weight="700" fill="#000000" text-anchor="middle">Pozitiv</text>
|
||||
<text x="130" y="131" font-family="system-ui, sans-serif" font-size="16" font-weight="700" fill="#000000" text-anchor="middle">și în creștere</text>
|
||||
<line x1="44" y1="144" x2="216" y2="144" stroke="#1a5fb4" stroke-width="1"/>
|
||||
<text x="44" y="168" font-family="system-ui, sans-serif" font-size="13" font-weight="400" fill="#777777" text-anchor="start">Ce înseamnă</text>
|
||||
<text x="44" y="188" font-family="system-ui, sans-serif" font-size="13" font-weight="400" fill="#000000" text-anchor="start">se finanțează,</text>
|
||||
<text x="44" y="207" font-family="system-ui, sans-serif" font-size="13" font-weight="400" fill="#000000" text-anchor="start">stă în picioare</text>
|
||||
<rect x="44" y="222" width="172" height="58" rx="6" fill="#ffffff" stroke="#1a5fb4" stroke-width="1"/>
|
||||
<text x="130" y="241" font-family="system-ui, sans-serif" font-size="13" font-weight="700" fill="#000000" text-anchor="middle">Îl cauți chiar și la</text>
|
||||
<text x="130" y="258" font-family="system-ui, sans-serif" font-size="13" font-weight="700" fill="#000000" text-anchor="middle">companiile pe</text>
|
||||
<text x="130" y="275" font-family="system-ui, sans-serif" font-size="13" font-weight="700" fill="#000000" text-anchor="middle">pierdere</text>
|
||||
<text x="130" y="311" font-family="system-ui, sans-serif" font-size="13" font-weight="400" fill="#777777" text-anchor="middle">66_18052026.txt:120-121</text>
|
||||
<rect x="250" y="80" width="200" height="216" rx="10" fill="#ffffff" stroke="#1a5fb4" stroke-width="1.5"/>
|
||||
<text x="350" y="110" font-family="system-ui, sans-serif" font-size="16" font-weight="700" fill="#000000" text-anchor="middle">Pozitiv, dar</text>
|
||||
<text x="350" y="131" font-family="system-ui, sans-serif" font-size="16" font-weight="700" fill="#000000" text-anchor="middle">în scădere</text>
|
||||
<line x1="264" y1="144" x2="436" y2="144" stroke="#1a5fb4" stroke-width="1"/>
|
||||
<text x="264" y="168" font-family="system-ui, sans-serif" font-size="13" font-weight="400" fill="#777777" text-anchor="start">Ce înseamnă</text>
|
||||
<text x="264" y="188" font-family="system-ui, sans-serif" font-size="13" font-weight="400" fill="#000000" text-anchor="start">deși e pozitiv,</text>
|
||||
<text x="264" y="207" font-family="system-ui, sans-serif" font-size="13" font-weight="400" fill="#000000" text-anchor="start">s-a deteriorat</text>
|
||||
<rect x="264" y="222" width="172" height="58" rx="6" fill="#ffffff" stroke="#1a5fb4" stroke-width="1"/>
|
||||
<text x="350" y="241" font-family="system-ui, sans-serif" font-size="13" font-weight="700" fill="#000000" text-anchor="middle">Nu-l pui pe lista</text>
|
||||
<text x="350" y="258" font-family="system-ui, sans-serif" font-size="13" font-weight="700" fill="#000000" text-anchor="middle">de investiție</text>
|
||||
<text x="350" y="311" font-family="system-ui, sans-serif" font-size="13" font-weight="400" fill="#777777" text-anchor="middle">72_08052026.txt:241-243</text>
|
||||
<rect x="470" y="80" width="200" height="216" rx="10" fill="#ffffff" stroke="#1a5fb4" stroke-width="1.5"/>
|
||||
<text x="570" y="110" font-family="system-ui, sans-serif" font-size="16" font-weight="700" fill="#000000" text-anchor="middle">Negativ</text>
|
||||
<line x1="484" y1="144" x2="656" y2="144" stroke="#1a5fb4" stroke-width="1"/>
|
||||
<text x="484" y="168" font-family="system-ui, sans-serif" font-size="13" font-weight="400" fill="#777777" text-anchor="start">Ce înseamnă</text>
|
||||
<text x="484" y="188" font-family="system-ui, sans-serif" font-size="13" font-weight="400" fill="#000000" text-anchor="start">speculă pe momentum</text>
|
||||
<rect x="484" y="222" width="172" height="58" rx="6" fill="#ffffff" stroke="#1a5fb4" stroke-width="1"/>
|
||||
<text x="570" y="241" font-family="system-ui, sans-serif" font-size="13" font-weight="700" fill="#000000" text-anchor="middle">Nu investiție</text>
|
||||
<text x="570" y="311" font-family="system-ui, sans-serif" font-size="13" font-weight="400" fill="#777777" text-anchor="middle">„Ce faci”, secțiunea 1.5</text>
|
||||
<text x="350" y="336" font-family="system-ui, sans-serif" font-size="13" font-weight="400" fill="#000000" text-anchor="middle">Cash flow-ul pozitiv e ce ține compania în picioare — îl cauți chiar și la</text>
|
||||
<text x="350" y="356" font-family="system-ui, sans-serif" font-size="13" font-weight="400" fill="#000000" text-anchor="middle">companiile încă pe pierdere.</text>
|
||||
<text x="350" y="376" font-family="system-ui, sans-serif" font-size="13" font-weight="400" fill="#777777" text-anchor="middle">Pozitiv dar în scădere = deteriorare (53_09062026.txt:122-124).</text>
|
||||
<text x="350" y="396" font-family="system-ui, sans-serif" font-size="13" font-weight="400" fill="#777777" text-anchor="middle">Surse: 66_18052026.txt:120-121; 72_08052026.txt:241-243.</text>
|
||||
<text x="350" y="416" font-family="system-ui, sans-serif" font-size="13" font-weight="400" fill="#777777" text-anchor="middle">Secțiunea 1.5 Cash flow — summaries/08_ANALIZA_ZILNICA/11_selectia_simbolului.md.</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 5.6 KiB |
146
gen/selectia/funnel.py
Normal file
@@ -0,0 +1,146 @@
|
||||
"""Funnel-ul de selecție din `summaries/08_ANALIZA_ZILNICA/11_selectia_simbolului.md`.
|
||||
|
||||
Fluxul din scanner, de sus în jos, așa cum îl spune documentul:
|
||||
scanner (multe semnale) -> sectorul care domină -> doar suport/pivot, după
|
||||
downtrend -> RS cadranul 1-2 -> relative volume peste 100% -> stele (Trinity)
|
||||
-> listă scurtă.
|
||||
|
||||
SVG scris de mână (nu e grafic de preț, deci nu se folosește svg_candles).
|
||||
Fără dependențe: doar stdlib. Rulare:
|
||||
.venv/bin/python gen/selectia/funnel.py
|
||||
Scrie `gen/selectia/funnel.svg`.
|
||||
"""
|
||||
from pathlib import Path
|
||||
from xml.sax.saxutils import escape
|
||||
|
||||
OUT = Path(__file__).resolve().parent / "funnel.svg"
|
||||
|
||||
BLACK = "#000000"
|
||||
GRAY = "#777777"
|
||||
BLUE = "#1a5fb4"
|
||||
|
||||
W = 700
|
||||
TITLE_Y = 34
|
||||
SUB_Y = 56
|
||||
|
||||
BAND_H = 58
|
||||
GAP = 24
|
||||
STEP = BAND_H + GAP
|
||||
TOP0 = 84
|
||||
|
||||
WIDTHS = [620, 566, 512, 458, 404, 350, 296]
|
||||
|
||||
STAGES = [
|
||||
("Scanner — multe semnale", "zeci sau sute de semnale"),
|
||||
("Sectorul care domină", "multe simboluri din același sector = rotație de bani"),
|
||||
("Doar suport / pivot, după downtrend",
|
||||
"suport monthly / quarterly / anual sau pivot"),
|
||||
("Relative strength: cadran 1-2", "doar cadranele 1 și 2, RS pozitiv"),
|
||||
("Relative volume peste 100%",
|
||||
"candela de volum / media ultimelor 10 candele"),
|
||||
("Stele (Trinity)", "3 stele: RS + power + volum, cadran 1"),
|
||||
("Listă scurtă", "taie lista la câteva idei"),
|
||||
]
|
||||
|
||||
N_BANDS = len(STAGES)
|
||||
BANDS_BOTTOM = TOP0 + (N_BANDS - 1) * STEP + BAND_H
|
||||
FOOT_Y = BANDS_BOTTOM + 34
|
||||
H = FOOT_Y + 96
|
||||
|
||||
|
||||
def est_width(text, size, bold=False):
|
||||
"""Lățime aproximativă pentru system-ui, folosită la verificarea suprapunerilor."""
|
||||
factor = 0.62 if bold else 0.56
|
||||
return len(text) * size * factor
|
||||
|
||||
|
||||
def txt(x, y, s, size=13, bold=False, fill=BLACK, anchor="middle"):
|
||||
weight = "700" if bold else "400"
|
||||
return (
|
||||
f'<text x="{x}" y="{y}" font-family="system-ui, sans-serif" '
|
||||
f'font-size="{size}" font-weight="{weight}" fill="{fill}" '
|
||||
f'text-anchor="{anchor}">{escape(s)}</text>'
|
||||
)
|
||||
|
||||
|
||||
def band(i):
|
||||
name, detail = STAGES[i]
|
||||
w = WIDTHS[i]
|
||||
x = (W - w) / 2
|
||||
y = TOP0 + i * STEP
|
||||
parts = [
|
||||
f'<rect x="{x:g}" y="{y}" width="{w}" height="{BAND_H}" rx="8" '
|
||||
f'fill="#ffffff" stroke="{BLUE}" stroke-width="1.5"/>',
|
||||
txt(W / 2, y + 25, name, size=15, bold=True),
|
||||
txt(W / 2, y + 45, detail, size=13, fill=GRAY),
|
||||
]
|
||||
return parts, (y, detail)
|
||||
|
||||
|
||||
def arrow(y):
|
||||
"""Chevron în jos, în golul dintre două benzi."""
|
||||
return (
|
||||
f'<path d="M 341 {y} L 359 {y} L 350 {y + 12} Z" fill="{BLUE}"/>'
|
||||
)
|
||||
|
||||
|
||||
svg = [
|
||||
f'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 {W} {H}" '
|
||||
f'width="{W}" height="{H}" role="img" '
|
||||
f'aria-label="Funnel de selecție: scanner, sector, suport/pivot, RS 1-2, '
|
||||
f'relative volume peste 100%, stele, listă scurtă">',
|
||||
f'<rect x="0" y="0" width="{W}" height="{H}" fill="#ffffff"/>',
|
||||
txt(W / 2, TITLE_Y, "Funnel de selecție — fluxul din scanner", size=19, bold=True),
|
||||
txt(W / 2, SUB_Y, "selecția se face înainte de semnalul tehnic", size=13, fill=GRAY),
|
||||
]
|
||||
|
||||
labels = [] # (x_left, x_right, y_baseline, text) pentru verificarea suprapunerilor
|
||||
for i in range(N_BANDS):
|
||||
parts, (y, detail) = band(i)
|
||||
svg.extend(parts)
|
||||
w = WIDTHS[i]
|
||||
half = est_width(detail, 13) / 2
|
||||
labels.append((W / 2 - half, W / 2 + half, y + 45, detail))
|
||||
name = STAGES[i][0]
|
||||
halfn = est_width(name, 15, bold=True) / 2
|
||||
labels.append((W / 2 - halfn, W / 2 + halfn, y + 25, name))
|
||||
if i < N_BANDS - 1:
|
||||
svg.append(arrow(y + BAND_H + 6))
|
||||
|
||||
svg.append(txt(W / 2, FOOT_Y,
|
||||
"Sub 100% relative volume: 0.96 și 0.74 au fost respinse; el vrea peste 100%.",
|
||||
size=13, fill=GRAY))
|
||||
svg.append(txt(W / 2, FOOT_Y + 20,
|
||||
"Neconfirmat: nu reiese din sesiuni dacă sub prag oprește orice intrare.",
|
||||
size=13, fill=GRAY))
|
||||
svg.append(txt(W / 2, H - 52,
|
||||
"Cifre din text: peste 100%, 10 candele, 0.96 / 0.74, cadranele 1-2.",
|
||||
size=13, fill=GRAY))
|
||||
svg.append(txt(W / 2, H - 32,
|
||||
"Sursa: 11_selectia_simbolului.md — fluxul din scanner.",
|
||||
size=13, fill=GRAY))
|
||||
svg.append("</svg>")
|
||||
|
||||
# ------------------------------------------------------------------ verificare
|
||||
for i, (name, detail) in enumerate(STAGES):
|
||||
w = WIDTHS[i]
|
||||
for s, size, bold in ((name, 15, True), (detail, 13, False)):
|
||||
ew = est_width(s, size, bold)
|
||||
assert ew <= w - 24, f"eticheta nu incape in banda {i}: {s!r} ({ew:.0f} > {w - 24})"
|
||||
assert ew <= W - 20, f"eticheta iese din panza: {s!r}"
|
||||
|
||||
for k in range(N_BANDS):
|
||||
y0 = TOP0 + k * STEP
|
||||
assert 0 < y0 and y0 + BAND_H < H, "banda iese din viewBox"
|
||||
|
||||
seen = []
|
||||
for l, r, y, s in labels:
|
||||
assert 0 <= l and r <= W, f"eticheta iese din panza pe orizontala: {s!r} ({l:.0f},{r:.0f})"
|
||||
assert 0 <= y <= H, f"eticheta iese din panza pe verticala: {s!r}"
|
||||
for l2, r2, y2, s2 in seen:
|
||||
if abs(y - y2) < 14 and not (r < l2 or r2 < l):
|
||||
raise AssertionError(f"posibile etichete suprapuse: {s!r} / {s2!r}")
|
||||
seen.append((l, r, y, s))
|
||||
|
||||
OUT.write_text("\n".join(svg) + "\n", encoding="utf-8")
|
||||
print(f"scris {OUT} ({W}x{H}), {N_BANDS} trepte, verificare OK")
|
||||
1
gen/selectia/funnel.spec.txt
Normal file
@@ -0,0 +1 @@
|
||||
funnel de selecție: univers 3.000-12.000 companii -> scanner (multe semnale) -> sectorul care domină -> doar suport/pivot după downtrend -> relative strength cadran 1-2 -> relative volume >100% -> stele (Trinity) -> shortlist de 5-10 simboluri
|
||||
36
gen/selectia/funnel.svg
Normal file
@@ -0,0 +1,36 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 700 764" width="700" height="764" role="img" aria-label="Funnel de selecție: scanner, sector, suport/pivot, RS 1-2, relative volume peste 100%, stele, listă scurtă">
|
||||
<rect x="0" y="0" width="700" height="764" fill="#ffffff"/>
|
||||
<text x="350.0" y="34" font-family="system-ui, sans-serif" font-size="19" font-weight="700" fill="#000000" text-anchor="middle">Funnel de selecție — fluxul din scanner</text>
|
||||
<text x="350.0" y="56" font-family="system-ui, sans-serif" font-size="13" font-weight="400" fill="#777777" text-anchor="middle">selecția se face înainte de semnalul tehnic</text>
|
||||
<rect x="40" y="84" width="620" height="58" rx="8" fill="#ffffff" stroke="#1a5fb4" stroke-width="1.5"/>
|
||||
<text x="350.0" y="109" font-family="system-ui, sans-serif" font-size="15" font-weight="700" fill="#000000" text-anchor="middle">Scanner — multe semnale</text>
|
||||
<text x="350.0" y="129" font-family="system-ui, sans-serif" font-size="13" font-weight="400" fill="#777777" text-anchor="middle">zeci sau sute de semnale</text>
|
||||
<path d="M 341 148 L 359 148 L 350 160 Z" fill="#1a5fb4"/>
|
||||
<rect x="67" y="166" width="566" height="58" rx="8" fill="#ffffff" stroke="#1a5fb4" stroke-width="1.5"/>
|
||||
<text x="350.0" y="191" font-family="system-ui, sans-serif" font-size="15" font-weight="700" fill="#000000" text-anchor="middle">Sectorul care domină</text>
|
||||
<text x="350.0" y="211" font-family="system-ui, sans-serif" font-size="13" font-weight="400" fill="#777777" text-anchor="middle">multe simboluri din același sector = rotație de bani</text>
|
||||
<path d="M 341 230 L 359 230 L 350 242 Z" fill="#1a5fb4"/>
|
||||
<rect x="94" y="248" width="512" height="58" rx="8" fill="#ffffff" stroke="#1a5fb4" stroke-width="1.5"/>
|
||||
<text x="350.0" y="273" font-family="system-ui, sans-serif" font-size="15" font-weight="700" fill="#000000" text-anchor="middle">Doar suport / pivot, după downtrend</text>
|
||||
<text x="350.0" y="293" font-family="system-ui, sans-serif" font-size="13" font-weight="400" fill="#777777" text-anchor="middle">suport monthly / quarterly / anual sau pivot</text>
|
||||
<path d="M 341 312 L 359 312 L 350 324 Z" fill="#1a5fb4"/>
|
||||
<rect x="121" y="330" width="458" height="58" rx="8" fill="#ffffff" stroke="#1a5fb4" stroke-width="1.5"/>
|
||||
<text x="350.0" y="355" font-family="system-ui, sans-serif" font-size="15" font-weight="700" fill="#000000" text-anchor="middle">Relative strength: cadran 1-2</text>
|
||||
<text x="350.0" y="375" font-family="system-ui, sans-serif" font-size="13" font-weight="400" fill="#777777" text-anchor="middle">doar cadranele 1 și 2, RS pozitiv</text>
|
||||
<path d="M 341 394 L 359 394 L 350 406 Z" fill="#1a5fb4"/>
|
||||
<rect x="148" y="412" width="404" height="58" rx="8" fill="#ffffff" stroke="#1a5fb4" stroke-width="1.5"/>
|
||||
<text x="350.0" y="437" font-family="system-ui, sans-serif" font-size="15" font-weight="700" fill="#000000" text-anchor="middle">Relative volume peste 100%</text>
|
||||
<text x="350.0" y="457" font-family="system-ui, sans-serif" font-size="13" font-weight="400" fill="#777777" text-anchor="middle">candela de volum / media ultimelor 10 candele</text>
|
||||
<path d="M 341 476 L 359 476 L 350 488 Z" fill="#1a5fb4"/>
|
||||
<rect x="175" y="494" width="350" height="58" rx="8" fill="#ffffff" stroke="#1a5fb4" stroke-width="1.5"/>
|
||||
<text x="350.0" y="519" font-family="system-ui, sans-serif" font-size="15" font-weight="700" fill="#000000" text-anchor="middle">Stele (Trinity)</text>
|
||||
<text x="350.0" y="539" font-family="system-ui, sans-serif" font-size="13" font-weight="400" fill="#777777" text-anchor="middle">3 stele: RS + power + volum, cadran 1</text>
|
||||
<path d="M 341 558 L 359 558 L 350 570 Z" fill="#1a5fb4"/>
|
||||
<rect x="202" y="576" width="296" height="58" rx="8" fill="#ffffff" stroke="#1a5fb4" stroke-width="1.5"/>
|
||||
<text x="350.0" y="601" font-family="system-ui, sans-serif" font-size="15" font-weight="700" fill="#000000" text-anchor="middle">Listă scurtă</text>
|
||||
<text x="350.0" y="621" font-family="system-ui, sans-serif" font-size="13" font-weight="400" fill="#777777" text-anchor="middle">taie lista la câteva idei</text>
|
||||
<text x="350.0" y="668" font-family="system-ui, sans-serif" font-size="13" font-weight="400" fill="#777777" text-anchor="middle">Sub 100% relative volume: 0.96 și 0.74 au fost respinse; el vrea peste 100%.</text>
|
||||
<text x="350.0" y="688" font-family="system-ui, sans-serif" font-size="13" font-weight="400" fill="#777777" text-anchor="middle">Neconfirmat: nu reiese din sesiuni dacă sub prag oprește orice intrare.</text>
|
||||
<text x="350.0" y="712" font-family="system-ui, sans-serif" font-size="13" font-weight="400" fill="#777777" text-anchor="middle">Cifre din text: peste 100%, 10 candele, 0.96 / 0.74, cadranele 1-2.</text>
|
||||
<text x="350.0" y="732" font-family="system-ui, sans-serif" font-size="13" font-weight="400" fill="#777777" text-anchor="middle">Sursa: 11_selectia_simbolului.md — fluxul din scanner.</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.8 KiB |
122
gen/selectia/pe_venit.py
Normal file
@@ -0,0 +1,122 @@
|
||||
"""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})")
|
||||
1
gen/selectia/pe_venit.spec.txt
Normal file
@@ -0,0 +1 @@
|
||||
matrice 2x2 forward P/E (mic/mare) x venit (creste/scade): PE mic + venit creste = candidat (TWLO); PE mic + venit scade = probleme (CRM); PE mare + venit creste = bun dar scump; PE mare + venit scade = eviti
|
||||
32
gen/selectia/pe_venit.svg
Normal file
@@ -0,0 +1,32 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 700 620" width="700" height="620" font-family="system-ui, -apple-system, Segoe UI, Roboto, sans-serif">
|
||||
<rect x="0" y="0" width="700" height="620" fill="#fff"/>
|
||||
<rect x="120" y="110" width="270" height="205" fill="#eaf1fb" stroke="#1a5fb4" stroke-width="2"/>
|
||||
<rect x="390" y="110" width="270" height="205" fill="#fff" stroke="#999" stroke-width="1"/>
|
||||
<rect x="120" y="315" width="270" height="205" fill="#fff" stroke="#999" stroke-width="1"/>
|
||||
<rect x="390" y="315" width="270" height="205" fill="#fff" stroke="#999" stroke-width="1"/>
|
||||
<text x="350.0" y="34.0" font-size="20" font-weight="bold" fill="#000" text-anchor="middle">Forward P/E × venit: candidat sau capcană</text>
|
||||
<text x="350.0" y="58.0" font-size="13" font-weight="normal" fill="#777" text-anchor="middle">Forward P/E mic contează doar dacă veniturile cresc.</text>
|
||||
<text x="255.0" y="96.0" font-size="15" font-weight="bold" fill="#000" text-anchor="middle">Forward P/E mic</text>
|
||||
<text x="525.0" y="96.0" font-size="15" font-weight="bold" fill="#000" text-anchor="middle">Forward P/E mare</text>
|
||||
<text x="110.0" y="217.5" font-size="14" font-weight="bold" fill="#000" text-anchor="end">venit crește</text>
|
||||
<text x="110.0" y="422.5" font-size="14" font-weight="bold" fill="#000" text-anchor="end">venit scade</text>
|
||||
<text x="30.0" y="315.0" font-size="14" font-weight="bold" fill="#000" text-anchor="middle" transform="rotate(-90 30 315)">venit</text>
|
||||
<text x="255.0" y="192.0" font-size="17" font-weight="bold" fill="#1a5fb4" text-anchor="middle">Candidat</text>
|
||||
<text x="255.0" y="220.0" font-size="15" font-weight="bold" fill="#000" text-anchor="middle">TWLO</text>
|
||||
<text x="255.0" y="244.0" font-size="14" font-weight="normal" fill="#000" text-anchor="middle">forward P/E ~23</text>
|
||||
<text x="255.0" y="264.0" font-size="14" font-weight="normal" fill="#000" text-anchor="middle">venit în creștere</text>
|
||||
<text x="525.0" y="192.0" font-size="17" font-weight="bold" fill="#000" text-anchor="middle">Bun, dar scump</text>
|
||||
<text x="525.0" y="226.0" font-size="14" font-weight="normal" fill="#000" text-anchor="middle">scor fundamental bun,</text>
|
||||
<text x="525.0" y="246.0" font-size="14" font-weight="normal" fill="#000" text-anchor="middle">dar „nu aș cumpăra aici sus”</text>
|
||||
<text x="255.0" y="398.0" font-size="17" font-weight="bold" fill="#000" text-anchor="middle">Probleme, nu chilipir</text>
|
||||
<text x="255.0" y="426.0" font-size="15" font-weight="bold" fill="#000" text-anchor="middle">CRM</text>
|
||||
<text x="255.0" y="450.0" font-size="14" font-weight="normal" fill="#000" text-anchor="middle">forward P/E ~15</text>
|
||||
<text x="255.0" y="470.0" font-size="14" font-weight="normal" fill="#000" text-anchor="middle">veniturile sunt în scădere</text>
|
||||
<text x="525.0" y="400.0" font-size="16" font-weight="bold" fill="#000" text-anchor="middle">Scump + venit în scădere</text>
|
||||
<text x="525.0" y="432.0" font-size="14" font-weight="normal" fill="#000" text-anchor="middle">business care se micșorează</text>
|
||||
<text x="525.0" y="452.0" font-size="14" font-weight="normal" fill="#000" text-anchor="middle">scepticism</text>
|
||||
<text x="110.0" y="540.0" font-size="13" font-weight="normal" fill="#777" text-anchor="start">Sursă: TWLO forward P/E ~23, venit în creștere</text>
|
||||
<text x="110.0" y="558.0" font-size="13" font-weight="normal" fill="#777" text-anchor="start">și CRM forward P/E ~15, venituri în scădere</text>
|
||||
<text x="110.0" y="576.0" font-size="13" font-weight="normal" fill="#777" text-anchor="start">(125_18022026.txt:20-35). P/E mare = scump,</text>
|
||||
<text x="110.0" y="594.0" font-size="13" font-weight="normal" fill="#777" text-anchor="start">„nu aș cumpăra aici sus” (44_22062026.txt:167).</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.7 KiB |
100
gen/selectia/scor.py
Normal file
@@ -0,0 +1,100 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Diagrama pentru summaries/08_ANALIZA_ZILNICA/11_selectia_simbolului.md (SVG:scor).
|
||||
|
||||
Bara ATM score 0-14, impartita in F-score 0-9 (max 9) si value-score 0-5
|
||||
(max 5). Marcate: 7/14 = "galben", 12-13 = "spuma", peste 7-8 = intra in
|
||||
analiza. Fara semafor rosu/verde (nu e in sursa).
|
||||
|
||||
Cifrele si etichetele vin din text:
|
||||
- ATM score = F-score (max 9) + value-score (max 5) = max 14 (.md:128)
|
||||
- 7/14 "galben" (.md:137, 81_27042026.txt:180)
|
||||
- 12-13 "spuma" (.md:136, 83_22042026.txt:197)
|
||||
- peste 7-8 "intra in analiza" (.md:135, 83_22042026.txt:195)
|
||||
|
||||
Ruleaza: .venv/bin/python gen/selectia/scor.py -> gen/selectia/scor.svg
|
||||
"""
|
||||
from pathlib import Path
|
||||
|
||||
OUT = Path(__file__).with_suffix(".svg")
|
||||
|
||||
W, H = 700, 360
|
||||
X0, X1 = 60, 640
|
||||
ATM_MAX, F_MAX, V_MAX = 14, 9, 5
|
||||
UNIT = (X1 - X0) / ATM_MAX
|
||||
|
||||
GRAY, BLUE, ORANGE, TXT, WHITE = "#777", "#1a5fb4", "#b06000", "#000000", "#ffffff"
|
||||
BAR_TOP, BAR_H = 190, 46
|
||||
BAR_BOT = BAR_TOP + BAR_H
|
||||
|
||||
|
||||
def x(v):
|
||||
return X0 + v * UNIT
|
||||
|
||||
|
||||
def text(px, py, s, size=14, fill=TXT, anchor="middle", weight="normal"):
|
||||
return (
|
||||
f'<text x="{px:.1f}" y="{py:.1f}" font-size="{size}" fill="{fill}" '
|
||||
f'text-anchor="{anchor}" font-weight="{weight}">{s}</text>'
|
||||
)
|
||||
|
||||
|
||||
def line(x1, y1, x2, y2, stroke=GRAY, width=1.5, dash=None):
|
||||
d = f' stroke-dasharray="{dash}"' if dash else ""
|
||||
return (
|
||||
f'<line x1="{x1:.1f}" y1="{y1:.1f}" x2="{x2:.1f}" y2="{y2:.1f}" '
|
||||
f'stroke="{stroke}" stroke-width="{width}"{d}/>'
|
||||
)
|
||||
|
||||
|
||||
def main():
|
||||
p = []
|
||||
p.append(f'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 {W} {H}" '
|
||||
f'width="{W}" height="{H}">')
|
||||
p.append(f'<rect x="0" y="0" width="{W}" height="{H}" fill="{WHITE}"/>')
|
||||
p.append('<g font-family="system-ui, sans-serif">')
|
||||
|
||||
p.append(text(X0, 38, "ATM score: scala 0-14", 18, TXT, "start", "700"))
|
||||
p.append(text(X0, 66, "ATM score = F-score (max 9) + value-score (max 5) = max 14",
|
||||
14, TXT, "start"))
|
||||
|
||||
p.append('<defs><clipPath id="barclip">'
|
||||
f'<rect x="{X0}" y="{BAR_TOP}" width="{X1 - X0}" height="{BAR_H}" rx="8"/>'
|
||||
'</clipPath></defs>')
|
||||
p.append(f'<rect x="{X0}" y="{BAR_TOP}" width="{X1 - X0}" height="{BAR_H}" '
|
||||
f'rx="8" fill="{BLUE}"/>')
|
||||
xf = x(F_MAX)
|
||||
p.append(f'<rect x="{xf:.1f}" y="{BAR_TOP}" width="{X1 - xf:.1f}" height="{BAR_H}" '
|
||||
f'fill="{ORANGE}" clip-path="url(#barclip)"/>')
|
||||
p.append(line(xf, BAR_TOP, xf, BAR_BOT, WHITE, 2))
|
||||
|
||||
p.append(text((X0 + xf) / 2, BAR_TOP + 29, "F-score 0-9", 15, WHITE, "middle", "600"))
|
||||
p.append(text((xf + X1) / 2, BAR_TOP + 29, "value-score 0-5", 15, WHITE, "middle", "600"))
|
||||
|
||||
for v in (0, 7, 9, 12, 13, 14):
|
||||
xx = x(v)
|
||||
p.append(line(xx, BAR_BOT, xx, BAR_BOT + 8))
|
||||
p.append(text(xx, BAR_BOT + 28, str(v), 14, TXT))
|
||||
|
||||
xg = x(7)
|
||||
p.append(line(xg, BAR_TOP - 30, xg, BAR_TOP, GRAY, 1.5, "3 3"))
|
||||
p.append(text(xg, BAR_TOP - 38, "7/14 = galben", 14, TXT))
|
||||
|
||||
xa, xb = x(12), x(13)
|
||||
yl = BAR_TOP - 35
|
||||
p.append(f'<path d="M {xa:.1f} {BAR_TOP} L {xa:.1f} {yl:.1f} L {xb:.1f} {yl:.1f} '
|
||||
f'L {xb:.1f} {BAR_TOP}" fill="none" stroke="{GRAY}" stroke-width="1.5"/>')
|
||||
p.append(text((xa + xb) / 2, yl - 8, "12-13 = spuma", 14, TXT))
|
||||
|
||||
xa, xb = x(7), x(8)
|
||||
yb = BAR_BOT + 60
|
||||
p.append(f'<path d="M {xa:.1f} {yb - 10:.1f} L {xa:.1f} {yb:.1f} L {xb:.1f} {yb:.1f} '
|
||||
f'L {xb:.1f} {yb - 10:.1f}" fill="none" stroke="{GRAY}" stroke-width="1.5"/>')
|
||||
p.append(text((xa + xb) / 2, yb + 22, "peste 7-8: intră în analiză", 14, TXT))
|
||||
|
||||
p.append('</g></svg>')
|
||||
OUT.write_text("\n".join(p) + "\n", encoding="utf-8")
|
||||
print("scris", OUT)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
1
gen/selectia/scor.spec.txt
Normal file
@@ -0,0 +1 @@
|
||||
bara ATM score 0-14, impartita in F-score 0-9 si value-score 0-5; marcate 7/14 = "galben" (81_27042026.txt:180), 12-13 = "spuma" (83_22042026.txt:197), peste 7-8 = intra in analiza (83_22042026.txt:195); fara semafor rosu/verde, care nu e in sursa
|
||||
30
gen/selectia/scor.svg
Normal file
@@ -0,0 +1,30 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 700 360" width="700" height="360">
|
||||
<rect x="0" y="0" width="700" height="360" fill="#ffffff"/>
|
||||
<g font-family="system-ui, sans-serif">
|
||||
<text x="60.0" y="38.0" font-size="18" fill="#000000" text-anchor="start" font-weight="700">ATM score: scala 0-14</text>
|
||||
<text x="60.0" y="66.0" font-size="14" fill="#000000" text-anchor="start" font-weight="normal">ATM score = F-score (max 9) + value-score (max 5) = max 14</text>
|
||||
<defs><clipPath id="barclip"><rect x="60" y="190" width="580" height="46" rx="8"/></clipPath></defs>
|
||||
<rect x="60" y="190" width="580" height="46" rx="8" fill="#1a5fb4"/>
|
||||
<rect x="432.9" y="190" width="207.1" height="46" fill="#b06000" clip-path="url(#barclip)"/>
|
||||
<line x1="432.9" y1="190.0" x2="432.9" y2="236.0" stroke="#ffffff" stroke-width="2"/>
|
||||
<text x="246.4" y="219.0" font-size="15" fill="#ffffff" text-anchor="middle" font-weight="600">F-score 0-9</text>
|
||||
<text x="536.4" y="219.0" font-size="15" fill="#ffffff" text-anchor="middle" font-weight="600">value-score 0-5</text>
|
||||
<line x1="60.0" y1="236.0" x2="60.0" y2="244.0" stroke="#777" stroke-width="1.5"/>
|
||||
<text x="60.0" y="264.0" font-size="14" fill="#000000" text-anchor="middle" font-weight="normal">0</text>
|
||||
<line x1="350.0" y1="236.0" x2="350.0" y2="244.0" stroke="#777" stroke-width="1.5"/>
|
||||
<text x="350.0" y="264.0" font-size="14" fill="#000000" text-anchor="middle" font-weight="normal">7</text>
|
||||
<line x1="432.9" y1="236.0" x2="432.9" y2="244.0" stroke="#777" stroke-width="1.5"/>
|
||||
<text x="432.9" y="264.0" font-size="14" fill="#000000" text-anchor="middle" font-weight="normal">9</text>
|
||||
<line x1="557.1" y1="236.0" x2="557.1" y2="244.0" stroke="#777" stroke-width="1.5"/>
|
||||
<text x="557.1" y="264.0" font-size="14" fill="#000000" text-anchor="middle" font-weight="normal">12</text>
|
||||
<line x1="598.6" y1="236.0" x2="598.6" y2="244.0" stroke="#777" stroke-width="1.5"/>
|
||||
<text x="598.6" y="264.0" font-size="14" fill="#000000" text-anchor="middle" font-weight="normal">13</text>
|
||||
<line x1="640.0" y1="236.0" x2="640.0" y2="244.0" stroke="#777" stroke-width="1.5"/>
|
||||
<text x="640.0" y="264.0" font-size="14" fill="#000000" text-anchor="middle" font-weight="normal">14</text>
|
||||
<line x1="350.0" y1="160.0" x2="350.0" y2="190.0" stroke="#777" stroke-width="1.5" stroke-dasharray="3 3"/>
|
||||
<text x="350.0" y="152.0" font-size="14" fill="#000000" text-anchor="middle" font-weight="normal">7/14 = galben</text>
|
||||
<path d="M 557.1 190 L 557.1 155.0 L 598.6 155.0 L 598.6 190" fill="none" stroke="#777" stroke-width="1.5"/>
|
||||
<text x="577.9" y="147.0" font-size="14" fill="#000000" text-anchor="middle" font-weight="normal">12-13 = spuma</text>
|
||||
<path d="M 350.0 286.0 L 350.0 296.0 L 391.4 296.0 L 391.4 286.0" fill="none" stroke="#777" stroke-width="1.5"/>
|
||||
<text x="370.7" y="318.0" font-size="14" fill="#000000" text-anchor="middle" font-weight="normal">peste 7-8: intră în analiză</text>
|
||||
</g></svg>
|
||||
|
After Width: | Height: | Size: 2.8 KiB |
232
gen/selectia/sectoare.py
Normal file
@@ -0,0 +1,232 @@
|
||||
"""Clasamentul celor 11 sectoare S&P: sortare după cadranul 1 + valoarea RS,
|
||||
cu rotația banilor între sectoare.
|
||||
|
||||
Diagramă conceptuală pentru `summaries/08_ANALIZA_ZILNICA/11_selectia_simbolului.md`
|
||||
(marcaj `<!--SVG:sectoare-->`), §4.1 „Sectorul care domină".
|
||||
|
||||
Ce susține textul din jurul marcajului:
|
||||
- „cele 11 sectoare S&P" (rândul 439) și „clasamentul sectoarelor | 11 rânduri,
|
||||
sortate săptămânal" (rândul 88);
|
||||
- sortarea după „cadranul 1 + valoarea relativ strength"
|
||||
(`54_08062026.txt:14`, citat la rândul 441-443);
|
||||
- top 4-ul din citatul sursă: XLV primul, apoi energia, financiarul, real
|
||||
estate (`54_08062026.txt:9-10`);
|
||||
- rotația sectorială: „banii migrează dintr-un sector în altul" (rândul 71),
|
||||
„se mută bani [...] rotație a banilor în acel sector"
|
||||
(`32_15072026.txt:43-49`), „uită-te ce predomină, în ce sectoare"
|
||||
(`148_11122025.txt:725-727`).
|
||||
|
||||
CE NU AM PUS, pentru că sursa nu-l susține:
|
||||
- valorile numerice de RS: textul/citatul dă doar regula de sortare
|
||||
(„cu cât este mai mare histograma de relativ strength și cadranul 1"),
|
||||
nu și cifre concrete. Desenul redă deci ordinea, nu valori inventate;
|
||||
- săgeți cu schimbări concrete „săptămâna trecută X, săptămâna asta Y":
|
||||
nu există asemenea date în sursă. Săgețile arată doar conceptul de rotație
|
||||
ciclică (semnale în același sector -> bani acolo -> sortare refăcută
|
||||
săptămânal);
|
||||
- ordinea celor 7 sectoare din afara top-ului: sursa enumeră doar primele 4.
|
||||
|
||||
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/sectoare.py
|
||||
Scrie `gen/selectia/sectoare.svg`.
|
||||
|
||||
Fără semafor: textul nu leagă cadranul 1 de verde sau restul de roșu. Accentele
|
||||
sunt albastru (informativ) și gri (secundar).
|
||||
"""
|
||||
import html
|
||||
from pathlib import Path
|
||||
|
||||
W, H = 700, 640
|
||||
|
||||
BLACK = "#000000"
|
||||
GRAY = "#777777"
|
||||
BLUE = "#1a5fb4"
|
||||
|
||||
texts = [] # (x, y, text, size, weight, fill, w_est, anchor)
|
||||
|
||||
|
||||
def add(x, y, s, size=13, weight="normal", fill=BLACK, anchor="middle"):
|
||||
factor = 0.62 if weight == "bold" else 0.56
|
||||
w = len(s) * size * factor
|
||||
texts.append((x, y, s, size, weight, fill, w, anchor))
|
||||
return texts[-1]
|
||||
|
||||
|
||||
def box(x, y, w, h, stroke=GRAY):
|
||||
return (
|
||||
f'<rect x="{x}" y="{y}" width="{w}" height="{h}" fill="#ffffff" '
|
||||
f'stroke="{stroke}" stroke-width="1.5"/>'
|
||||
)
|
||||
|
||||
|
||||
def arrow_h(x0, x1, y, color=BLUE):
|
||||
"""Săgeată orizontală de la x0 la x1, vârful la x1."""
|
||||
return (
|
||||
f'<line x1="{x0}" y1="{y}" x2="{x1 - 12}" y2="{y}" '
|
||||
f'stroke="{color}" stroke-width="2"/>'
|
||||
f'<path d="M {x1} {y} L {x1 - 14} {y - 7} L {x1 - 14} {y + 7} Z" '
|
||||
f'fill="{color}"/>'
|
||||
)
|
||||
|
||||
|
||||
parts = [
|
||||
f'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 {W} {H}" '
|
||||
f'width="{W}" height="{H}" role="img" '
|
||||
f'aria-label="Clasamentul celor 11 sectoare S&P sortate după cadranul 1 și '
|
||||
f'valoarea relative strength, cu rotația banilor între sectoare">',
|
||||
f'<rect x="0" y="0" width="{W}" height="{H}" fill="#ffffff"/>',
|
||||
]
|
||||
|
||||
# ---------------------------------------------------------------- titluri
|
||||
add(W / 2, 34, "Sectoare S&P 500 — cadranul 1 + valoarea RS", 20, "bold")
|
||||
add(W / 2, 58,
|
||||
"Sortare după cadranul 1 + valoarea RS; clasamentul se reface săptămânal.",
|
||||
13, "normal", GRAY)
|
||||
add(W / 2, 76,
|
||||
"Ordinea primelor 4 sectoare e dată în sursă; restul nu au ordine.",
|
||||
13, "normal", GRAY)
|
||||
|
||||
# ---------------------------------------------------------------- panoul listei
|
||||
P_X, P_Y, P_W, P_H = 40, 92, 620, 328
|
||||
parts.append(box(P_X, P_Y, P_W, P_H))
|
||||
|
||||
# primele 4 rânduri, sortate (ordinea din 54_08062026.txt:9-10)
|
||||
TOP4 = [
|
||||
("1", "Healthcare (XLV)", "cadran 1"),
|
||||
("2", "Energie (XLE)", "cadran 1"),
|
||||
("3", "Financiar (XLF)", "cadran 1"),
|
||||
("4", "Imobiliare (XLRE)", "locul 4"),
|
||||
]
|
||||
ROW_X, ROW_W, ROW_H = 56, 588, 34
|
||||
ROW_Y0, ROW_STEP = 110, 40
|
||||
for i, (num, name, badge) in enumerate(TOP4):
|
||||
ry = ROW_Y0 + i * ROW_STEP
|
||||
parts.append(box(ROW_X, ry, ROW_W, ROW_H, stroke="#cccccc"))
|
||||
add(ROW_X + 18, ry + 23, num, 16, "bold", BLUE)
|
||||
add(ROW_X + 44, ry + 23, name, 14, anchor="start")
|
||||
# badge: dreptunghi + text
|
||||
bx, bw = 540, 92
|
||||
parts.append(box(bx, ry + 5, bw, 24, stroke=BLUE))
|
||||
add(bx + bw / 2.0, ry + 22, badge, 13, "bold", BLUE)
|
||||
|
||||
# separator + restul de 7 sectoare
|
||||
parts.append(
|
||||
f'<line x1="{ROW_X}" y1="276" x2="{ROW_X + ROW_W}" y2="276" '
|
||||
f'stroke="{GRAY}" stroke-width="1"/>'
|
||||
)
|
||||
add(64, 296, "celelalte 7 sectoare S&P — ordinea nu reiese din sursă",
|
||||
13, "normal", GRAY, anchor="start")
|
||||
|
||||
REST = [
|
||||
"Tehnologie (XLK)",
|
||||
"Consum discreționar (XLY)",
|
||||
"Comunicații (XLC)",
|
||||
"Industrii (XLI)",
|
||||
"Consum de bază (XLP)",
|
||||
"Utilități (XLU)",
|
||||
"Materiale (XLB)",
|
||||
]
|
||||
COLS = [64, 380]
|
||||
for idx, name in enumerate(REST):
|
||||
col = idx // 4
|
||||
row = idx % 4
|
||||
add(COLS[col], 322 + row * 26, name, 13, "normal", BLACK, anchor="start")
|
||||
|
||||
# ---------------------------------------------------------------- rotația
|
||||
add(W / 2, 438,
|
||||
"rotația banilor: banii migrează dintr-un sector în altul",
|
||||
13, "bold", BLUE)
|
||||
|
||||
CYCLE = [
|
||||
(40, ["Multe semnale din", "același sector"]),
|
||||
(260, ["Banii se mută,", "este rotație"]),
|
||||
(480, ["Sortarea se reface", "săptămânal"]),
|
||||
]
|
||||
CB_Y, CB_W, CB_H = 448, 180, 58
|
||||
for cx, lines in CYCLE:
|
||||
parts.append(box(cx, CB_Y, CB_W, CB_H))
|
||||
for k, line in enumerate(lines):
|
||||
add(cx + CB_W / 2.0, CB_Y + 24 + k * 20, line, 13)
|
||||
|
||||
# săgeți 1->2, 2->3
|
||||
parts.append(arrow_h(222, 256, 477))
|
||||
parts.append(arrow_h(442, 476, 477))
|
||||
# săgeată de revenire 3->1 (pe sub cutii)
|
||||
ret = (
|
||||
f'<line x1="570" y1="506" x2="570" y2="536" stroke="{BLUE}" stroke-width="2"/>'
|
||||
f'<line x1="570" y1="536" x2="130" y2="536" stroke="{BLUE}" stroke-width="2"/>'
|
||||
f'<line x1="130" y1="536" x2="130" y2="512" stroke="{BLUE}" stroke-width="2"/>'
|
||||
f'<path d="M 130 506 L 123 520 L 137 520 Z" fill="{BLUE}"/>'
|
||||
)
|
||||
parts.append(ret)
|
||||
add(W / 2, 556, "săptămâna următoare: rotația continuă", 13, "normal", GRAY)
|
||||
|
||||
# ---------------------------------------------------------------- sursă
|
||||
add(W / 2, 586,
|
||||
"Sursa: 11_selectia_simbolului.md §4.1 „Sectorul care domină”",
|
||||
13, "normal", GRAY)
|
||||
add(W / 2, 604,
|
||||
"(54_08062026.txt:9-10,14; 32_15072026.txt:43-49; 148_11122025.txt:725-727).",
|
||||
13, "normal", GRAY)
|
||||
add(W / 2, 622,
|
||||
"Sursa dă doar ordinea, nu și valori numerice de RS — desenul nu inventează cifre.",
|
||||
13, "normal", GRAY)
|
||||
|
||||
# ---------------------------------------------------------------- emitere
|
||||
for x, y, s, size, weight, fill, _, anchor in texts:
|
||||
parts.append(
|
||||
f'<text x="{x:.1f}" y="{y:.1f}" font-size="{size}" '
|
||||
f'font-weight="{weight}" fill="{fill}" text-anchor="{anchor}">'
|
||||
f'{html.escape(s, quote=False)}</text>'
|
||||
)
|
||||
parts.append("</svg>")
|
||||
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, anchor in texts:
|
||||
assert size >= 13, f"font sub 13px: {s!r} ({size})"
|
||||
if anchor == "start":
|
||||
left, right = x, x + w
|
||||
elif anchor == "end":
|
||||
left, right = x - w, x
|
||||
else:
|
||||
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} ({left:.0f}..{right:.0f})"
|
||||
)
|
||||
assert 0 <= top and bottom <= H, (
|
||||
f"etichetă în afara panzei pe verticală: {s!r} ({top:.0f}..{bottom:.0f})"
|
||||
)
|
||||
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 ("Sectoare S&P 500", "cadranul 1", "valoarea RS",
|
||||
"Healthcare (XLV)", "Energie (XLE)", "Financiar (XLF)",
|
||||
"Imobiliare (XLRE)", "Tehnologie (XLK)",
|
||||
"Consum discreționar (XLY)", "Comunicații (XLC)",
|
||||
"Industrii (XLI)", "Consum de bază (XLP)",
|
||||
"Utilități (XLU)", "Materiale (XLB)",
|
||||
"rotația banilor", "săptămânal", "11 sectoare"):
|
||||
assert html.escape(required, quote=False) in svg, \
|
||||
f"lipsește din desen: {required!r}"
|
||||
|
||||
# cele 11 sectoare apar exact o dată fiecare
|
||||
for _, name, _ in TOP4:
|
||||
assert svg.count(name) == 1, f"sector duplicat sau lipsă: {name!r}"
|
||||
for name in REST:
|
||||
assert svg.count(name) == 1, f"sector duplicat sau lipsă: {name!r}"
|
||||
|
||||
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")
|
||||
1
gen/selectia/sectoare.spec.txt
Normal file
@@ -0,0 +1 @@
|
||||
cele 11 sectoare S&P sortate dupa cadranul 1 si valoarea RS, cu sageti care arata rotatia banilor dintr-o saptamana in alta
|
||||
53
gen/selectia/sectoare.svg
Normal file
@@ -0,0 +1,53 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 700 640" width="700" height="640" role="img" aria-label="Clasamentul celor 11 sectoare S&P sortate după cadranul 1 și valoarea relative strength, cu rotația banilor între sectoare">
|
||||
<rect x="0" y="0" width="700" height="640" fill="#ffffff"/>
|
||||
<rect x="40" y="92" width="620" height="328" fill="#ffffff" stroke="#777777" stroke-width="1.5"/>
|
||||
<rect x="56" y="110" width="588" height="34" fill="#ffffff" stroke="#cccccc" stroke-width="1.5"/>
|
||||
<rect x="540" y="115" width="92" height="24" fill="#ffffff" stroke="#1a5fb4" stroke-width="1.5"/>
|
||||
<rect x="56" y="150" width="588" height="34" fill="#ffffff" stroke="#cccccc" stroke-width="1.5"/>
|
||||
<rect x="540" y="155" width="92" height="24" fill="#ffffff" stroke="#1a5fb4" stroke-width="1.5"/>
|
||||
<rect x="56" y="190" width="588" height="34" fill="#ffffff" stroke="#cccccc" stroke-width="1.5"/>
|
||||
<rect x="540" y="195" width="92" height="24" fill="#ffffff" stroke="#1a5fb4" stroke-width="1.5"/>
|
||||
<rect x="56" y="230" width="588" height="34" fill="#ffffff" stroke="#cccccc" stroke-width="1.5"/>
|
||||
<rect x="540" y="235" width="92" height="24" fill="#ffffff" stroke="#1a5fb4" stroke-width="1.5"/>
|
||||
<line x1="56" y1="276" x2="644" y2="276" stroke="#777777" stroke-width="1"/>
|
||||
<rect x="40" y="448" width="180" height="58" fill="#ffffff" stroke="#777777" stroke-width="1.5"/>
|
||||
<rect x="260" y="448" width="180" height="58" fill="#ffffff" stroke="#777777" stroke-width="1.5"/>
|
||||
<rect x="480" y="448" width="180" height="58" fill="#ffffff" stroke="#777777" stroke-width="1.5"/>
|
||||
<line x1="222" y1="477" x2="244" y2="477" stroke="#1a5fb4" stroke-width="2"/><path d="M 256 477 L 242 470 L 242 484 Z" fill="#1a5fb4"/>
|
||||
<line x1="442" y1="477" x2="464" y2="477" stroke="#1a5fb4" stroke-width="2"/><path d="M 476 477 L 462 470 L 462 484 Z" fill="#1a5fb4"/>
|
||||
<line x1="570" y1="506" x2="570" y2="536" stroke="#1a5fb4" stroke-width="2"/><line x1="570" y1="536" x2="130" y2="536" stroke="#1a5fb4" stroke-width="2"/><line x1="130" y1="536" x2="130" y2="512" stroke="#1a5fb4" stroke-width="2"/><path d="M 130 506 L 123 520 L 137 520 Z" fill="#1a5fb4"/>
|
||||
<text x="350.0" y="34.0" font-size="20" font-weight="bold" fill="#000000" text-anchor="middle">Sectoare S&P 500 — cadranul 1 + valoarea RS</text>
|
||||
<text x="350.0" y="58.0" font-size="13" font-weight="normal" fill="#777777" text-anchor="middle">Sortare după cadranul 1 + valoarea RS; clasamentul se reface săptămânal.</text>
|
||||
<text x="350.0" y="76.0" font-size="13" font-weight="normal" fill="#777777" text-anchor="middle">Ordinea primelor 4 sectoare e dată în sursă; restul nu au ordine.</text>
|
||||
<text x="74.0" y="133.0" font-size="16" font-weight="bold" fill="#1a5fb4" text-anchor="middle">1</text>
|
||||
<text x="100.0" y="133.0" font-size="14" font-weight="normal" fill="#000000" text-anchor="start">Healthcare (XLV)</text>
|
||||
<text x="586.0" y="132.0" font-size="13" font-weight="bold" fill="#1a5fb4" text-anchor="middle">cadran 1</text>
|
||||
<text x="74.0" y="173.0" font-size="16" font-weight="bold" fill="#1a5fb4" text-anchor="middle">2</text>
|
||||
<text x="100.0" y="173.0" font-size="14" font-weight="normal" fill="#000000" text-anchor="start">Energie (XLE)</text>
|
||||
<text x="586.0" y="172.0" font-size="13" font-weight="bold" fill="#1a5fb4" text-anchor="middle">cadran 1</text>
|
||||
<text x="74.0" y="213.0" font-size="16" font-weight="bold" fill="#1a5fb4" text-anchor="middle">3</text>
|
||||
<text x="100.0" y="213.0" font-size="14" font-weight="normal" fill="#000000" text-anchor="start">Financiar (XLF)</text>
|
||||
<text x="586.0" y="212.0" font-size="13" font-weight="bold" fill="#1a5fb4" text-anchor="middle">cadran 1</text>
|
||||
<text x="74.0" y="253.0" font-size="16" font-weight="bold" fill="#1a5fb4" text-anchor="middle">4</text>
|
||||
<text x="100.0" y="253.0" font-size="14" font-weight="normal" fill="#000000" text-anchor="start">Imobiliare (XLRE)</text>
|
||||
<text x="586.0" y="252.0" font-size="13" font-weight="bold" fill="#1a5fb4" text-anchor="middle">locul 4</text>
|
||||
<text x="64.0" y="296.0" font-size="13" font-weight="normal" fill="#777777" text-anchor="start">celelalte 7 sectoare S&P — ordinea nu reiese din sursă</text>
|
||||
<text x="64.0" y="322.0" font-size="13" font-weight="normal" fill="#000000" text-anchor="start">Tehnologie (XLK)</text>
|
||||
<text x="64.0" y="348.0" font-size="13" font-weight="normal" fill="#000000" text-anchor="start">Consum discreționar (XLY)</text>
|
||||
<text x="64.0" y="374.0" font-size="13" font-weight="normal" fill="#000000" text-anchor="start">Comunicații (XLC)</text>
|
||||
<text x="64.0" y="400.0" font-size="13" font-weight="normal" fill="#000000" text-anchor="start">Industrii (XLI)</text>
|
||||
<text x="380.0" y="322.0" font-size="13" font-weight="normal" fill="#000000" text-anchor="start">Consum de bază (XLP)</text>
|
||||
<text x="380.0" y="348.0" font-size="13" font-weight="normal" fill="#000000" text-anchor="start">Utilități (XLU)</text>
|
||||
<text x="380.0" y="374.0" font-size="13" font-weight="normal" fill="#000000" text-anchor="start">Materiale (XLB)</text>
|
||||
<text x="350.0" y="438.0" font-size="13" font-weight="bold" fill="#1a5fb4" text-anchor="middle">rotația banilor: banii migrează dintr-un sector în altul</text>
|
||||
<text x="130.0" y="472.0" font-size="13" font-weight="normal" fill="#000000" text-anchor="middle">Multe semnale din</text>
|
||||
<text x="130.0" y="492.0" font-size="13" font-weight="normal" fill="#000000" text-anchor="middle">același sector</text>
|
||||
<text x="350.0" y="472.0" font-size="13" font-weight="normal" fill="#000000" text-anchor="middle">Banii se mută,</text>
|
||||
<text x="350.0" y="492.0" font-size="13" font-weight="normal" fill="#000000" text-anchor="middle">este rotație</text>
|
||||
<text x="570.0" y="472.0" font-size="13" font-weight="normal" fill="#000000" text-anchor="middle">Sortarea se reface</text>
|
||||
<text x="570.0" y="492.0" font-size="13" font-weight="normal" fill="#000000" text-anchor="middle">săptămânal</text>
|
||||
<text x="350.0" y="556.0" font-size="13" font-weight="normal" fill="#777777" text-anchor="middle">săptămâna următoare: rotația continuă</text>
|
||||
<text x="350.0" y="586.0" font-size="13" font-weight="normal" fill="#777777" text-anchor="middle">Sursa: 11_selectia_simbolului.md §4.1 „Sectorul care domină”</text>
|
||||
<text x="350.0" y="604.0" font-size="13" font-weight="normal" fill="#777777" text-anchor="middle">(54_08062026.txt:9-10,14; 32_15072026.txt:43-49; 148_11122025.txt:725-727).</text>
|
||||
<text x="350.0" y="622.0" font-size="13" font-weight="normal" fill="#777777" text-anchor="middle">Sursa dă doar ordinea, nu și valori numerice de RS — desenul nu inventează cifre.</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 6.4 KiB |
158
gen/selectia/trinity.py
Normal file
@@ -0,0 +1,158 @@
|
||||
"""Trinity — 3 stele vs. trei divergențe simultane.
|
||||
|
||||
Diagramă conceptuală pentru `summaries/08_ANALIZA_ZILNICA/11_selectia_simbolului.md`
|
||||
(marcaj `<!--SVG:trinity-->`), §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'<rect x="{x}" y="{y}" width="{w}" height="{h}" fill="#ffffff" '
|
||||
f'stroke="{GRAY}" stroke-width="1.5"/>'
|
||||
)
|
||||
|
||||
|
||||
parts = [
|
||||
f'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 {W} {H}" '
|
||||
f'width="{W}" height="{H}" role="img" '
|
||||
f'aria-label="Trinity: relative strength, power și volum toate în cadranul 1 '
|
||||
f'înseamnă 3 stele; trei divergențe simultane pe volum, relative strength și '
|
||||
f'power sunt doar setup de bottom, fără trigger">',
|
||||
f'<rect x="0" y="0" width="{W}" height="{H}" fill="#ffffff"/>',
|
||||
]
|
||||
|
||||
# ---------------------------------------------------------------- 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'<text x="{x:.1f}" y="{y:.1f}" font-size="{size}" '
|
||||
f'font-weight="{weight}" fill="{fill}" text-anchor="middle">'
|
||||
f'{html.escape(s, quote=False)}</text>'
|
||||
)
|
||||
parts.append("</svg>")
|
||||
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}</text>") == 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")
|
||||
1
gen/selectia/trinity.spec.txt
Normal file
@@ -0,0 +1 @@
|
||||
Trinity: trei coloane (relative strength, power, volum) toate in cadranul 1 => 3 stele; al doilea panou cu trei divergente simultane (volum, RS, power) => setup fara trigger inca
|
||||
36
gen/selectia/trinity.svg
Normal file
@@ -0,0 +1,36 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 700 640" width="700" height="640" role="img" aria-label="Trinity: relative strength, power și volum toate în cadranul 1 înseamnă 3 stele; trei divergențe simultane pe volum, relative strength și power sunt doar setup de bottom, fără trigger">
|
||||
<rect x="0" y="0" width="700" height="640" fill="#ffffff"/>
|
||||
<rect x="40" y="92" width="620" height="198" fill="#ffffff" stroke="#777777" stroke-width="1.5"/>
|
||||
<rect x="60.0" y="136" width="180" height="96" fill="#ffffff" stroke="#777777" stroke-width="1.5"/>
|
||||
<rect x="260.0" y="136" width="180" height="96" fill="#ffffff" stroke="#777777" stroke-width="1.5"/>
|
||||
<rect x="460.0" y="136" width="180" height="96" fill="#ffffff" stroke="#777777" stroke-width="1.5"/>
|
||||
<rect x="40" y="306" width="620" height="214" fill="#ffffff" stroke="#777777" stroke-width="1.5"/>
|
||||
<rect x="60.0" y="350" width="180" height="96" fill="#ffffff" stroke="#777777" stroke-width="1.5"/>
|
||||
<rect x="260.0" y="350" width="180" height="96" fill="#ffffff" stroke="#777777" stroke-width="1.5"/>
|
||||
<rect x="460.0" y="350" width="180" height="96" fill="#ffffff" stroke="#777777" stroke-width="1.5"/>
|
||||
<text x="350.0" y="32.0" font-size="20" font-weight="bold" fill="#000000" text-anchor="middle">Trinity — 3 stele</text>
|
||||
<text x="350.0" y="56.0" font-size="13" font-weight="normal" fill="#777777" text-anchor="middle">Relative strength, power și volum aliniate în aceeași direcție, în cadranul 1;</text>
|
||||
<text x="350.0" y="74.0" font-size="13" font-weight="normal" fill="#777777" text-anchor="middle">„3 stele cu plus” pe simbol.</text>
|
||||
<text x="350.0" y="118.0" font-size="16" font-weight="bold" fill="#1a5fb4" text-anchor="middle">DA — Trinity complet: 3 stele cu plus</text>
|
||||
<text x="150.0" y="162.0" font-size="15" font-weight="bold" fill="#000000" text-anchor="middle">Relative strength</text>
|
||||
<text x="150.0" y="192.0" font-size="14" font-weight="bold" fill="#1a5fb4" text-anchor="middle">cadranul 1</text>
|
||||
<text x="350.0" y="162.0" font-size="15" font-weight="bold" fill="#000000" text-anchor="middle">Power</text>
|
||||
<text x="350.0" y="192.0" font-size="14" font-weight="bold" fill="#1a5fb4" text-anchor="middle">cadranul 1</text>
|
||||
<text x="550.0" y="162.0" font-size="15" font-weight="bold" fill="#000000" text-anchor="middle">Volum</text>
|
||||
<text x="550.0" y="192.0" font-size="14" font-weight="bold" fill="#1a5fb4" text-anchor="middle">cadranul 1</text>
|
||||
<text x="350.0" y="258.0" font-size="14" font-weight="bold" fill="#000000" text-anchor="middle">toate trei: cadranul 1, aceeași direcție</text>
|
||||
<text x="350.0" y="280.0" font-size="15" font-weight="bold" fill="#1a5fb4" text-anchor="middle">→ 3 stele cu plus</text>
|
||||
<text x="350.0" y="332.0" font-size="16" font-weight="bold" fill="#000000" text-anchor="middle">NU — trei divergențe simultane: nu sunt 3 stele</text>
|
||||
<text x="150.0" y="376.0" font-size="15" font-weight="bold" fill="#000000" text-anchor="middle">Volum</text>
|
||||
<text x="150.0" y="406.0" font-size="14" font-weight="bold" fill="#777777" text-anchor="middle">divergență</text>
|
||||
<text x="350.0" y="376.0" font-size="15" font-weight="bold" fill="#000000" text-anchor="middle">Relative strength</text>
|
||||
<text x="350.0" y="406.0" font-size="14" font-weight="bold" fill="#777777" text-anchor="middle">divergență</text>
|
||||
<text x="550.0" y="376.0" font-size="15" font-weight="bold" fill="#000000" text-anchor="middle">Power</text>
|
||||
<text x="550.0" y="406.0" font-size="14" font-weight="bold" fill="#777777" text-anchor="middle">divergență</text>
|
||||
<text x="350.0" y="472.0" font-size="14" font-weight="normal" fill="#000000" text-anchor="middle">setup de bottom: prețul e la fund, dar încă nu s-a întors</text>
|
||||
<text x="350.0" y="496.0" font-size="14" font-weight="bold" fill="#777777" text-anchor="middle">aștepți trigger-ul — deocamdată doar pe listă</text>
|
||||
<text x="350.0" y="548.0" font-size="13" font-weight="normal" fill="#777777" text-anchor="middle">Trinity — NU și: două din trei aliniate, a treia în roșu;</text>
|
||||
<text x="350.0" y="566.0" font-size="13" font-weight="normal" fill="#c00" text-anchor="middle">„3 stele cu roșu” pe indice = short, nu cumperi contra.</text>
|
||||
<text x="350.0" y="596.0" font-size="13" font-weight="normal" fill="#777777" text-anchor="middle">Sursa: 11_selectia_simbolului.md §3 — 75_05052026.txt:101-106;</text>
|
||||
<text x="350.0" y="614.0" font-size="13" font-weight="normal" fill="#777777" text-anchor="middle">82_24042026.txt:334-335; 98_30032026.txt:395-397.</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.4 KiB |