#!/usr/bin/env python3 """M2D, pasul 2 — pullback-ul (M) pe TF mic: DA (există) vs. NU (lipsește). Pentru `summaries/17_STRATEGII/03_m2d.md`, marcajul ``. Panoul de sus e chiar graficul de 3 minute din diagrama de ansamblu, primele 13 bare — aceleași cifre în tot capitolul. Panoul de jos pornește din același preț, dar urcă fără să se oprească: nu există pullback, deci nu există setup M2D, oricât de frumoasă ar fi mișcarea. Definiția pe care o susține desenul: pullback = revenire scurtă contra direcției mari care NU rupe minimul din care a pornit mișcarea. Minimul acela e low-ul candelei de 15 min care a dat semnalul (99.80) și e desenat pe ambele panouri. Rulare: python3 gen/strategii/m2d_secventa.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, 108.0, 32, 66 GREEN, RED, BLUE, CYAN, GREY = "#188a3e", "#c00", "#1a5fb4", "#0891b2", "#555" REF_LOW = 99.80 # low-ul candelei de 15 min de semnal (vezi diagrama „directie") SEMNAL = 100.60 # close-ul ei = open-ul primei bare de 3 min # --- DA: primele 13 bare din seria de 3 min a diagramei de ansamblu -------- DA = [(100.60, 100.70, 100.40, 100.45), (100.45, 100.50, 100.25, 100.30), (100.30, 100.35, 100.15, 100.20), (100.20, 100.25, 100.05, 100.10), (100.10, 100.15, 100.00, 100.05), (100.05, 100.10, 99.95, 100.00), (100.00, 100.05, 99.92, 99.95), (99.95, 100.00, 99.90, 99.98), (99.98, 100.08, 99.94, 100.02), # 8: capătul pullback-ului (100.02, 100.45, 100.00, 100.40), # 9: primul Buy = trigger (100.40, 100.60, 100.35, 100.55), (100.55, 100.75, 100.50, 100.70), (100.70, 101.00, 100.65, 100.95)] PULL_END = 8 # --- NU: aceeași plecare, dar urcare continuă, fără pullback -------------- NU = [(100.60, 100.80, 100.55, 100.75), (100.75, 100.95, 100.70, 100.90), (100.90, 101.10, 100.85, 101.05), (101.05, 101.20, 101.00, 101.15), (101.15, 101.35, 101.10, 101.30), (101.30, 101.45, 101.25, 101.40), (101.40, 101.60, 101.35, 101.55), (101.55, 101.75, 101.50, 101.70), (101.70, 101.90, 101.65, 101.85), (101.85, 102.00, 101.80, 101.95), (101.95, 102.15, 101.90, 102.10), (102.10, 102.25, 102.05, 102.20), (102.20, 102.40, 102.15, 102.35)] # ---------------------------------------------------------------- verificări for nume, bars in (("DA", DA), ("NU", NU)): 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" assert abs(bars[0][0] - SEMNAL) < 1e-9, f"{nume}: nu pornește din close-ul semnalului" def run_in_jos(bars): """Cel mai lung șir de close-uri strict descrescătoare.""" best = cur = 1 for a, b in zip(bars, bars[1:]): cur = cur + 1 if b[3] < a[3] else 1 best = max(best, cur) return best assert run_in_jos(DA) >= 3, "DA nu are pullback (niciun șir de 3 close-uri în jos)" assert run_in_jos(NU) < 3, "NU are totuși un pullback" PULL_LOW = min(b[2] for b in DA[:PULL_END + 1]) assert abs(PULL_LOW - 99.90) < 1e-9, PULL_LOW assert PULL_LOW > REF_LOW, "pullback-ul rupe minimul din care a pornit mișcarea" assert DA[0][3] > DA[PULL_END - 1][3], "pullback-ul nu coboară" assert DA[9][3] > max(b[1] for b in DA[6:9]), "bara 9 nu rupe în sus capătul pullback-ului" assert min(b[3] for b in NU) > SEMNAL, "NU: o bară se închide sub prețul de plecare" def chart(bars, arrows): return Chart(candles=[Candle(*b) for b in bars], arrows=arrows, scale=SCALE, gap=GAP, body_w=11, left=LEFT, width=W, top=64) ch_da = chart(DA, [ Arrow(PULL_END - 1, "M: pullback-ul obligatoriu — prețul merge\n" "contra direcției mari, dar se oprește la 99.90", where="below", color=RED, dx=-24, dy=46), Arrow(9, "prima bară care urcă peste capătul pullback-ului:\n" "aici începe pasul 3 (trigger-ul)", where="above", color=GREEN, dx=-40, dy=6)]) ch_nu = chart(NU, [ Arrow(6, "urcare continuă, fără nicio pauză în jos:\n" "asta e breakout, nu M2D — nu ai unde pune stopul", where="below", color=RED, dx=-70, dy=40)]) body_da, h_da = duo.panel(ch_da, W) body_nu, h_nu = duo.panel(ch_nu, W) extra_da = (duo.titlu("DA — pullback pe 3 min, care nu rupe minimul de referință", color=GREEN) + duo.zone(ch_da, 0, PULL_END, "M: pullback", RED, pad=12, lift=26) + duo.level(ch_da, REF_LOW, ["minimul din care a pornit mișcarea 99.80 — sub el nu mai e pullback"], GREY, W, below=True, dash="2 3") + duo.level(ch_da, PULL_LOW, ["cel mai jos punct al pullback-ului 99.90"], RED, W, below=False, at_left=False)) extra_nu = (duo.titlu("NU — niciun pullback: nu ai setup, oricât de frumoasă e urcarea", color=RED) + duo.level(ch_nu, SEMNAL, ["close-ul candelei de 15 min de semnal 100.60"], CYAN, W, below=True, at_left=False)) svg, H, n = duo.build( [("DA", body_da, h_da, extra_da), ("NU", body_nu, h_nu, extra_nu)], ["Ambele panouri: 3 minute, serie construită, aceleași cifre ca în diagrama de ansamblu.", 'Pullback nu înseamnă „nu mai face minim nou": înseamnă că nu rupe minimul ' 'din care a pornit mișcarea.'], W) duo.fara_preturi_straine(svg, [REF_LOW, PULL_LOW, SEMNAL]) OUT.write_text(svg, encoding="utf-8") print(f"scris {OUT} ({W}x{H:.0f}), {n} etichete | DA: șir în jos de {run_in_jos(DA)} bare, " f"minim {PULL_LOW:.2f} peste referința {REF_LOW:.2f} | NU: șir în jos de " f"{run_in_jos(NU)} bare")