MODUL 5: 24 de reparatii pe cele doua strategii, dupa verificare

Opt erau blocante, adica un cititor ar fi executat gresit pe cont:

- 56% era legat de o intrare la 133 care da de fapt 35,3%; cifra se propaga in
  dimensionarea pozitiei. Acum spune ca 56% e rostit separat si ca raportul nu
  se verifica.
- "2/3 taie jumatate din risc" era fals (29,3% vs 35,3%).
- definitia lui buy doji se contrazicea cu regula de 4 luni din acelasi
  document.
- diagrama de trail arata stopul deasupra pretului: liniile traversau tot
  graficul, deci in stanga stateau peste candele. Acum fiecare porneste din
  swing low-ul care o declanseaza, cu cele patru swing low-uri marcate.
- linia gatului era data ca "media celor doua varfuri", ceea ce nu reproduce
  63.00 pe propriul desen; e din interiorul gatului.
- singura regula mecanica de "nu intra" nu era desenata cu mecanismul ei:
  panoul de volum avea bare egale de la o baza, in loc de histograma cu semn.
- "SL minim 54.17" se citea invers decat intelege textul: e pretul cel mai
  sus permis, nu stopul cel mai stramt.
- lectura "TP2 vine inaintea lui TP1" era prezentata ca geometrie in doua
  diagrame, desi fraza-sursa e neinteligibila si sursa sprijina la fel de bine
  varianta in care se recalculeaza si TP si SL. Acum e marcata ca lectura
  chiar acolo unde e folosita, cu varianta alternativa alaturi.

svg_candles are trei adaugiri optionale (Level.i0, Level.label_dy,
Chart.vol_zero) cerute de reparatiile de mai sus; selftest-ul trece si
diagramele existente nu se schimba daca nu sunt regenerate.

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 02:35:51 +00:00
parent 0dd358802d
commit c198cd0a7b
7 changed files with 1184 additions and 945 deletions

View File

