diagrame: trei desene care aratau altceva decat spune regula

m2d_exemplu (Riot): eticheta zicea 'close peste bear trail = iesi' pe o bara
care se inchidea SUB linie - doar fitilul trecea, adica exact cazul pe care
capitolul il interzice de trei ori. Seria refacuta, cu assert care recalculeaza
trail-ul din serie si cere ca bara de iesire sa inchida peste el.

mbp_exemplu: candela M se inchidea la 660.50 sub banda de 660.90, iar
generatorul chiar afirma relatia inversa ('assert BAND > c'); desenul arata un
setup invalid ca valid. Plus stopul 662.71, care plutea 1.71 deasupra extremei
celor 4 bare - exact marja pe care capitolul o declara absenta la MBP.
Ambele reparate, cu assert pe fiecare.

atmi_orizont: trei etichete purtau 'fisier:linie' in desen.

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-09-22 20:08:48 +00:00
parent f80fef4e2f
commit c564bf1be5
7 changed files with 265 additions and 111 deletions

View File

@@ -13,6 +13,12 @@ Legenda spune explicit lucrul ăsta.
Serie continuă de 30 de candele din lista de close-uri din specificație,
open = close-ul precedentului, wick-uri alese ca să scoată exact nivelurile
(candela 13: high 100.85 = SL), panou de volum dedesubt.
Bear trail-ul e calculat din serie cu regula capitolului (linia sare pe
high-ul barei care se închide sub low-ul precedentei; violare = o bară care
se închide PESTE linie) și desenat ca scară portocalie. Seria e construită
astfel încât fitilele să atingă linia de trei ori (barele 16, 20, 24) fără
s-o violeze, iar ieșirea să vină abia la bara 29, care închide peste linie.
"""
import re
import sys
@@ -34,22 +40,36 @@ closes = [
97.6, 97.1, 96.7, 96.3, 95.9, 95.6, 95.9, # 0-6 coborâre
96.4, 96.9, 97.5, 98.2, 99.0, 99.7, 100.3, # 7-13 pullback Buy (M)
100.00, # 14 trigger Sell (A)
99.5, 99.0, 98.5, 98.0, 97.6, 97.2, 96.9, 96.6, 96.3, 96.1,
95.8, 95.5, 95.2, 95.35, 95.45, # 15-29 bear trail
99.45, 99.80, 98.58, 98.10, 97.60, 97.95, 97.40, 96.90, 96.40, 96.75,
96.20, 95.70, 95.20, 95.00, 95.45, # 15-29 bear trail
]
assert len(closes) == 30, len(closes)
# wick-uri: candela 13 are nevoie de un wick sus până la 100.85 (SL-ul de 4
# candele); restul sunt mici, ca seria să arate ca un grafic real.
# candele); barele 16, 20 și 24 sunt retestări ale liniei de trail, deci au
# fitil lung în sus care atinge linia, dar se închid sub ea. Restul sunt mici,
# ca seria să arate ca un grafic real.
wicks = [
(0.15, 0.20), (0.12, 0.18), (0.15, 0.15), (0.10, 0.20), (0.12, 0.25),
(0.10, 0.30), (0.25, 0.15),
(0.20, 0.15), (0.15, 0.20), (0.20, 0.15), (0.20, 0.20), (0.20, 0.18),
(0.25, 0.15), (SL - 100.3, 0.15), # 13: high = 100.85
(0.15, 0.25), # 14: trigger Sell
(0.15, 0.20), (0.15, 0.18), (0.12, 0.20), (0.15, 0.18), (0.15, 0.20),
(0.12, 0.18), (0.15, 0.20), (0.12, 0.18), (0.15, 0.20), (0.12, 0.18),
(0.15, 0.20), (0.12, 0.18), (0.15, 0.20), (0.20, 0.15), (0.30, 0.10),
(0.15, 0.15), # 15
(0.45, 0.10), # 16 retest trail
(0.15, 0.15), # 17
(0.15, 0.15), # 18
(0.15, 0.15), # 19
(0.40, 0.10), # 20 retest trail
(0.15, 0.15), # 21
(0.15, 0.15), # 22
(0.15, 0.15), # 23
(0.40, 0.10), # 24 retest trail
(0.15, 0.15), # 25
(0.15, 0.15), # 26
(0.15, 0.15), # 27
(0.05, 0.05), # 28: coboară trail-ul
(0.20, 0.10), # 29: close peste trail
]
assert len(wicks) == 30, len(wicks)
@@ -95,6 +115,53 @@ for a, b in zip(cds, cds[1:]):
assert cds[14].c < cds[13].c, (cds[13].c, cds[14].c)
assert any(cds[i].c > cds[i].o for i in range(7, 14)), "pullback Buy absent"
# 10. bara 17 e la 1.42% profit (procentul real din curs)
assert abs((ENTRY - cds[17].c) / ENTRY - 0.0142) < 1e-4, cds[17].c
# ── bear trail, recalculat din serie cu regula capitolului ─────────────────
# Linia sare pe high-ul fiecărei bare care se închide sub low-ul precedentei;
# violarea = prima bară care se închide PESTE linie (fitilul singur nu scoate).
def bear_trail(candles, trig):
"""linie[i] = nivelul trail-ului în vigoare la începutul barei i (None până
se formează linia); iesire = prima bară închisă peste linie."""
linie, linie_la, iesire = None, [], None
for i, c in enumerate(candles):
linie_la.append(linie)
if i > trig and linie is not None and c.c > linie:
iesire = i
break
if i > trig:
p = candles[i - 1]
if c.c < p.l:
linie = c.h
while len(linie_la) < len(candles):
linie_la.append(linie)
return linie_la, iesire
LINIE, EXIT_BAR = bear_trail(cds, 14)
# 11. ieșirea e pe bara 29, care se închide PESTE linia de trail (nu doar fitilul)
assert EXIT_BAR == 29, f"ieșirea trebuie să fie pe bara 29, nu {EXIT_BAR}"
TRAIL_EXIT = LINIE[29]
assert TRAIL_EXIT is not None and cds[29].c > TRAIL_EXIT, (cds[29].c, TRAIL_EXIT)
# 12. linia nu urcă niciodată (e trail, nu nivel fix)
pas = [v for v in LINIE[15:30] if v is not None]
assert all(b <= a + 1e-9 for a, b in zip(pas, pas[1:])), pas
# 13. nicio bară dinainte de ieșire nu închide peste linie
for i in range(15, 29):
if LINIE[i] is not None:
assert cds[i].c < LINIE[i], (i, cds[i].c, LINIE[i])
# 14. fitilele au atins linia de mai multe ori fără s-o violeze pe close
atingeri = [i for i in range(15, 29) if LINIE[i] is not None
and cds[i].h >= LINIE[i] - 1e-9 and cds[i].c < LINIE[i]]
assert len(atingeri) >= 2, f"fitilele ating linia doar la barele {atingeri}"
assert set(atingeri) >= {16, 20, 24}, f"retestările lipsesc: {atingeri}"
# ── geometria, ca să așez marks-urile „below" fără suprapunere ─────────────
SCALE = 42
levels = [
@@ -177,8 +244,11 @@ definiții = [
def natural_y(i: int, where: str) -> float:
# „below": sub candelă, dar înainte de panoul de volum. Plafonul e cu 1 px
# sub limita de jos a candidaților, altfel când bara i are chiar minimul
# seriei nu mai rămâne niciun y întreg de testat și plasarea pică.
return (y(cds[i].h) - 10 if where == "above"
else min(y(cds[i].l) + 20, body_bottom + 14))
else min(y(cds[i].l) + 20, body_bottom + 13))
# candidați: toate y-urile care nu ating niciun corp de candelă, ordonate
@@ -239,6 +309,33 @@ ch = Chart(
svg = ch.render()
assert "<svg" in svg and svg.count("\n\n") == 0, "linie goală în SVG"
# ── linia de trail, desenată ca scară portocalie ───────────────────────────
# Nivelul desenat la bara i e cel în vigoare după bara i (linia sare pe bara
# care a închis sub low-ul precedentei). Eticheta stă la stânga primului
# palier, ca să nu treacă peste fitilul lung al barei 13.
def nivel_desen(i: int):
return LINIE[i + 1] if i + 1 < len(LINIE) else LINIE[i]
trail_parts, prev_y = [], None
for i in range(15, len(cds)):
v = nivel_desen(i)
if v is None:
continue
x0, x1, yv = cx(i) - GAP / 2, cx(i) + GAP / 2, y(v)
trail_parts.append(
f'<line x1="{x0:.1f}" y1="{yv:.1f}" x2="{x1:.1f}" y2="{yv:.1f}" '
f'stroke="#b06000" stroke-width="2"/>')
if prev_y is not None and abs(prev_y - yv) > 1e-9:
trail_parts.append(
f'<line x1="{x0:.1f}" y1="{prev_y:.1f}" x2="{x0:.1f}" y2="{yv:.1f}" '
f'stroke="#b06000" stroke-width="1.2" stroke-dasharray="2 2"/>')
prev_y = yv
trail_parts.append(
f'<text x="{cx(13) - 6:.1f}" y="{y(nivel_desen(15)) - 14:.1f}" fill="#b06000" '
f'text-anchor="end" style="font-weight:bold">bear trail</text>')
svg = svg.replace("\n</svg>", "\n " + "\n ".join(trail_parts) + "\n</svg>")
# ── verificare: fiecare etichetă e în pânză și nu se suprapune alta ────────
m = re.search(r'viewBox="0 0 (\d+) (\d+)"', svg)
W, H = int(m.group(1)), int(m.group(2))