Files
atm-curs-dl/gen/strategii/hammer_mbt10_bh_labu.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

178 lines
8.4 KiB
Python

#!/usr/bin/env python3
"""LABU — buy hammer la suportul monthly, +20 % în jumătate de lună (Nivel 2.5).
Citește `gen/strategii/hammer_mbt10_bh_labu.spec.txt` și scrie
`gen/strategii/hammer_mbt10_bh_labu.svg`: 38 de candele daily, downtrend +
consolidare până pe suportul monthly 80.00, hammer chiar pe nivel
(o 80.80 / h 80.90 / l 79.60 / c 80.90), confirmarea de la 81.30 (close peste
high-ul hammer-ului) și urcarea în ~11 bare până la 97.56 = +20,0 % față de
intrare.
REAL din curs (`18_2025-09-23.md:721-726`): că e buy hammer la suportul
monthly și că a dat +20 % în jumătate de lună. CONSTRUITE de sumarizator:
suportul 80.00, cifrele hammer-ului (80.80/80.90/79.60), intrarea 81.30, SL
79.60 și seria de prețuri — materialul nu dă entry, SL sau TP pentru cazul
ăsta. Legenda din desen spune exact asta.
Rulare: python3 gen/strategii/hammer_mbt10_bh_labu.py
"""
import re
import sys
from pathlib import Path
sys.path.insert(0, "/workspace/atm-curs-dl")
from svg_candles import Chart, Level, MA, Mark, from_closes # noqa: E402
OUT = Path(__file__).with_suffix(".svg")
# --- nivelurile din specificație (toate CONSTRUITE) ------------------------
SUPPORT = 80.00 # suport monthly (construit)
HAMMER_H = 80.90 # high-ul hammer-ului = close (H=C)
HAMMER_L = 79.60 # low-ul hammer-ului = SL propus
ENTRY = 81.30 # intrare = close-ul confirmării, peste 80.90 (construit)
SL = 79.60 # SL sub low-ul hammer-ului (construit)
PEAK = 97.56 # max close de după intrare: 97.56 / 81.30 = +20,0 % (real)
# --- seria de close-uri din specificație (38 bare, o = close precedent) ----
closes = [
# downtrend + consolidare 0-22
92.00, 90.50, 91.20, 89.40, 88.10, 88.60, 87.00, 85.80, 86.30, 84.90,
83.60, 82.40, 83.00, 81.80, 80.70, 81.20, 80.20, 80.80, 81.50, 80.40,
80.10, 80.60, 80.80,
80.90, # 23 = hammer: o 80.80, c 80.90, h 80.90, l 79.60
81.30, # 24 = confirmare, close > high[23] = intrare
# 25..37 = 13 bare de urcare; max close 97.56 / 81.30 - 1 = +20,0 %
81.90, 82.60, 83.40, 84.20, 85.10, 86.00, 87.00, 88.10, 89.30, 90.60,
92.00, 93.50, 97.56,
]
assert len(closes) == 38, f"seria are {len(closes)} bare, spec cere 38"
# wick-uri: (up, down). Restul (0.30, 0.40); hammer-ul are fitil jos 1.20 și
# zero fitil sus ca low-ul/high-urile cerute de asserts să iasă exact.
wicks = [(0.30, 0.40)] * len(closes)
wicks[23] = (0.00, 1.20) # hammer: h 80.90, l 79.60
# volume construite (nu sunt cifre de curs, doar panoul de volum cerut de
# stil): vânzare în creștere spre suport, explozie la hammer + confirmare.
vols = [
52, 50, 47, 49, 45, 46, 43, 45, 41, 43,
44, 47, 42, 45, 48, 46, 50, 44, 43, 46,
51, 49, 55, 88, 84,
70, 62, 58, 54, 52, 50, 48, 46, 45, 47,
55, 62, 72,
]
assert len(vols) == len(closes), f"volume {len(vols)} != bare {len(closes)}"
cds = from_closes(closes, wicks=wicks, vols=vols, start=94.00)
assert abs(cds[0].o - 94.00) < 1e-9 and cds[0].c < cds[0].o, "prima bară nu e roșie"
for a, b in zip(cds, cds[1:]):
assert abs(a.c - b.o) < 1e-9, "open != close-ul barei anterioare"
# --- assert-urile din specificație (toate, ca assert reale) ----------------
# 1. hammer-ul stă pe suportul monthly 80.00
assert cds[23].l == 79.60 <= SUPPORT <= cds[23].h == HAMMER_H, \
f"hammer 79.60 <= 80.00 <= 80.90 nu iese: {cds[23].l}/{cds[23].h}"
# 2. forma hammer-ului: o = close[22] = 80.80, corp 0.10, fitil jos 1.20,
# 1.20 >= 2 * 0.10, close == high (fără fitil sus)
assert cds[23].o == cds[22].c == 80.80, "open[23] nu e close[22] = 80.80"
body = abs(cds[23].c - cds[23].o)
lower_wick = min(cds[23].o, cds[23].c) - cds[23].l
upper_wick = cds[23].h - max(cds[23].o, cds[23].c)
assert abs(body - 0.10) < 1e-9, f"corpul hammer-ului e {body}, nu 0.10"
assert abs(lower_wick - 1.20) < 1e-9, f"fitilul jos e {lower_wick}, nu 1.20"
assert abs(upper_wick) < 1e-9, f"hammer-ul are fitil sus {upper_wick}"
assert lower_wick >= 2 * body, f"1.20 < 2 * {body} (nu e hammer valid)"
assert cds[23].c == cds[23].h == HAMMER_H, "close[23] != high[23] == 80.90"
# 3. confirmarea: close[24] == 81.30 == intrare și > high[23] == 80.90
assert abs(cds[24].c - ENTRY) < 1e-9, f"close[24]={cds[24].c} != intrare 81.30"
assert cds[24].c > cds[23].h == HAMMER_H, "confirmarea nu închide peste 80.90"
# 4. SL = min(low[22:25]) = low-ul hammer-ului = 79.60
min_22_25 = min(c.l for c in cds[22:25])
assert abs(min_22_25 - SL) < 1e-9, f"min low[22:25]={min_22_25} != SL {SL}"
assert abs(SL - cds[23].l) < 1e-9, "SL nu e low-ul hammer-ului"
# 5. procentul REAL al cursului: +20,0 % de la intrare la vârf
peak = max(c.c for c in cds[25:38])
pct = round((peak / ENTRY - 1) * 100, 1)
assert abs(peak - PEAK) < 1e-9, f"max close[25:38]={peak} != 97.56"
assert pct == 20.0, f"procentul iese {pct}, nu +20,0 %"
assert abs(peak / ENTRY - 1.20) < 1e-9, "97.56 / 81.30 nu e exact 1.20"
# 6. downtrend/consolidare înainte de hammer
assert cds[23].c < cds[0].c - 8, f"close[23]={cds[23].c} nu e cu 8 sub close[0]"
# 7. ordinea nivelurilor
assert SL < ENTRY < peak, f"SL/entry/vârf: {SL}/{ENTRY}/{peak}"
# --- verificarea serii (stil + geometrie) ----------------------------------
entry = cds[24].c
w = 760
ch = Chart(
candles=cds,
levels=[
Level(SUPPORT, "suport monthly 80.00", "#777",
dash="4 3", label_at="left"),
Level(SL, "SL sub low-ul hammer-ului 79.60 (construit)", "#c00",
label_at="right"),
Level(ENTRY, "intrare 81.30 (construit)", "#188a3e", label_at="right"),
],
marks=[
Mark(23, "hammer 80.90 / 79.60 (construit)", "#b06000", "below"),
Mark(37, f"+20 % (cifra cursului): {peak:.2f} / {ENTRY:.2f}",
"#188a3e", "above"),
],
mas=[MA(20, "#b06000", dash="4 3", text="SMA-20")],
scale=8.0, gap=15, body_w=9, left=44, width=w, vol_h=70,
vol_label="volum", title="LABU — daily",
caption="nivelurile de preț sunt construite de sumarizator.",
)
# note pe bare: explicațiile hammer-ului/confirmării și legenda obligatorie
# (textul lung de nivel ar intra peste candele, deci stă în blocul de jos).
cds[0].note = ("bara 23 = hammer: o 80.80, h 80.90, l 79.60, c 80.90 — "
"corp 0.10, fitil jos 1.20 (H=C), pe suportul 80.00")
cds[1].note = ("bara 24 = confirmare: close 81.30 — close peste high-ul "
"hammer-ului 80.90 = intrare 81.30 (construit)")
cds[2].note = ("Procentul (+20 % în jumătate de lună) și contextul (suport "
"monthly) sunt ale cursului;")
svg = ch.render()
assert "\n\n" not in svg and "<svg" in svg, "SVG invalid (linie goală?)"
# --- citirea SVG-ului: etichete în panză, fără suprapuneri -----------------
TEXT = re.compile(r'<text x="([-\d.]+)" y="([-\d.]+)"([^>]*)>(.*?)</text>', re.S)
H = float(re.search(r'viewBox="0 0 (\d+) ([\d.]+)"', svg).group(2))
labels = []
for m in TEXT.finditer(svg):
x, y, attrs, s = float(m.group(1)), float(m.group(2)), m.group(3), m.group(4)
anchor = "end" if 'text-anchor="end"' in attrs else \
("middle" if 'text-anchor="middle"' in attrs else "start")
bold = "bold" in attrs
wpx = len(s) * 12 * (0.62 if bold else 0.52)
left = x - wpx if anchor == "end" else (x - wpx / 2 if anchor == "middle" else x)
labels.append((left, left + wpx, y, s))
for left, right, y, s in labels:
assert left >= -2 and right <= w + 2, f"text iese din panză: {s!r} [{left:.0f},{right:.0f}]"
assert 0 <= y <= H, f"text în afara înălțimii: {s!r} (y={y:.0f})"
for i in range(len(labels)):
li, ri, yi, si = labels[i]
for j in range(i + 1, len(labels)):
lj, rj, yj, sj = labels[j]
if abs(yi - yj) < 12 and li < rj - 4 and lj < ri - 4:
raise AssertionError(f"texte suprapuse: {si!r} / {sj!r}\n {labels[i]}\n {labels[j]}")
# candelele sunt o serie continuă, lipită
xs = [ch._cx(i) for i in range(len(cds))]
assert xs[-1] + ch.body_w / 2 < w, f"ultima candelă iese din panză (x={xs[-1]})"
assert all(xs[i + 1] - xs[i] == ch.gap for i in range(len(xs) - 1)), "serie cu goluri"
print(f"LABU: {len(cds)} bare, hammer {cds[23].l:.2f}/{cds[23].h:.2f} pe suport "
f"{SUPPORT:.2f}, intrare {entry:.2f}, SL {cds[23].l:.2f}, vârf {peak:.2f}")
print(f"procent: {peak:.2f} / {entry:.2f} - 1 = +{pct:.1f} % (cifra cursului)")
print(f"scris {OUT} ({w}x{H:.0f}); {len(labels)} etichete verificate, fără suprapuneri")
OUT.write_text(svg, encoding="utf-8")