#!/usr/bin/env python3 """ATMI — Pasul 3: nivelul trăiește pe weekly, declanșatorul pe daily. Panoul din stânga (weekly) arată de unde pleacă corecția și unde stă zona de cumpărare. Panoul din dreapta NU e o serie care vine după el: e **descompunerea reală** a ultimelor 3 candele weekly — fiecare candelă weekly = 5 bare daily, cu open, close, max(high) și min(low) care coincid, verificat bară cu bară. Sursa de adevăr e seria daily (`FINE`); candelele weekly din casetă se calculează din ea prin agregare, deci corespondența nu poate rămâne în urmă. Nivelul comun e PPQ 95.80 (calculat în `atmi_pivoti.py`): e desenat pe ambele panouri, cu același număr, și legat printr-un conector — scările au pante diferite, e o lupă, deci același preț cade la înălțimi diferite. Rulare: python3 gen/strategii/atmi_perechi_tf.py """ from pathlib import Path OUT = Path(__file__).with_suffix(".svg") W = 720 K = 5 # 5 zile de bursă într-o săptămână NZ = 3 # câte candele weekly se sparg în dreapta PPQ, SQ = 95.80, 90.80 # din atmi_pivoti.py SWING = 100.00 GREEN, RED, GRAY, INK, BLUE = "#188a3e", "#c00", "#888", "#333", "#1a5fb4" PURPLE, CYAN, LINK = "#7b2fbf", "#0a7f9e", "#aaa" # --- seria daily: 15 bare = 3 săptămâni ------------------------------------ D_CLOSES = [98.60, 98.20, 97.90, 97.60, 97.90, # săptămâna 1 97.50, 97.10, 96.80, 97.00, 96.60, # săptămâna 2 96.30, 96.00, 95.90, 95.70, 96.00] # săptămâna 3 (13 = BIM, 14 = intrare) D_START = 98.90 BIM_I, ENTRY_I = 13, 14 wick = [(0.15, 0.15)] * 15 wick[BIM_I] = (0.10, 0.15) # bara de BIM coboară sub PPQ: 95.70 - 0.15 = 95.55 wick[ENTRY_I] = (0.15, 0.05) FINE, o = [], D_START for i, c in enumerate(D_CLOSES): wu, wd = wick[i] FINE.append((o, max(o, c) + wu, min(o, c) - wd, c)) o = c # --- barele weekly dinaintea lupei ---------------------------------------- # (open, high, low, close); ultima închide exact pe open-ul primei bare daily BASE = [(93.60, 94.80, 93.30, 94.50), (94.50, 95.90, 94.20, 95.60), (95.60, 97.10, 95.30, 96.80), (96.80, 98.30, 96.50, 98.00), (98.00, 99.50, 97.70, 99.20), (99.20, 100.00, 98.90, 100.00), (100.00, 100.00, 98.60, 98.90)] SWING_I = 5 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_ ZOOM = aggregate(FINE, K, NZ) BIG = BASE + ZOOM # --- ce trebuie să fie adevărat ------------------------------------------- eq = lambda a, b: abs(a - b) < 0.005 # noqa: E731 assert len(FINE) == K * NZ for name, bars in (("weekly", BIG), ("daily", FINE)): for i, (o_, h, l, c) in enumerate(bars): assert h >= max(o_, c) and l <= min(o_, c), f"{name}[{i}] h/l incoerent" if i: assert eq(o_, bars[i - 1][3]), f"{name}[{i}] open != close precedent" # lupa se leagă de restul graficului mare assert eq(FINE[0][0], BASE[-1][3]), "open-ul primei bare daily != close-ul ultimei weekly" # ce pretinde textul: corecția se întinde peste mai multe candele weekly, iar # trigger-ul cade în ultima assert eq(max(b[1] for b in BASE), SWING), max(b[1] for b in BASE) assert BIM_I // K == ENTRY_I // K == NZ - 1, "BIM și intrarea nu cad în aceeași săptămână" assert (BIM_I // K) > 0, "trigger-ul ar încăpea în prima candelă weekly a lupei" ENTRY = FINE[ENTRY_I][3] assert eq(ENTRY, 96.00) and eq((SWING - ENTRY) / SWING * 100, 4.0), ENTRY assert FINE[ENTRY_I][3] > FINE[ENTRY_I][0], "candela de intrare nu e verde" assert FINE[BIM_I][2] < PPQ < ENTRY, "prețul nu intră în zona PPQ înainte de intrare" assert eq(ZOOM[-1][3], ENTRY), "close-ul săptămânii = close-ul barei daily de intrare" assert eq(ZOOM[-1][2], FINE[BIM_I][2]), "low-ul săptămânii != low-ul barei de BIM" assert min(b[2] for b in BIG) > SQ, "seria desenată atinge deja SQ" # cât de aproape e fundul de PPQ, în procente — asta face zona „atinsă" DIST = abs(FINE[BIM_I][2] - PPQ) / PPQ * 100 assert DIST < 0.5, DIST # --- desen ----------------------------------------------------------------- PLOT_H = 190 BX0, BGAP = 132, 16 SX0, SGAP = 392, 14 BODY_W = 9 FRAME_L = SX0 - 12 COL_X = 608 MIDX = FRAME_L - 20 PAD = 0.07 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) * (size * 0.5 if size < 12 else 5.7) 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): for i, (o_, h, l, c) in enumerate(bars): cx = x0 + i * gap 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'') out.append('' '') text(20, 22, "Nivelul trăiește pe weekly, declanșatorul pe daily", bold=True, size=13) text(20, 38, f"Fiecare candelă weekly din casetă = {K} bare daily (o săptămână de bursă):", fill="#555", size=11) text(20, 52, "același open, același close, max(high) și min(low) identice.", fill="#555", size=11) top = 104 text(20, top - 12, "weekly: de unde pleacă corecția", fill="#555", size=11) text(FRAME_L, top - 12, f"daily: ultimele {NZ} candele weekly, desfăcute", fill="#555", size=11) zlo_, zhi_ = min(b[2] for b in FINE), max(b[1] for b in FINE) pad = PAD * (zhi_ - zlo_) lo, hi = zlo_ - pad, zhi_ + pad bmax, bmin = max(b[1] for b in BIG), min(min(b[2] for b in BIG), SQ, 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 weekly --------------------------------------------------------- lx0, lx1 = BX0 + len(BASE) * BGAP - 7, BX0 + (len(BIG) - 1) * BGAP + 9 ly0, ly1 = yb(hi), yb(lo) rect(lx0, ly0, lx1, ly1, BLUE, fill_op=0.06, dash="4 3") hline(BX0 - 22, lx1, yb(PPQ), PURPLE, "4 3") hline(BX0 - 22, lx1, yb(SQ), GREEN, "4 3") hline(BX0 - 22, lx1, yb(SWING), "#555", "3 3", width=1.0) text(24, yb(SWING) + 13, f"swing high {SWING:.2f}", fill="#555", size=11) text(24, yb(PPQ) - 6, f"PPQ {PPQ:.2f}", fill=PURPLE, size=11, bold=True) text(24, yb(SQ) - 6, f"SQ {SQ:.2f}", fill=GREEN, size=11, bold=True) text(24, yb(SQ) + 13, "zona 2, dacă", fill=GREEN, size=10) text(24, yb(SQ) + 25, "panica merge", fill=GREEN, size=10) text(24, yb(SQ) + 37, "mai jos", fill=GREEN, size=10) candles(BIG, BX0, BGAP, yb) # paranteza corecției: de la swing high până la intrare, pe panoul mare brx = BX0 - 16 out.append(f'') text(brx - 3, (yb(SWING) + yb(ENTRY)) / 2 + 4, f"-{(SWING - ENTRY) / SWING * 100:.1f} %".replace(".", ","), fill=RED, anchor="end", bold=True, size=11) text(BX0 + 2 * BGAP, top + PLOT_H + 20, "corecția de minim 4 % se măsoară aici, pe weekly", fill=RED, size=11) # ---- 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) # ---- conectorul nivelului comun ------------------------------------------- ycb, ycs = yb(PPQ), ys(PPQ) mid = (lx1 + FRAME_L) / 2 out.append(f'') text(mid + 4, (ycb + ycs) / 2 + 4, "același", fill="#999", size=10) text(mid + 4, (ycb + ycs) / 2 + 16, "preț", fill="#999", size=10) # ---- panoul daily ---------------------------------------------------------- frame_r = SX0 + (len(FINE) - 1) * SGAP + 10 rect(FRAME_L, top, frame_r, top + PLOT_H, BLUE, fill_op=0.03) y_ppq, y_e = ys(PPQ), ys(ENTRY) assert top + PLOT_H - ys(zlo_) > 6, "minimul e lipit de marginea de jos a cadrului" assert ys(zhi_) - top > 6, "maximul e lipit de marginea de sus a cadrului" # zona de decizie: bara de BIM + candela verde sx0, sx1 = SX0 + BIM_I * SGAP - 10, SX0 + ENTRY_I * SGAP + 10 rect(sx0, ys(FINE[ENTRY_I][1]) - 6, sx1, ys(min(b[2] for b in FINE)) + 6, GREEN) hline(FRAME_L, frame_r, y_ppq, PURPLE, "4 3") hline(FRAME_L, frame_r, y_e, GREEN, "6 3") candles(FINE, SX0, SGAP, ys) text(COL_X, y_ppq + 4, f"PPQ {PPQ:.2f}", fill=PURPLE, size=10, bold=True) text(COL_X, y_ppq + 16, "același nivel,", fill=PURPLE, size=10) text(COL_X, y_ppq + 28, "văzut de aproape", fill=PURPLE, size=10) text(COL_X, y_e - 8, f"intrare {ENTRY:.2f}", fill=GREEN, size=10, bold=True) line(SX0 + BIM_I * SGAP, ys(FINE[BIM_I][2]) + 4, SX0 + BIM_I * SGAP, top + PLOT_H + 14, CYAN, width=1) text(SX0 + BIM_I * SGAP - 6, top + PLOT_H + 28, "BIM: aici se oprește vânzarea", fill=CYAN, anchor="middle", size=11, bold=True) text(SX0 + BIM_I * SGAP - 6, top + PLOT_H + 42, f"(fundul e la {DIST:.2f} % de PPQ)".replace(".", ","), fill=CYAN, anchor="middle", size=10) text(20, top + PLOT_H + 58, "Pe weekly nu vezi unde s-a oprit căderea; pe daily, da — de aceea intri de aici.", fill="#555", size=11) # --- prețurile rămân fiecare pe panoul lui, cu excepția nivelului comun ---- COMUN = {f"{PPQ:.2f}"} MARE = {f"{SWING:.2f}", f"{SQ:.2f}"} - COMUN MIC = {f"{ENTRY:.2f}"} - COMUN for x0_, _x1, _y, s_ in LABELS: rau = MARE if x0_ >= MIDX else MIC for v in rau: assert v not in s_, f"eticheta {s_!r} poartă prețul {v} al celuilalt panou" st_ = [s for x0_, _x1, _y, s in LABELS if f"{PPQ:.2f}" in s and x0_ < MIDX] dr_ = [s for x0_, _x1, _y, s in LABELS if f"{PPQ:.2f}" in s and x0_ >= MIDX] assert st_ and dr_, f"nivelul comun {PPQ} lipsește de pe un panou ({st_} / {dr_})" H = top + PLOT_H + 76 for i in range(len(LABELS)): for j in range(i + 1, len(LABELS)): xa0, xa1, ya, sa = LABELS[i] xb0, xb1, yb_, sb = LABELS[j] assert not (abs(ya - yb_) < 12 and xa0 < xb1 - 4 and xb0 < xa1 - 4), \ f"etichete suprapuse: {sa!r} / {sb!r}" assert 0 < LABELS[i][2] < H, f"etichetă în afara înălțimii: {LABELS[i][3]!r}" svg = (f'\n ' + "\n ".join(out) + "\n") assert "\n\n" not in svg and ".md:" not in svg OUT.write_text(svg, encoding="utf-8") print(f"weekly {len(BIG)} bare (ultimele {NZ} = {len(FINE)} daily) | swing {SWING:.2f} " f"| PPQ {PPQ} | intrare {ENTRY:.2f} | fund {min(b[2] for b in FINE):.2f} " f"({DIST:.2f} % sub PPQ) | scris {OUT.name} ({W}x{H:.0f})")