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
135 lines
6.4 KiB
Python
135 lines
6.4 KiB
Python
#!/usr/bin/env python3
|
|
"""ATMI — Pasul 6: ieșirea la +10 %, cash, apoi reinvestirea integrală.
|
|
|
|
Nu e un grafic de preț: e ciclul banilor. Trei cicluri, fiecare cu aceeași
|
|
regulă — ieși cu TOATĂ poziția la +10 %, banii stau în cash până la
|
|
următorul trigger valid, iar acolo intră înapoi integral, plus contribuția
|
|
nouă. Coloana din dreapta arată ce iese dacă reinvestești doar contribuțiile
|
|
și lași profitul deoparte: aceeași sumă pusă, mai puțin la final.
|
|
|
|
Contribuția de 600 $/trimestru e cifra cursului („1 unitate SPY"); ținta de
|
|
+10 % e regula lui. Suma finală e calculată aici, nu preluată din backtest —
|
|
backtest-ul cursului (~16.300 $ → ~52-53.000 $ în 20 de ani) are alt
|
|
orizont și e citat în text, nu desenat.
|
|
|
|
Rulează: python3 gen/strategii/atmi_reinvestire.py
|
|
"""
|
|
from pathlib import Path
|
|
|
|
OUT = Path(__file__).with_suffix(".svg")
|
|
W = 700
|
|
CONTRIB, TINTA = 600.0, 0.10
|
|
N = 3
|
|
|
|
GREEN, RED, BLUE, INK, GRAY, PURPLE = "#188a3e", "#c00", "#1a5fb4", "#333", "#777", "#7b2fbf"
|
|
|
|
# --- cele două scenarii ----------------------------------------------------
|
|
cicluri, capital = [], 0.0
|
|
for i in range(N):
|
|
intrat = round(capital + CONTRIB, 2)
|
|
iesit = round(intrat * (1 + TINTA), 2)
|
|
cicluri.append((i + 1, intrat, iesit))
|
|
capital = iesit
|
|
|
|
fara = round(N * CONTRIB * (1 + TINTA), 2) # fiecare contribuție câștigă 10 % o dată
|
|
pus = round(N * CONTRIB, 2)
|
|
|
|
assert cicluri[0] == (1, 600.00, 660.00), cicluri[0]
|
|
assert cicluri[1] == (2, 1260.00, 1386.00), cicluri[1]
|
|
assert cicluri[2] == (3, 1986.00, 2184.60), cicluri[2]
|
|
assert fara == 1980.00 and pus == 1800.00
|
|
CASTIG = round(cicluri[-1][2] - fara, 2)
|
|
assert CASTIG == 204.60, CASTIG
|
|
# fiecare ciclu intră cu exact ce a ieșit din precedentul plus contribuția
|
|
for (_, intr, _), (_, _, ies) in zip(cicluri[1:], cicluri):
|
|
assert round(intr - ies, 2) == CONTRIB
|
|
assert all(round(ies / intr, 3) == 1.100 for _, intr, ies in cicluri)
|
|
|
|
# --- desen -----------------------------------------------------------------
|
|
out, LABELS = [], []
|
|
BASE_Y = 250 # linia de zero a coloanelor
|
|
PXPU = 0.072 # px per dolar
|
|
COL_W, X0, STEP = 46, 60, 190
|
|
assert cicluri[-1][2] * PXPU < BASE_Y - 80, "coloana cea mai înaltă iese din desen"
|
|
|
|
|
|
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.52)
|
|
x0 = x if anchor == "start" else (x - w / 2 if anchor == "middle" else x - w)
|
|
assert 2 <= x0 and x0 + w <= W - 2, f"text iese din panză: {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 bar(x, val, color, label, sub):
|
|
h = val * PXPU
|
|
out.append(f'<rect x="{x:.1f}" y="{BASE_Y - h:.1f}" width="{COL_W}" height="{h:.1f}" '
|
|
f'fill="{color}" fill-opacity="0.85"/>')
|
|
text(x + COL_W / 2, BASE_Y - h - 8, label, fill=color, anchor="middle", bold=True, size=11)
|
|
text(x + COL_W / 2, BASE_Y + 15, sub, fill=GRAY, anchor="middle", size=10)
|
|
|
|
|
|
out.append('<defs><marker id="ar" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" '
|
|
f'markerHeight="7" orient="auto"><path d="M0,0 L10,5 L0,10 z" fill="{BLUE}"/></marker>'
|
|
'</defs>')
|
|
text(20, 22, "Ciclul ATMI: ieși cu tot la +10 %, stai în cash, reintri cu tot plus contribuția",
|
|
bold=True, size=13)
|
|
text(20, 40, f"Contribuție {CONTRIB:.0f} $ la fiecare trigger valid; ținta +10 % pe poziție.",
|
|
fill="#555", size=11)
|
|
|
|
for i, (nr, intrat, iesit) in enumerate(cicluri):
|
|
xa = X0 + i * STEP
|
|
xb = xa + COL_W + 26
|
|
text(xa, 72, f"ciclul {nr}", fill=INK, bold=True, size=12)
|
|
bar(xa, intrat, BLUE, f"{intrat:,.0f} $".replace(",", "."), "intri cu tot")
|
|
bar(xb, iesit, GREEN, f"{iesit:,.2f} $".replace(",", "x").replace(".", ",").replace("x", "."),
|
|
"ieși la +10 %")
|
|
# săgeata dintre intrare și ieșire (ce face trade-ul)
|
|
out.append(f'<line x1="{xa + COL_W + 4:.1f}" y1="{BASE_Y - 30:.1f}" x2="{xb - 6:.1f}" '
|
|
f'y2="{BASE_Y - 30:.1f}" stroke="{GREEN}" stroke-width="1.4" '
|
|
f'marker-end="url(#ar)"/>')
|
|
if i + 1 < len(cicluri):
|
|
# săgeata spre ciclul următor: cash + contribuție nouă
|
|
xn = X0 + (i + 1) * STEP
|
|
y = BASE_Y + 46
|
|
out.append(f'<path d="M{xb + COL_W / 2:.1f},{BASE_Y + 22:.1f} V{y:.1f} '
|
|
f'H{xn + COL_W / 2:.1f} V{BASE_Y + 22:.1f}" fill="none" stroke="{BLUE}" '
|
|
f'stroke-width="1.4" stroke-dasharray="4 3" marker-end="url(#ar)"/>')
|
|
text((xb + xn) / 2 + COL_W / 2, y + 16,
|
|
f"cash, apoi + {CONTRIB:.0f} $ nou", fill=BLUE,
|
|
anchor="middle", size=11, bold=True)
|
|
|
|
out.append(f'<line x1="24" y1="{BASE_Y:.1f}" x2="{W - 24}" y2="{BASE_Y:.1f}" stroke="#bbb"/>')
|
|
|
|
# comparația de la final
|
|
yc = BASE_Y + 92
|
|
out.append(f'<rect x="24" y="{yc - 16:.0f}" width="{W - 48}" height="56" fill="#f6f6f9" '
|
|
f'stroke="#ccc"/>')
|
|
text(34, yc, f"Ai pus în total {pus:,.0f} $".replace(",", ".") + ", în trei contribuții egale.",
|
|
fill=INK, size=11, bold=True)
|
|
text(34, yc + 17, f"Cu reinvestire integrală: "
|
|
f"{cicluri[-1][2]:,.2f} $".replace(",", "x").replace(".", ",").replace("x", ".")
|
|
+ f" · dacă lași profitul deoparte: {fara:,.0f} $".replace(",", ".")
|
|
+ f" · diferența: {CASTIG:,.2f} $".replace(",", "x").replace(".", ",").replace("x", "."),
|
|
fill=INK, size=11)
|
|
text(34, yc + 34, "Cifrele astea sunt calculul meu pe trei cicluri, nu backtest-ul cursului.",
|
|
fill=GRAY, size=10)
|
|
|
|
H = yc + 56
|
|
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(" | ".join(f"c{n}: {a:.0f} -> {b:.2f}" for n, a, b in cicluri)
|
|
+ f" | fără reinvestire {fara:.0f} | diferență {CASTIG:.2f} | scris {OUT.name} ({W}x{H:.0f})")
|