#!/usr/bin/env python3 """M2D, pasul 5 — ieșirea: TP1 la 1:1 și restul pe trail, long și short. Pentru `summaries/17_STRATEGII/03_m2d.md`, marcajul ``. Sus, LONG: barele de după trigger din seria de 3 minute a diagramei de ansamblu. Prima tranșă iese la TP1 = 1:1. Restul merge pe bull trail: linia sare pe low-ul fiecărei bare care se ÎNCHIDE peste high-ul precedentei și nu coboară niciodată. Ieșirea vine la prima bară care se închide sub linie — fitilul de dedesubt al barei dinainte nu scoate pe nimeni. Jos, SHORT: exact aceeași serie oglindită în jurul lui 100.00 (p' = 200 − p), deci bear trail-ul e construit de același cod, doar cu semnele întoarse. Oglinda e calculată, nu desenată din ochi. Rulare: python3 gen/strategii/m2d_iesire.py """ import sys from pathlib import Path sys.path.insert(0, "/workspace/atm-curs-dl") sys.path.insert(0, str(Path(__file__).resolve().parent)) from svg_candles import Arrow, Candle, Chart # noqa: E402 import m2d_duo as duo # noqa: E402 OUT = Path(__file__).with_suffix(".svg") W, SCALE, GAP, LEFT = 680, 150.0, 40, 66 GREEN, RED, ORANGE, GREY = "#188a3e", "#c00", "#b06000", "#555" OGLINDA = 200.0 # axa de oglindire: p' = OGLINDA − p LONG = [(99.98, 100.08, 99.94, 100.02), (100.02, 100.45, 100.00, 100.40), # 1: trigger, intrare (100.40, 100.60, 100.35, 100.55), (100.55, 100.75, 100.50, 100.70), (100.70, 101.00, 100.65, 100.95), # 4: TP1 atins (100.95, 101.10, 100.88, 101.05), (101.05, 101.15, 100.95, 101.00), (101.00, 101.40, 100.95, 101.35), (101.35, 101.55, 101.25, 101.45), (101.45, 101.50, 101.20, 101.25), # 9: fitil sub linie, close peste (101.25, 101.30, 101.05, 101.10), # 10: close sub linie = ieși (101.10, 101.15, 100.90, 100.95)] TRIG, SL_LONG = 1, 99.90 # stopul vine din pasul 4 SHORT = [(OGLINDA - o, OGLINDA - l, OGLINDA - h, OGLINDA - c) for o, h, l, c in LONG] SL_SHORT = OGLINDA - SL_LONG def trepte(bars, trig, sus): """Linia de trail, bară cu bară, de la trigger încolo. sus=True: bull trail — sare pe low-ul barei care se ÎNCHIDE peste high-ul precedentei. sus=False: bear trail — pe high-ul barei care se închide sub low-ul precedentei. `None` cât timp linia nu există încă. """ linie, out, iesire = None, [], None for i, (o, h, l, c) in enumerate(bars): if i > trig and linie is not None and ((c < linie) if sus else (c > linie)): iesire = i out.append(linie) break if i > trig: p = bars[i - 1] if (c > p[1]) if sus else (c < p[2]): linie = l if sus else h out.append(linie) return out + [linie] * (len(bars) - len(out)), iesire # ---------------------------------------------------------------- verificări for nume, bars in (("LONG", LONG), ("SHORT", SHORT)): for i, (o, h, l, c) in enumerate(bars): assert h >= max(o, c) and l <= min(o, c), f"{nume}[{i}]: high/low incoerent" if i: assert abs(o - bars[i - 1][3]) < 1e-9, f"{nume}[{i}]: open != close precedent" E_L, E_S = LONG[TRIG][3], SHORT[TRIG][3] R_L, R_S = round(E_L - SL_LONG, 2), round(SL_SHORT - E_S, 2) TP_L, TP_S = round(E_L + R_L, 2), round(E_S - R_S, 2) assert (E_L, R_L, TP_L) == (100.40, 0.50, 100.90), (E_L, R_L, TP_L) assert (E_S, R_S, TP_S) == (99.60, 0.50, 99.10), (E_S, R_S, TP_S) assert abs((OGLINDA - E_L) - E_S) < 1e-9 and abs((OGLINDA - TP_L) - TP_S) < 1e-9, \ "oglinda nu e exactă" TP_BAR_L = next(i for i in range(TRIG + 1, len(LONG)) if LONG[i][1] >= TP_L - 1e-9) TP_BAR_S = next(i for i in range(TRIG + 1, len(SHORT)) if SHORT[i][2] <= TP_S + 1e-9) assert TP_BAR_L == TP_BAR_S == 4, (TP_BAR_L, TP_BAR_S) LIN_L, EX_L = trepte(LONG, TRIG, True) LIN_S, EX_S = trepte(SHORT, TRIG, False) assert EX_L == EX_S == 10, (EX_L, EX_S) TRAIL_L, TRAIL_S = LIN_L[EX_L], LIN_S[EX_S] assert (TRAIL_L, TRAIL_S) == (101.25, 98.75), (TRAIL_L, TRAIL_S) assert abs((OGLINDA - TRAIL_L) - TRAIL_S) < 1e-9, "trail-ul oglindit nu se potrivește" # linia urcă (long) / coboară (short) și nu se întoarce niciodată pas_l = [v for v in LIN_L[:EX_L + 1] if v is not None] pas_s = [v for v in LIN_S[:EX_S + 1] if v is not None] assert all(b >= a for a, b in zip(pas_l, pas_l[1:])), "bull trail-ul coboară" assert all(b <= a for a, b in zip(pas_s, pas_s[1:])), "bear trail-ul urcă" # bara 9: fitilul trece de linie, close-ul nu — deci nu scoate assert LONG[9][2] < TRAIL_L <= LONG[9][3], "bara 9 nu mai ilustrează fitilul sub linie" assert SHORT[9][1] > TRAIL_S >= SHORT[9][3], "oglinda barei 9 nu ține" EXIT_L, EXIT_S = LONG[EX_L][3], SHORT[EX_S][3] RR_L = round((EXIT_L - E_L) / R_L, 1) RR_S = round((E_S - EXIT_S) / R_S, 1) assert RR_L == RR_S == 1.4, (RR_L, RR_S) def chart(bars, arrows, top=70): return Chart(candles=[Candle(*b) for b in bars], arrows=arrows, scale=SCALE, gap=GAP, body_w=14, left=LEFT, width=W, top=top) def scara(ch, linie, pana_la, color): """Linia de trail desenată ca scară: un palier pe fiecare bară.""" seg, prev = [], None for i, v in enumerate(linie[:pana_la + 1]): if v is None: continue y, x0, x1 = ch._y(v), ch._cx(i) - GAP / 2, ch._cx(i) + GAP / 2 seg.append(f'') if prev is not None and abs(prev - v) > 1e-9: seg.append(f'') prev = v return seg ch_l = chart(LONG, [ Arrow(TP_BAR_L, "prima tranșă iese aici, la TP1", where="above", color=GREEN, dx=-60, dy=-8), Arrow(9, "fitilul trece sub linie, close-ul nu:\nnu ieși pe fitil", where="below", color=GREY, dx=-30, dy=86), Arrow(EX_L, "prima bară ÎNCHISĂ sub linie:\niese restul poziției, 1.4R", where="below", color=ORANGE, dx=-20, dy=18)]) ch_s = chart(SHORT, [ Arrow(TP_BAR_S, "prima tranșă iese aici, la TP1", where="below", color=GREEN, dx=-60, dy=20), Arrow(EX_S, "prima bară ÎNCHISĂ peste linie:\niese restul poziției, 1.4R", where="above", color=ORANGE, dx=-20, dy=-8)]) body_l, h_l = duo.panel(ch_l, W) body_s, h_s = duo.panel(ch_s, W) extra_l = (duo.titlu("LONG — bull trail: linia urcă, nu coboară", color=GREEN) + scara(ch_l, LIN_L, EX_L, ORANGE) + duo.level(ch_l, TP_L, ["TP1 100.90 = 1:1"], GREEN, W, at_left=False) + duo.level(ch_l, E_L, ["intrare 100.40"], GREEN, W, below=True, at_left=False) + duo.level(ch_l, SL_LONG, ["stop 99.90 (pasul 4)"], RED, W, below=True)) extra_s = (duo.titlu("SHORT — bear trail: exact aceeași serie, oglindită", color=RED) + scara(ch_s, LIN_S, EX_S, ORANGE) + duo.level(ch_s, SL_SHORT, ["stop 100.10 (pasul 4)"], RED, W, at_left=False) + duo.level(ch_s, E_S, ["intrare 99.60"], GREEN, W, below=False) + duo.level(ch_s, TP_S, ["TP1 99.10 = 1:1"], GREEN, W, below=True, at_left=False)) svg, H, n = duo.build( [("LONG", body_l, h_l, extra_l), ("SHORT", body_s, h_s, extra_s)], ["Serii construite, aceleași cifre ca în restul capitolului.", "Scara portocalie e linia de trail, palier cu palier.", "Panoul de jos e panoul de sus oglindit în jurul lui 100.00, calculat, " "nu desenat din ochi."], W) duo.fara_preturi_straine(svg, [E_L, TP_L, SL_LONG, TRAIL_L, EXIT_L, E_S, TP_S, SL_SHORT, TRAIL_S, EXIT_S, OGLINDA, 100.00]) OUT.write_text(svg, encoding="utf-8") print(f"scris {OUT} ({W}x{H:.0f}), {n} etichete | LONG: intrare {E_L:.2f} SL {SL_LONG:.2f} " f"TP1 {TP_L:.2f} la bara {TP_BAR_L}, trail {TRAIL_L:.2f}, ieșire bara {EX_L} la " f"{EXIT_L:.2f} = {RR_L}R | SHORT (oglindit): intrare {E_S:.2f} SL {SL_SHORT:.2f} " f"TP1 {TP_S:.2f}, trail {TRAIL_S:.2f}, ieșire {EXIT_S:.2f} = {RR_S}R")