Doua proceduri complet separate, nu una amestecata, pentru ca se fac altfel: buy hammer e pattern public, desenabil si exersabil retroactiv; MBT-10 e cutie neagra din care se poate reface o singura conditie. Nivelul 1 le pune fata in fata, ca cititorul sa stie din prima de ce nu le poate trata la fel. Cate 5 pasi fiecare, oglinda pe short la hammer, checklist dublu. Sursele in fise/06_hammer_mbt10_surse.md. Zece diagrame noi: hammer-ul nu era definit vizual nicaieri, iar hanging man era doar text — acum e aceeasi bara in doua contexte, cu assert ca e identica. Cele 7 vechi si-au pierdut trimiterile fisier:linie din legende; toate le aveau. Doua serii partajate (bh_ansamblu si mbt_confluente expun serie()), deci un trade nu poate avea doua variante. Oglinda pe short e trade-ul long oglindit in jurul lui K=162.30, deci riscul ramane 1.30 in ambele. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ThN7ixZrowca8aTGrK1MUJ
267 lines
11 KiB
Python
267 lines
11 KiB
Python
#!/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 `<!-- DIAGRAMA: harta -->`.
|
|
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'<text x="{x:.1f}" y="{y:.1f}" font-size="{size}" font-weight="{weight}" '
|
|
f'fill="{fill}" text-anchor="{anchor}">{escape(s)}</text>'
|
|
)
|
|
|
|
|
|
def line(x1, y1, x2, y2, stroke=GRAY, width=1.0, 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 rect(x, y, w, h, fill=WHITE, stroke=GRAY, width=1.2):
|
|
return (f'<rect x="{x:.1f}" y="{y:.1f}" width="{w:.1f}" height="{h:.1f}" '
|
|
f'rx="8" fill="{fill}" stroke="{stroke}" stroke-width="{width}"/>')
|
|
|
|
|
|
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'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 {W} {H}" '
|
|
f'width="{W}" height="{H}" role="img" '
|
|
f'aria-label="Harta capitolului: acelasi trigger de bottom, doua naturi '
|
|
f'- buy hammer, pattern standard public, si MBT-10, cutie neagra cu '
|
|
f'indicator proprietar">',
|
|
'<g font-family="system-ui, sans-serif">',
|
|
f'<rect x="0" y="0" width="{W}" height="{H}" fill="{WHITE}"/>',
|
|
txt(W / 2, 34, "Același trigger de bottom, două naturi", 19, "700", BLACK),
|
|
txt(W / 2, 58, "buy hammer (public) vs. MBT-10 (cutie neagră)",
|
|
14, "400", GRAY),
|
|
]
|
|
|
|
# ------- zona 1: bara comună
|
|
p.append(rect(BAR_X0, BAR_Y, BAR_X1 - BAR_X0, BAR_H, fill="#f2f4f7",
|
|
stroke=GRAY, width=1.2))
|
|
p.append(txt(BAR_X0 + PAD, BAR_Y + 26, "Trigger de bottom: unde + când",
|
|
15, "700", BLUE, "start"))
|
|
p.append(txt(BAR_X0 + PAD, BAR_Y + 48,
|
|
"Setup-urile de fond — suport, locul crimei, double bottom, buy "
|
|
"doji,", 13, "400", BLACK, "start"))
|
|
p.append(txt(BAR_X0 + PAD, BAR_Y + 66,
|
|
"divergență de volum — spun UNDE. Trigger-ul de candelă spune "
|
|
"CÂND.", 13, "400", BLACK, "start"))
|
|
p.append(txt(BAR_X1 - PAD, BAR_Y + 26, "",
|
|
13, "400", GRAY, "end"))
|
|
|
|
# ------- conector bară -> cutii (un cot, fără săgeți redundante)
|
|
lx = BOX_LX + BOX_W / 2
|
|
rx = BOX_RX + BOX_W / 2
|
|
p.append(line(W / 2, BAR_Y + BAR_H, W / 2, 172.0, GRAY, 1.2))
|
|
p.append(line(lx, 172.0, rx, 172.0, GRAY, 1.2))
|
|
p.append(line(lx, 172.0, lx, BOX_Y, GRAY, 1.2))
|
|
p.append(line(rx, 172.0, rx, BOX_Y, GRAY, 1.2))
|
|
|
|
# ------- zona 2: cele două cutii
|
|
left_rows = [
|
|
(['„formă de candelă + downtrend”'], ""),
|
|
(['„trigger: close peste', 'high-ul hammer-ului”'],
|
|
""),
|
|
(['„fără niciun indicator proprietar”'], None),
|
|
]
|
|
right_rows = [
|
|
(['„formula nu e publică nicăieri”'], ""),
|
|
(['„reconstituibil doar:', 'close peste pivotul daily”'],
|
|
""),
|
|
(['„scanner + indicator proprietar”'], ""),
|
|
]
|
|
left_svg, left_area = box(
|
|
BOX_LX, "BUY HAMMER — pattern standard", BLUE, left_rows,
|
|
"„pe BVB: se poate rula long-only”", GREEN, "#eef4fd")
|
|
right_svg, right_area = box(
|
|
BOX_RX, "MBT-10 — cutie neagră", BORDER, right_rows,
|
|
"„pe BVB: nu, ca sistem”", RED, "#f5f5f5")
|
|
p += left_svg + right_svg
|
|
|
|
# ------- zona 3: ambele cutii -> caseta finală
|
|
p.append(line(lx, BOX_Y + BOX_H, lx, BUS_Y, GRAY, 1.2))
|
|
p.append(line(rx, BOX_Y + BOX_H, rx, BUS_Y, GRAY, 1.2))
|
|
p.append(line(lx, BUS_Y, rx, BUS_Y, GRAY, 1.2))
|
|
p.append(line(W / 2, BUS_Y, W / 2, TRI_TIP - 11, GRAY, 1.4))
|
|
p.append(f'<polygon points="{W/2:.1f},{TRI_TIP:.1f} '
|
|
f'{W/2-6:.1f},{TRI_TIP-11:.1f} {W/2+6:.1f},{TRI_TIP-11:.1f}" '
|
|
f'fill="{GRAY}"/>')
|
|
|
|
p.append(rect(FINAL_X0, FINAL_Y, FINAL_W, FINAL_H, fill="#fdf0e0",
|
|
stroke=ORANGE, width=1.6))
|
|
p.append(txt(W / 2, FINAL_Y + 24,
|
|
"Ce rămâne pe BVB: hammer la un nivel major", 14, "700",
|
|
BLACK))
|
|
p.append(txt(W / 2, FINAL_Y + 44, "+ close peste PPD", 14, "700", BLACK))
|
|
|
|
# ------- legendă
|
|
p.append(txt(W / 2, 578,
|
|
"Regulile = materialul cursului (cu trimiteri); verdictul de "
|
|
"BVB = judecata mea", 13, "400", GRAY))
|
|
p.append(txt(W / 2, 596, "din secțiunea Transfer.", 13, "400", GRAY))
|
|
|
|
p.append('</g></svg>')
|
|
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}")
|