#!/usr/bin/env python3
"""Ce e doji și ce nu — patru candele cu același high 50.10 și low 49.20.
Citește `gen/strategii/buy_doji_doji_forma.spec.txt` și scrie
`gen/strategii/buy_doji_doji_forma.svg`. Definiție de o candelă (stilul
vechi, candele izolate): diferă doar corpul, deci testul „corp ≤ 10 % din
range, fitil în ambele părți" se vede direct pe formă.
Desenat direct, nu cu `Chart`: `tag`/`note` din svg_candles pun etichetele
pe un singur rând sau în blocul de jos, iar aici fiecare etichetă trebuie să
stea lipită sub candela ei, pe 2-3 rânduri.
Rulare: python3 gen/strategii/buy_doji_doji_forma.py
"""
from pathlib import Path
OUT = Path(__file__).with_suffix(".svg")
GREEN, RED, GREY = "#188a3e", "#c00", "#555"
H, L = 50.10, 49.20
RNG = H - L
# (nume, o, h, l, c, verdict, rânduri de etichetă sub candelă)
CANDLES = [
("A", 49.60, 50.10, 49.20, 49.55, "DA", ["DA: corp 0.05", "= 5.6 % din range"]),
("B", 49.62, 50.10, 49.20, 49.53, "DA", ["DA, la limită:", "corp 0.09 = 10 %", "(parametrul 10)"]),
("C", 49.80, 50.10, 49.20, 49.50, "NU", ["NU: corp 0.30", "= 33 %"]),
("D", 50.05, 50.10, 49.20, 50.10, "NU", ["NU: corp mic (5.6 %),", "dar fără fitil sus"]),
]
# --- assert-urile din spec --------------------------------------------------
ratio = {n: round(abs(c - o) / (h - l), 4) for n, o, h, l, c, *_ in CANDLES}
for n, o, h, l, c, *_ in CANDLES:
assert h == H and l == L, (n, h, l) # 1
assert ratio == {"A": 0.0556, "B": 0.1000, "C": 0.3333, "D": 0.0556}, ratio # 2
for n, o, h, l, c, *_ in CANDLES[:2]: # 3
assert ratio[n] <= 0.10 and min(o, c) > l and max(o, c) < h, n
assert ratio["C"] > 0.10 # 4
_, oD, hD, _, cD, *_ = CANDLES[3]
assert max(oD, cD) == hD
# --- geometrie ---------------------------------------------------------------
SCALE, TOP, W = 260, 72, 740
XS = [150, 300, 450, 600] # centrele candelelor
BODY_W = 26
def y(p):
return TOP + (H - p) * SCALE
oA, cA = CANDLES[0][1], CANDLES[0][4]
assert y(oA) < y(cA), "open A trebuie să fie mai sus pe ecran decât close A" # 5
out = []
t = out.append
def text(x, yy, s, color=GREY, anchor="start", bold=False, size=12):
style = f' style="font-weight:bold;font-size:{size}px"' if bold else (
f' style="font-size:{size}px"' if size != 12 else "")
t(f'{s}')
text(24, 22, "Ce e doji și ce nu — aceeași bară 50.10 / 49.20", "#333", bold=True)
# high și low comune: linii subțiri pe toată lățimea
for p in (H, L):
t(f'')
text(XS[0] - 36, y(H) + 4, "high 50.10", anchor="end")
text(XS[0] - 36, y(L) + 4, "low 49.20", anchor="end")
label_y0 = y(L) + 24
for (n, o, h, l, c, verdict, lines), x in zip(CANDLES, XS):
color = GREEN if c >= o else RED
vcol = GREEN if verdict == "DA" else RED
t(f'')
top, bot = y(max(o, c)), y(min(o, c))
t(f'')
text(x, y(H) - 14, verdict, vcol, "middle", bold=True, size=18)
for k, s in enumerate(lines):
text(x, label_y0 + 16 * k, s, vcol, "middle", bold=(k == 0))
# prețurile pe A: open la stânga (mai sus), close la dreapta (mai jos)
x = XS[0]
text(x - 36, y(oA) + 2, "open 49.60", anchor="end")
t(f'')
text(x + 36, y(cA) + 8, "close 49.55")
t(f'')
# D: close = high, deci fitilul de sus lipsește
x = XS[3]
text(x + 20, y(H) + 12, "← fără fitil sus", RED)
cap_y = label_y0 + 16 * 3 + 14
text(24, cap_y, f"Test doji: corp ≤ 10 % din range ({RNG:.2f}) și fitil în ambele părți. "
"Scară: 0.10 = 26 px.")
height = cap_y + 12
svg = (f'\n")
OUT.write_text(svg, encoding="utf-8")
print(f"scris {OUT} ({W}x{height:.0f})")