"""Buy Doji — ieșirea: prima treime la țintă, a doua pe trailing stop, limita de 8 candele. Citește `gen/strategii/buy_doji_iesire.spec.txt`. Barele vin din `buy_doji_ansamblu.serie()` — același trade ca în restul capitolului (doji 50.10/49.25, intrare 50.40, stop 49.20, ținta 1 51.60). A — prima treime iese la ținta 51.60; apoi două candele cu gap urcă linia de trailing stop (51.95, apoi 52.60); a doua treime iese la prima închidere sub linie, 52.30 (+1.58R). B — același setup, dar prețul stă 8 candele între stop și țintă: ieși la close-ul celei de-a 8-a, 50.70 (+0.25R). Barele de după intrare din panoul B sunt CONSTRUITE. """ import re import sys from pathlib import Path sys.path.insert(0, "/workspace/atm-curs-dl") sys.path.insert(0, str(Path(__file__).parent)) from svg_candles import Chart, Level, Zone, Arrow, from_closes # noqa: E402 from buy_doji_ansamblu import serie, CONF, ENTRY, SL, TP1, R, trail # noqa: E402 OUT = Path(__file__).with_suffix(".svg") S = 10 # prima bară arătată GREEN, RED, ORANGE, BLUE, GREY = "#188a3e", "#c00", "#b06000", "#1a5fb4", "#555" W, VGAP = 760, 14 full = serie() steps, iesire = trail(full) # [(21, 51.95), (27, 52.60)], 28 a = full[S:iesire + 1] cf = CONF - S EXIT_A = a[iesire - S].c R_A = round((EXIT_A - ENTRY) / R, 2) tp_i = next(i for i in range(CONF + 1, len(full)) if full[i].h >= TP1) # --- aserțiunile panoului A ------------------------------------------------------- assert steps == [(21, 51.95), (27, 52.60)] and iesire == 28, (steps, iesire) for i, t in steps: # gap-urile sunt reale assert full[i].l > full[i - 1].h, f"bara {i} nu deschide cu gap" assert all(full[i].c >= steps[0][1] for i in range(steps[0][0], iesire)), \ "ar fi ieșit mai devreme: un close sub linia de trail" assert EXIT_A == 52.30 and R_A == 1.58 and EXIT_A < steps[-1][1] assert tp_i == 19 and full[tp_i].h >= TP1 > full[tp_i - 1].h, tp_i # --- panoul B: 8 candele între stop și țintă -------------------------------------- b = full[S:CONF + 1] b += from_closes([50.60, 50.30, 50.90, 50.50, 51.10, 50.80, 51.20, 50.70], wick=0.15, start=ENTRY) opt = b[cf + 1:] EXIT_B = opt[-1].c R_B = round((EXIT_B - ENTRY) / R, 2) assert len(opt) == 8, len(opt) for i in range(1, len(b)): assert abs(b[i].o - b[i - 1].c) < 1e-9, f"B: open[{i}] != close[{i-1}]" assert all(c.h < TP1 and c.l > SL for c in opt), "B: o bară atinge ținta sau stopul" assert (round(max(c.h for c in opt), 2), round(min(c.l for c in opt), 2)) == (51.35, 50.15) assert EXIT_B == 50.70 and R_B == 0.25 print(f"iesire A: treapta 1 {steps[0][1]:.2f}, treapta 2 {steps[1][1]:.2f}, " f"ieșire {EXIT_A:.2f} = +{R_A}R (ținta 1 {TP1:.2f} la bara {tp_i}); " f"B: ieșire {EXIT_B:.2f} = +{R_B}R") common: dict = dict(scale=40.0, gap=18, body_w=9, left=48, width=W) ch_a = Chart( candles=a, levels=[Level(TP1, "ținta 1: 51.60 (= 1R)", GREEN, "2 3"), Level(ENTRY, "intrarea: 50.40", GREY, "4 3"), Level(SL, "stopul: 49.20", RED, "4 3")], zones=[Zone(cf, tp_i - S, "", GREEN), Zone(tp_i - S + 1, iesire - S, "", BLUE)], arrows=[ Arrow(tp_i - S, "ținta 1 atinsă: vinzi prima treime\nai luat exact cât riscai (1R)", where="above", price=TP1, color=GREEN, dx=10, dy=-26), Arrow(steps[0][0] - S, "gap: low-ul de azi e peste high-ul de ieri\nlinia de trail urcă la 51.95", where="below", price=steps[0][1], color=ORANGE, dx=130, dy=40), Arrow(steps[1][0] - S, "încă un gap: linia urcă la 52.60\nurcă doar, nu coboară niciodată", where="above", price=steps[1][1], color=ORANGE, dx=150), Arrow(iesire - S, "prima închidere sub linie: ieși\ncu a doua treime, la 52.30 (+1.58R)", where="below", price=EXIT_A, color=RED, dx=110, dy=96), ], title="A — prima treime la țintă, a doua pe trailing stop", **common, ) ch_b = Chart( candles=b, levels=[Level(TP1, "ținta 1: 51.60 — neatinsă", GREEN, "2 3"), Level(ENTRY, "intrarea: 50.40", GREY, "4 3"), Level(SL, "stopul: 49.20 — neatins", RED, "4 3")], zones=[Zone(cf + 1, len(b) - 1, "", GREY, p_top=TP1, p_bot=SL)], arrows=[ Arrow(len(b) - 1, "a 8-a candelă: ieși oricum, la close 50.70 (+0.25R)\nbanii blocați aici lipsesc din alt trade", where="below", price=EXIT_B, color=GREY, dx=120, dy=40), ], title="B — limita de 8 candele: nici ținta, nici stopul", **common, ) for k, c in enumerate(opt, 1): c.tag = str(k) def panou(ch, sus_de=0.0): ch.top += sus_de svg = ch.render() h = float(re.search(r'viewBox="0 0 [\d.]+ ([\d.]+)"', svg).group(1)) # render() mută `top`/`left` intern și le restaurează: tot ce desenăm noi # după randare trebuie deplasat cu aceiași pixeli off_y = float(re.search(r']*stroke="#1a1a1a"', svg).group(1)) - ch._cx(0) return svg[svg.index(">") + 1:svg.rindex("")].strip(), h, off_x, off_y body_a, h_a, ax, ay = panou(ch_a) Y, X = (lambda p: ch_a._y(p) + ay), (lambda i: ch_a._cx(i) + ax) (i1, t1), (i2, t2) = [(i - S, t) for i, t in steps] extra_a = [ # linia de trail, în trepte f'', f'' f'linia de trailing stop', # etichetele zonelor, lângă ele (Zone le-ar ridica deasupra candelelor vecine) f'' f'prima treime: de la intrare la ținta 1:1', f'' f'a doua treime: merge pe trailing stop', ] extra_a.append(f'A treia treime o lași mai ' f'departe, cu stopul mutat la intrare (break-even).') h_a = max(h_a, Y(SL) + 112) body_b, h_b, bx, by = panou(ch_b, h_a + VGAP) extra_b = [ f'8 candele în care nu se întâmplă nimic', ] H = h_b + 8 svg = "\n ".join([ f'', body_a, *extra_a, f'', body_b, *extra_b, ]) + "\n" assert "\n\n" not in svg, "linie goală în SVG" for tx, ty in re.findall(r'