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

126 lines
6.4 KiB
Python

#!/usr/bin/env python3
"""Buy hammer — locația: hammer la un nivel major vs. hammer în mijlocul range-ului.
Două panouri alăturate cu ACEEAȘI serie de 12 bare, pe aceeași scară și
aceeași axă de preț (candelele stau la aceleași y). Stânga: suport monthly
62.00, la 0,16 % sub low-ul hammer-ului. Dreapta: cel mai apropiat nivel de
sub el e la 58.00, adică un gol de 7,07 %. Nivelurile se desenează de mână,
cu coordonate absolute (liniile din Chart pornesc de la x=24 și ar trece
peste panoul vecin).
Rulare: python3 gen/strategii/hammer_mbt10_bh_locatie.py
"""
import re
import sys; sys.path.insert(0, "/workspace/atm-curs-dl")
from pathlib import Path
from svg_candles import Arrow, Chart, Mark, from_closes
OUT = Path(__file__).with_suffix(".svg")
GREEN, RED, BLUE, ORANGE, GREY = "#188a3e", "#c00", "#1a5fb4", "#b06000", "#555"
CLOSES = [65.40, 64.90, 64.40, 64.70, 64.00, 63.50, 63.05,
63.30, # 7 = hammer: o 63.05, h = c 63.30, l 62.10
63.35, # 8 = confirmare, close peste high-ul hammer-ului
63.80, 64.20, 64.60]
wicks = [(0.15, 0.15)] * len(CLOSES)
wicks[7] = (0.00, 0.95)
cds = from_closes(CLOSES, wicks=wicks, start=65.90)
ham = cds[7]
SUP_DA, SUP_NU, REZ_NU = 62.00, 58.00, 67.00
# --- verificări -------------------------------------------------------------
for i in range(1, len(cds)):
assert abs(cds[i].o - cds[i - 1].c) < 1e-9, f"open[{i}] != close[{i-1}]"
assert (round(ham.h, 2), round(ham.l, 2), round(ham.c, 2)) == (63.30, 62.10, 63.30)
corp, jos = ham.c - ham.o, ham.o - ham.l
assert jos >= 2 * corp, "bara 7 nu e hammer"
assert cds[8].c > ham.h, "bara 8 nu confirmă"
d_da = round((ham.l - SUP_DA) / SUP_DA * 100, 2)
assert d_da == 0.16 and d_da <= 2, d_da
d_nu = round((ham.l - SUP_NU) / SUP_NU * 100, 2)
assert d_nu == 7.07 and d_nu > 2, d_nu
d_rez = round((REZ_NU - ham.h) / ham.h * 100, 2)
assert d_rez == 5.85 and d_rez > 2, d_rez
assert not any(SUP_NU < p < REZ_NU for p in (SUP_NU, REZ_NU) if False)
# --- layout -----------------------------------------------------------------
W, PW = 760, 350
PX = [24, 24 + PW + 28]
SCALE, TOP = 34.0, 96
ARR = {
PX[0]: Arrow(4, "suport monthly: nivel urmărit de mulți —\naici au cumpărători motiv să apară",
price=SUP_DA, where="below", color=BLUE, dx=30, dy=22),
PX[1]: Arrow(7, "aceeași formă, dar nimic sub ea:\nfitilul n-a fost oprit de un nivel,\nci s-a oprit singur",
where="below", color=RED, dx=70, dy=30),
}
charts = []
for px in PX:
ch = Chart(candles=cds,
marks=[Mark(7, "hammer 63.30 / 62.10", ORANGE, "above", dy=-16)],
arrows=[ARR[px]],
scale=SCALE, gap=17, body_w=10, left=px + 34, width=px + PW, top=TOP)
body = ch._render()
assert f'viewBox="0 0 {px + PW} ' in body, f"panoul {px}: textul unei săgeți iese"
assert (ch.top, ch.left) == (TOP, px + 34), "Chart a mutat graficul: panourile s-ar dezalinia"
charts.append((ch, body.split(">", 1)[1].rsplit("</svg>", 1)[0].strip()))
(ch_da, body_da), (ch_nu, body_nu) = charts
assert ch_da.scale == ch_nu.scale and ch_da._pmax == ch_nu._pmax, "scări diferite"
y = ch_da._y
def level(px, price, text, color, below=False):
yy = y(price)
ty, anchor, tx = (yy + 14, "end", px + PW - 4) if below else (yy - 6, "end", px + PW - 4)
return [f'<line x1="{px}" y1="{yy:.1f}" x2="{px + PW}" y2="{yy:.1f}" stroke="{color}" '
f'stroke-width="1.5" stroke-dasharray="4 3"/>',
f'<text x="{tx}" y="{ty:.1f}" fill="{color}" text-anchor="{anchor}">{text}</text>']
def zone(px, p_top, p_bot, label, color, label_at_bottom=False):
y0, y1 = y(p_top), y(p_bot)
ly, anchor, lx = ((y1 - 12, "start", px + 8) if label_at_bottom
else (y0 - 6, "end", px + PW - 4))
return [f'<rect x="{px}" y="{y0:.1f}" width="{PW}" height="{y1 - y0:.1f}" fill="{color}" '
f'fill-opacity="0.12" stroke="{color}" stroke-width="1"/>',
f'<text x="{lx}" y="{ly:.1f}" fill="{color}" text-anchor="{anchor}" '
f'style="font-weight:bold">{label}</text>']
PCT_DA, PCT_NU = f"{d_da:.2f}".replace(".", ","), f"{d_nu:.2f}".replace(".", ",")
y_bot = y(SUP_NU)
parts = [
f'<svg viewBox="0 0 {W} {y_bot + 80:.0f}" xmlns="http://www.w3.org/2000/svg" '
f'style="width:100%;height:auto;font:12px sans-serif">',
'<text x="24" y="22" fill="#333" style="font-weight:bold">Hammer la un nivel vs. hammer '
'în mijlocul range-ului — daily, exemplu construit</text>',
f'<text x="{PX[0]}" y="48" fill="{GREEN}" style="font-weight:bold">DA — hammer pe suportul '
'monthly</text>',
f'<text x="{PX[1]}" y="48" fill="{RED}" style="font-weight:bold">NU — hammer în mijlocul '
'range-ului</text>',
f'<line x1="{PX[1] - 14}" y1="36" x2="{PX[1] - 14}" y2="{y_bot + 10:.0f}" stroke="#ccc"/>',
*zone(PX[0], ham.l, SUP_DA, f"low-ul hammer-ului: {ham.l:.2f}", GREEN),
*zone(PX[1], ham.l, SUP_NU, f"low-ul hammer-ului: {ham.l:.2f}", RED),
f'<text x="{PX[0] + PW - 4}" y="{y(SUP_DA) + 34:.1f}" fill="{GREEN}" text-anchor="end" '
f'style="font-weight:bold">de la {ham.l:.2f} la {SUP_DA:.2f} = {PCT_DA} % — la nivel</text>',
f'<text x="{PX[1] + 8}" y="{y(SUP_NU) - 12:.1f}" fill="{RED}" style="font-weight:bold">'
f'de la {ham.l:.2f} la {SUP_NU:.2f} = gol de {PCT_NU} % — hammer în aer</text>',
*level(PX[0], SUP_DA, "suport monthly 62.00", BLUE, below=True),
*level(PX[1], REZ_NU, "rezistența range-ului 67.00", BLUE, below=True),
*level(PX[1], SUP_NU, "baza range-ului 58.00", BLUE, below=True),
body_da, body_nu,
f'<text x="24" y="{y_bot + 34:.0f}" fill="{GREY}">Completare proprie: materialul cere '
'„un nivel major (weekly / monthly / quarterly), nu mijlocul</text>',
f'<text x="24" y="{y_bot + 50:.0f}" fill="{GREY}">range-ului", dar nu dă un prag numeric. '
f'Pragul de 1-2 % e împrumutat de la buy doji, ca să ai ce</text>',
f'<text x="24" y="{y_bot + 66:.0f}" fill="{GREY}">măsura: aici {PCT_DA} % în stânga, '
f'{PCT_NU} % în dreapta.</text>',
"</svg>",
]
svg = "\n ".join(parts).replace("\n </svg>", "\n</svg>")
assert "\n\n" not in svg, "linie goală în SVG"
for m in re.finditer(r'<text x="([\d.]+)"[^>]*>(.*?)</text>', body_da):
assert float(m.group(1)) < PX[1] - 14, f"text din DA trece în NU: {m.group(2)}"
OUT.write_text(svg, encoding="utf-8")
print(f"scris {OUT.name} ({W}x{y_bot + 80:.0f}); DA {d_da}% NU {d_nu}% (rezistență {d_rez}%)")