Files
atm-curs-dl/gen/strategii/atmi_perechi_tf.py
Claude Agent c08c857274 atmi: diagramele refacute, cinci noi, si fisa de surse
Noi: pivoti (PPQ/SQ calculati din trimestrul precedent, cu formula pe
desen), perechi_tf (weekly -> daily ca descompunere reala, cu caseta,
ghidaje si PPQ pe ambele panouri), trigger (BIM apoi verde, fata de
verde inainte de BIM — cutitul in cadere), risc (ce inseamna "fara
stop": 24 de bare sub apa, -20 % pe pozitie), reinvestire (ciclul
banilor pe trei cicluri).

aplus devine corectie: diagrama arata un prag de corectie, nu un setup
A+, si numele vechi trimitea la altceva.

Trei bug-uri reale: exemplul era etichetat weekly cu 20 de bare, dar
textul spunea "un an mai tarziu" — trecut pe monthly cu assert pe numarul
de luni; ansamblul avea o trimitere fisier:linie desenata in SVG; si
benzile PPQ/SQ erau inversate ca sens, cu SQ fix pe intrare.

PPQ 95.80 si SQ 90.80 sunt completare proprie — cursul spune explicit ca
nu da valorile; derivate cu formula standard, cu assert.

Textul capitolului e neatins — agentul a atins pragul de context.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ThN7ixZrowca8aTGrK1MUJ
2026-09-20 12:46:55 +00:00

261 lines
12 KiB
Python

#!/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'<text x="{x:.1f}" y="{y:.1f}" fill="{fill}"{a} style="{st}">{s}</text>')
def hline(x1, x2, y, color, dash, width=1.5):
out.append(f'<line x1="{x1:.1f}" y1="{y:.1f}" x2="{x2:.1f}" y2="{y:.1f}" '
f'stroke="{color}" stroke-width="{width}" stroke-dasharray="{dash}"/>')
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'<line x1="{x1:.1f}" y1="{y1:.1f}" x2="{x2:.1f}" y2="{y2:.1f}" '
f'stroke="{color}" stroke-width="{width}"{d}{m}/>')
def rect(x0_, y0_, x1, y1, color, fill_op=0.10, dash=""):
d = f' stroke-dasharray="{dash}"' if dash else ""
out.append(f'<rect x="{x0_:.1f}" y="{y0_:.1f}" width="{x1 - x0_:.1f}" '
f'height="{y1 - y0_:.1f}" fill="{color}" fill-opacity="{fill_op}" '
f'stroke="{color}" stroke-width="1.2"{d}/>')
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'<rect x="{cx - BODY_W / 2:.1f}" y="{top_:.1f}" width="{BODY_W}" '
f'height="{hh:.1f}" fill="{GREEN if c >= o_ else RED}"/>')
out.append('<defs><marker id="pt" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" '
'markerHeight="7" orient="auto"><path d="M0,0 L10,5 L0,10 z" fill="#c00"/></marker>'
'</defs>')
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'<path d="M{brx + 5:.1f},{yb(SWING):.1f} H{brx:.1f} V{yb(ENTRY):.1f} '
f'H{brx + 5:.1f}" fill="none" stroke="{RED}" stroke-width="1.6"/>')
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'<path d="M{lx1:.1f},{ycb:.1f} H{mid:.1f} V{ycs:.1f} H{FRAME_L:.1f}" '
f'fill="none" stroke="{LINK}" stroke-width="1.0" stroke-dasharray="3 3"/>')
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'<svg viewBox="0 0 {W} {H:.0f}" xmlns="http://www.w3.org/2000/svg" '
f'style="width:100%;height:auto;font:12px sans-serif">\n ' + "\n ".join(out) + "\n</svg>")
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})")