Files
atm-curs-dl/gen/strategii/mdsrt_masurare.py
Claude Agent 01599d2e9d strategii: primele 30 de diagrame, cu generatoarele lor
Cate un agent grafician per diagrama, fiecare cu generatorul si SVG-ul lui.
Graficele de pret trec prin svg_candles, cu assert-urile din specificatie
scrise in generator — verifica structura, nu doar ca ruleaza: ca al doilea
picior e mai jos, ca intrarea e close-ul barei de confirmare, ca 1:1 e exact.

Verificare prin sondaj pe buy_doji_ansamblu, cu screenshot citit: serie
continua de 32 de candele, panou de volum, MA(20), doji-ul si ieșirea pe
trail etichetate, cifrele identice cu specificatia.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ThN7ixZrowca8aTGrK1MUJ
2026-09-18 19:22:13 +00:00

255 lines
11 KiB
Python

#!/usr/bin/env python3
"""Cum se masoara divergenta MDSRT — schema pe doua panouri (pret + indicator).
Pentru `strategii/07_mdsrt.md`, marcajul `<!-- DIAGRAMA: masurare -->` (Nivel 1,
§1.2 „Setup", dupa pasul 5). NU e grafic de pret cu candele: spec-ul cere o
schema de regula, cu linii, fara axe de pret reale, fara ticker, fara cifre de
pret. De aceea se scrie SVG de mana, doar stdlib — nu se folosesc
svg_candles / pchart.
Ce arata (toate etichetele si pragurile vin din spec / din Q&A):
- panoul de sus: linia pretului intr-un downtrend; L1 primul swing low
important, L2 al doilea, mai jos → lower low;
- panoul de jos: linia indicatorului (Power / volum / RS) cu I1 si I2, al
doilea mai sus → higher low = divergenta bull;
- bracket orizontal intre L1 si L2: „minim 5 candele intre puncte";
- bracket orizontal in stanga lui L2, pana la 60 de pasi in spate:
„maxim 60 de candele in spate";
- al treilea swing low mic, la 2-3 pasi de L2, taiat cu cruce rosie si
etichetat „2-3 candele → NU e divergenta".
Pragurile, cu sursa:
- „ultimele 2 swing low-uri importante" + „60 de candele in spate si minim 5
candele intre divergente": `09_QA/24_2025-07-15.md:151-155`;
- „Daca aveti pe 3 candele divergenta, nu e": `09_QA/23_2025-07-29.md:873-874`;
- mecanismul pret-vs-volum (lower low pe pret, higher low pe indicator):
`09_QA/19_2025-09-08.md:168-177`;
- perechea de low-uri se muta pe masura ce apare un low nou, mai jos:
`09_QA/24_2025-07-15.md:159-162`.
Ruleaza: .venv/bin/python gen/strategii/mdsrt_masurare.py
-> gen/strategii/mdsrt_masurare.svg
"""
from pathlib import Path
from xml.sax.saxutils import escape
OUT = Path(__file__).with_suffix(".svg")
W, H = 700, 620
BLACK, GRAY, RED, GREEN, BLUE, ORANGE, WHITE = (
"#000000", "#777777", "#c00", "#188a3e", "#1a5fb4", "#b06000", "#ffffff")
# ---- grila de timp: un pas = o candela. L2 e la 60 de pasi de la marginea
# stanga a panoului, ca bracket-ul „maxim 60 de candele" sa aiba exact 60 de
# pasi spre stanga si sa acopere si L1.
X0, STEPW = 60.0, 7.0
L1_STEP, L2_STEP, L3_STEP = 54.0, 60.0, 62.5
X_L1, X_L2, X_L3 = X0 + L1_STEP * STEPW, X0 + L2_STEP * STEPW, X0 + L3_STEP * STEPW
# ---- coordonatele verticale ale punctelor
Y_L1, Y_L2, Y_L3 = 252.0, 278.0, 272.0 # pret: L2 mai jos (lower low)
Y_I1, Y_I2 = 486.0, 468.0 # indicator: I2 mai sus (higher low)
PRICE_BOX = (40.0, 76.0, 620.0, 250.0) # y 76 .. 326
IND_BOX = (40.0, 344.0, 620.0, 200.0) # y 344 .. 544
BR60_Y = 134.0 # bracket-ul de 60 de candele, sus
BR5_Y = 300.0 # bracket-ul de 5 candele, sub cele doua low-uri
TEXTS = [] # (x, y, text, size, weight, anchor) pentru verificarea finala
def txt(x, y, s, size=13, weight="400", fill=BLACK, anchor="middle"):
TEXTS.append((x, y, s, size, weight, anchor))
return (f'<text x="{x:.1f}" y="{y:.1f}" font-size="{size}" '
f'font-weight="{weight}" fill="{fill}" text-anchor="{anchor}">'
f'{escape(s)}</text>')
def line(x1, y1, x2, y2, stroke=BLACK, width=1.4, dash=None):
d = f' stroke-dasharray="{dash}"' if dash else ""
return (f'<line x1="{x1:.1f}" y1="{y1:.1f}" x2="{x2:.1f}" y2="{y2:.1f}" '
f'stroke="{stroke}" stroke-width="{width}"{d}/>')
def poly(points, stroke=BLACK, width=1.8):
pts = " ".join(f"{x:.1f},{y:.1f}" for x, y in points)
return (f'<polyline points="{pts}" fill="none" stroke="{stroke}" '
f'stroke-width="{width}" stroke-linejoin="round"/>')
def dot(x, y, color=BLACK, r=3.6):
return f'<circle cx="{x:.1f}" cy="{y:.1f}" r="{r}" fill="{color}"/>'
def hbracket(x0, x1, y, color, tick=7.0):
"""Bracket orizontal de dimensiune: linie + capete verticale."""
return [
line(x0, y, x1, y, color, 1.4),
line(x0, y - tick, x0, y + tick, color, 1.4),
line(x1, y - tick, x1, y + tick, color, 1.4),
]
def cross(cx, cy, color=RED, arm=10.0):
"""Crucea rosie care marcheaza al treilea low (NU e divergenta)."""
return [
line(cx - arm, cy - arm, cx + arm, cy + arm, color, 2.0),
line(cx - arm, cy + arm, cx + arm, cy - arm, color, 2.0),
]
def build():
p = [
f'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 {W} {H}" '
f'width="{W}" height="{H}" role="img" '
f'aria-label="Cum se masoara divergenta: pretul face un lower low (L2 sub '
f'L1), indicatorul face un higher low (I2 peste I1); intre L1 si L2 trebuie '
f'minim 5 candele si cel mult 60 in spate, iar un low la 2-3 candele nu '
f'conteaza">',
'<g font-family="system-ui, sans-serif">',
f'<rect x="0" y="0" width="{W}" height="{H}" fill="{WHITE}"/>',
txt(W / 2, 34, "Divergența: cum o măsori corect", 19, "700", BLACK),
txt(W / 2, 58, "Aceeași structură pe preț și pe indicator: două swing "
"low-uri, două minime", 13, "400", GRAY),
]
# ---------------- panoul de sus: pretul
bx, by, bw, bh = PRICE_BOX
p.append(f'<rect x="{bx}" y="{by}" width="{bw}" height="{bh}" rx="8" '
f'fill="{WHITE}" stroke="{GRAY}" stroke-width="1.2"/>')
p.append(txt(bx + 16, by + 20, "PREȚ (schematic) — downtrend, lower low",
14, "700", BLACK, "start"))
# bracket „maxim 60 de candele in spate": de la L2 spre stanga, 60 de pasi
x60_left, x60_right = X0, X_L2
p.extend(hbracket(x60_left, x60_right, BR60_Y, BLACK))
p.append(txt((x60_left + x60_right) / 2, BR60_Y - 12,
"maxim 60 de candele în spate", 13, "400", BLACK))
# linia pretului: downtrend cu L1, apoi L2 mai jos, apoi un low mic L3
price = [(0, 158), (6, 178), (12, 166), (18, 192), (24, 180), (30, 206),
(36, 194), (42, 218), (48, 232), (L1_STEP, Y_L1), (57, 226),
(L2_STEP, Y_L2), (61.3, 248), (L3_STEP, Y_L3), (65, 250)]
p.append(poly([(X0 + s * STEPW, y) for s, y in price], BLACK, 1.8))
p.extend([dot(X_L1, Y_L1, BLACK), dot(X_L2, Y_L2, BLACK), dot(X_L3, Y_L3, BLACK)])
p.append(txt(X_L1 - 10, Y_L1 + 6, "L1", 13, "700", BLACK, "end"))
p.append(txt(X_L2 - 12, Y_L2 + 8, "L2", 13, "700", BLACK, "end"))
# bracket „minim 5 candele intre puncte": intre L1 si L2
p.extend(hbracket(X_L1, X_L2, BR5_Y, BLACK))
p.append(txt((X_L1 + X_L2) / 2, BR5_Y + 18,
"minim 5 candele între puncte", 13, "400", BLACK))
# al treilea low: cruce rosie + „NU" + eticheta
p.extend(cross(X_L3, Y_L3, RED, arm=10.0))
p.append(txt(X_L3, 234, "NU", 14, "700", RED))
p.append(txt(525, 246, "2-3 candele", 13, "700", RED, "start"))
p.append(txt(525, 266, "NU e divergență", 13, "700", RED, "start"))
# ---------------- panoul de jos: indicatorul
bx, by, bw, bh = IND_BOX
p.append(f'<rect x="{bx}" y="{by}" width="{bw}" height="{bh}" rx="8" '
f'fill="{WHITE}" stroke="{GRAY}" stroke-width="1.2"/>')
p.append(txt(bx + 16, by + 20,
"INDICATOR (Power / volum / RS) — higher low",
14, "700", BLUE, "start"))
ind = [(0, 400), (6, 424), (12, 410), (18, 436), (24, 422), (30, 448),
(36, 434), (42, 458), (48, 466), (L1_STEP, Y_I1), (57, 458),
(L2_STEP, Y_I2), (65, 440)]
p.append(poly([(X0 + s * STEPW, y) for s, y in ind], BLUE, 1.8))
p.extend([dot(X_L1, Y_I1, BLUE), dot(X_L2, Y_I2, BLUE)])
p.append(txt(X_L1 - 10, Y_I1 + 6, "I1", 13, "700", BLUE, "end"))
p.append(txt(X_L2 + 10, Y_I2 + 6, "I2", 13, "700", BLUE, "start"))
# ---------------- legenda de jos
p.append(txt(W / 2, 570, "Prețul face un lower low (L2 sub L1), indicatorul "
"face un higher low (I2 peste I1):", 13, "400", BLACK))
p.append(txt(W / 2, 590, "divergență bull. Dar dacă al doilea punct e la "
"doar 2-3 candele, nu se numără.", 13, "400", BLACK))
p.append('</g></svg>')
return "\n".join(p) + "\n"
# ---------------------------------------------------------------- verificare
def est_w(s, size, weight):
factor = 0.62 if weight == "700" else 0.56
return len(s) * size * factor
def verify(svg):
# (1) geometria schemei, exact ce cere spec-ul
assert Y_L2 > Y_L1, "L2 trebuie desenat mai jos decat L1 (lower low)"
assert Y_I2 < Y_I1, "I2 trebuie desenat mai sus decat I1 (higher low)"
step_l1_l2 = (X_L2 - X_L1) / STEPW
step_l2_l3 = (X_L3 - X_L2) / STEPW
assert step_l1_l2 >= 5.0 - 1e-9, f"prea putine candele L1-L2: {step_l1_l2}"
assert 2.0 - 1e-9 <= step_l2_l3 <= 3.0 + 1e-9, f"L3 nu e la 2-3 pasi: {step_l2_l3}"
# (4) bracket-ul de 60 de candele acopera de la L2 spre stanga si trece de L1
x60_left, x60_right = X0, X_L2
assert x60_right == X_L2, "bracket-ul de 60 porneste din L2"
assert x60_left <= X_L1, "bracket-ul de 60 nu ajunge pana la L1"
assert x60_right - x60_left >= (X_L2 - X_L1), "bracket prea scurt fata de L1"
assert (x60_right - x60_left) / STEPW == 60.0, "nu sunt exact 60 de pasi"
# (5) „NU" si crucea rosie chiar pe al treilea low
assert "NU" in svg
assert RED in svg and "L3" not in svg # L3 e marcat doar prin cruce, nu cu eticheta
cross_lines = cross(X_L3, Y_L3)
for ln in cross_lines:
assert f'x1="{X_L3 - 10:.1f}"' in ln or f'x2="{X_L3 - 10:.1f}"' in ln
assert f'y1="{Y_L3 - 10:.1f}"' in ln or f'y2="{Y_L3 - 10:.1f}"' in ln
# etichetele obligatorii din spec sunt in desen
for cheie in ("maxim 60 de candele în spate", "minim 5 candele între puncte",
"2-3 candele", "NU e divergență", "lower low", "higher low",
"L1", "L2", "I1", "I2"):
assert cheie in svg, f"lipsește din desen: {cheie!r}"
# (2) toate etichetele in panza
for x, y, s, size, weight, anchor in TEXTS:
ew = est_w(s, size, weight)
left = x - ew if anchor == "end" else (x - ew / 2 if anchor == "middle" else x)
right = left + ew
assert -2 <= left and right <= W + 2, f"text in afara panzei: {s!r}"
assert 0 <= y <= H, f"text in afara inaltimii: {s!r} (y={y})"
# (3) fara doua texte pe aceeasi pozitie
for i in range(len(TEXTS)):
xi, yi, si, szi, wi, ai = TEXTS[i]
we = est_w(si, szi, wi)
li = xi - we if ai == "end" else (xi - we / 2 if ai == "middle" else xi)
ri = li + we
for j in range(i + 1, len(TEXTS)):
xj, yj, sj, szj, wj, aj = TEXTS[j]
wej = est_w(sj, szj, wj)
lj = xj - wej if aj == "end" else (xj - wej / 2 if aj == "middle" else xj)
rj = lj + wej
if abs(yi - yj) < 13 and li < rj - 4 and lj < ri - 4:
raise AssertionError(f"texte suprapuse: {si!r} / {sj!r}")
# etichetele panourilor incap in panouri
for x, y, s, size, weight, anchor in TEXTS:
for (px, py, pw, ph) in (PRICE_BOX, IND_BOX):
if py <= y <= py + ph and px - 2 <= x <= px + pw + 2:
assert x >= px + 12, f"eticheta lipita de margine: {s!r}"
assert est_w(s, size, weight) <= pw - 24, f"prea lata in panou: {s!r}"
# fara linie goala in SVG
assert "" not in svg.split("\n")[:-1], "SVG-ul are linie goala"
assert "\n\n" not in svg, "SVG-ul are linie goala"
return True
SVG = build()
assert verify(SVG), "verificare esuata"
OUT.write_text(SVG, encoding="utf-8")
print(f"scris {OUT} ({W}x{H}), verificare OK: {len(TEXTS)} etichete, "
f"L1-L2={(X_L2 - X_L1) / STEPW:.1f} pasi, "
f"L2-L3={(X_L3 - X_L2) / STEPW:.1f} pasi, "
f"fereastra 60 = {(X_L2 - X0) / STEPW:.0f} pasi")