"""Diagrama de ansamblu M2D (strategii/03_m2d.md, marcajul `ansamblu`). „Setup M2D LONG complet pe TF mic (3 min), cu TF mare (15 min) în Buy": urcare -> pullback (M = Sell Momentum) -> trigger (A = primul Buy) -> intrare -> SL de 4 candele -> TP1 la 1:1 -> continuare -> vârf -> ieșire pe bull trail. Toate close-urile, wick-urile, volumele și nivelurile vin din `gen/strategii/m2d_ansamblu.spec.txt`. Seria e construită (exemplu generic, fără ticker), la scara 1.00 $ risc / 2.2R randament — nu e un trade real. Rulează: python3 gen/strategii/m2d_ansamblu.py Scrie lângă el `m2d_ansamblu.svg`. """ import sys from pathlib import Path sys.path.insert(0, "/workspace/atm-curs-dl") from svg_candles import Chart, Level, Mark, MA, from_closes # noqa: E402 OUT = Path(__file__).resolve().parent / "m2d_ansamblu.svg" GRAY, RED, GREEN, BLUE, ORANGE = "#777", "#c00", "#188a3e", "#1a5fb4", "#b06000" # ---------------------------------------------------------------- seria (32 bare) CLOSES = [ 96.8, 97.2, 97.7, 98.1, 98.6, 99.1, 99.5, 100.0, # 0-7 urcare 99.8, 99.4, 99.0, 99.2, 98.9, 99.15, 99.3, # 8-14 pullback (M) 99.8, 100.2, # 15-16 trigger (A) 100.7, 101.2, 101.6, 102.0, 102.4, 102.7, 103.0, # 17-23 continuare 103.3, 103.1, 102.8, 102.7, 102.9, 102.5, 102.2, 102.0, # 24-31 vârf + violare ] assert len(CLOSES) == 32, len(CLOSES) # fitile (sus, jos); jos mai mari pe 10-12, ca minimul de 4 candele (11:15) să # fie clar sub intrare, la exact 98.80 (candela 12). WICKS = ( [(0.15, 0.10)] * 8 # 0-7 + [(0.10, 0.15), (0.10, 0.20), (0.10, 0.15), # 8-10 (0.10, 0.15), (0.10, 0.10), (0.10, 0.05), (0.10, 0.10)] # 11-14 + [(0.15, 0.10), (0.15, 0.10)] # 15-16 + [(0.15, 0.10)] * 7 # 17-23 + [(0.20, 0.10), (0.10, 0.15), (0.10, 0.15), (0.12, 0.15), # 24-27 (0.10, 0.15), (0.10, 0.15), (0.10, 0.15), (0.10, 0.20)] # 28-31 ) assert len(WICKS) == 32, len(WICKS) VOLS = ( [62, 66, 60, 68, 64, 70, 66, 74] # 0-7 trend + [44, 40, 38, 36, 34, 33, 35] # 8-14 pullback mic + [82, 88] # 15-16 trigger + [72, 70, 74, 68, 72, 66, 70] # 17-23 trend + [76, 64, 60, 58, 62, 56, 54, 66] # 24-31 vârf + ieșire ) assert len(VOLS) == 32, len(VOLS) cds = from_closes(CLOSES, wicks=WICKS, vols=VOLS, start=96.4) # ---------------------------------------------------------------- assert-urile din spec entry = cds[15].c # 1. intrarea = close-ul trigger-ului SL = min(c.l for c in cds[11:15]) # 2. minimul ultimelor 4 dinainte TP1 = entry + (entry - SL) # 4. TP1 exact 1:1 assert entry == round(cds[15].c, 2), (entry, cds[15].c) assert SL == min(c.l for c in cds[11:15]), SL assert SL < entry, (SL, entry) assert abs(TP1 - (entry + (entry - SL))) < 1e-6 for a, b in zip(cds, cds[1:]): # 5. serie continuă, fără goluri assert a.c == b.o, (a.c, b.o) assert cds[15].c > cds[14].c # 6. trigger în sus după pullback assert any(c.c < c.o for c in cds[8:15]) # 7. pullback = Sell assert any(c.c > c.o for c in cds[0:8]) # context în sus exit_price = cds[31].c assert exit_price > entry # 8. ieșirea e peste intrare # bull trail = low-ul ultimei candele care a închis peste high-ul precedentei qual = [i for i in range(1, 31) if cds[i].c > cds[i - 1].h] assert qual and qual[-1] == 24, qual bull = cds[24].l assert cds[31].c < bull, (cds[31].c, bull) # finala închide sub linie = ieșire aici r_mult = (exit_price - entry) / (entry - SL) # 9. R-ul afișat assert round(r_mult, 1) == 2.2, r_mult # ---------------------------------------------------------------- niveluri, etichete, marks levels = [ Level(entry, f"intrare {entry:.2f}", GREEN, label_at="left"), Level(SL, f"SL {SL:.2f} (4 candele)", RED, label_at="right"), Level(TP1, f"TP1 {TP1:.2f} = 1:1", BLUE, label_at="right"), ] assert [round(lv.price, 2) for lv in levels] == [99.80, 98.80, 100.80] exit_text = f"ieșire {exit_price:.2f} pe bull trail ({r_mult:.1f}R)" assert f"{r_mult:.1f}R" in exit_text marks = [ Mark(0, "TF mare (15 min) în Buy", BLUE, "above", dy=-227), Mark(11, "M: Sell Momentum (pullback)", RED, "below"), Mark(15, "A: primul Buy = trigger", GREEN, "above", dy=-122), # „vârf" stă sub candela 24: deasupra ei, orice text de la y-ul vârfului se # calcă pe titlul din stânga-sus (ambele pornesc din `top`). Mark(24, "vârf 103.30", GRAY, "below", dy=25), Mark(31, "close sub bull trail = ieși", RED, "below", dy=55), Mark(31, exit_text, RED, "below", dy=73), ] caption = (f"Serie construită (32 bare, 3 min). R = ({exit_price:.2f} − {entry:.2f}) / " f"{entry - SL:.2f} = {r_mult:.1f}R.") assert f"{r_mult:.1f}R" in caption ch = Chart( candles=cds, levels=levels, marks=marks, mas=[MA(20, ORANGE, dash="4 3", text="SMA-20")], scale=52.0, gap=16, body_w=9, left=44, width=720, vol_h=70, vol_label="volum", title="Simbol construit — 3 min (TF mare 15 min în Buy)", caption=caption, ) svg = ch.render() # bull trail: nu e un `Level` (spec-ul cere exact trei niveluri), deci se # injectează ca linie separată, la aceeași scară (`ch._y`). yb = ch._y(bull) bull_layer = ( f'' f'bull trail {bull:.2f}' ) i = svg.index(">") + 1 svg = svg[:i] + "\n " + bull_layer + svg[i:] assert svg.count("\n\n") == 0, "linie goală în SVG rupe blocul din markdown" OUT.write_text(svg) print(f"scris {OUT.name}: {len(cds)} candele, intrare {entry:.2f}, SL {SL:.2f}, " f"TP1 {TP1:.2f}, ieșire {exit_price:.2f} = {r_mult:.1f}R, bull trail {bull:.2f}")