Files
atm-curs-dl/gen/strategii/intrarea_cazul_lui.py
Claude Agent 84f933bcfa svg_candles: cautarea nivelurilor, scoasa in functie comuna; 11 generatoare reparate
Unsprezece generatoare nu mai rulau deloc la HEAD: cautau <line x1="24", iar
svg_candles scrie x1="24.0" de cand are Level.i0. Un generator mort inseamna
ca diagrama nu mai poate fi regenerata, si nimeni n-ar fi aflat pana cand ar
fi incercat cineva, peste luni.

Cele 11 copii ale aceluiasi regex au disparut: y_niveluri() si offset_render()
se agata acum de semnatura liniei de Level (stroke-width + dasharray), nu de
x-ul ei. Caz nou in selftest, cu Level(i0=) si panou de volum - combinatia la
care varianta veche fie crapa, fie prindea tacut linia de zero a volumului.

41 de SVG-uri regenerate, toate identice cu cele comise dupa normalizarea
x1=24 -> x1=24.0, deci niciun desen n-a sarit. Verificarea asta a prins o
regresie reala: la buy_doji_transe substitutia lasase scaderea veche pe randul
urmator, offsetul se scadea de doua ori si desenul urca 92 px.

Inca doua generatoare moarte din aceeasi familie, gasite pe drum
(intrarea_cazul_lui, intrarea_doua_variante): desenau peste grafic citind
ch._y() fara offset. Nu crapau zgomotos - produceau suprapuneri la 22 px.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ThN7ixZrowca8aTGrK1MUJ
2026-10-02 06:12:32 +00:00

