#!/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("", 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'',
f'{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'',
f'{label}']
PCT_DA, PCT_NU = f"{d_da:.2f}".replace(".", ","), f"{d_nu:.2f}".replace(".", ",")
y_bot = y(SUP_NU)
parts = [
f'",
]
svg = "\n ".join(parts).replace("\n ", "\n")
assert "\n\n" not in svg, "linie goală în SVG"
for m in re.finditer(r']*>(.*?)', 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}%)")