#!/usr/bin/env python3
"""Buy Doji — locația: doji la nivel vs. doji în aer (exemplu construit).
Citește `gen/strategii/buy_doji_locatie.spec.txt`. Două panouri alăturate cu
ACEEAȘI serie de 10 bare, pe aceeași scară și aceeași axă de preț (candelele
stau la aceleași y). Stânga: pivot lunar 49.00, la 0,51 % sub low-ul
dojiului. Dreapta: S1 46.00 și PP 53.50 — cel mai apropiat nivel sub doji e
la 7,07 %. Nivelurile se desenează de mână, cu coordonate absolute (liniile
din Chart pornesc mereu de la x=24, ar trece peste panoul vecin).
Rulare: python3 gen/strategii/buy_doji_locatie.py
"""
import re
import sys
sys.path.insert(0, "/workspace/atm-curs-dl")
from svg_candles import Arrow, Chart, Mark, from_closes # noqa: E402
OUT = "/workspace/atm-curs-dl/gen/strategii/buy_doji_locatie.svg"
CLOSES = [52.20, 51.40, 50.60, 49.65, 49.60, 49.90, 50.40, 50.80, 51.10, 51.50]
wicks = [(0.15, 0.15)] * len(CLOSES)
wicks[4] = (0.45, 0.35) # doji: h 50.10 / l 49.25
cds = from_closes(CLOSES, wicks=wicks, start=53.00)
doji = cds[4]
S1_DA, S1_NU, PP_NU = 49.00, 46.00, 53.50
NIVELURI_NU = [S1_NU, PP_NU]
# --- assert-urile din spec ---------------------------------------------------
for i in range(1, len(cds)): # 1
assert cds[i].o == cds[i - 1].c, f"open[{i}] != close[{i-1}]"
assert abs(doji.h - 50.10) < 1e-9 and abs(doji.l - 49.25) < 1e-9
assert abs(doji.o - doji.c) <= 0.10 * (doji.h - doji.l), "bara 4 nu e doji"
assert cds[6].c > doji.h, "bara 6 nu confirmă"
d_da = round((doji.l - S1_DA) / S1_DA * 100, 2) # 2
assert d_da == 0.51 and d_da <= 2
d_nu = round((doji.l - S1_NU) / S1_NU * 100, 2) # 3
assert d_nu == 7.07 and d_nu > 2 and (PP_NU - doji.h) > 0
assert not any(S1_NU < p < PP_NU for p in NIVELURI_NU)
d_pp = round((PP_NU - doji.l) / doji.l * 100, 2) # PP e deasupra, mai departe
assert d_pp == 8.63 and d_pp > d_nu
# --- layout: două panouri, aceeași axă --------------------------------------
BLUE, GREEN, RED, ORANGE, GREY = "#1a5fb4", "#188a3e", "#c00", "#b06000", "#555"
W, PW = 760, 350 # lățime totală, lățime panou
PX = [24, 24 + PW + 28] # x-ul stâng al fiecărui panou
SCALE, TOP = 36.0, 96 # px/unitate și y-ul celui mai mare preț
ARR = {
PX[0]: Arrow(2, "pivot lunar: nivel urmărit\nde mulți — aici apar\ncumpărătorii",
price=S1_DA, where="below", color=BLUE, dy=20),
PX[1]: Arrow(4, "aceeași formă, dar nimic sub el\nnu are cine să-l susțină",
where="below", color=RED, dx=100),
}
charts = []
for px in PX:
ch = Chart(candles=cds, marks=[Mark(4, "doji", ORANGE, "above", dy=-16),
Mark(6, "confirmare", GREEN, "above", dy=-26)],
arrows=[ARR[px]],
scale=SCALE, gap=17, body_w=10, left=px + 34, width=px + PW, top=TOP)
# `_render()`, nu `render()`: al doilea restaurează top/left, iar zonele și
# etichetele desenate de mână ar pica deplasate față de body-ul randat
body = ch._render()
assert f'viewBox="0 0 {px + PW} ' in body, "textul unei săgeți iese din panou"
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" # 4
y = ch_da._y # axa e comună: același y pe ambele panouri
def level(px, price, text, color, below=False, right=False):
yy = y(price)
ty, anchor, tx = (yy + 14, "start", px + 6) if below else (yy - 6, "end", px + PW - 4)
if below and right: # ca să nu stea în tija unei săgeți
anchor, tx = "end", px + PW - 4
return [f'',
f'{text}']
def zone(px, p_top, p_bot, label, color, label_at_bottom=False):
"""Dreptunghi de evidențiere pe toată lățimea panoului, între două prețuri."""
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(S1_NU)
parts = [
f'",
]
svg = "\n ".join(parts).replace("\n ", "\n")
assert "\n\n" not in svg, "linie goală în SVG"
# nimic din panoul stâng nu trece de separator (etichete ancorate pe candele)
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)}"
with open(OUT, "w") as f:
f.write(svg)
print(f"scris {OUT} ({W}x{y_bot + 62:.0f}); DA +{d_da}% NU {d_nu}% (PP {d_pp}%)")