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
126 lines
6.4 KiB
Python
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}%)")
|