Files
atm-curs-dl/gen/strategii/hammer_mbt10_bh_labu.py
Claude Agent f60d32cb34 17_STRATEGII/06_hammer_mbt10: toate cele 9 restante, plus cele 3 cosmetice
Cea mai grava, si cea care a cerut refacerea seriei: exemplul central de
MBT-10 era luat de pe 60 de minute si bagat in procedura de daily, cu barele
numite "zile", desi sursa spune explicit de ce nu merge pe daily. Pe langa
asta, suportul fusese schimbat din 88.00 (testat de trei ori, cu compresie,
cum e in sursa) in 87.50 cu un double bottom si o divergenta de volum
imprumutate din alt exemplu.

Seria e refacuta pe exemplul materialului, cu assert pe cele trei atingeri ale
lui 88.00 si pe compresia varfurilor. Adaosurile au fost scoase, nu doar
marcate: cu ele in desen, suportul din sursa n-ar fi avut sens. Aritmetica a
supravietuit neatinsa.

Restul: stopul de 60.40 sta acum sub o structura reala; pivotul daily nu mai e
o linie fixa peste tot graficul, ci un segment peste ziua intrarii; SL-ul de
pe LABU respecta regula proprie de 5 centi; citatul de la A2 nu mai e dat ca
al autorului; pragul de 1-2% isi spune originea (cursul da 0,5%, pe 60 min);
o singura conventie de distanta, impartirea la nivel; swing low definit la fel
in pas si in glosar.

eticheta_libera a prins 7 suprapuneri reale. La bh_aplus a trebuit rulat pe
fiecare panou separat: wrapper-ul le muta cu <g transform>, pe care unealta
nu-l citeste - de notat pentru data viitoare.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ThN7ixZrowca8aTGrK1MUJ
2026-10-02 05:29:33 +00:00

190 lines
9.1 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/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.55 (low − 5 cenți, regula proprie a capitolului) ș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")
sys.path.insert(0, "/workspace/atm-curs-dl/gen")
from eticheta_libera import eticheta_libera # noqa: E402
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
ENTRY = 81.30 # intrare = close-ul confirmării, peste 80.90 (construit)
SL = 79.55 # low 79.60 − 5 cenți: marja folosită în tot capitolul
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 = low-ul hammer-ului (cel mai jos din 22:25) minus marja de 5 cenți.
# Prețurile LABU sunt construite, deci se aplică regula proprie din capitol
# (summaries/17_STRATEGII/06_hammer_mbt10.md, pasul A4): stopul nu stă
# exact pe low, ci cu 5 cenți sub el.
min_22_25 = min(c.l for c in cds[22:25])
assert abs(min_22_25 - cds[23].l) < 1e-9, f"min low[22:25]={min_22_25} != low[23]"
assert abs(SL - (cds[23].l - 0.05)) < 1e-9, f"SL {SL} nu e low 79.60 − 5 cenți"
assert SL < min_22_25, "linia stopului trebuie să stea sub toate fitilele"
# 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 79.55 = low 79.60 − 5 cenți (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ă?)"
# eticheta de stop stă lipită de linia ei prin construcție: verificăm doar
# candelele și media mobilă de sub ea
eticheta_libera(svg, "bh_labu",
{"SL 79.55 = low 79.60 − 5 cenți (construit)",
"hammer 80.90 / 79.60 (construit)"}, rects=True, segmente=False)
# --- 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 {SL:.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")