Files
atm-curs-dl/gen/strategii/selectia_proximitate.py
Claude Agent f1033a916c selectia: diagramele refacute dupa sablon (textul urmeaza)
Agentul a atins pragul de context dupa diagrame; capitolul 04_selectia
e inca in formatul vechi si se reia separat.

Ce s-a schimbat: ansamblul nu mai arata ULTA mergand la TP2 — ULTA pica
chiar filtrul de selectie (RVOL 0.61 si 5.01 % de suport), deci nu putea
fi exemplul-vitrina; acum arata doar ce citeste selectia, cu granita
"aici se termina selectia" marcata. Funnel-ul trece de la 8 trepte la 6,
aliniat cu pasii nivelului 2, si fiecare treapta spune de ce. Cadranele
primesc mini-histograme RS, ca sa se recunoasca dintr-un desen, nu
dintr-o propozitie. Trimiterile fisier:linie sunt scoase de pe desene.

Noi: univers (pragurile de pret si volum), sectoare (dominantul din cele
11), proximitate (banda de 3 % peste suportul monthly, cu observatia ca
nu e pragul de 1-2 % din Buy Doji — masoara altceva).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ThN7ixZrowca8aTGrK1MUJ
2026-09-20 12:43:12 +00:00

172 lines
7.3 KiB
Python

