#!/usr/bin/env python3
"""Cum se masoara divergenta MDSRT — schema pe doua panouri (pret + indicator).
Pentru `strategii/07_mdsrt.md`, marcajul `` (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''
f'{escape(s)}')
def line(x1, y1, x2, y2, stroke=BLACK, width=1.4, dash=None):
d = f' stroke-dasharray="{dash}"' if dash else ""
return (f'')
def poly(points, stroke=BLACK, width=1.8):
pts = " ".join(f"{x:.1f},{y:.1f}" for x, y in points)
return (f'')
def dot(x, y, color=BLACK, r=3.6):
return f''
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'')
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")