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:
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user