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

132 lines
6.6 KiB
Python

"""Diagrama de ansamblu ATMI (strategii/05_atmi.md, marcajul `ansamblu`).
„SPY — daily: corecție ≥4 % → zona PPQ → BIM + verde → intrare → TP +10 %".
Structura seriei și nivelurile vin din spec-ul
`gen/strategii/atmi_ansamblu.spec.txt`. Toate cifrele desenate (100.00 swing
high, 96.00 intrare, 105.60 = +10 %) sunt din exemplul „DA" al cursului
(`summaries/04_MODUL_3/03_strategia_2_atmi.md:185-247`). Benzile PPQ/SQ sunt
schematice: cursul spune explicit că nu dă cifrele lor
(`...:143`), deci nu poartă prețuri.
Rulează:
python3 gen/strategii/atmi_ansamblu.py
Scrie lângă el `atmi_ansamblu.svg`.
"""
import sys
from pathlib import Path
sys.path.insert(0, "/workspace/atm-curs-dl")
from svg_candles import Chart, Level, Mark, MA, from_closes # noqa: E402
OUT = Path(__file__).resolve().parent / "atmi_ansamblu.svg"
GRAY, RED, GREEN, BLUE, ORANGE = "#777", "#c00", "#188a3e", "#1a5fb4", "#b06000"
PURPLE = "#7b2fbf" # folosit în materialul de curs pentru PPQ
# ---------------------------------------------------------------- seria (33 bare)
# 15 bare de urcare ~88 -> 100, 8 de corecție 100 -> 96 (ultima = intrarea),
# 10 de urcare 96 -> 105.60. open-ul fiecărei bare = close-ul precedentei.
UP_A = [96.60, 97.10, 97.60, 98.00, 98.50, 98.20, 98.70, 99.20,
99.60, 99.30, 99.70, 100.00] # 0-11
CORR = [99.30, 98.50, 97.80, 97.20, 96.70, 96.30, 95.90, 96.00] # 12-19 (18 = BIM)
UP_B = [96.60, 97.20, 97.90, 98.60, 99.40, 100.30, 101.40, 102.60,
103.40, 104.20, 104.90, 105.60] # 20-31
CLOSES = UP_A + CORR + UP_B
assert len(CLOSES) == 32, len(CLOSES)
PEAK, BIM, ENTRY, LAST = 11, 18, 19, 31
# fitile (sus, jos) per bară; jos pe corecție ținut ≥ 0 ca minimul să fie fix 96.00
WICKS = ([(0.25, 0.25)] * 11
+ [(0.00, 0.25)] # 11: vârful atinge exact 100.00
+ [(0.00, 0.20), (0.05, 0.20), (0.05, 0.20), (0.05, 0.20),
(0.05, 0.20), (0.05, 0.20),
(0.05, 0.35), # 18: bara de BIM, fundul la 95.55
(0.15, 0.05)] # 19: candela verde de intrare
+ [(0.25, 0.00)] + [(0.25, 0.25)] * 10
+ [(0.00, 0.20)]) # 31: închide/atinge exact 105.60
assert len(WICKS) == len(CLOSES), (len(WICKS), len(CLOSES))
VOLS = ([44, 47, 42, 50, 45, 41, 48, 43, 46, 51, 44, 58] # 0-11 urcare
+ [64, 70, 66, 61, 58, 74, 96, 88] # 12-19 corecție + BIM/intrare
+ [55, 58, 52, 60, 57, 63, 59, 66, 70, 74, 78, 84]) # 20-31 urcare spre TP
assert len(VOLS) == len(CLOSES), (len(VOLS), len(CLOSES))
cds = from_closes(CLOSES, wicks=WICKS, vols=VOLS, start=96.20)
# ---------------------------------------------------------------- assert-urile din spec
high = 100.00
entry = cds[ENTRY].c
tp = cds[LAST].h
assert abs((high - 96.00) / high * 100 - 4.0) < 0.05 # corecția e 4.0 %
assert abs(entry - 96.00) < 0.05 # intrarea = close-ul candelei verzi
assert abs(tp / entry - 1.10) < 0.005 # TP = +10 %
assert abs(tp - max(c.h for c in cds[-12:])) < 1e-9 # TP atins la final, e maximul barelor
assert BIM < ENTRY # BIM înainte de candela verde
assert abs(min(c.l for c in cds[12:20]) - 95.55) < 1e-9 # fundul corecției = 95.55
assert cds[ENTRY].c >= cds[ENTRY].o # candela de intrare e verde
assert abs(cds[PEAK].h - high) < 1e-9 # swing high = 100.00
assert abs(cds[LAST].c - tp) < 1e-9 # ultima bară închide pe TP
levels = [
Level(high, "100.00 — swing high recent", "#333", label_at="left"),
Level(entry, f"intrare {entry:.2f}", GREEN, label_at="right"),
Level(tp, f"TP +10 % = {tp:.2f}", BLUE, label_at="right"),
]
# nicio linie de stop-loss și niciun nivel sub intrare (absența SL e regula)
assert all("stop" not in lv.text.lower() for lv in levels)
assert all(lv.price >= entry - 1e-9 for lv in levels)
marks = [
Mark(13, "corecție 4,0 %", ORANGE, "below", dy=26),
Mark(BIM, "BIM", "#0a7f9e", "above"),
Mark(ENTRY, "prima candelă verde → intrare", GREEN, "below", dy=30),
Mark(LAST, "ieși cu tot", BLUE, "below", dy=22),
]
ch = Chart(candles=cds, levels=levels, marks=marks,
scale=22.0, gap=17, body_w=9, left=44, width=720, vol_h=70, vol_gap=72, top=88,
vol_label="volum",
caption="100.00 / 96.00 / 105.60 sunt cifrele exemplului din curs; PPQ 95.80 e calculat "
"în diagrama pasului 1 (cursul nu dă valorile pivoților).")
svg = ch.render()
def band(price: float, half: float, color: str, label: str) -> str:
"""Bandă schematică orizontală, desenată în spatele candelelor.
Se injectează în SVG după randare, pentru că `Chart` nu are benzi; y-ul
se calculează cu aceeași scară (`ch._y`), deci banda e aliniată corect.
"""
y_top, y_bot = ch._y(price + half), ch._y(price - half)
return (f'<rect x="24" y="{y_top:.1f}" width="{ch.width - 38}" '
f'height="{y_bot - y_top:.1f}" fill="{color}" opacity="0.10"/>'
f'<text x="28" y="{y_bot + 14:.1f}" fill="{color}">{label}</text>')
# PPQ 95.80 e pivotul trimestrial calculat în diagrama pasului 1; banda e
# zona de cumpărare din jurul lui. SQ 90.80 e sub scara acestui desen, deci
# nu se desenează aici — apare pe diagrama pasului 1.
PPQ = 95.80
assert abs(min(c.l for c in cds) - 95.55) < 1e-9
assert PPQ - 0.30 <= min(c.l for c in cds) <= PPQ + 0.30, "fundul nu cade în banda PPQ"
assert entry > PPQ, "candela verde trebuie să închidă peste pivot"
ppq = band(PPQ, 0.30, PURPLE, f"zona PPQ {PPQ:.2f} — aici cumperi")
sq = ""
# „fără stop-loss standard" stă sub zona de intrare, unde nu are candele
no_sl = (f'<text x="{ch._cx(ENTRY):.0f}" y="{ch._y(entry) + 46:.1f}" fill="{RED}" '
f'text-anchor="middle">fără stop-loss standard — nicio linie sub intrare</text>')
# titlul e injectat deasupra, ca rând separat de eticheta TP (care stă chiar
# sub marginea de sus a panoului); altfel cele două texte se calcă la dreapta
titlu = ('<text x="24" y="26" fill="#333" style="font-weight:bold">'
'SPY — daily: corecție ≥4 % → zona PPQ → BIM + verde → intrare → TP +10 %</text>')
i = svg.index(">") + 1
svg = svg[:i] + "\n " + "\n ".join([ppq, no_sl, titlu]) + svg[i:]
assert svg.count("\n\n") == 0, "linie goală în SVG rupe blocul din markdown"
assert ".md:" not in svg, "trimitere fișier:linie în SVG"
OUT.write_text(svg)
print(f"scris {OUT.name}: {len(cds)} candele, intrare {entry:.2f}, TP {tp:.2f}")