167 lines
7.6 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""Diagrama din `gen/strategii/intrarea_cazul_lui.spec.txt` (TIP: PRET).
Trade-ul userului din A3 §2.1, reconstruit din descriere: downtrend pe 15 min
până la suportul weekly 100.00, buy momentum pe o verde mare, intrare la
close 102.10, imediat o roșie mare care acoperă 83% din corpul verzii, apoi
trendul. Ieșirea userului la 102.60, înainte de TP1; vârful 105.20 după.
Pe același grafic, nivelul 1/2 = 101.20 unde s-ar fi umplut varianta B.
Toate cifrele sunt CONSTRUITE (fără ticker); mediile SMA 5 / SMA 10 sunt
ilustrative — lecția 10 nu dă lungimile reale.
Rulează:
python3 gen/strategii/intrarea_cazul_lui.py
Scrie lângă el `intrarea_cazul_lui.svg`.
"""
import re
import sys
from pathlib import Path
sys.path.insert(0, "/workspace/atm-curs-dl")
from svg_candles import (Chart, Level, Mark, MA, from_closes, # noqa: E402
offset_render)
OUT = Path(__file__).resolve().parent / "intrarea_cazul_lui.svg"
GRAY, RED, GREEN, BLUE, ORANGE = "#777", "#c00", "#188a3e", "#1a5fb4", "#b06000"
MA5_C, MA10_C = "#8a3ea8", "#2a8a8a"
SUPORT, SL, ENTRY, HALF, TP1 = 100.00, 99.90, 102.10, 101.20, 104.30
CLOSES = [
103.80, 103.50, 103.60, 103.20, 102.90, 103.00, 102.60, 102.30, 101.90, 101.40, 101.00, # 0-10
100.70, 100.45, 100.30, # 11-13 la suport
102.10, # 14 buy momentum
100.60, # 15 roșie mare
101.10, 101.60, 101.90, # 16-18
102.60, # 19 ieșirea userului
102.90, 102.70, 103.20, 103.60, 103.40, 103.90, 104.20, 104.10, 104.60,
104.90, 104.70, 105.00, 105.20, 105.00, # 20-33
]
assert len(CLOSES) == 34, len(CLOSES)
# (sus, jos); excepțiile fixează exact low-urile/high-urile din specificație
WICKS = [(0.12, 0.10)] * 34
WICKS[10] = (0.10, 0.20) # low 100.80
WICKS[11] = (0.10, 0.30) # low 100.40
WICKS[12] = (0.10, 0.55) # low 99.90, wick sub 100.00
WICKS[13] = (0.10, 0.20) # low 100.10
WICKS[14] = (0.15, 0.10) # h 102.25, l 100.20
WICKS[15] = (0.10, 0.10) # h 102.20, l 100.50
# spec zice „candela 31 (close 105.20)", dar în lista lui 105.20 e la indexul 32;
# vârful e pus pe bara 32, unde e de fapt close-ul 105.20.
WICKS[31] = (0.10, 0.10) # h 105.10 < 105.20
WICKS[32] = (0.00, 0.10) # h 105.20 = maximul
WICKS[33] = (0.00, 0.10) # open 105.20 → high nu poate fi sub 105.20
cds = from_closes(CLOSES, wicks=WICKS, start=104.00)
R = lambda x: round(x, 2) # noqa: E731
# ── assert-urile din specificație ──────────────────────────────────────────
# 1. open == close anterior
for a, b in zip(cds, cds[1:]):
assert a.c == b.o, (a.c, b.o)
g, r = cds[14], cds[15]
assert (R(g.o), R(g.c), R(g.h), R(g.l)) == (100.30, 102.10, 102.25, 100.20), g
assert (R(r.o), R(r.c), R(r.h), R(r.l)) == (102.10, 100.60, 102.20, 100.50), r
assert (R(cds[10].l), R(cds[11].l), R(cds[12].l), R(cds[13].l)) == (100.80, 100.40, 99.90, 100.10)
# 2. SL = low 4 candele; nici verdea, nici roșia nu îl ating
assert R(min(c.l for c in cds[10:14])) == SL
assert g.l > SL and r.l > SL
# 3. culori
assert g.c > g.o and r.c < r.o
# 4. roșia acoperă 83% din corpul verzii
acop = (r.o - r.c) / (g.c - g.o)
assert abs(acop - 0.83) <= 0.01, acop
# 5. 1/2 din corp, atins de roșie
half = (g.o + g.c) / 2
assert R(half) == HALF and r.l <= half, (half, r.l)
# 6. R-urile
risc = ENTRY - SL
assert R(risc) == 2.20 and R(ENTRY + risc) == TP1
flot = (r.c - ENTRY) / risc
iesire = (cds[19].c - ENTRY) / risc
varf = (105.20 - ENTRY) / risc
assert (R(flot), R(iesire), R(varf)) == (-0.68, 0.23, 1.41), (flot, iesire, varf)
# 7. mediile mobile
sma = lambda n, i: sum(CLOSES[i + 1 - n:i + 1]) / n # noqa: E731
assert g.c > sma(5, 14) and g.c > sma(10, 14)
assert r.c < sma(5, 15) and r.c < sma(10, 15)
assert (R(sma(5, 15)), round(sma(10, 15), 3)) == (100.83, 101.335)
# 8. ieșirea înainte de TP1; vârful
assert cds[19].c < TP1 and all(c.h < TP1 for c in cds[15:20])
assert R(max(c.h for c in cds)) == 105.20 and R(cds[32].h) == 105.20 == cds[32].c
assert all(c.h < 105.20 - 1e-9 for c in cds[:32])
# varianta B (doar în legendă)
assert R(g.c - (g.c - g.o) / 3) == 101.50
assert R(HALF - SL) == 1.30 and R(HALF + (HALF - SL)) == 102.50
levels = [
Level(TP1, "TP1 104.30 (1:1)", BLUE, label_at="left"),
Level(ENTRY, "intrare la close 102.10", GREEN),
Level(HALF, "1/2 = 101.20 (B s-ar fi umplut aici)", ORANGE, dash="2 3"),
Level(SUPORT, "suport weekly 100.00", GRAY, label_at="left"),
Level(SL, "", RED), # eticheta SL sub linie, mai jos (0.10 sub suport)
Level(99.50, "", "none", dash=""), # rezervă loc sub SL pentru etichete
]
marks = [
Mark(14, "▲ buy momentum", GREEN, "above", dy=-20), # peste SMA 10
Mark(32, "vârf 105.20 = +1.41R", GRAY, "above"),
]
ch = Chart(
candles=cds, levels=levels, marks=marks,
mas=[MA(5, MA5_C), MA(10, MA10_C, dash="5 3")],
scale=60, gap=16, body_w=10, left=44, width=640,
title="Exemplu construit (fără ticker) — 15 min, suport weekly",
)
svg = ch.render()
# barele 15 și 19 au candele lipite în stânga/dreapta: un Mark centrat le-ar
# tăia, așa că eticheta lor stă în golul de lângă candelă (roșia: ▲ chiar sub
# low-urile barelor 16+, peste suport; ieșirea: deasupra barelor 14-19,
# terminată în bara 19, cu linie de legătură).
# render() ridică `top` pentru titlu și îl pune la loc: ch._y() de aici e cu
# OFF mai sus decât desenul. Fără asta, legenda de SMA cădea peste titlu și
# „SL 99.90" peste „suport weekly 100.00" — se vedea în SVG-ul regenerat.
OFF = offset_render(svg, ch)
def Y(p):
return ch._y(p) + OFF
y19 = Y(103.30)
extra = [
f'<text x="{ch._cx(15) - 5:.0f}" y="{Y(100.20):.1f}" fill="{RED}" '
f'style="font-weight:bold">▲ roșie mare: −0.68R flotant</text>',
f'<text x="{ch._cx(19) + 4:.0f}" y="{y19:.1f}" fill="{ORANGE}" text-anchor="end" '
f'style="font-weight:bold">ieși aici: 102.60 = +0.23R</text>',
f'<line x1="{ch._cx(19):.1f}" y1="{y19 + 4:.1f}" x2="{ch._cx(19):.1f}" '
f'y2="{Y(cds[19].h) - 3:.1f}" stroke="{ORANGE}" stroke-width="1"/>',
f'<text x="30" y="{Y(SL) + 15:.1f}" fill="{RED}">SL 99.90 (low 4 candele)</text>',
f'<text x="24" y="{ch.top + OFF + 12:.1f}" fill="#555">'
f'<tspan fill="{MA5_C}">── SMA 5</tspan> / <tspan fill="{MA10_C}">- - SMA 10</tspan> ilustrative</text>',
]
legend = [
"Exemplu construit, fără ticker. Prețurile, mediile mobile și ieșirea sunt",
"ilustrative; secvența (verde mare → roșie mare → trend) e cea descrisă de user.",
"Varianta B (ordin la 1/2 = 101.20, risc 1.30, TP1 102.50) e aritmetică pe",
"aceleași cifre, nu un trade din curs. 1/3 din corp = 101.50.",
"Close-ul roșiei (100.60): sub ambele MA (SMA 5 = 100.83, SMA 10 = 101.34).",
]
H0 = float(re.findall(r'viewBox="0 0 \d+ ([0-9.]+)"', svg)[0])
extra += [f'<text x="24" y="{H0 + 10 + 17 * i:.1f}" fill="#555">{t}</text>'
for i, t in enumerate(legend)]
new_h = H0 + 10 + 17 * (len(legend) - 1) + 12
svg = re.sub(r'(viewBox="0 0 \d+ )[0-9.]+(")',
lambda m: m.group(1) + f"{new_h:.0f}" + m.group(2), svg, count=1)
svg = svg.replace("\n</svg>", "\n " + "\n ".join(extra) + "\n</svg>")
svg = re.sub(r"\n <text [^>]*></text>", "", svg) # etichete goale (SL, rezervă)
svg = re.sub(r"\n <line [^>]*stroke=\"none\"[^>]*/>", "", svg)
assert "\n\n" not in svg
OUT.write_text(svg, encoding="utf-8")
print(f"scris {OUT.name}: acoperire {acop:.3f}, flotant {flot:+.2f}R, "
f"ieșire {iesire:+.2f}R, vârf {varf:+.2f}R, SMA5/10@15 = "
f"{sma(5, 15):.2f}/{sma(10, 15):.3f}")