Files
PythagorasGoal/tests/test_deposits.py
T
Adriano Dal Pastro 4703f75f9f research: i versamenti — le 4 ipotesi che il piano non aveva mai fatto
Tutte le traiettorie del 25-26/07 assumevano versamento PIATTO, ININTERROTTO, PER SEMPRE:
l'ipotesi meno realistica dell'intero piano. Misurate le deviazioni che succedono davvero,
con la stessa macchineria (block bootstrap sui ritorni reali del book live, fattore
d'ancora x0.89 misurato).

(1) SMETTERE — il costo non e' proporzionale ai soldi mancanti. EUR 250/m per K anni poi
stop, orizzonte 20a: 3a ($10.410) -> $202.771 / P(muro) 32.7%; 5a -> $287.081 / 53.3%;
20a ($66.818) -> $496.778 / 90.0%. I PRIMI 5 ANNI SONO IL 25% DEI SOLDI E IL 58% DEL
RISULTATO -> un'interruzione al 12° anno costa poco, una al 3° quasi tutto. Argomento per
partire con un importo sostenibile invece che ambizioso.

(2) CRESCENTE E' PEGGIO DI PIATTO A PARI SOLDI. EUR 150/m +5%/anno versa EUR 67.998 ->
$401.889; piatto EUR 250 versa EUR 66.818 -> $496.778 = +24% con gli stessi soldi, solo
perche' entrano prima. Metrica giusta per confrontare piani di taglia diversa =
$ finale / $ versato (piatti 7.4x, crescenti 5.1-5.9x).

(3) STESSO TOTALE, CALENDARIO DIVERSO = FATTORE 6. EUR 60.000 distribuiti: ultimi 10 anni
$178.494 (11%) / piatto 20a $496.778 (90%) / primi 5 anni $1.104.587 (99.4%). NON
significa "versa tutto subito": un piano che non si sostiene non e' un piano.
Verificato COL RISCHIO DI VENUE DENTRO (il front-load mette piu' capitale sull'exchange
prima = proprio il rischio del giorno): REGGE, 2.22x -> 2.09x a p=2%, perche' il rischio
colpisce il tempo, non il calendario. MA a p=5% il capitale mediano e' $0 PER OGNI
CALENDARIO -> formulazione piu' netta del rischio di venue trovata finora: non erode il
piano, lo CANCELLA.

(4) LA DOMANDA INVERSA — rendita netta EUR/g mediana per versamento e orizzonte. EUR 150/m
-> 23.96/g a 15 anni; EUR 250/m -> 39.11/g a 15a e 91.30/g a 20a (P(EUR 50/g) 90%).
Riformula l'obiettivo: EUR 50/g e' UN punto sulla griglia, non l'unico risultato.
Non-linearita': da 15 a 20 anni la rendita piu' che raddoppia a ogni livello.

(5) FREQUENZA = la decisione meno importante. Mensile fino a ~$2/trasferimento, bimestrale
sopra; differenze 1-3% del capitale finale. Verificato che un deposito NON resta
strozzato: col cap dinamico cap = equity/2 = il nozionale massimo richiedibile.

ORDINE DI IMPORTANZA: versare o no (da mai a 16 anni) > quando (6x) > quanto presto si
smette (5 anni = 58% del risultato) > piatto vs crescente (24%) > frequenza (1-3%).

Book, pesi, cron, config INVARIATI. 377 test verdi (+12).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-26 20:23:46 +00:00

134 lines
5.3 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Test del piano versamenti (scripts/research/r0726_deposits.py).
Il test piu' importante e' `test_la_rendita_al_muro_e_esattamente_il_bersaglio`: lega la tabella
della "domanda inversa" al muro calcolato altrove. Se le due macchinerie divergono, una delle due
tabelle sta mentendo e nessun altro controllo se ne accorgerebbe.
"""
from __future__ import annotations
import sys
from pathlib import Path
import numpy as np
import pytest
ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT))
sys.path.insert(0, str(ROOT / "scripts" / "research"))
import r0725_capcurve as CC # noqa: E402
import r0726_deposits as DP # noqa: E402
# ===========================================================================
# calendario dei versamenti
# ===========================================================================
def test_versamento_mensile_conta_i_periodi_giusti():
s = DP.monthly_schedule(365, 100.0)
assert (s > 0).sum() == 12, "12 versamenti in un anno di 365 giorni a passo 30"
assert s[0] == 0.0, "il primo versamento non e' al giorno 0 (li' c'e' il capitale iniziale)"
def test_stop_year_interrompe_davvero():
s = DP.monthly_schedule(365 * 10, 100.0, stop_year=3)
ultimo = np.nonzero(s)[0].max()
assert ultimo < 3 * 365
assert (s > 0).sum() == pytest.approx(36, abs=1)
def test_la_crescita_aumenta_gli_importi_nel_tempo():
s = DP.monthly_schedule(365 * 10, 100.0, growth=0.05)
nz = s[s > 0]
assert nz[-1] > nz[0] * 1.5, "dopo ~10 anni al 5% l'importo deve essere >1.5x"
piatto = DP.monthly_schedule(365 * 10, 100.0)
assert s.sum() > piatto.sum()
def test_crescita_zero_equivale_al_piatto():
a = DP.monthly_schedule(365 * 5, 200.0, growth=0.0)
b = DP.monthly_schedule(365 * 5, 200.0)
assert np.allclose(a, b)
# ===========================================================================
# simulazione
# ===========================================================================
def test_senza_versamenti_e_solo_capitalizzazione():
paths = np.full((3, 100), 0.001)
cap, paid = DP.simulate(paths, np.zeros(100))
atteso = DP.START * (1.001 ** 100)
assert cap[0] == pytest.approx(atteso, rel=1e-9)
assert paid[0] == pytest.approx(DP.START)
def test_a_rendimento_zero_il_capitale_e_la_somma_dei_versamenti():
paths = np.zeros((2, 365))
s = DP.monthly_schedule(365, 100.0)
cap, paid = DP.simulate(paths, s)
assert cap[0] == pytest.approx(DP.START + s.sum())
assert paid[0] == pytest.approx(DP.START + s.sum())
def test_il_totale_versato_include_il_capitale_iniziale():
"""Confrontare un capitale finale con un 'versato' che dimentica i $600 iniziali gonfia il
rapporto capitale/versato — errore gia' fatto una volta oggi nel contatore `paid`."""
s = DP.monthly_schedule(365, 50.0)
_, paid = DP.simulate(np.zeros((2, 365)), s)
assert paid[0] == pytest.approx(DP.START + s.sum())
assert paid[0] > s.sum()
# ===========================================================================
# il legame con il muro — il test che lega le due macchinerie
# ===========================================================================
def test_la_rendita_al_muro_e_esattamente_il_bersaglio():
"""Un capitale pari al muro deve dare, per costruzione, esattamente TARGET_EUR_DAY.
Se questa identita' si rompe, la tabella (D) e la tabella dei muri non parlano piu' della
stessa cosa."""
import r0726_capwall_refresh as WR
r = DP.deluck_returns()
gross = CC.TARGET_EUR_DAY * 365 * CC.EURUSD / (1 - CC.TAX_RATE)
_, perp, wall = WR.perp_and_wall(r, 1.0, gross)
rent = DP.rent_from_capital(np.array([wall]), perp)[0]
assert rent == pytest.approx(CC.TARGET_EUR_DAY, rel=1e-6)
def test_la_rendita_e_lineare_nel_capitale():
r = DP.rent_from_capital(np.array([100_000.0, 200_000.0]), 0.10)
assert r[1] == pytest.approx(2 * r[0])
# ===========================================================================
# i FINDING: se cambiano, e' una notizia
# ===========================================================================
def test_anticipare_batte_posticipare_a_pari_totale():
"""Il finding di (C). Su ritorni a drift positivo deterministico e' un'identita' algebrica,
e serve come controllo di direzione: se uscisse il contrario, la simulazione e' rotta."""
n = 365 * 20
paths = np.full((1, n), 0.0004)
tot = 60_000.0 * CC.EURUSD
presto, tardi = np.zeros(n), np.zeros(n)
k = len(range(30, 365 * 5, 30))
for t in range(30, 365 * 5, 30):
presto[t] = tot / k
k2 = len(range(365 * 10, n, 30))
for t in range(365 * 10, n, 30):
tardi[t] = tot / k2
assert presto.sum() == pytest.approx(tardi.sum(), rel=1e-9)
cap_p, _ = DP.simulate(paths, presto)
cap_t, _ = DP.simulate(paths, tardi)
assert cap_p[0] > cap_t[0] * 1.5
def test_il_fattore_deluck_e_quello_misurato_non_quello_a_occhio():
"""Il ×0.6 del 25/07 era scelto a occhio ed e' stato misurato ×0.87-0.91 il 26/07."""
assert DP.DELUCK == 0.89
def test_hazard_giornaliero_ricompone_alla_probabilita_annua():
"""Il salto di venue: (1-haz)^365 deve dare (1-p). Sbagliare qui cambia tutte le colonne."""
for p in (0.005, 0.01, 0.02, 0.05):
haz = 1.0 - (1.0 - p) ** (1.0 / 365.0)
assert (1.0 - haz) ** 365 == pytest.approx(1.0 - p, rel=1e-9)