Files
atm-curs-dl/gen/strategii/hammer_mbt10_bh_forma.py
Claude Agent a7785740cc 06_hammer_mbt10: capitol reconstruit dupa sablon
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
2026-09-20 13:05:41 +00:00

155 lines
6.9 KiB
Python

#!/usr/bin/env python3
"""Ce e un hammer și ce nu — patru candele cu același high 63.30 și low 62.10.
Prima imagine a capitolului: cine o vede nu știe ce e un hammer, deci candela
A primește săgeți pe corp și pe fiecare fitil, iar C și D sunt contraexemple
cu aceeași înălțime. Zonă verde peste A și B, roșie peste C și D.
Desenat direct, nu cu `Chart`: fiecare etichetă trebuie să stea lipită sub
candela ei, pe 2-3 rânduri, plus săgeți cu text pe lateral.
Rulare: python3 gen/strategii/hammer_mbt10_bh_forma.py
"""
from pathlib import Path
OUT = Path(__file__).with_suffix(".svg")
GREEN, RED, GREY, BLUE = "#188a3e", "#c00", "#555", "#1a5fb4"
H, L = 63.30, 62.10
RNG = H - L # 1.20
PRAG_SUS = L + 2 / 3 * RNG # 62.90 — corpul trebuie să stea peste el
# (nume, o, h, l, c, verdict, rânduri de etichetă sub candelă)
CANDLES = [
("A", 63.05, H, L, 63.30, "DA", ["DA: corp 0.25,", "fitil jos 0.95 = 3,8x corpul"]),
("B", 62.90, H, L, 63.30, "DA", ["DA, la limită:", "corp 0.40, fitil 0.80"]),
("C", 62.60, H, L, 63.30, "NU", ["NU: corp 0.70,", "fitil jos 0.50 = 0,7x corpul"]),
("D", 62.25, H, L, 62.45, "NU", ["NU: corpul e jos, fitilul", "lung e sus — alt pattern"]),
]
def masuri(o, h, l, c):
return abs(c - o), min(o, c) - l, h - max(o, c) # corp, fitil jos, fitil sus
# --- verificările -----------------------------------------------------------
M = {n: masuri(o, h, l, c) for n, o, h, l, c, *_ in CANDLES}
for n, o, h, l, c, *_ in CANDLES:
assert (h, l) == (H, L), (n, h, l) # 1 aceeași bară
assert [tuple(round(v, 2) for v in M[n]) for n in "ABCD"] == [ # 2 cifrele din etichete
(0.25, 0.95, 0.00), (0.40, 0.80, 0.00), (0.70, 0.50, 0.00), (0.20, 0.15, 0.85)], M
for n in "AB": # 3 A și B trec ambele reguli
corp, jos, _ = M[n]
o, c = [(o, c) for nn, o, _, _, c, *_ in CANDLES if nn == n][0]
assert jos >= 2 * corp - 1e-9, (n, jos, corp)
assert min(o, c) >= PRAG_SUS - 1e-9, (n, min(o, c), PRAG_SUS)
assert M["C"][1] < 2 * M["C"][0], "C ar trebui să pice pe regula fitilului" # 4
oD, cD = CANDLES[3][1], CANDLES[3][4]
assert max(oD, cD) < PRAG_SUS, "D ar trebui să pice pe regula treimii de sus" # 5
assert round(M["A"][1] / M["A"][0], 1) == 3.8 and round(M["B"][1] / M["B"][0], 1) == 2.0
assert round(M["C"][1] / M["C"][0], 1) == 0.7
# --- geometrie --------------------------------------------------------------
SCALE, TOP, W = 260, 210, 1000
XS = [350, 490, 690, 860]
BODY_W = 26
def y(p):
return TOP + (H - p) * SCALE
assert y(CANDLES[0][1]) > y(CANDLES[0][4]), "open A trebuie sub close A pe ecran"
out = []
t = out.append
def text(x, yy, s, color=GREY, anchor="start", bold=False, size=12):
style = f' style="font-weight:bold;font-size:{size}px"' if bold else (
f' style="font-size:{size}px"' if size != 12 else "")
t(f'<text x="{x:.1f}" y="{yy:.1f}" fill="{color}" text-anchor="{anchor}"{style}>{s}</text>')
def zone(x0, x1, y0, y1, label, color):
t(f'<rect x="{x0:.1f}" y="{y0:.1f}" width="{x1 - x0:.1f}" height="{y1 - y0:.1f}" '
f'fill="{color}" fill-opacity="0.10" stroke="{color}" stroke-width="1"/>')
text(x0 + 6, y0 - 7, label, color, bold=True)
def arrow(x1, y1, x2, y2, color, lines, anchor="start"):
d = max(((x2 - x1) ** 2 + (y2 - y1) ** 2) ** 0.5, 1e-9)
ux, uy = (x2 - x1) / d, (y2 - y1) / d
bx, by = x2 - 8 * ux, y2 - 8 * uy
t(f'<line x1="{x1:.1f}" y1="{y1:.1f}" x2="{bx:.1f}" y2="{by:.1f}" '
f'stroke="{color}" stroke-width="1.6"/>')
t(f'<polygon points="{x2:.1f},{y2:.1f} {bx - 4 * uy:.1f},{by + 4 * ux:.1f} '
f'{bx + 4 * uy:.1f},{by - 4 * ux:.1f}" fill="{color}"/>')
dx = -6 if anchor == "end" else (0 if anchor == "middle" else 6)
y0 = (y1 - 10 - 14 * (len(lines) - 1) if anchor == "middle"
else y1 + 4 - 14 * (len(lines) - 1) / 2)
for k, s in enumerate(lines):
text(x1 + dx, y0 + 14 * k, s, color, anchor, bold=(k == 0))
text(24, 30, "Ce e un hammer și ce nu — aceeași bară, 63.30 sus și 62.10 jos", "#333",
bold=True, size=14)
label_y0 = y(L) + 28
z_bot = label_y0 + 16 * 2 - 4
zone(XS[0] - 58, XS[1] + 66, y(H) - 30, z_bot, "hammer", GREEN)
zone(XS[2] - 60, XS[3] + 74, y(H) - 30, z_bot, "nu e hammer", RED)
for p in (H, L):
t(f'<line x1="{XS[0] - 100}" y1="{y(p):.1f}" x2="{W - 24}" y2="{y(p):.1f}" '
f'stroke="#bbb" stroke-dasharray="4 3"/>')
text(24, y(H) - 6, "cât de sus a urcat prețul: 63.30")
text(24, y(L) + 14, "cât de jos a coborât: 62.10")
# pragul treimii de sus: linia pe care corpul trebuie s-o depășească
t(f'<line x1="{XS[0] - 100}" y1="{y(PRAG_SUS):.1f}" x2="{W - 24}" y2="{y(PRAG_SUS):.1f}" '
f'stroke="{BLUE}" stroke-width="1.2" stroke-dasharray="2 3"/>')
text(24, y(PRAG_SUS) - 4, f"treimea de sus începe la {PRAG_SUS:.2f}", BLUE)
for (n, o, h, l, c, verdict, lines), x in zip(CANDLES, XS):
color = GREEN if c >= o else RED
vcol = GREEN if verdict == "DA" else RED
t(f'<line x1="{x}" y1="{y(h):.1f}" x2="{x}" y2="{y(l):.1f}" stroke="#1a1a1a" stroke-width="1.4"/>')
top, bot = y(max(o, c)), y(min(o, c))
t(f'<rect x="{x - BODY_W / 2:.1f}" y="{top:.1f}" width="{BODY_W}" '
f'height="{max(bot - top, 1.6):.1f}" fill="{color}"/>')
for k, s in enumerate(lines):
text(x, label_y0 + 16 * k, s, vcol, "middle", bold=(k == 0))
xA = XS[0]
arrow(xA - 60, y(63.10), xA - 15, y(63.17), BLUE, anchor="end",
lines=["corpul: mic și sus în bară",
"ziua s-a închis aproape de maxim —",
"cumpărătorii au ținut până la final"])
arrow(xA - 60, y(62.55), xA - 3, y(62.60), BLUE, anchor="end",
lines=["fitilul de jos, lung",
"vânzătorii au dus prețul până la 62.10,",
"dar cumpărătorii l-au adus înapoi sus"])
text(xA + 20, y(63.05) + 12, "open 63.05")
text(xA + 20, y(63.30) - 4, "close = high 63.30")
xC, xD = XS[2], XS[3]
arrow(xC + 34, y(H) - 112, xC + 14, y(62.95), RED,
lines=["corp prea gros", "fitilul de jos nu e nici cât corpul —", "nimeni n-a fost respins jos"])
arrow(xD, y(H) - 34, xD, y(63.24), RED, anchor="middle",
lines=["fitilul lung e sus, nu jos", "aici cumpărătorii au fost respinși;",
"e inverted hammer, alt pattern"])
cap_y = z_bot + 30
text(24, cap_y, "Hammer = fitilul de jos e cel puțin dublul corpului ȘI corpul stă în treimea "
"de sus a barei (aici peste 62.90).")
text(24, cap_y + 16, "Pragul „fitil ≥ 2x corpul\" e al cursului; „corpul în treimea de sus\" e "
"traducerea mea a lui „corp mic sus, fitil de sus aproape inexistent\".", GREY)
height = cap_y + 40
svg = (f'<svg viewBox="0 0 {W} {height:.0f}" xmlns="http://www.w3.org/2000/svg" '
f'style="width:100%;height:auto;font:12px sans-serif">\n '
+ "\n ".join(out) + "\n</svg>\n")
assert "\n\n" not in svg
OUT.write_text(svg, encoding="utf-8")
print(f"scris {OUT.name} ({W}x{height:.0f})")