#!/usr/bin/env python3
"""Hartă de orientare: buy hammer (public) vs. MBT-10 (cutie neagră), același
trigger de bottom.
Pentru `strategii/06_hammer_mbt10.md`, marcajul ``.
Nu e grafic de preț (spec: TIP CONCEPT, CHEIE harta) — SVG scris de mână,
doar stdlib; nu se folosește svg_candles / pchart.
Ce arată (toate citatele și trimiterile vin din spec, nu se inventează):
- bara comună de sus: setup-urile de fond spun UNDE, trigger-ul de candelă
spune CÂND (`02_mbt10.md:9-11`, `43-49`);
- două cutii alăturate: BUY HAMMER (pattern standard, public) și MBT-10
(cutie neagră, indicator proprietar), fiecare cu regulile ei și cu
trimiterile de rigoare;
- săgeata de jos spre caseta finală: ce rămâne utilizabil pe BVB.
Verdictele de BVB din cutii sunt judecata mea (secțiunea Transfer), marcate
ca atare în legendă, exact cum cere specul.
Rulare: python3 gen/strategii/hammer_mbt10_harta.py -> hammer_mbt10_harta.svg
"""
from pathlib import Path
from xml.sax.saxutils import escape
OUT = Path(__file__).with_suffix(".svg")
W, H = 700, 610
BLACK, GRAY, BLUE, GREEN, RED, ORANGE, WHITE = (
"#000000", "#777777", "#1a5fb4", "#188a3e", "#c00", "#b06000", "#ffffff")
BORDER = "#333333"
# ---------------------------------------------------------------- geometrie
BAR_X0, BAR_X1, BAR_Y, BAR_H = 40.0, 660.0, 82.0, 76.0
BOX_W = 302.0
BOX_LX, BOX_RX, BOX_Y = 40.0, 358.0, 190.0
BOX_H = 256.0
PAD = 16.0
ROW0 = BOX_Y + 56.0 # baseline-ul primei reguli
ROW_H = 52.0
VERDICT_SEP = BOX_Y + 216.0
VERDICT_Y = VERDICT_SEP + 26.0
BUS_Y = 460.0 # linia orizontală de unire a celor două cutii
TRI_TIP = 500.0
FINAL_X0, FINAL_Y, FINAL_W, FINAL_H = 110.0, 500.0, 480.0, 56.0
TEXTS = [] # (x, y, text, size, weight, anchor) pentru verificare
def txt(x, y, s, size=13, weight="400", fill=BLACK, anchor="middle"):
TEXTS.append((x, y, s, size, weight, anchor))
return (
f'{escape(s)}'
)
def line(x1, y1, x2, y2, stroke=GRAY, width=1.0, dash=None):
d = f' stroke-dasharray="{dash}"' if dash else ""
return (f'')
def rect(x, y, w, h, fill=WHITE, stroke=GRAY, width=1.2):
return (f'')
def box(bx, title, title_fill, rows, verdict, verdict_fill, fill):
"""O cutie cu chenar: titlu, reguli (linii + trimitere), verdict."""
x = bx + PAD
area = BOX_W - 2 * PAD
out = [rect(bx, BOX_Y, BOX_W, BOX_H, fill=fill, stroke=title_fill, width=1.4)]
out.append(txt(x, BOX_Y + 26, title, 15, "700", title_fill, "start"))
out.append(line(bx + PAD, BOX_Y + 38, bx + BOX_W - PAD, BOX_Y + 38,
title_fill, 1.0))
for i, (mains, ref) in enumerate(rows):
top = ROW0 + i * ROW_H
for k, m in enumerate(mains):
out.append(txt(x, top + 14 + k * 17, m, 13, "400", BLACK, "start"))
if ref is not None:
out.append(txt(x, top + 48, ref, 13, "400", GRAY, "start"))
out.append(line(bx + PAD, VERDICT_SEP, bx + BOX_W - PAD, VERDICT_SEP,
verdict_fill, 1.2))
out.append(txt(x, VERDICT_Y, verdict, 13, "700", verdict_fill, "start"))
return out, area
def build():
p = [
f'')
return "\n".join(p) + "\n", left_area, right_area
# ---------------------------------------------------------------- verificare
def est_w(s, size, weight):
factor = 0.62 if weight == "700" else 0.56
return len(s) * size * factor
def box_of(x, y):
for bx in (BOX_LX, BOX_RX):
if bx <= x <= bx + BOX_W and BOX_Y <= y <= BOX_Y + BOX_H:
return bx
return None
def verify(svg, left_area, right_area):
# 1. geometrie: zonele nu se calcă
assert BAR_Y + BAR_H < BOX_Y, "bara comună intră în cutii"
assert BOX_Y + BOX_H < BUS_Y < TRI_TIP <= FINAL_Y, "săgeata nu are loc"
assert FINAL_Y + FINAL_H < 578, "caseta finală intră în legendă"
assert left_area == right_area == BOX_W - 2 * PAD
# 2. toate textele în panză
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 în afara panzei: {s!r}"
assert 0 <= y <= H, f"text în afara înălțimii: {s!r} (y={y})"
# 3. texte care nu se suprapun
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}")
# 4. textele din cutii încap în cutii
for x, y, s, size, weight, anchor in TEXTS:
b = box_of(x, y)
if b is not None:
ew = est_w(s, size, weight)
assert x + ew <= b + BOX_W - PAD + 1, f"text iese din cutie: {s!r}"
# 5. verdictul e sub linia lui de separare
assert VERDICT_SEP < VERDICT_Y < BOX_Y + BOX_H
# 6. conținutul obligatoriu din spec
for cheie in (
"Trigger de bottom: unde + când",
"Setup-urile de fond — suport, locul crimei, double bottom, buy doji,",
"divergență de volum — spun UNDE. Trigger-ul de candelă spune CÂND.",
"",
"BUY HAMMER — pattern standard",
"„formă de candelă + downtrend”",
"",
"„trigger: close peste",
"high-ul hammer-ului”",
"",
"„fără niciun indicator proprietar”",
"„pe BVB: se poate rula long-only”",
"MBT-10 — cutie neagră",
"„formula nu e publică nicăieri”",
"",
"„reconstituibil doar:",
"close peste pivotul daily”",
"",
"„scanner + indicator proprietar”",
"",
"„pe BVB: nu, ca sistem”",
"Ce rămâne pe BVB: hammer la un nivel major",
"+ close peste PPD",
"verdictul de BVB = judecata mea",
"din secțiunea Transfer.",
):
assert cheie in svg, f"lipsește din desen: {cheie!r}"
# 7. fără axe de preț / candele / cifre inventate
for interzis in ("candela", "SL ", "intrare ", "TP1", "TP2", "$", "USD"):
assert interzis not in svg, f"nu ar trebui să apară preț: {interzis!r}"
return True
SVG, LEFT_AREA, RIGHT_AREA = build()
assert verify(SVG, LEFT_AREA, RIGHT_AREA), "verificare eșuată"
OUT.write_text(SVG, encoding="utf-8")
print(f"scris {OUT} ({W}x{H}), verificare OK: {len(TEXTS)} etichete, "
f"cutii {BOX_W:g}x{BOX_H:g} la {BOX_LX:g}/{BOX_RX:g}")