diff --git a/gen/strategii/m2d_ansamblu.py b/gen/strategii/m2d_ansamblu.py index 1bf4f40..f6af71d 100644 --- a/gen/strategii/m2d_ansamblu.py +++ b/gen/strategii/m2d_ansamblu.py @@ -1,136 +1,390 @@ -"""Diagrama de ansamblu M2D (strategii/03_m2d.md, marcajul `ansamblu`). +#!/usr/bin/env python3 +"""M2D — tot trade-ul, pe perechea 15 min → 3 min, cu lupă. -„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. +Pentru `summaries/17_STRATEGII/03_m2d.md`, marcajul ``. -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. +Tiparul e cel din `buy_doji_perechi_tf.py`: panoul din dreapta NU e o serie +care vine după cea din stânga — e **descompunerea reală** a ultimelor 4 +candele de 15 minute. O candelă de 15 min = k = 5 candele de 3 min, cu +open, close, max(high) și min(low) care coincid, verificat bară cu bară. -Rulează: - python3 gen/strategii/m2d_ansamblu.py -Scrie lângă el `m2d_ansamblu.svg`. +Sursa de adevăr e seria fină (`FINE`, 3 min). Candelele de 15 min din zona +mărită se CALCULEAZĂ din ea prin agregare; barele de dinainte (`BASE`) sunt +date separat și se leagă de lupă prin close == open. + +Toate cifrele din desen ies din serii. Regula de stop din tot capitolul: +stopul stă FIX pe extrema ultimelor 4 candele de pe TF mic, fără marjă. + +Rulare: python3 gen/strategii/m2d_ansamblu.py """ -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__).with_suffix(".svg") +W = 720 +K = 5 # 5 candele de 3 min într-una de 15 min +NZ = 4 # câte candele de 15 min sunt mărite +STOP_BARS = 4 # stopul = extrema ultimelor 4 bare de pe TF mic +PULLBACK_N = 9 # barele 0..8 = pullback-ul (M) +TRIG = 9 # bara de trigger (A) pe TF mic -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), +# --- seria fină (3 min), 20 de bare: (open, high, low, close) -------------- +FINE = [ + (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: A = trigger, intri + (100.40, 100.60, 100.35, 100.55), (100.55, 100.75, 100.50, 100.70), + (100.70, 101.00, 100.65, 100.95), # 12: 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), + (101.25, 101.30, 101.05, 101.10), # 18: close sub trail = ieși + (101.10, 101.15, 100.90, 100.95), ] -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 +# --- barele de 15 min dinaintea lupei; ultima poartă semnalul Buy Momentum -- +BASE = [ + (98.00, 98.35, 97.85, 98.25), (98.25, 98.70, 98.15, 98.60), + (98.60, 99.10, 98.50, 99.00), (99.00, 99.45, 98.90, 99.30), + (99.30, 99.95, 99.20, 99.85), + (99.85, 100.75, 99.80, 100.60), # 5: Buy Momentum pe 15 min +] +SIG_B = 5 # candela de 15 min cu semnalul -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:] +def aggregate(fine, k, n): + out_ = [] + for j in range(n): + part = fine[j * k:(j + 1) * k] + out_.append((part[0][0], max(b[1] for b in part), + min(b[2] for b in part), part[-1][3])) + return out_ -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}") + +def eq(a, b): + return abs(a - b) < 0.005 + + +ZOOM = aggregate(FINE, K, NZ) +BIG = BASE + ZOOM + +# ---------------------------------------------------------------- verificări +for name, bars in (("15 min", BIG), ("3 min", FINE)): + for i, (o, h, l, c) in enumerate(bars): + assert h >= max(o, c) and l <= min(o, c), f"{name}[{i}]: high/low incoerent" + if i: + assert eq(o, bars[i - 1][3]), f"{name}[{i}]: open != close precedent" +assert len(FINE) == K * NZ, len(FINE) + +# (1) unde cad granițele candelelor de 15 min peste poveste — exact ce spune +# textul capitolului, și poate fi fals +span = len({i // K for i in range(PULLBACK_N)}) +assert span == 2, f"pullback-ul se întinde peste {span} candele de 15 min, nu 2" +trig_big = TRIG // K +assert trig_big == 1, f"trigger-ul cade în candela mare {trig_big}" +assert eq(FINE[0][0], BASE[SIG_B][3]), "open-ul primei bare fine != close-ul semnalului" + +# (2) pullback-ul (M): coboară, dar NU rupe low-ul candelei de 15 min din care +# a pornit mișcarea — definiția ancorată din mini-glosar +PULL = FINE[:PULLBACK_N] +ref_low = BASE[SIG_B][2] +pull_low = min(b[2] for b in PULL) +assert PULL[0][3] > PULL[-1][3], "pullback-ul nu coboară" +assert pull_low > ref_low, f"pullback-ul rupe low-ul de referință ({pull_low} <= {ref_low})" + +# (3) trigger-ul (A). Săgeata vine dintr-un indicator proprietar, deci forma +# ei pe desen e construită de mine: bara de trigger e PRIMA din pullback +# care se închide peste high-urile ultimelor LOOK bare. Verific că e +# într-adevăr prima — altfel desenul ar contrazice regula „primul semnal". +LOOK = 3 + + +def rupe(i): + return i >= LOOK and FINE[i][3] > max(b[1] for b in FINE[i - LOOK:i]) + + +assert rupe(TRIG), "bara de trigger nu rupe high-urile barelor dinainte" +assert not any(rupe(i) for i in range(LOOK, TRIG)), \ + "o bară dinaintea trigger-ului rupea deja — nu mai e primul semnal" + +# (4) trade-ul +ENTRY = FINE[TRIG][3] +SL = min(b[2] for b in FINE[TRIG - STOP_BARS + 1:TRIG + 1]) +RISK = round(ENTRY - SL, 2) +TP1 = round(ENTRY + RISK, 2) +assert eq(SL, pull_low), "stopul nu e minimul pullback-ului" +assert eq(SL, min(b[2] for b in FINE)), "stopul nu e minimul zonei mărite" +TP_BAR = next(i for i in range(TRIG + 1, len(FINE)) if FINE[i][1] >= TP1 - 1e-9) +assert TP_BAR == 12, TP_BAR + +# (5) bull trail: low-ul ultimei bare care s-a închis peste high-ul precedentei; +# ieșirea = prima bară care se ÎNCHIDE sub linie +trail, EXIT_BAR = None, None +for i in range(1, len(FINE)): + if trail is not None and FINE[i][3] < trail: + EXIT_BAR = i + break + if i > TRIG and FINE[i][3] > FINE[i - 1][1]: + trail = FINE[i][2] +TRAIL = trail +EXIT_BAR = EXIT_BAR if EXIT_BAR is not None else len(FINE) - 1 +EXIT = FINE[EXIT_BAR][3] +assert (TRAIL, EXIT_BAR) == (101.25, 18), (TRAIL, EXIT_BAR) +assert EXIT > ENTRY, "ieșirea pe trail e sub intrare" +R = (EXIT - ENTRY) / RISK +assert round(R, 1) == 1.4, R +# wick-uri sub linie nu scot: bara 17 coboară sub trail, dar se închide deasupra +assert FINE[17][2] < TRAIL <= FINE[17][3], "bara 17 nu mai ilustrează wick sub trail" + +LEVEL = BASE[SIG_B][3] # nivelul comun celor două panouri +assert eq(LEVEL, FINE[0][0]) + +# ---------------------------------------------------------------- desen +GREEN, RED, INK, BLUE, GRAY = "#188a3e", "#c00", "#333", "#1a5fb4", "#888" +LINK, CYAN = "#aaa", "#0891b2" +PLOT_H = 168 +BX0, BGAP, BW = 104, 15, 9 +SX0, SGAP, SW = 372, 11, 7 +FRAME_L = SX0 - 8 +COL_X = 600 +MIDX = FRAME_L - 20 +PAD = 0.06 + +out, LABELS = [], [] + + +def text(x, y, s, fill=INK, anchor="start", bold=False, size=12): + st = [f"font-size:{size}px"] + (["font-weight:bold"] if bold else []) + a = f' text-anchor="{anchor}"' if anchor != "start" else "" + w = len(s) * (5.7 if size >= 12 else size * 0.5) + x0 = x if anchor == "start" else (x - w / 2 if anchor == "middle" else x - w) + assert 4 <= x0 and x0 + w <= W - 4, f"text iese din viewBox: {s!r} ({x0:.0f}..{x0 + w:.0f})" + LABELS.append((x0, x0 + w, y, s)) + out.append(f'{s}') + + +def hline(x1, x2, y, color, dash, width=1.5): + out.append(f'') + + +def line(x1, y1, x2, y2, color, dash="", width=1.2, marker=""): + d = f' stroke-dasharray="{dash}"' if dash else "" + m = f' marker-end="url(#{marker})"' if marker else "" + out.append(f'') + + +def rect(x0_, y0_, x1, y1, color, fill_op=0.10, dash=""): + d = f' stroke-dasharray="{dash}"' if dash else "" + out.append(f'') + + +def candles(bars, x0, gap, yf, bw): + for i, (o, h, l, c) in enumerate(bars): + cx = x0 + i * gap + color = GREEN if c >= o else RED + line(cx, yf(h), cx, yf(l), "#1a1a1a", width=1.2) + top_ = yf(max(o, c)) + hh = max(yf(min(o, c)) - top_, 1.6) + out.append(f'') + + +def stack(items, x_line): + y_prev = None + for y, lines, fill in sorted(items, key=lambda t: t[0]): + y_lab = y if y_prev is None or y >= y_prev + 16 else y_prev + 16 + if y_lab - y > 2: + out.append(f'') + else: + line(x_line + 2, y - 4, COL_X - 4, y_lab - 4, fill, width=0.8) + for j, s in enumerate(lines): + text(COL_X, y_lab + j * 14, s, fill=fill, size=10) + y_prev = y_lab + (len(lines) - 1) * 14 + + +def seg_dist(p, a, b): + (px, py), (ax, ay), (bx, by) = p, a, b + dx, dy = bx - ax, by - ay + t = 0.0 if dx == dy == 0 else max(0.0, min(1.0, ((px - ax) * dx + (py - ay) * dy) + / (dx * dx + dy * dy))) + return ((px - ax - t * dx) ** 2 + (py - ay - t * dy) ** 2) ** 0.5 + + +out.append('' + '' + '') +text(20, 24, "M2D long, tot trade-ul: 15 min dă direcția, 3 min dă intrarea", + bold=True, size=14) +text(20, 42, "Graficul din dreapta nu e altă bucată de piață: e aceeași, mărită. " + "O candelă de 15 min = 5 candele de 3 min.", fill="#555", size=11) + +top = 80 +text(20, top - 12, "15 min: direcția", fill="#555", size=11) +text(FRAME_L, top - 12, f"3 min: descompunerea ultimelor {NZ} candele de 15 min", + fill="#555", size=11) + +zlo_, zhi_ = SL, max(b[1] for b in FINE) +pad = PAD * (zhi_ - zlo_) +lo, hi = zlo_ - pad, zhi_ + pad + +bmax = max(b[1] for b in BIG) +bmin = min(min(b[2] for b in BIG), lo) +bs, ss = PLOT_H / (bmax - bmin), PLOT_H / (hi - lo) +yb = lambda p: top + (bmax - p) * bs # noqa: E731 +ys = lambda p: top + (hi - p) * ss # noqa: E731 + +# ---- panoul de 15 min ------------------------------------------------------ +sx_a, sx_b = BX0 + SIG_B * BGAP - 8, BX0 + SIG_B * BGAP + 8 +rect(sx_a, yb(BASE[SIG_B][1]) - 5, sx_b, yb(BASE[SIG_B][2]) + 5, CYAN) +line(BX0 + SIG_B * BGAP, yb(BASE[SIG_B][2]) + 6, BX0 + SIG_B * BGAP, top + PLOT_H + 12, + CYAN, width=1) +text(BX0 + SIG_B * BGAP - 30, top + PLOT_H + 26, "Buy Momentum pe 15 min:", + fill=CYAN, anchor="middle", bold=True, size=11) +text(BX0 + SIG_B * BGAP - 30, top + PLOT_H + 39, "de aici ai voie doar long", + fill=CYAN, anchor="middle", size=11) + +# caseta de lupă peste exact candelele de 15 min care se sparg în dreapta +lx0, lx1 = BX0 + len(BASE) * BGAP - 7, BX0 + (len(BIG) - 1) * BGAP + 8 +ly0, ly1 = yb(hi), yb(lo) +rect(lx0, ly0, lx1, ly1, BLUE, fill_op=0.06, dash="4 3") + +# minimul de referință al pullback-ului: trăiește pe TF-ul mare (pe panoul mic +# e sub scară), deci se marchează aici și se trimite acolo din etichetă +hline(BX0 - 14, lx1, yb(ref_low), "#555", "2 3", width=1.2) +text(24, yb(ref_low) + 12, "minimul din care a pornit", fill="#555", size=10) +text(24, yb(ref_low) + 26, f"mișcarea: {ref_low:.2f}", fill="#555", size=10) + +hline(BX0 - 14, lx1, yb(LEVEL), LINK, "3 3", width=1.0) +candles(BIG, BX0, BGAP, yb, BW) + +# ---- ghidajele de lupă ----------------------------------------------------- +guides = [((lx1, ly0), (FRAME_L, top)), ((lx1, ly1), (FRAME_L, top + PLOT_H))] +for a_, b_ in guides: + line(a_[0], a_[1], b_[0], b_[1], BLUE, dash="5 3", width=1.1) + +# ---- nivelul comun: conector între panouri, numărul scris o singură dată --- +ycb, ycs = yb(LEVEL), ys(LEVEL) + + +def clearance(mx): + conn = [((lx1, ycb), (mx, ycb)), ((mx, ycb), (mx, ycs)), ((mx, ycs), (FRAME_L, ycs))] + pts = [(sx + (ex - sx) * t / 24, sy + (ey - sy) * t / 24) + for (sx, sy), (ex, ey) in conn for t in range(25)] + return min(seg_dist(q, ga, gb) for q in pts if q[0] >= lx1 + 10 for ga, gb in guides) + + +mid = max(range(int(lx1) + 14, int(FRAME_L) - 13), key=clearance) +dmin = clearance(mid) +assert dmin > 10, f"conectorul trece la {dmin:.1f} px de un ghidaj" +out.append(f'') +tx = min(mid + 5, FRAME_L - 60) +text(tx, (ycb + ycs) / 2 + 4, f"{LEVEL:.2f}", fill="#777", size=10) +text(tx, (ycb + ycs) / 2 + 17, "același preț", fill="#999", size=10) + +# ---- panoul de 3 min ------------------------------------------------------- +y_e, y_sl, y_tp, y_tr = ys(ENTRY), ys(SL), ys(TP1), ys(TRAIL) +assert abs((y_sl - y_e) / (y_e - y_tp) - 1) < 1e-6, "SL-intrare != intrare-TP1 în px" +assert top + PLOT_H - y_sl > 6, "linia de stop e lipită de marginea cadrului" +frame_r = SX0 + (len(FINE) - 1) * SGAP + 8 +rect(FRAME_L, top, frame_r, top + PLOT_H, BLUE, fill_op=0.03) + +# zona de evidențiere peste pullback + trigger: acolo se decide intrarea +zx0 = SX0 - 7 +zx1 = SX0 + TRIG * SGAP + 7 +rect(zx0, ys(max(b[1] for b in FINE[:TRIG + 1])) - 5, zx1, ys(SL) + 5, GREEN) + +hline(FRAME_L, frame_r, ycs, LINK, "3 3", width=1.0) +hline(FRAME_L, frame_r, y_tp, GREEN, "2 3") +hline(FRAME_L, frame_r, y_e, GREEN, "6 3") +hline(FRAME_L, frame_r, y_sl, RED, "6 3") +hline(FRAME_L, frame_r, y_tr, "#b06000", "2 2", width=1.2) +candles(FINE, SX0, SGAP, ys, SW) + +stack([(y_tp + 4, [f"TP1 {TP1:.2f} = 1:1", "iese prima tranșă"], GREEN), + (y_tr + 4, [f"bull trail {TRAIL:.2f}", "urcă, nu coboară"], "#b06000"), + (y_e + 4, [f"intrare {ENTRY:.2f}", "close-ul trigger-ului"], GREEN), + (y_sl + 4, [f"stop {SL:.2f}", f"minimul a {STOP_BARS} bare"], RED)], + frame_r) + +# paranteza pullback-ului, sub cadru +pxa, pxb = SX0 - 5, SX0 + (PULLBACK_N - 1) * SGAP + 5 +out.append(f'') +text((pxa + pxb) / 2, top + PLOT_H + 22, f"M: pullback, {PULLBACK_N} bare", + fill=RED, anchor="middle", bold=True, size=11) +text((pxa + pxb) / 2, top + PLOT_H + 36, "fără să rupă minimul", + fill=RED, anchor="middle", size=11) + +# ieșirea: săgeată în sus spre bara 18, cu explicația sub cadru, la dreapta +exx = SX0 + EXIT_BAR * SGAP +line(exx, top + PLOT_H + 14, exx, ys(FINE[EXIT_BAR][2]) + 5, "#b06000", + dash="3 3", width=1.1, marker="pt-o") +text(frame_r, top + PLOT_H + 22, "close sub trail", + fill="#b06000", anchor="end", bold=True, size=11) +text(frame_r, top + PLOT_H + 36, f"ieșire {EXIT:.2f} = {R:.1f}R", + fill="#b06000", anchor="end", size=11) + +# trigger-ul: săgeată în sus spre bara 9, cu explicația dedesubt +tgx = SX0 + TRIG * SGAP +line(tgx, top + PLOT_H + 50, tgx, ys(FINE[TRIG][2]) + 5, GREEN, width=1.6, marker="pt") +text(pxa, top + PLOT_H + 58, "A: primul Buy după pullback — aici intri,", + fill=GREEN, bold=True, size=11) +text(pxa, top + PLOT_H + 72, "pentru că 15 min e încă în Buy.", fill=GREEN, size=11) +text(pxa, top + PLOT_H + 86, "Wick-urile nu contează: se citește close-ul.", + fill="#555", size=11) + +LAB_N = len(LABELS) # etichetele de pe panouri; ce urmează e subsol +H = top + PLOT_H + 108 +text(20, H - 6, f"Serie construită, fără ticker. Risc = {ENTRY:.2f} − {SL:.2f} = {RISK:.2f}; " + f"TP1 la aceeași distanță în sus; ieșirea pe trail la {R:.1f}R.", + fill="#555", size=11) +H += 12 + +# ---- prețul fiecărui panou rămâne pe panoul lui --------------------------- +COMUNE = {round(LEVEL, 2)} +MARE = {"ref_low": ref_low} +MIC = {k_: v for k_, v in (("entry", ENTRY), ("sl", SL), ("tp", TP1), ("trail", TRAIL)) + if round(v, 2) not in COMUNE} +for x0_, _x1, _y, s_ in LABELS[:LAB_N]: + rau = MARE if x0_ >= MIDX else MIC + for kk, v in rau.items(): + assert f"{v:.2f}" not in s_, \ + f"eticheta {s_!r} de pe panoul {'mic' if x0_ >= MIDX else 'mare'} " \ + f"conține prețul {kk}={v:.2f} al celuilalt panou" +cx_ = [x0_ for x0_, _x1, _y, s_ in LABELS[:LAB_N] if f"{LEVEL:.2f}" in s_] +assert len(cx_) == 1 and lx1 < cx_[0] < FRAME_L, \ + f"{LEVEL:.2f} nu e scris exact o dată, în cotul conectorului ({cx_})" +for nume, v in (("intrarea", ENTRY), ("stopul", SL), ("TP1", TP1), ("trail-ul", TRAIL)): + assert any(f"{v:.2f}" in s_ and x0_ >= MIDX for x0_, _x1, _y, s_ in LABELS[:LAB_N]), \ + f"{nume} nu apare pe panoul mic" + +for a in range(len(LABELS)): + assert 0 < LABELS[a][2] < H, f"etichetă în afara înălțimii: {LABELS[a][3]!r}" + for b in range(a + 1, len(LABELS)): + xa0, xa1, ya_, sa = LABELS[a] + xb0, xb1, yb_, sb = LABELS[b] + assert not (abs(ya_ - yb_) < 13 and xa0 < xb1 - 4 and xb0 < xa1 - 4), \ + f"etichete suprapuse: {sa!r} / {sb!r}" + +svg = (f'\n ' + "\n ".join(out) + "\n") +assert "\n\n" not in svg +assert ".md:" not in svg, "trimiteri la surse în SVG" +OUT.write_text(svg, encoding="utf-8") +print(f"scris {OUT} ({W}x{H:.0f}) | 15 min: {len(BIG)} candele, lupă pe ultimele {NZ} " + f"= {len(FINE)} bare de 3 min | nivel comun {LEVEL:.2f} | " + f"intrare {ENTRY:.2f} SL {SL:.2f} risc {RISK:.2f} TP1 {TP1:.2f} la bara {TP_BAR} | " + f"trail {TRAIL:.2f}, ieșire bara {EXIT_BAR} la {EXIT:.2f} = {R:.2f}R | " + f"pullback peste {span} candele de 15 min, trigger în candela mare {trig_big}") diff --git a/gen/strategii/m2d_ansamblu.svg b/gen/strategii/m2d_ansamblu.svg index 47c536d..6333415 100644 --- a/gen/strategii/m2d_ansamblu.svg +++ b/gen/strategii/m2d_ansamblu.svg @@ -1,117 +1,111 @@ - - bull trail 102.90 - Simbol construit — 3 min (TF mare 15 min în Buy) - - intrare 99.80 - - SL 98.80 (4 candele) - - TP1 100.80 = 1:1 - - SMA-20 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - volum - TF mare (15 min) în Buy - M: Sell Momentum (pullback) - A: primul Buy = trigger - vârf 103.30 - close sub bull trail = ieși - ieșire 102.00 pe bull trail (2.2R) - Serie construită (32 bare, 3 min). R = (102.00 − 99.80) / 1.00 = 2.2R. + + + M2D long, tot trade-ul: 15 min dă direcția, 3 min dă intrarea + Graficul din dreapta nu e altă bucată de piață: e aceeași, mărită. O candelă de 15 min = 5 candele de 3 min. + 15 min: direcția + 3 min: descompunerea ultimelor 4 candele de 15 min + + + Buy Momentum pe 15 min: + de aici ai voie doar long + + + minimul din care a pornit + mișcarea: 99.80 + + + + + + + + + + + + + + + + + + + + + + + + + 100.60 + același preț + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + bull trail 101.25 + urcă, nu coboară + + TP1 100.90 = 1:1 + iese prima tranșă + + intrare 100.40 + close-ul trigger-ului + + stop 99.90 + minimul a 4 bare + + M: pullback, 9 bare + fără să rupă minimul + + close sub trail + ieșire 101.10 = 1.4R + + A: primul Buy după pullback — aici intri, + pentru că 15 min e încă în Buy. + Wick-urile nu contează: se citește close-ul. + Serie construită, fără ticker. Risc = 100.40 − 99.90 = 0.50; TP1 la aceeași distanță în sus; ieșirea pe trail la 1.4R. \ No newline at end of file diff --git a/gen/strategii/m2d_directie.py b/gen/strategii/m2d_directie.py new file mode 100644 index 0000000..482554f --- /dev/null +++ b/gen/strategii/m2d_directie.py @@ -0,0 +1,114 @@ +#!/usr/bin/env python3 +"""M2D, pasul 1 — direcția o dă TF-ul mare: DA (rămâne în Buy) vs. NU (se închide Sell). + +Pentru `summaries/17_STRATEGII/03_m2d.md`, marcajul ``. + +Ambele panouri sunt graficul de 15 minute al aceluiași exemplu construit ca în +diagrama de ansamblu — primele 6 candele sunt identice, deci cifrele din +capitol nu se bat cap în cap de la o diagramă la alta. + +Sus: pullback-ul de pe 3 min lasă pe 15 min o candelă roșie, dar indicatorul +nu dă Sell și candela nu se închide sub nivelul semnalului — setup-ul long +trăiește. Jos: candela de 15 min se ÎNCHIDE Sell — setup-ul long e anulat. + +Rulare: python3 gen/strategii/m2d_directie.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, Mark # noqa: E402 +import m2d_duo as duo # noqa: E402 + +OUT = Path(__file__).with_suffix(".svg") +W, SCALE, GAP, LEFT = 680, 46.0, 46, 74 +GREEN, RED, BLUE, CYAN, GREY = "#188a3e", "#c00", "#1a5fb4", "#0891b2", "#555" + +# --- aceleași candele de 15 min ca în diagrama de ansamblu ----------------- +BASE = [(98.00, 98.35, 97.85, 98.25), (98.25, 98.70, 98.15, 98.60), + (98.60, 99.10, 98.50, 99.00), (99.00, 99.45, 98.90, 99.30), + (99.30, 99.95, 99.20, 99.85), + (99.85, 100.75, 99.80, 100.60)] # 5: Buy Momentum pe 15 min +DA = BASE + [(100.60, 100.70, 100.00, 100.05), # 6: roșie, dar nu Sell + (100.05, 100.45, 99.90, 100.40), # 7: aici cade trigger-ul de pe 3 min + (100.40, 101.15, 100.35, 101.00)] +NU = BASE + [(100.60, 100.70, 100.00, 100.05), + (100.05, 100.10, 99.20, 99.30), # 7: se ÎNCHIDE Sell — setup anulat + (99.30, 99.40, 98.60, 98.75)] +SIG = 5 + +# ---------------------------------------------------------------- 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 DA[:SIG + 2] == NU[:SIG + 2], "panourile nu pornesc din aceeași serie" + +SEMNAL = BASE[SIG][3] # 100.60 — același nivel ca în diagrama de ansamblu +REF_LOW = BASE[SIG][2] # 99.80 — minimul din care a pornit mișcarea +assert BASE[SIG][3] > BASE[SIG][0], "candela de semnal nu e verde" + +# DA: nicio candelă de după semnal nu se închide sub minimul de referință +assert all(b[3] > REF_LOW for b in DA[SIG + 1:]), "DA: o candelă închide sub referință" +assert DA[6][3] < DA[6][0], "DA: candela 6 ar trebui să fie roșie (pullback pe 3 min)" +assert DA[6][3] > REF_LOW, "DA: candela roșie rupe deja referința" +# NU: exact una rupe — a șaptea, care se închide sub minimul de referință +rupe = [i for i, b in enumerate(NU) if i > SIG and b[3] < REF_LOW] +assert rupe == [7, 8], rupe +assert NU[7][3] < NU[7][0] and NU[7][3] < REF_LOW, "NU: candela 7 nu se închide Sell" + + +def chart(bars, marks, arrows, extra_h=0.0): + return Chart(candles=[Candle(*b) for b in bars], marks=marks, arrows=arrows, + scale=SCALE, gap=GAP, body_w=15, left=LEFT, width=W, top=64, + label_gap=34 + extra_h) + + +TITLU_DA = "DA — 15 min e încă în Buy când vine trigger-ul de pe 3 min" +TITLU_NU = "NU — 15 min s-a închis Sell: setup-ul long e anulat" + +ch_da = chart( + DA, + [], + [Arrow(SIG, "Buy Momentum pe 15 min:\nde aici ai voie doar long", + where="below", color=CYAN, dx=-60, dy=54), + Arrow(6, "candelă roșie pe 15 min, dar close-ul\nrămâne peste 99.80: pullback, nu întoarcere", + where="below", color=GREY, dx=-30, dy=26), + Arrow(7, "aici cade trigger-ul de pe 3 min", + where="above", color=GREEN, dx=-24, dy=18)]) +ch_nu = chart( + NU, + [], + [Arrow(SIG, "Buy Momentum pe 15 min:\nde aici ai voie doar long", + where="below", color=CYAN, dx=-60, dy=54), + Arrow(7, "close 99.30, sub 99.80: direcția mare\ns-a răsucit — nu mai intri long", + where="below", color=RED, dx=-40, dy=20), + Arrow(8, "de aici cauți short, nu long", where="below", color=RED, dx=-120, dy=82)]) + +body_da, h_da = duo.panel(ch_da, W) +body_nu, h_nu = duo.panel(ch_nu, W) + +extra_da = (duo.titlu(TITLU_DA, color=GREEN) + + duo.zone(ch_da, SIG, 7, "zona în care ai voie long", GREEN, pad=20, lift=26) + + duo.level(ch_da, SEMNAL, ["close-ul candelei de semnal 100.60"], CYAN, W, + below=False, at_left=False) + + duo.level(ch_da, REF_LOW, ["minimul din care a pornit mișcarea 99.80"], + GREY, W, below=True, dash="2 3")) +extra_nu = (duo.titlu(TITLU_NU, color=RED) + + duo.zone(ch_nu, 7, 8, "setup anulat", RED, pad=20) + + duo.level(ch_nu, SEMNAL, ["close-ul candelei de semnal 100.60"], CYAN, W, + below=False, at_left=False) + + duo.level(ch_nu, REF_LOW, ["minimul din care a pornit mișcarea 99.80"], + GREY, W, below=True, dash="2 3")) + +svg, H, n = duo.build( + [("DA", body_da, h_da, extra_da), ("NU", body_nu, h_nu, extra_nu)], + ["Același grafic de 15 minute ca în diagrama de ansamblu; serie construită, fără ticker.", + "Pe 15 min se citește DIRECȚIA, nu intrarea. Intrarea vine de pe 3 min (pasul 3)."], + W) +duo.fara_preturi_straine(svg, [SEMNAL, REF_LOW, NU[7][3]]) +OUT.write_text(svg, encoding="utf-8") +print(f"scris {OUT} ({W}x{H:.0f}), {n} etichete | semnal {SEMNAL:.2f}, referință {REF_LOW:.2f} | " + f"DA: ultima închidere {DA[-1][3]:.2f} | NU: candela 7 închide {NU[7][3]:.2f}") diff --git a/gen/strategii/m2d_directie.svg b/gen/strategii/m2d_directie.svg new file mode 100644 index 0000000..b96e4e8 --- /dev/null +++ b/gen/strategii/m2d_directie.svg @@ -0,0 +1,66 @@ + + DA — 15 min e încă în Buy când vine trigger-ul de pe 3 minzona în care ai voie longclose-ul candelei de semnal 100.60minimul din care a pornit mișcarea 99.80 + + + + + + + + + + + + + + + + + + + + + Buy Momentum pe 15 min: + de aici ai voie doar long + + + candelă roșie pe 15 min, dar close-ul + rămâne peste 99.80: pullback, nu întoarcere + + + aici cade trigger-ul de pe 3 min + + NU — 15 min s-a închis Sell: setup-ul long e anulatsetup anulatclose-ul candelei de semnal 100.60minimul din care a pornit mișcarea 99.80 + + + + + + + + + + + + + + + + + + + + + Buy Momentum pe 15 min: + de aici ai voie doar long + + + close 99.30, sub 99.80: direcția mare + s-a răsucit — nu mai intri long + + + de aici cauți short, nu long + + Același grafic de 15 minute ca în diagrama de ansamblu; serie construită, fără ticker. + Pe 15 min se citește DIRECȚIA, nu intrarea. Intrarea vine de pe 3 min (pasul 3). + \ No newline at end of file diff --git a/gen/strategii/m2d_duo.py b/gen/strategii/m2d_duo.py new file mode 100644 index 0000000..4135105 --- /dev/null +++ b/gen/strategii/m2d_duo.py @@ -0,0 +1,124 @@ +"""Ajutor comun pentru diagramele capitolului M2D cu două panouri (DA / NU). + +Tiparul e cel din `buy_doji_fereastra.py`: două `Chart`-uri desenate cu +`_render()` și puse unul sub altul într-un singur SVG, plus verificarea că +nicio etichetă nu iese din pânză și că nicio pereche de etichete de pe +ACELAȘI panou nu se suprapune. E scos aici ca să nu fie copiat în patru +generatoare; nu are alt rol. + +Nu se rulează singur: îl importă `m2d_directie.py`, `m2d_secventa.py`, +`m2d_trigger.py`, `m2d_stop.py`, `m2d_iesire.py`. +""" +import re + +TEXT = re.compile(r']*)>(.*?)', re.S) +GREY = "#555" + + +def panel(ch, width): + """Corpul SVG al unui Chart, fără , plus înălțimea lui. + + `_render()`, nu `render()`: al doilea restaurează top/left după desen, + iar liniile desenate de mână ar cădea deplasate față de corp. + """ + svg = ch._render() + m = re.search(r'viewBox="0 0 ([\d.]+) ([\d.]+)"', svg) + assert m, "viewBox lipsă" + assert float(m.group(1)) == width, f"textul unei săgeți iese din {width}px" + return svg[svg.index(">") + 1:svg.rindex("")], float(m.group(2)) + + +def level(ch, price, lines, color, width, below=False, at_left=True, dash="4 3"): + """Linie punctată pe toată lățimea + etichetă, desenată de mână. + + Eticheta din `Level` stă lipită de linie și cade peste candelele care o + traversează; aici o pun în marginea liberă a panoului. + """ + y = ch._y(price) + out = [f''] + for k, s in enumerate(lines): + ty = (y + 21 + 14 * k) if below else (y - 6 - 14 * (len(lines) - 1 - k)) + tx, anchor = (30, "start") if at_left else (width - 24, "end") + out.append(f'{s}') + return out + + +def zone(ch, i0, i1, label, color, lift=0.0, pad=16): + """Dreptunghi de evidențiere peste candelele i0..i1, cu eticheta deasupra.""" + cds = ch.candles[i0:i1 + 1] + x0, x1 = ch._cx(i0) - pad, ch._cx(i1) + pad + y0 = ch._y(max(c.h for c in cds)) - 8 - lift + y1 = ch._y(min(c.l for c in cds)) + 8 + out = [f''] + if label: + out.append(f'{label}') + return out + + +def titlu(s, y=22, color="#333", x=24): + """Titlul panoului, desenat de mână. + + `Chart(title=...)` îl pune la `top - 8`, adică la 7 px de eticheta unei + zone desenate la `y(pmax) - 15` — se calcă mereu, indiferent de `top`. + """ + return [f'{s}'] + + +def _texts(body, y_off, tag): + out = [] + for m in TEXT.finditer(body): + x, y = float(m.group(1)), float(m.group(2)) + y_off + attrs, s = m.group(3), m.group(4) + anchor = ("end" if 'text-anchor="end"' in attrs + else "middle" if 'text-anchor="middle"' in attrs else "start") + w = len(s) * 12 * (0.62 if "bold" in attrs else 0.52) + left = x - w if anchor == "end" else (x - w / 2 if anchor == "middle" else x) + out.append((left, left + w, y, s, tag)) + return out + + +def build(panels, captions, width, vgap=0): + """`panels` = listă de (eticheta panoului, corp SVG, înălțime, extra). + + Întoarce SVG-ul întreg, după ce verifică etichetele. + """ + parts = [f''] + labels, off = [], 0.0 + for tag, body, h, extra in panels: + parts.append(f'{"".join(extra)}{body}') + labels += _texts(body + "".join(extra), off, tag) + off += h + vgap + cap_top = off + 6 + for i, c in enumerate(captions): + assert "<" not in c + parts.append(f'{c}') + labels.append((24, 24 + len(c) * 6.3, cap_top + 17 * i, c, "cap")) + H = cap_top + 17 * len(captions) + 6 + parts.append("") + svg = "\n ".join(parts).replace("{H}", f"{H:.0f}") + assert "\n\n" not in svg, "linie goală în SVG" + assert ".md:" not in svg, "trimitere fișier:linie în SVG" + + for left, right, y, s, tag in labels: + assert left >= -2 and right <= width + 2, f"[{tag}] text iese din pânză: {s!r}" + assert 0 <= y <= H, f"[{tag}] text în afara înălțimii: {s!r} (y={y:.0f})" + for i in range(len(labels)): + li, ri, yi, si, ti = labels[i] + for j in range(i + 1, len(labels)): + lj, rj, yj, sj, tj = labels[j] + if ti == tj and abs(yi - yj) < 12 and li < rj - 4 and lj < ri - 4: + raise AssertionError(f"[{ti}] texte suprapuse: {si!r} / {sj!r}") + return svg, H, len(labels) + + +def fara_preturi_straine(svg, permise): + """Nicio etichetă nu numește un `\\d+\\.\\d\\d` care nu e un nivel desenat.""" + gasite = {float(x) for x in re.findall(r"\d+\.\d\d", " ".join( + m.group(4) for m in TEXT.finditer(svg)))} + strain = {g for g in gasite if not any(abs(g - p) < 0.005 for p in permise)} + assert not strain, f"prețuri în etichete care nu sunt niveluri desenate: {sorted(strain)}" diff --git a/gen/strategii/m2d_iesire.py b/gen/strategii/m2d_iesire.py new file mode 100644 index 0000000..0725007 --- /dev/null +++ b/gen/strategii/m2d_iesire.py @@ -0,0 +1,169 @@ +#!/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") diff --git a/gen/strategii/m2d_iesire.svg b/gen/strategii/m2d_iesire.svg new file mode 100644 index 0000000..6f061d7 --- /dev/null +++ b/gen/strategii/m2d_iesire.svg @@ -0,0 +1,75 @@ + + LONG — bull trail: linia urcă, nu coboarăTP1 100.90 = 1:1intrare 100.40stop 99.90 (pasul 4) + + + + + + + + + + + + + + + + + + + + + + + + + + + prima tranșă iese aici, la TP1 + + + fitilul trece sub linie, close-ul nu: + nu ieși pe fitil + + + prima bară ÎNCHISĂ sub linie: + iese restul poziției, 1.4R + + SHORT — bear trail: exact aceeași serie, oglindităstop 100.10 (pasul 4)intrare 99.60TP1 99.10 = 1:1 + + + + + + + + + + + + + + + + + + + + + + + + + + + prima tranșă iese aici, la TP1 + + + prima bară ÎNCHISĂ peste linie: + iese restul poziției, 1.4R + + 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. + \ No newline at end of file diff --git a/gen/strategii/m2d_secventa.py b/gen/strategii/m2d_secventa.py index db1c725..6a3ff2d 100644 --- a/gen/strategii/m2d_secventa.py +++ b/gen/strategii/m2d_secventa.py @@ -1,268 +1,125 @@ #!/usr/bin/env python3 -"""Secvența M → A a strategiei M2D — schemă conceptuală pe două panouri. +"""M2D, pasul 2 — pullback-ul (M) pe TF mic: DA (există) vs. NU (lipsește). -Pentru `strategii/03_m2d.md`, marcajul ``. -Nu e grafic de preț (spec: „schemă pe două panouri, nu candele de trade”): -SVG scris de mână, doar stdlib — nu se folosește svg_candles / pchart. +Pentru `summaries/17_STRATEGII/03_m2d.md`, marcajul ``. -Ce arată (toate etichetele vin din spec; nicio cifră de preț nu se inventează): -- panoul de sus, LONG: pe TF mare (15 min) polilinia urcă și „rămâne în Buy pe - tot parcursul”; pe TF mic (3 min) urcă → pullback (M: Sell Momentum) → urcă - peste vârful anterior (A: Buy Momentum — TRIGGER, intri long); -- panoul de jos, SHORT: oglindit — TF mare coboară („rămâne în Sell”), TF mic - coboară → pullback în sus (M: Buy Momentum) → coboară sub minimul anterior - (A: Sell Momentum — TRIGGER, intri short); -- două linii verticale punctate leagă momentul M și momentul A de rândul de sus - (verificarea că TF mare rămâne în aceeași direcție); -- textul de subsol: „Secvența contează, nu starea...”. +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. -Rulare: python3 gen/strategii/m2d_secventa.py -> gen/strategii/m2d_secventa.svg +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 -from xml.sax.saxutils import escape + +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" -W, H = 700, 780 -BLACK, GRAY, RED, GREEN, BLUE, CYAN, WHITE = ( - "#000000", "#777777", "#c00", "#188a3e", "#1a5fb4", "#00b8c4", "#ffffff") -PANEL_BG, PANEL_EDGE = "#fafafa", "#ddd" +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 -# ---------------------------------------------------------------- geometrie -# Fiecare polilinie: listă de (x, y). y mic = sus. -# Index: 0 = start, 1 = vârf/trough anterior, 2 = M, 3 = A, 4 = continuare. -LONG_TOP = [(40, 162), (150, 148), (250, 138), (430, 128), (660, 118)] -LONG_BOT = [(40, 255), (180, 225), (250, 282), (430, 210), (660, 198)] -SHORT_TOP = [(40, 430), (150, 444), (250, 454), (430, 464), (660, 474)] -SHORT_BOT = [(40, 527), (180, 562), (250, 500), (430, 574), (660, 588)] +# --- 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 -L_M, L_A, S_M, S_A = 2, 3, 2, 3 # indicii momentelor M și A +# --- 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)] -PANELS = { - "LONG": (14, 92, 672, 250), - "SHORT": (14, 362, 672, 270), -} - -TEXTS = [] # (x, y, text, size, weight, anchor, color) pentru verificare +# ---------------------------------------------------------------- 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 txt(x, y, s, size=13, weight="400", fill=BLACK, anchor="middle"): - TEXTS.append((x, y, s, size, weight, anchor, fill)) - return (f'' - f'{escape(s)}') +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 -def line(x1, y1, x2, y2, stroke=GRAY, width=1.0, dash=None): - d = f' stroke-dasharray="{dash}"' if dash else "" - return (f'') +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 poly(pts, stroke, width=2.0): - p = " ".join(f"{x:.1f},{y:.1f}" for x, y in pts) - return f'' +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) -def dot(x, y, fill, r=5.0): - return f'' +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) -def build(): - p = [ - f'', - '', - f'', - txt(W / 2, 30, "Secvența M → A: nu e o stare, e o secvență", - 19, "700", BLACK), - txt(W / 2, 52, "M2D — același moment, două time frame-uri: " - "TF mare dă direcția, TF mic dă trigger-ul", - 13, "400", GRAY), - txt(W / 2, 72, "sus = LONG; jos = SHORT — aceleași momente, în oglindă", - 13, "400", GRAY), - ] +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)) - # ------------------------------------------------------ panouri - for name, (px, py, pw, ph) in PANELS.items(): - p.append(f'') - - # ------------------------------------------------------ panoul LONG - p.append(txt(30, 116, "LONG", 15, "700", GREEN, "start")) - p.append(txt(30, 140, "TF mare (ex. 15 min)", 13, "400", GRAY, "start")) - p.append(txt(660, 114, "rămâne în Buy pe tot parcursul", 13, "400", - GREEN, "end")) - for x, y_top, y_bot in ((LONG_TOP[L_M][0], LONG_TOP[L_M][1], LONG_BOT[L_M][1]), - (LONG_TOP[L_A][0], LONG_TOP[L_A][1], LONG_BOT[L_A][1])): - p.append(line(x, y_top, x, y_bot, "#999999", 1.2, "4 4")) - p.append(poly(LONG_TOP, GREEN)) - p.append(dot(*LONG_TOP[1], CYAN)) - p.append(txt(185, 126, "Buy Momentum", 13, "400", CYAN, "start")) - - p.append(txt(30, 202, "TF mic (ex. 3 min)", 13, "400", GRAY, "start")) - p.append(poly(LONG_BOT, "#333333")) - p.append(dot(*LONG_BOT[L_M], RED)) - p.append(txt(262, 296, "M: Sell Momentum (pullback)", 13, "400", - RED, "start")) - p.append(dot(*LONG_BOT[L_A], GREEN, r=6)) - p.append(txt(660, 240, "A: Buy Momentum — TRIGGER, intri long", - 13, "400", GREEN, "end")) - - # ------------------------------------------------------ panoul SHORT - p.append(txt(30, 386, "SHORT", 15, "700", RED, "start")) - p.append(txt(30, 410, "TF mare (ex. 15 min)", 13, "400", GRAY, "start")) - p.append(txt(660, 494, "rămâne în Sell pe tot parcursul", 13, "400", - RED, "end")) - for x, y_top, y_bot in ((SHORT_TOP[S_M][0], SHORT_TOP[S_M][1], SHORT_BOT[S_M][1]), - (SHORT_TOP[S_A][0], SHORT_TOP[S_A][1], SHORT_BOT[S_A][1])): - p.append(line(x, y_top, x, y_bot, "#999999", 1.2, "4 4")) - p.append(poly(SHORT_TOP, RED)) - p.append(dot(*SHORT_TOP[1], RED)) - p.append(txt(168, 432, "Sell Momentum", 13, "400", RED, "start")) - - p.append(txt(30, 470, "TF mic (ex. 3 min)", 13, "400", GRAY, "start")) - p.append(poly(SHORT_BOT, "#333333")) - p.append(dot(*SHORT_BOT[S_M], CYAN)) - p.append(txt(238, 494, "M: Buy Momentum (pullback)", 13, "400", - CYAN, "end")) - p.append(dot(*SHORT_BOT[S_A], RED, r=6)) - p.append(txt(660, 610, "A: Sell Momentum — TRIGGER, intri short", - 13, "400", RED, "end")) - - # ------------------------------------------------------ subsol - p.append(f'') - p.append(txt(W / 2, 676, - "Secvența contează, nu starea: mare în Buy → mic în Sell → " - "mic în Buy", 13, "400", BLACK)) - p.append(txt(W / 2, 696, - "(cu mare încă în Buy). Un Buy direct pe TF mic, fără Sell " - "între ele,", 13, "400", BLACK)) - p.append(txt(W / 2, 716, "NU e trigger (vezi 1.3).", 13, "400", BLACK)) - p.append(txt(W / 2, 746, - "Sursă: 14_strategia_1_m2d.md:30-38, 172-180; " - "09_QA/08_2026-01-20.md:65-67", 13, "400", GRAY)) - - p.append('') - return "\n".join(p) + "\n" - - -# ---------------------------------------------------------------- verificare -def est_w(s, size, weight): - factor = 0.62 if weight == "700" else 0.56 - return len(s) * size * factor - - -def verify(svg): - # 1. geometria: panourile nu se suprapun, sunt în panză - long_ = PANELS["LONG"] - short_ = PANELS["SHORT"] - assert long_[1] + long_[3] < short_[1], "panourile LONG/SHORT se suprapun" - assert short_[1] + short_[3] < 648, "panoul SHORT intră în subsol" - for _, (px, py, pw, ph) in PANELS.items(): - assert px >= 0 and py >= 0 and px + pw <= W and py + ph <= H - - # 2. secvența LONG: TF mare rămâne în Buy (urcă monoton: y strict descrescător) - ys = [y for _, y in LONG_TOP] - assert all(ys[i] > ys[i + 1] for i in range(len(ys) - 1)), \ - "LONG: TF mare nu urcă monoton (nu rămâne în Buy)" - # M = pullback (minim local), A = reluare peste vârful anterior - assert LONG_BOT[L_M][1] > LONG_BOT[L_M - 1][1] and \ - LONG_BOT[L_M][1] > LONG_BOT[L_M + 1][1], "LONG: M nu e un minim local" - assert LONG_BOT[L_A][1] < LONG_BOT[1][1], \ - "LONG: A nu urcă peste vârful anterior (pullback-ul)" - assert LONG_TOP[L_M][0] < LONG_TOP[L_A][0], "LONG: M trebuie înainte de A" - assert LONG_TOP[L_M][0] == LONG_BOT[L_M][0] and \ - LONG_TOP[L_A][0] == LONG_BOT[L_A][0], \ - "LONG: liniile verticale nu leagă exact M și A de rândul de sus" - - # 3. secvența SHORT: oglindit - ys = [y for _, y in SHORT_TOP] - assert all(ys[i] < ys[i + 1] for i in range(len(ys) - 1)), \ - "SHORT: TF mare nu coboară monoton (nu rămâne în Sell)" - assert SHORT_BOT[S_M][1] < SHORT_BOT[S_M - 1][1] and \ - SHORT_BOT[S_M][1] < SHORT_BOT[S_M + 1][1], "SHORT: M nu e un maxim local" - assert SHORT_BOT[S_A][1] > SHORT_BOT[1][1], \ - "SHORT: A nu coboară sub minimul anterior" - assert SHORT_TOP[S_M][0] < SHORT_TOP[S_A][0], "SHORT: M trebuie înainte de A" - assert SHORT_TOP[S_M][0] == SHORT_BOT[S_M][0] and \ - SHORT_TOP[S_A][0] == SHORT_BOT[S_A][0], \ - "SHORT: liniile verticale nu leagă exact M și A de rândul de sus" - - # 4. verticalele chiar traversează de la rândul de sus până la moment - assert LONG_TOP[2][1] < LONG_BOT[L_M][1] - assert LONG_TOP[3][1] < LONG_BOT[L_A][1] - assert SHORT_TOP[2][1] < SHORT_BOT[S_M][1] - assert SHORT_TOP[3][1] < SHORT_BOT[S_A][1] - - # 5. toate etichetele în panză - for x, y, s, size, weight, anchor, _ in TEXTS: - ew = est_w(s, size, weight) - left = x - ew if anchor == "end" else (x - ew / 2 if anchor == "middle" else x) - right = left + ew - assert -2 <= left and right <= W + 2, f"text în afara panzei: {s!r}" - assert 0 <= y <= H, f"text în afara înălțimii: {s!r} (y={y})" - - # 6. etichete care nu se suprapun - for i in range(len(TEXTS)): - xi, yi, si, szi, wi, ai, _ = TEXTS[i] - we = est_w(si, szi, wi) - li = xi - we if ai == "end" else (xi - we / 2 if ai == "middle" else xi) - ri = li + we - for j in range(i + 1, len(TEXTS)): - xj, yj, sj, szj, wj, aj, _ = TEXTS[j] - wej = est_w(sj, szj, wj) - lj = xj - wej if aj == "end" else (xj - wej / 2 if aj == "middle" else xj) - rj = lj + wej - if abs(yi - yj) < 13 and li < rj - 4 and lj < ri - 4: - raise AssertionError(f"texte suprapuse: {si!r} / {sj!r}") - - # 7. niciun text nu se tastează peste o polilinie (coliziune text-linie) - segments = [(LONG_TOP, PANELS["LONG"]), (LONG_BOT, PANELS["LONG"]), - (SHORT_TOP, PANELS["SHORT"]), (SHORT_BOT, PANELS["SHORT"])] - for x, y, s, size, weight, anchor, _ in TEXTS: - ew = est_w(s, size, weight) - left = x - ew if anchor == "end" else (x - ew / 2 if anchor == "middle" else x) - right = left + ew - top, bot = y - size + 1, y - for pts, _ in segments: - for (x1, y1), (x2, y2) in zip(pts, pts[1:]): - xa, xb = min(x1, x2), max(x1, x2) - if xb < left - 2 or xa > right + 2: - continue - for xc in (max(xa, left), min(xb, right)): - t = 0 if x2 == x1 else (xc - x1) / (x2 - x1) - yc = y1 + t * (y2 - y1) - if top - 1 <= yc <= bot + 1: - raise AssertionError( - f"text peste polilinie: {s!r} (y={y}, linia y={yc:.0f})") - - # 8. etichetele obligatorii din spec sunt prezente - for cheie in ("LONG", "SHORT", - "TF mare (ex. 15 min)", "TF mic (ex. 3 min)", - "rămâne în Buy pe tot parcursul", - "rămâne în Sell pe tot parcursul", - "Buy Momentum", "Sell Momentum", - "M: Sell Momentum (pullback)", - "A: Buy Momentum — TRIGGER, intri long", - "M: Buy Momentum (pullback)", - "A: Sell Momentum — TRIGGER, intri short", - "Secvența contează, nu starea", - "NU e trigger (vezi 1.3)"): - assert cheie in svg, f"lipsește din desen: {cheie!r}" - - # 9. fără cifre de preț inventate (spec: diagramă fără niveluri) - for interzis in ("$", "%", "€", "intrare ", "SL ", "TP"): - assert interzis not in svg, f"nu ar trebui să apară preț: {interzis!r}" - return True - - -SVG = build() -assert verify(SVG), "verificare eșuată" -OUT.write_text(SVG, encoding="utf-8") -print(f"scris {OUT} ({W}x{H}), verificare OK: {len(TEXTS)} etichete, " - f"{len(LONG_TOP) + len(LONG_BOT) + len(SHORT_TOP) + len(SHORT_BOT)} puncte") +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") diff --git a/gen/strategii/m2d_secventa.svg b/gen/strategii/m2d_secventa.svg index f835cf0..065798c 100644 --- a/gen/strategii/m2d_secventa.svg +++ b/gen/strategii/m2d_secventa.svg @@ -1,42 +1,72 @@ - - - -Secvența M → A: nu e o stare, e o secvență -M2D — același moment, două time frame-uri: TF mare dă direcția, TF mic dă trigger-ul -sus = LONG; jos = SHORT — aceleași momente, în oglindă - - -LONG -TF mare (ex. 15 min) -rămâne în Buy pe tot parcursul - - - - -Buy Momentum -TF mic (ex. 3 min) - - -M: Sell Momentum (pullback) - -A: Buy Momentum — TRIGGER, intri long -SHORT -TF mare (ex. 15 min) -rămâne în Sell pe tot parcursul - - - - -Sell Momentum -TF mic (ex. 3 min) - - -M: Buy Momentum (pullback) - -A: Sell Momentum — TRIGGER, intri short - -Secvența contează, nu starea: mare în Buy → mic în Sell → mic în Buy -(cu mare încă în Buy). Un Buy direct pe TF mic, fără Sell între ele, -NU e trigger (vezi 1.3). -Sursă: 14_strategia_1_m2d.md:30-38, 172-180; 09_QA/08_2026-01-20.md:65-67 - + + DA — pullback pe 3 min, care nu rupe minimul de referințăM: pullbackminimul din care a pornit mișcarea 99.80 — sub el nu mai e pullbackcel mai jos punct al pullback-ului 99.90 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + M: pullback-ul obligatoriu — prețul merge + contra direcției mari, dar se oprește la 99.90 + + + prima bară care urcă peste capătul pullback-ului: + aici începe pasul 3 (trigger-ul) + + NU — niciun pullback: nu ai setup, oricât de frumoasă e urcareaclose-ul candelei de 15 min de semnal 100.60 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + urcare continuă, fără nicio pauză în jos: + asta e breakout, nu M2D — nu ai unde pune stopul + + 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. + \ No newline at end of file diff --git a/gen/strategii/m2d_stop.py b/gen/strategii/m2d_stop.py new file mode 100644 index 0000000..0ca2407 --- /dev/null +++ b/gen/strategii/m2d_stop.py @@ -0,0 +1,136 @@ +#!/usr/bin/env python3 +"""M2D, pasul 4 — stopul: extrema ultimelor 4 bare, și alternativa 2 × ATR(10). + +Pentru `summaries/17_STRATEGII/03_m2d.md`, marcajul ``. + +Sus: exemplul construit din tot capitolul (3 min). Numeri 4 bare înapoi de la +bara de trigger, iei cel mai jos low și acolo stă stopul — fix pe el, fără +marjă; asta e regula folosită în TOATE diagramele capitolului. + +Jos: exemplul SPY din curs, singurul loc din material unde apar cifre +absolute pentru stop. Reale din curs: close-ul de trigger 517.78, ATR(10) +2.88, stopul 2×ATR 512.02 și low-ul metodei pe 4 candele 512.87. Restul +seriei e construit ca să scoată exact acel low. + +Rulare: python3 gen/strategii/m2d_stop.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 = 680 +GREEN, RED, BLUE, ORANGE, GREY = "#188a3e", "#c00", "#1a5fb4", "#b06000", "#555" +STOP_BARS = 4 + +# --- panoul 1: exemplul construit, 3 min (aceleași bare ca în ansamblu) ---- +MIC = [(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), + (100.02, 100.45, 100.00, 100.40), # 9: trigger + (100.40, 100.60, 100.35, 100.55), (100.55, 100.75, 100.50, 100.70)] +TRIG_M = 9 + +# --- panoul 2: SPY, 15 min. Reale: 517.78, 2.88, 512.02, 512.87 ----------- +SPY_CLOSE_TRIG = 517.78 +SPY_ATR = 2.88 +SPY_ATR_STOP = 512.02 +SPY_LOW4 = 512.87 +SPY = [(521.40, 521.80, 520.10, 520.50), (520.50, 520.90, 519.20, 519.60), + (519.60, 519.80, 518.10, 518.40), (518.40, 518.70, 516.90, 517.20), + (517.20, 517.40, 515.70, 516.10), (516.10, 516.40, 514.90, 515.30), + (515.30, 515.60, 513.40, 514.60), + (514.60, 514.90, 513.40, 514.00), # 7 + (514.00, 514.30, SPY_LOW4, 513.50), # 8: cel mai jos low din 4 + (513.50, 514.20, 513.20, 513.90), # 9 + (513.90, 518.10, 513.80, SPY_CLOSE_TRIG)] # 10: trigger +TRIG_S = 10 + +# ---------------------------------------------------------------- verificări +for nume, bars in (("mic", MIC), ("SPY", SPY)): + 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" + +ENTRY_M = MIC[TRIG_M][3] +SL_M = min(b[2] for b in MIC[TRIG_M - STOP_BARS + 1:TRIG_M + 1]) +RISK_M = round(ENTRY_M - SL_M, 2) +assert (ENTRY_M, SL_M, RISK_M) == (100.40, 99.90, 0.50), (ENTRY_M, SL_M, RISK_M) +# stopul nu e „undeva pe-aproape": e minimul celor 4 bare, și e minimul întregii serii +assert abs(SL_M - min(b[2] for b in MIC)) < 1e-9, "stopul nu e minimul seriei mici" +# a cincea bară înapoi are un low mai mare — deci numărul 4 chiar contează +assert MIC[TRIG_M - STOP_BARS][2] > SL_M, "a 5-a bară strică demonstrația celor 4" +PCT_M = RISK_M / ENTRY_M * 100 + +SL_S = min(b[2] for b in SPY[TRIG_S - STOP_BARS + 1:TRIG_S + 1]) +assert abs(SL_S - SPY_LOW4) < 1e-9, f"low-ul pe 4 bare iese {SL_S}, nu {SPY_LOW4}" +assert abs(SPY[TRIG_S][3] - SPY_CLOSE_TRIG) < 1e-9 +assert abs((SPY_CLOSE_TRIG - 2 * SPY_ATR) - SPY_ATR_STOP) < 1e-9, \ + "2×ATR nu dă stopul din curs" +MARJA = round(SPY_LOW4 - SPY_ATR_STOP, 2) +assert MARJA == 0.85, MARJA +RISK_S4 = round(SPY_CLOSE_TRIG - SPY_LOW4, 2) +RISK_SA = round(SPY_CLOSE_TRIG - SPY_ATR_STOP, 2) +assert (RISK_S4, RISK_SA) == (4.91, 5.76), (RISK_S4, RISK_SA) +assert SPY_ATR_STOP < SPY_LOW4, "ATR ar trebui să dea un stop mai larg" + + +def chart(bars, scale, gap, left, body_w, arrows, top=64): + return Chart(candles=[Candle(*b) for b in bars], arrows=arrows, scale=scale, + gap=gap, body_w=body_w, left=left, width=W, top=top) + + +ch_m = chart(MIC, 150.0, 34, 62, 13, [ + Arrow(TRIG_M, "bara de trigger: de la ea numeri înapoi", + where="above", color=GREEN, dx=-30, dy=-6), + Arrow(TRIG_M - STOP_BARS + 2, "cel mai jos low dintre cele 4 bare: 99.90.\n" + "Stopul stă FIX acolo, fără marjă", + where="below", color=RED, dx=-40, dy=40)]) +ch_s = chart(SPY, 17.0, 34, 62, 13, [ + Arrow(TRIG_S, "close-ul barei de trigger 517.78", + where="above", color=GREEN, dx=-60, dy=-4), + Arrow(8, "2 × ATR(10) = 5.76 sub close:\n" + "stop 512.02, cu 0.85 sub low-ul celor 4 bare", + where="below", color=ORANGE, dx=-20, dy=52)]) + +body_m, h_m = duo.panel(ch_m, W) +body_s, h_s = duo.panel(ch_s, W) + +extra_m = (duo.titlu("Metoda de bază — cele 4 bare", color=RED) + + duo.zone(ch_m, TRIG_M - STOP_BARS + 1, TRIG_M, + f"{STOP_BARS} bare, numărate de la trigger înapoi", RED, + pad=13, lift=30) + + duo.level(ch_m, ENTRY_M, ["intrare 100.40"], GREEN, W, at_left=False) + + duo.level(ch_m, SL_M, ["stop 99.90 — risc 0.50 pe acțiune (0,50 %)"], + RED, W, below=True)) +extra_s = (duo.titlu("Alternativa din curs — 2 × ATR(10), pe SPY", color=ORANGE) + + duo.zone(ch_s, TRIG_S - STOP_BARS + 1, TRIG_S, "", RED, pad=13) + + duo.level(ch_s, SPY_CLOSE_TRIG, ["intrare 517.78"], GREEN, W, at_left=False) + + duo.level(ch_s, SPY_LOW4, ["stop pe 4 bare 512.87 — risc 4.91"], + RED, W, below=False) + + duo.level(ch_s, SPY_ATR_STOP, ["stop 2×ATR 512.02 — risc 5.76"], + ORANGE, W, below=True, dash="2 3")) + +svg, H, n = duo.build( + [("mic", body_m, h_m, extra_m), ("SPY", body_s, h_s, extra_s)], + ["Sus: serie construită, aceleași cifre ca în restul capitolului.", + "Jos: din curs sunt reale doar 517.78, ATR(10) = 2.88, 512.02 și 512.87; " + "restul barelor sunt construite.", + "ATR-ul dă un stop mai larg, deci un risc mai mare pe acțiune — și, la același", + "risc în dolari, mai puține acțiuni."], + W) +duo.fara_preturi_straine(svg, [ENTRY_M, SL_M, RISK_M, SPY_CLOSE_TRIG, SPY_LOW4, + SPY_ATR_STOP, SPY_ATR, RISK_S4, RISK_SA, MARJA, + 2 * SPY_ATR, PCT_M]) +OUT.write_text(svg, encoding="utf-8") +print(f"scris {OUT} ({W}x{H:.0f}), {n} etichete | mic: intrare {ENTRY_M:.2f} stop {SL_M:.2f} " + f"risc {RISK_M:.2f} ({PCT_M:.2f} %) | SPY: intrare {SPY_CLOSE_TRIG:.2f}, 4 bare " + f"{SPY_LOW4:.2f} (risc {RISK_S4:.2f}), 2×ATR {SPY_ATR_STOP:.2f} (risc {RISK_SA:.2f}), " + f"diferență {MARJA:.2f}") diff --git a/gen/strategii/m2d_stop.svg b/gen/strategii/m2d_stop.svg new file mode 100644 index 0000000..cf86490 --- /dev/null +++ b/gen/strategii/m2d_stop.svg @@ -0,0 +1,70 @@ + + Metoda de bază — cele 4 bare4 bare, numărate de la trigger înapoiintrare 100.40stop 99.90 — risc 0.50 pe acțiune (0,50 %) + + + + + + + + + + + + + + + + + + + + + + + + + + + bara de trigger: de la ea numeri înapoi + + + cel mai jos low dintre cele 4 bare: 99.90. + Stopul stă FIX acolo, fără marjă + + Alternativa din curs — 2 × ATR(10), pe SPYintrare 517.78stop pe 4 bare 512.87 — risc 4.91stop 2×ATR 512.02 — risc 5.76 + + + + + + + + + + + + + + + + + + + + + + + + + close-ul barei de trigger 517.78 + + + 2 × ATR(10) = 5.76 sub close: + stop 512.02, cu 0.85 sub low-ul celor 4 bare + + Sus: serie construită, aceleași cifre ca în restul capitolului. + Jos: din curs sunt reale doar 517.78, ATR(10) = 2.88, 512.02 și 512.87; restul barelor sunt construite. + ATR-ul dă un stop mai larg, deci un risc mai mare pe acțiune — și, la același + risc în dolari, mai puține acțiuni. + \ No newline at end of file diff --git a/gen/strategii/m2d_trigger.py b/gen/strategii/m2d_trigger.py new file mode 100644 index 0000000..3e06d42 --- /dev/null +++ b/gen/strategii/m2d_trigger.py @@ -0,0 +1,118 @@ +#!/usr/bin/env python3 +"""M2D, pasul 3 — trigger-ul (A): DA (bară închisă peste) vs. NU (doar atins). + +Pentru `summaries/17_STRATEGII/03_m2d.md`, marcajul ``. + +Aceeași serie de 3 minute ca în diagrama de ansamblu până la bara 8; de acolo +încolo, două continuări. Sus: bara 9 se ÎNCHIDE peste capătul pullback-ului, +deci e trigger, și intri la close. Jos: bara 9 atinge același nivel cu +fitilul, dar se închide sub el — nu e trigger; pe barele următoare pullback-ul +rupe minimul de referință și setup-ul moare. + +Indicatorul care dă săgeata de Buy Momentum e proprietar cursului. Pe desen, +săgeata e pusă acolo unde prețul se închide peste capătul pullback-ului — +construcția mea, ca regula să fie verificabilă cu ochiul pe un grafic. + +Rulare: python3 gen/strategii/m2d_trigger.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, 104.0, 30, 56 +GREEN, RED, GREY = "#188a3e", "#c00", "#555" + +REF_LOW = 99.80 # minimul din care a pornit mișcarea (vezi pașii 1 și 2) + +COMUN = [(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)] +TRIG = 9 +DA = COMUN + [(100.02, 100.45, 100.00, 100.40), # 9: se ÎNCHIDE peste capăt + (100.40, 100.60, 100.35, 100.55), (100.55, 100.75, 100.50, 100.70), + (100.70, 101.00, 100.65, 100.95)] +NU = COMUN + [(100.02, 100.45, 99.98, 100.00), # 9: atinge, dar se închide sub + (100.00, 100.05, 99.82, 99.85), + (99.85, 99.90, 99.55, 99.60), # 11: rupe minimul de referință + (99.60, 99.70, 99.45, 99.50)] + +# ---------------------------------------------------------------- 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 DA[:TRIG] == NU[:TRIG] == COMUN, "panourile nu pornesc din aceeași serie" + +CAPAT = max(b[1] for b in COMUN[6:]) # capătul pullback-ului = 100.08 +assert abs(CAPAT - 100.08) < 1e-9, CAPAT +# nicio bară dinaintea trigger-ului nu se închidea deja peste capăt +assert all(b[3] <= CAPAT for b in COMUN[6:]), "o bară dinainte închidea deja peste capăt" + +# DA: bara 9 se închide peste capăt — e trigger, și e PRIMA care face asta +assert DA[TRIG][3] > CAPAT, "DA: bara de trigger nu se închide peste capăt" +assert all(b[3] <= CAPAT for b in DA[6:TRIG]), "DA: trigger-ul nu e primul de după minim" +ENTRY = DA[TRIG][3] +assert abs(ENTRY - 100.40) < 1e-9, ENTRY +# al doilea semnal, pe care NU mai intri +AL_DOILEA = next(i for i in range(TRIG + 1, len(DA)) + if DA[i][3] > max(b[1] for b in DA[i - 3:i])) +assert AL_DOILEA == 10, AL_DOILEA + +# NU: bara 9 ATINGE capătul cu fitilul, dar se închide sub el +assert NU[TRIG][1] > CAPAT, "NU: bara 9 nici nu atinge capătul" +assert NU[TRIG][3] < CAPAT, "NU: bara 9 se închide totuși peste capăt" +assert all(b[3] <= CAPAT for b in NU[6:]), "NU: o bară se închide peste capăt" +RUPT = next(i for i, b in enumerate(NU) if b[3] < REF_LOW) +assert RUPT == 11, RUPT + + +def chart(bars, arrows): + return Chart(candles=[Candle(*b) for b in bars], arrows=arrows, + scale=SCALE, gap=GAP, body_w=12, left=LEFT, width=W, top=64) + + +ch_da = chart(DA, [ + Arrow(TRIG, "s-a ÎNCHIS peste 100.08: ăsta e trigger-ul.\n" + "Intri la close, adică la 100.40", + where="below", color=GREEN, dx=-40, dy=54), + Arrow(AL_DOILEA, "al doilea semnal — aici NU mai intri:\n" + "statistica cursului e măsurată pe primul", + where="above", color=RED, dx=40, dy=-4)]) +ch_nu = chart(NU, [ + Arrow(TRIG, "fitilul a trecut peste 100.08, close-ul nu:\n" + "un puseu de câteva secunde nu e trigger", + where="above", color=RED, dx=-90, dy=4), + Arrow(RUPT, "pullback-ul rupe 99.80:\nsetup mort, cauți altul", + where="below", color=RED, dx=-30, dy=24)]) + +body_da, h_da = duo.panel(ch_da, W) +body_nu, h_nu = duo.panel(ch_nu, W) + +nivel_capat = ["capătul pullback-ului 100.08", "peste el trebuie ÎNCHIS, nu doar atins"] +extra_da = (duo.titlu("DA — bara se închide peste capăt", color=GREEN) + + duo.zone(ch_da, TRIG - 2, TRIG, "aici se decide intrarea", GREEN, pad=13, lift=30) + + duo.level(ch_da, CAPAT, nivel_capat, GREY, W, below=True, at_left=False)) +extra_nu = (duo.titlu("NU — atins cu fitilul, închis dedesubt: nu e trigger", color=RED) + + duo.level(ch_nu, CAPAT, nivel_capat, GREY, W, below=False, at_left=False) + + duo.level(ch_nu, REF_LOW, ["minimul din care a pornit mișcarea 99.80"], + RED, W, below=True, dash="2 3")) + +svg, H, n = duo.build( + [("DA", body_da, h_da, extra_da), ("NU", body_nu, h_nu, extra_nu)], + ["Primele 9 bare sunt identice pe ambele panouri; se schimbă doar ce urmează.", + "Trigger-ul se citește pe bară ÎNCHISĂ, nu pe fitil — la fel se citește și " + "ieșirea pe trail (pasul 5)."], + W) +duo.fara_preturi_straine(svg, [CAPAT, ENTRY, REF_LOW]) +OUT.write_text(svg, encoding="utf-8") +print(f"scris {OUT} ({W}x{H:.0f}), {n} etichete | capăt {CAPAT:.2f} | " + f"DA: trigger la bara {TRIG}, intrare {ENTRY:.2f}, al doilea semnal la {AL_DOILEA} | " + f"NU: bara {TRIG} high {NU[TRIG][1]:.2f} close {NU[TRIG][3]:.2f}, rupe la bara {RUPT}") diff --git a/gen/strategii/m2d_trigger.svg b/gen/strategii/m2d_trigger.svg new file mode 100644 index 0000000..40a1b4d --- /dev/null +++ b/gen/strategii/m2d_trigger.svg @@ -0,0 +1,76 @@ + + DA — bara se închide peste capătaici se decide intrareacapătul pullback-ului 100.08peste el trebuie ÎNCHIS, nu doar atins + + + + + + + + + + + + + + + + + + + + + + + + + + + + + s-a ÎNCHIS peste 100.08: ăsta e trigger-ul. + Intri la close, adică la 100.40 + + + al doilea semnal — aici NU mai intri: + statistica cursului e măsurată pe primul + + NU — atins cu fitilul, închis dedesubt: nu e triggercapătul pullback-ului 100.08peste el trebuie ÎNCHIS, nu doar atinsminimul din care a pornit mișcarea 99.80 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + fitilul a trecut peste 100.08, close-ul nu: + un puseu de câteva secunde nu e trigger + + + pullback-ul rupe 99.80: + setup mort, cauți altul + + Primele 9 bare sunt identice pe ambele panouri; se schimbă doar ce urmează. + Trigger-ul se citește pe bară ÎNCHISĂ, nu pe fitil — la fel se citește și ieșirea pe trail (pasul 5). + \ No newline at end of file