#!/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 ' + "\n ".join(out) + "\n\n") OUT.write_text(svg, encoding="utf-8") print(f"scris {OUT} ({W}x{height:.0f})")