Files
atm-curs-dl/gen/strategii/atmi_trigger.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

188 lines
8.4 KiB
Python

#!/usr/bin/env python3
"""ATMI — Pasul 4: trigger-ul. DA (BIM, apoi candela verde) vs. NU (verde fără BIM).
Două panouri, unul sub altul, cu exact aceleași patru bare de corecție. Sus:
prețul intră în zona PPQ, apare săgeata BIM, iar prima candelă verde daily de
după ea închide la 96.00 — acolo intri. Jos: candela verde vine ÎNAINTE de
orice BIM, deci nu e trigger; prețul continuă să cadă spre SQ — „cuțitul în
cădere" din curs.
Numerele sunt cele ale capitolului: PPQ 95.80, SQ 90.80 (calculate în
`atmi_pivoti.py`), intrare 96.00 la close-ul candelei verzi.
Rulare: python3 gen/strategii/atmi_trigger.py
"""
import re
import sys
from pathlib import Path
sys.path.insert(0, "/workspace/atm-curs-dl")
from svg_candles import Arrow, Chart, from_closes # noqa: E402
OUT = Path(__file__).with_suffix(".svg")
W = 660
SCALE = 46.0
LEFT, GAP = 66, 44
RED, GREEN, BLUE, GREY, CYAN, PURPLE = "#c00", "#188a3e", "#1a5fb4", "#555", "#0a7f9e", "#7b2fbf"
PPQ, SQ, ENTRY = 95.80, 90.80, 96.00
COMUN = [98.60, 97.80, 97.00, 96.40] # corecția, identică pe ambele panouri
CLOSES_DA = COMUN + [95.70, 96.00, 96.60] # 4: BIM 5: prima verde = intrare
CLOSES_NU = COMUN + [96.80, 96.10, 95.10, 94.00, 92.90] # 4: verde fără BIM
w_da = [(0.15, 0.15)] * len(CLOSES_DA)
w_da[4] = (0.10, 0.15) # bara BIM coboară sub PPQ: 95.55
w_da[5] = (0.10, 0.10)
w_nu = [(0.15, 0.15)] * len(CLOSES_NU)
w_nu[4] = (0.10, 0.10)
START = COMUN[0] + 0.30
cds_da = from_closes(CLOSES_DA, wicks=w_da, start=START)
cds_nu = from_closes(CLOSES_NU, wicks=w_nu, start=START)
BIM_I, VERDE_DA, VERDE_NU = 4, 5, 4
# --- ce trebuie să fie adevărat ca desenul să susțină textul ---------------
ser = lambda c: (round(c.o, 2), round(c.h, 2), round(c.l, 2), round(c.c, 2)) # noqa: E731
assert [ser(c) for c in cds_da[:4]] == [ser(c) for c in cds_nu[:4]], \
"panourile nu pleacă din aceeași corecție"
# DA: BIM înainte de verde; verdele e chiar verde; close-ul lui = intrarea
assert BIM_I < VERDE_DA
assert cds_da[BIM_I].c < cds_da[BIM_I].o, "bara de BIM e bara de epuizare, roșie"
assert cds_da[VERDE_DA].c > cds_da[VERDE_DA].o, "candela de intrare nu e verde"
assert abs(cds_da[VERDE_DA].c - ENTRY) < 1e-9, cds_da[VERDE_DA].c
# criteriul A+: candela de intrare închide DEASUPRA pivotului
assert cds_da[VERDE_DA].c > PPQ, "candela de intrare nu închide peste PPQ"
# prețul a intrat efectiv în zona PPQ (a coborât sub pivot și a fost respins)
assert cds_da[BIM_I].l < PPQ < cds_da[VERDE_DA].c, (cds_da[BIM_I].l, PPQ)
assert min(c.l for c in cds_da) == cds_da[BIM_I].l, "fundul nu e pe bara de BIM"
# NU: verdele vine primul, și după el prețul face minime tot mai joase
assert cds_nu[VERDE_NU].c > cds_nu[VERDE_NU].o, "bara 4 de pe panoul NU nu e verde"
lows = [c.l for c in cds_nu[VERDE_NU + 1:]]
assert all(b < a for a, b in zip(lows, lows[1:])), "cuțitul nu cade continuu"
assert cds_nu[-1].c < PPQ - 2.5, "căderea de pe panoul NU e prea mică ca să se vadă"
assert min(c.l for c in cds_nu) > SQ, "căderea desenată trece deja de SQ"
# cele două panouri au aceeași scară, deci căderile se compară cu ochiul
PIERDERE = round((cds_nu[-1].c - cds_nu[VERDE_NU].c) / cds_nu[VERDE_NU].c * 100, 1)
assert PIERDERE < -3.0, PIERDERE
ch_da = Chart(
candles=cds_da,
marks=[],
arrows=[
Arrow(BIM_I, "BIM: săgeata indicatorului\ncyan = momentul în care\ntrendul se schimbă",
where="below", color=CYAN),
Arrow(VERDE_DA, f"AICI INTRI, la close {ENTRY:.2f}\nînchide peste pivot, deci\n"
f"cumpărătorii au ținut zona",
price=ENTRY, where="right", color=GREEN, dx=18, dy=40),
],
scale=SCALE, gap=GAP, body_w=13, left=LEFT, width=W,
title="DA — BIM, apoi prima candelă verde daily",
)
ch_nu = Chart(
candles=cds_nu,
marks=[],
arrows=[
Arrow(VERDE_NU, "aici NU intri: verdele a venit\nînainte de BIM, deci nimic\n"
"nu confirmă că vânzarea s-a oprit",
where="below", color=RED, dx=40),
Arrow(len(cds_nu) - 1, f"căderea continuă: {abs(PIERDERE):.1f} % mai jos\nde la candela verde —\n"
f"„cuțitul prins în cădere”",
where="below", color=RED, dx=-30),
],
scale=SCALE, gap=GAP, body_w=13, left=LEFT, width=W,
title="NU — candelă verde înainte de BIM",
)
def panel(ch):
svg = ch._render()
m = re.search(r'viewBox="0 0 ([\d.]+) ([\d.]+)"', svg)
assert float(m.group(1)) == W, f"o etichetă iese din {W}px"
return svg[svg.index(">") + 1:svg.rindex("</svg>")], float(m.group(2))
def level(ch, price, text, color, below=False):
y = ch._y(price)
out = [f'<line x1="24" y1="{y:.1f}" x2="{W - 14}" y2="{y:.1f}" stroke="{color}" '
f'stroke-width="1.5" stroke-dasharray="4 3"/>']
for k, s in enumerate(text):
ty = (y + 15 + 14 * k) if below else (y - 6 - 14 * (len(text) - 1 - k))
out.append(f'<text x="30" y="{ty:.1f}" fill="{color}" style="font-weight:bold">{s}</text>')
return out
def zone(ch, i0, i1, label, color, lift=0.0):
cds = ch.candles[i0:i1 + 1]
x0, x1 = ch._cx(i0) - 17, ch._cx(i1) + 17
y0 = ch._y(max(c.h for c in cds)) - 8 - lift
y1 = ch._y(min(c.l for c in cds)) + 8
return [f'<rect x="{x0:.1f}" y="{y0:.1f}" width="{x1 - x0:.1f}" height="{y1 - y0:.1f}" '
f'fill="{color}" fill-opacity="0.12" stroke="{color}" stroke-width="1"/>',
f'<text x="{x0 + 6:.1f}" y="{y0 - 7:.1f}" fill="{color}" '
f'style="font-weight:bold">{label}</text>']
body_da, h_da = panel(ch_da)
body_nu, h_nu = panel(ch_nu)
extra_da = (zone(ch_da, BIM_I, VERDE_DA, "zona de decizie: BIM + verde", BLUE, lift=16)
+ level(ch_da, PPQ, [f"PPQ {PPQ:.2f} — pivotul trimestrial"], PURPLE, below=True))
extra_nu = (zone(ch_nu, VERDE_NU, len(cds_nu) - 1, "fără BIM, nimic nu confirmă", RED, lift=16)
+ level(ch_nu, PPQ, [f"PPQ {PPQ:.2f} — pivotul trimestrial"], PURPLE, below=True))
CAP = ["Aceleași patru bare de corecție pe ambele panouri, aceeași scară.",
"PPQ 95.80 e calculat în diagrama pasului 1; intrarea 96.00 e close-ul candelei verzi."]
assert all(len(s) * 6.3 < W - 40 for s in CAP), "caption prea lung"
cap_top = h_da + h_nu + 20
H = cap_top + 24
svg = "\n ".join([
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">',
f'<g transform="translate(0,0)">{"".join(extra_da)}{body_da}</g>',
f'<g transform="translate(0,{h_da:.0f})">{"".join(extra_nu)}{body_nu}</g>',
f'<text x="24" y="{cap_top:.0f}" fill="{GREY}">{CAP[0]}</text>',
f'<text x="24" y="{cap_top + 16:.0f}" fill="{GREY}">{CAP[1]}</text>',
"</svg>"])
assert "\n\n" not in svg and ".md:" not in svg
TEXT = re.compile(r'<text x="([-\d.]+)" y="([-\d.]+)"([^>]*)>(.*?)</text>', re.S)
def labels(body, y_off, tag):
out = []
for m in TEXT.finditer(body):
x, y, attrs, s = float(m.group(1)), float(m.group(2)) + y_off, 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)
lf = x - w if anchor == "end" else (x - w / 2 if anchor == "middle" else x)
out.append((lf, lf + w, y, s, tag))
return out
LB = (labels(body_da + "".join(extra_da), 0.0, "DA")
+ labels(body_nu + "".join(extra_nu), h_da, "NU"))
for lf, rt, y, s, tag in LB:
assert -2 <= lf and rt <= W + 2, f"[{tag}] text iese din panză: {s!r}"
assert 0 <= y <= H, f"[{tag}] text în afara înălțimii: {s!r}"
for i in range(len(LB)):
for j in range(i + 1, len(LB)):
li, ri, yi, si, ti = LB[i]
lj, rj, yj, sj, tj = LB[j]
assert not (ti == tj and abs(yi - yj) < 12 and li < rj - 4 and lj < ri - 4), \
f"[{ti}] texte suprapuse: {si!r} / {sj!r}"
# niciun preț scris într-o etichetă care nu e un nivel desenat pe panoul lui
for lf, rt, y, s, tag in LB:
for p in re.findall(r"\d+\.\d\d", s):
assert float(p) in (PPQ, ENTRY), f"[{tag}] prețul {p} din {s!r} nu e un nivel desenat"
OUT.write_text(svg)
print(f"DA: BIM la {BIM_I} (low {cds_da[BIM_I].l:.2f} < PPQ {PPQ}), intrare {ENTRY:.2f}")
print(f"NU: verde la {VERDE_NU} fără BIM, apoi {PIERDERE:.1f} % în jos "
f"(ultimul close {cds_nu[-1].c:.2f}, peste SQ {SQ})")
print(f"scris {OUT.name} ({W}x{H:.0f})")