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

129 lines
6.6 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 % de low-ul hammer-ului (diferența împărțită la nivel, ca în
tot capitolul). 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 + 96:.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'Cursul dă 0,5 %, la buy doji și pe 60 de minute; 1-2 % pe</text>',
f'<text x="24" y="{y_bot + 66:.0f}" fill="{GREY}">daily e adaptarea mea, ca să ai ce '
f'măsura: aici {PCT_DA} % în stânga, {PCT_NU} % în dreapta.</text>',
f'<text x="24" y="{y_bot + 82:.0f}" fill="{GREY}">Distanța se împarte la nivel, nu la '
f'low-ul hammer-ului — aceeași convenție în tot capitolul.</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 + 96:.0f}); DA {d_da}% NU {d_nu}% (rezistență {d_rez}%)")