"""Pasul 4 — cât de aproape de suport e prețul (TIP: PRET).
Pentru `summaries/17_STRATEGII/04_selectia.md`, pasul 4. Un singur grafic
daily, cu suportul monthly și cu banda de 3 % de deasupra lui, desenată ca
dreptunghi: înăuntru simbolul e „la nivel", în afară e „în aer". Două momente
din aceeași serie, unul în fiecare parte, ca să se vadă diferența pe care
ochiul n-o prinde.
Pragul de 3 % (cu 4 % „cam departe") e al cursului. Seria e construită, la
scară; toate procentele din etichete se calculează din ea, cu assert.
Atenție la diferența față de capitolul Buy Doji: acolo pragul de 1-2 % se
măsoară pentru low-ul doji-ului. Aici măsori închiderea simbolului față de
suport, ca filtru de selecție — două măsurători diferite, praguri diferite.
Rulează:
python3 gen/strategii/selectia_proximitate.py
"""
import re
import sys
from pathlib import Path
sys.path.insert(0, "/workspace/atm-curs-dl")
from svg_candles import Chart, Level, Arrow, Zone, MA, from_closes # noqa: E402
OUT = Path(__file__).resolve().parent / "selectia_proximitate.svg"
GRAY, RED, GREEN, BLUE, ORANGE = "#777", "#c00", "#188a3e", "#1a5fb4", "#b06000"
SUPORT = 48.00
PRAG = 0.03 # 3 % — pragul cursului
BANDA_SUS = SUPORT * (1 + PRAG) # 49.44
DEPARTE, APROAPE = 10, 30 # barele comentate
CLOSES = [
56.00, 55.60, 55.10, 54.70, 54.20, # 0-4
53.80, 53.30, 52.90, 52.40, 52.00, # 5-9
51.60, 51.20, 50.90, 50.50, 50.20, # 10-14
49.90, 50.30, 50.00, 49.70, 49.40, # 15-19
49.10, 49.50, 49.20, 48.90, 49.20, # 20-24
48.90, 48.70, 49.00, 48.70, 48.50, # 25-29
48.80, 49.10, # 30-31
]
assert len(CLOSES) == 32, len(CLOSES)
WICKS = [(0.22, 0.24)] * 32
WICKS[0] = (0.30, 0.30)
WICKS[29] = (0.20, 0.50) # low exact pe suport: 48.50 - 0.50 = 48.00
assert len(WICKS) == 32
VOLS = [46, 44, 48, 45, 43, 47, 44, 42, 45, 41,
40, 42, 39, 41, 38, 44, 36, 38, 35, 37,
34, 40, 33, 36, 32, 35, 31, 34, 33, 48,
44, 46]
assert len(VOLS) == 32
cds = from_closes(CLOSES, wicks=WICKS, vols=VOLS, start=56.30)
# ---------------------------------------------------------------- assert-uri de conținut
for a, b in zip(cds, cds[1:]):
assert abs(a.c - b.o) < 1e-9, "serie cu gol artificial între bare"
assert abs(min(c.l for c in cds) - SUPORT) < 1e-9, "suportul nu e atins de niciun low"
pct_departe = (cds[DEPARTE].c - SUPORT) / SUPORT * 100
pct_aproape = (cds[APROAPE].c - SUPORT) / SUPORT * 100
assert pct_departe > PRAG * 100, "bara «departe» trebuie să fie peste prag"
assert pct_aproape < PRAG * 100, "bara «aproape» trebuie să fie sub prag"
assert abs(pct_departe - 7.50) < 0.01, pct_departe
assert abs(pct_aproape - 1.67) < 0.01, pct_aproape
# banda acoperă exact ce trebuie: bara «aproape» înăuntru, bara «departe» afară
assert SUPORT <= cds[APROAPE].c <= BANDA_SUS, "bara «aproape» nu e în bandă"
assert cds[DEPARTE].c > BANDA_SUS, "bara «departe» nu e în afara benzii"
# numărul din textul zonei == numărul de bare pe care le acoperă
in_banda = [i for i, c in enumerate(cds) if SUPORT <= c.c <= BANDA_SUS]
assert in_banda and in_banda[0] == 19, in_banda[:3]
levels = [
Level(SUPORT, f"suport monthly {SUPORT:.2f}", BLUE, label_at="left"),
Level(BANDA_SUS, f"suport + 3 % = {BANDA_SUS:.2f}", BLUE, dash="5 3"),
]
zones = [
# eticheta benzii o pun manual, după randare: banda ține toată lățimea, iar
# eticheta automată ar urca deasupra primei candele, peste titlu
Zone(0, 31, "", BLUE, p_top=BANDA_SUS, p_bot=SUPORT),
]
arrows = [
Arrow(DEPARTE, f"close {cds[DEPARTE].c:.2f} = {pct_departe:.2f} % peste suport\n"
f"prea departe: până acolo prețul mai are de căzut,\n"
f"deci nivelul nu-l poate opri acum",
where="above", color=RED, dx=-60, dy=-4),
Arrow(APROAPE, f"close {cds[APROAPE].c:.2f} = {pct_aproape:.2f} % peste suport\n"
f"la nivel: dacă mai scade puțin, dă de ordinele\n"
f"de cumpărare care au mai întors prețul de acolo",
where="below", color=GREEN, dx=-320, dy=42),
]
caption = ("Pragul de 3 % (cu 4 % „cam departe”) e al cursului; seria și prețurile sunt construite, "
"la scară, iar procentele sunt calculate din serie. În capitolul Buy Doji, pragul de "
"1-2 % măsoară altceva: low-ul doji-ului față de nivel, nu închiderea simbolului.")
ch = Chart(
candles=cds, levels=levels, zones=zones, arrows=arrows,
mas=[MA(20, ORANGE, dash="4 3", text="SMA-20")],
scale=26.0, gap=15, body_w=9, left=44, width=660, vol_h=60,
vol_label="volum",
title="Cât de aproape de suport e prețul — daily, exemplu construit",
caption=caption,
)
svg = ch.render()
# eticheta benzii, pusă în interiorul ei, în stânga
m_band = re.findall(r'<line x1="24" y1="([\d.]+)"', svg)
y_banda_sus, y_suport = float(m_band[1]), float(m_band[0])
assert y_suport > y_banda_sus, "banda e desenată invers"
svg = svg.replace("</svg>",
f'<text x="230" y="{(y_banda_sus + y_suport) / 2 + 4:.1f}" fill="{BLUE}" '
f'style="font-weight:bold">banda de 3 %: înăuntru simbolul e „la nivel”</text>'
"</svg>")
# „SMA-20" cade peste eticheta nivelului de bandă: o ridic pe rândul liber
svg, n = re.subn(r'(<text x="\d+" y=")([\d.]+)(" fill="#b06000">SMA-20</text>)',
lambda mm: f'{mm.group(1)}{float(mm.group(2)) - 16:.1f}{mm.group(3)}', svg)
assert n == 1, f"eticheta SMA-20: {n} potriviri"
# liniile nu au voie să cadă pe marginea cadrului
m_sup = re.findall(r'<line x1="24" y1="([\d.]+)"', svg)
assert m_sup, "nu găsesc liniile de nivel"
y_sup = float(m_sup[0])
m_vb = re.search(r'viewBox="0 0 (\d+) (\d+)"', svg)
assert m_vb
h_svg = int(m_vb.group(2))
assert y_sup > 0.06 * h_svg, "linia suportului e lipită de marginea de sus"
# nicio etichetă nu numește un preț care nu e desenat
desenate = {f"{SUPORT:.2f}", f"{BANDA_SUS:.2f}", f"{cds[DEPARTE].c:.2f}", f"{cds[APROAPE].c:.2f}",
f"{pct_departe:.2f}", f"{pct_aproape:.2f}"}
for t in re.findall(r"<text[^>]*>([^<]*)</text>", svg):
for nr in re.findall(r"\d+\.\d\d", t):
assert nr in desenate, f"eticheta numește {nr}, care nu e desenat: {t!r}"
def _wrap(text: str, max_chars: int) -> list[str]:
lines, cur = [], ""
for w in text.split():
if cur and len(cur) + 1 + len(w) > max_chars:
lines.append(cur)
cur = w
else:
cur = f"{cur} {w}".strip()
if cur:
lines.append(cur)
return lines
cap_lines = _wrap(caption, 96)
m = re.search(r'<text x="24" y="(\d+)" fill="#555">' + re.escape(caption) + r"</text>", svg)
assert m, "caption-ul nu a fost găsit în SVG"
y0 = int(m.group(1))
wrapped = "\n ".join(f'<text x="24" y="{y0 + 17 * i}" fill="#555">{ln}</text>'
for i, ln in enumerate(cap_lines))
svg = svg[:m.start()] + wrapped + svg[m.end():]
svg = svg.replace(m_vb.group(0),
f'viewBox="0 0 {m_vb.group(1)} {h_svg + 17 * (len(cap_lines) - 1)}"', 1)
assert "<svg" in svg and "\n\n" not in svg, "linie goală în SVG"
OUT.write_text(svg)
print(f"scris {OUT.name}: 32 candele, suport {SUPORT:.2f}, banda până la {BANDA_SUS:.2f}, "
f"bara {DEPARTE} la {pct_departe:.2f} % (afară), bara {APROAPE} la {pct_aproape:.2f} % (înăuntru)")