@@ -60,6 +60,8 @@ class Level:
color: str = "#1a5fb4"
dash: str = "4 3"
label_at: str = "right" # "right" | "left" — unde stă eticheta
i0: int | None = None # linia începe de la candela i0, nu din marginea stângă
label_dy: float = 0.0 # mută eticheta față de linie (implicit: 6px deasupra)
def __post_init__(self) -> None:
_check_dash(self.dash, "Level")
@@ -138,6 +140,10 @@ class Chart:
vol_h: float = 0.0 # înălțimea panoului de volum (0 = fără panou)
vol_gap: float = 26.0 # spațiu între panoul de preț și cel de volum
vol_label: str = "volum"
# valoarea de volum tratată ca linie de zero: barele peste ea se desenează
# în sus, cele sub ea în jos (histogramă cu semn, stil on balance volume).
# Fără ea, panoul e volumul brut, desenat de la bază.
vol_zero: float | None = None
title: str = "" # ex. "SPY — daily"
zones: list[Zone] = field(default_factory=list)
arrows: list[Arrow] = field(default_factory=list)
@@ -276,12 +282,14 @@ class Chart:
# nivelurile: linie pe toată lățimea, eticheta deasupra liniei
for lv in self.levels:
y = self._y(lv.price)
out.append(f'<line x1="24" y1="{y:.1f}" x2="{self.width - 14}" y2="{y:.1f}" '
xs = 24 if lv.i0 is None else self._cx(lv.i0) - self.body_w / 2
out.append(f'<line x1="{xs:.1f}" y1="{y:.1f}" x2="{self.width - 14}" y2="{y:.1f}" '
f'stroke="{lv.color}" stroke-width="1.5" stroke-dasharray="{lv.dash}"/>')
ty = y - 6 + lv.label_dy
if lv.label_at == "left":
out.append(f'<text x="30" y="{y - 6:.1f}" fill="{lv.color}">{lv.text}</text>')
out.append(f'<text x="{xs + 6:.1f}" y="{ty:.1f}" fill="{lv.color}">{lv.text}</text>')
else:
out.append(f'<text x="{self.width - 28}" y="{y - 6:.1f}" fill="{lv.color}" '
out.append(f'<text x="{self.width - 28}" y="{ty:.1f}" fill="{lv.color}" '
f'text-anchor="end">{lv.text}</text>')
x0 = self.left
@@ -329,17 +337,25 @@ class Chart:
# panoul de volum: bare colorate pe direcția candelei
if vol_top is not None:
assert vol_base is not None
vmax = max(vols)
out.append(f'<line x1="24" y1="{vol_base:.1f}" x2="{self.width - 14}" '
f'y2="{vol_base:.1f}" stroke="#888"/>')
z = self.vol_zero
vmax = max(vols) if z is None else max(abs(v - z) for v in vols)
y_zero = vol_base if z is None else vol_top + self.vol_h / 2
span = self.vol_h if z is None else self.vol_h / 2
out.append(f'<line x1="24" y1="{y_zero:.1f}" x2="{self.width - 14}" '
f'y2="{y_zero:.1f}" stroke="#888"/>')
if z is not None:
out.append(f'<text x="{self.width - 18}" y="{y_zero - 4:.1f}" '
f'fill="#888" text-anchor="end">0</text>')
for i, cd in enumerate(self.candles):
if cd.vol is None:
continue
bh = cd.vol / vmax * self.vol_h
color = cd.color or ("#188a3e" if cd.c >= cd.o else "#c00")
s = cd.vol if z is None else cd.vol - z
bh = s / vmax * span
color = (cd.color or ("#188a3e" if cd.c >= cd.o else "#c00")) if z is None \
else ("#188a3e" if s >= 0 else "#c00")
out.append(f'<rect x="{self._cx(i) - self.body_w / 2:.1f}" '
f'y="{vol_base - bh:.1f}" width="{self.body_w}" '
f'height="{bh:.1f}" fill="{color}" opacity="0.55"/>')
f'y="{min(y_zero, y_zero - bh):.1f}" width="{self.body_w}" '
f'height="{abs(bh):.1f}" fill="{color}" opacity="0.55"/>')
out.append(f'<text x="24" y="{vol_top - 6:.1f}" fill="#555">{self.vol_label}</text>')
# etichete ancorate pe candele anume
@@ -454,6 +470,24 @@ def selftest():
assert len(hs) == 3, f"panoul de volum a desenat {len(hs)} bare"
assert abs(hs[2] / hs[0] - 4.0) < 0.01, f"volum 4x desenat la {hs[2]/hs[0]:.2f}x"
assert abs(hs[1] / hs[0] - 2.0) < 0.01, f"volum 2x desenat la {hs[1]/hs[0]:.2f}x"
# histograma cu semn: bara sub zero se desenează în jos, nu în sus
ch2z = Chart(candles=cds, scale=40, gap=14, body_w=9, vol_h=70, vol_zero=20.0)
svgz = ch2z.render()
y0 = float(__import__("re").search(r'y1="([\d.]+)"[^/]*stroke="#888"', svgz).group(1))
bars = [(float(y), float(h)) for y, h in __import__("re").findall(
r'<rect x="[\d.]+" y="([\d.]+)" width="9" height="([\d.]+)" fill="#\w+" opacity', svgz)]
assert abs(bars[0][0] - y0) < 0.01 and bars[0][1] > 1, \
"bara sub zero trebuie să plece de pe linia de zero în jos"
assert abs(bars[2][0] + bars[2][1] - y0) < 0.01, \
"bara peste zero trebuie să se termine pe linia de zero"
# linia de nivel segmentată începe de la candela cerută, nu din margine
ch4 = Chart(candles=cds, scale=40, gap=14, body_w=9,
levels=[Level(99.0, "trail", i0=1)])
x1 = float(__import__("re").search(r'<line x1="([\d.]+)"[^/]*stroke-dasharray="4 3"',
ch4.render()).group(1))
assert abs(x1 - (ch4._cx(1) - 4.5)) < 0.01, f"nivelul segmentat începe la x={x1}"
# structura din close-uri e păstrată exact
cl = [100.0, 98.0, 99.0, 96.5, 98.5, 101.0]
ser = from_closes(cl, wick=0.2)