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>
This commit is contained in:
@@ -0,0 +1,133 @@
|
||||
"""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)
|
||||
Reference in New Issue
Block a user