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
This commit is contained in:
Claude Agent
2026-10-02 06:12:32 +00:00
parent d82e1d5ce5
commit 84f933bcfa
60 changed files with 505 additions and 444 deletions

View File

@@ -409,6 +409,35 @@ class Chart:
return "\n ".join(out).replace("\n </svg>", "\n</svg>")
# --------------------------------------------------------- citit din randare
# Un generator care desenează peste graficul randat are nevoie de deplasarea
# pe care `render()` a aplicat-o (face loc titlului și săgeților, apoi
# restaurează `top`), și o citește din prima linie de Level.
#
# Nu o căuta cu `x1="24"`. Formatarea e `:.1f` de când Level are `i0` ("24.0"),
# iar cu `i0` linia nici nu mai pornește din margine. Unsprezece generatoare
# aveau fiecare copia lui `re.search(r'<line x1="24" ...')`: cele fără panou de
# volum au început să crape, iar cele cu volum au prins TĂCUT linia de zero a
# volumului și au desenat cu offsetul ei. De aia căutarea stă aici, într-un
# singur loc, și se agață de semnătura liniei de Level, nu de x-ul ei.
_LINIE_NIVEL = re.compile(
r'<line x1="(?:-?[\d.]+)" y1="(-?[\d.]+)"[^/]*?'
r'stroke-width="1\.5" stroke-dasharray=')
def y_niveluri(svg: str) -> list[float]:
"""Y-urile liniilor de Level dintr-un SVG randat, în ordinea desenării."""
return [float(m.group(1)) for m in _LINIE_NIVEL.finditer(svg)]
def offset_render(svg: str, ch: "Chart") -> float:
"""Câți px a coborât `render()` desenul față de ce întoarce acum `ch._y()`."""
ys = y_niveluri(svg)
assert ys, "SVG fără linii de Level: nu pot citi offsetul randării"
assert ch.levels, "Chart fără levels: nu am față de ce măsura offsetul"
return ys[0] - ch._y(ch.levels[0].price)
def walk(start: float, moves: list[tuple[float, float, float]],
vols: list[float] | None = None) -> list[Candle]:
"""Construiește o serie continuă de candele dintr-un preț de start.
@@ -526,6 +555,21 @@ def selftest():
"zona trebuie desenată înaintea candelelor"
assert svg3.count("<polygon") == 3 and "pe close</text>" in svg3
# offset_render se agață de linia de Level, nu de x-ul ei: și cu `i0`
# (linia nu mai pornește din margine), și cu panou de volum (care are și el
# o linie de la x=24, și pe care varianta veche o prindea în loc)
cdv = [Candle(c.o, c.h, c.l, c.c, vol=100) for c in ser]
ch4 = Chart(candles=cdv, scale=40, gap=14, body_w=9, width=200, vol_h=40,
levels=[Level(ser[0].l, "nivel", i0=1), Level(ser[0].h, "sus")],
title="T")
svg4 = ch4.render()
ys = y_niveluri(svg4)
assert len(ys) == 2, f"am găsit {len(ys)} linii de Level, aștept 2"
assert abs(offset_render(svg4, ch4) - 22) < 1e-6, \
f"offsetul titlului nu e cel așteptat: {offset_render(svg4, ch4)}"
assert all(abs(y - ch4._y(lv.price) - offset_render(svg4, ch4)) < 1e-6
for y, lv in zip(ys, ch4.levels)), "y-urile nu sunt ale nivelurilor"
print("selftest ok: 2R la exact 2R, serie fără goluri, volum proporțional, zone+săgeți")