1227b2baab
L'operatore ha dato i numeri veri: EUR 5.000 subito + EUR 500/mese. Cambia la scala (conto da $597 a $6.047 = 10.1x), quindi ricalcolato in dedicata invece che estrapolato. QUANDO: traguardo EUR 50/g ($272.061) a p10 9.4a / MEDIANA 11.6a / p90 14.4a; P(entro 15a) 94%, P(entro 20a) 100%. Rendita mediana: EUR 6.37/g a 3 anni, EUR 11.57/g a 5, EUR 35.18/g a 10, EUR 87.63/g a 15. VALIDAZIONE INCROCIATA: la variante "solo EUR 500/mese da $600" da' 12.4 anni = esattamente il numero pubblicato il 25/07 con macchineria diversa. IL LUMP VALE 0.8 ANNI (12.4 -> 11.6), MENO di quanto suggerisse il "fattore 6" del calendario, e la ragione e' aritmetica: EUR 5.000 sono ~10 mesi di versamenti a EUR 500, quindi comprano ~10 mesi. Anticipare vale in proporzione a quanto si anticipa. La mia aspettativa era piu' alta ed e' corretta. SOGLIE: $3k e $5k superate il giorno 1 (GTAA01 e XSR01 diventano eseguibili); $13k a ~0.9 anni (GTAA01 entra nel book deployable); $20k a ~1.6 anni = LA DECISIONE VENUE DEL 26/07 SMETTE DI ESSERE UN'IPOTESI E DIVENTA UNA DATA (~19 mesi); $117k a ~7.6 anni (XS01). Le soglie superate subito NON autorizzano ad anticipare il gate XSR01 del 23/10. CON IL RISCHIO DI VENUE: P(traguardo) 100% -> 88% a p=1% (P(perso tutto) 23.2%); a p=5% il capitale mediano e' ZERO. Il piano regge fino a p=2%. ⚠️ AZIONE OPERATIVA TROVATA (non eseguita, e' config su soldi veri): `book._cap` ripiega su max_notional_per_asset_usd=$300 quando l'equity reale non e' leggibile. A $597 il fallback era INERTE (equity/2 = $298 ~ $300); dopo il versamento equity/2 vale ~$3.023, quindi un fallback strozzerebbe il book al ~10% del target, in silenzio. E' l'azione gia' pre-registrata il 2026-07-02 ("al deposito alzare il cap a equity/2"). Cablato test_il_cap_fisso_diventa_una_strozzatura_dopo_un_deposito, che FALLISCE se si deposita senza adeguare il cap. Aggiunta anche la sezione (F) frontiera di sostenibilita' a r0726_deposits.py: capitale e rendita per (importo x anni sostenuti), e il confronto "tirare poco tempo vs comodo a lungo" — EUR 400/m per 5a batte EUR 200/m per 20a, ma EUR 600/m per 3a perde contro EUR 250/m per 15a. Book, pesi, cron, config INVARIATI. 380 test verdi (+3). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
190 lines
8.0 KiB
Python
190 lines
8.0 KiB
Python
"""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)
|
||
|
||
|
||
# ===========================================================================
|
||
# il PIANO dichiarato (€5.000 subito + €500/mese) — r0726_piano_5k500.py
|
||
# ===========================================================================
|
||
def test_il_piano_riproduce_il_numero_gia_pubblicato_senza_lump():
|
||
"""Controllo incrociato: 'solo €500/mese da $600' deve dare ~12.4 anni, che e' il numero
|
||
pubblicato il 25/07 con macchineria DIVERSA. Se diverge, una delle due tabelle mente."""
|
||
import r0725_capcurve as CC2
|
||
import r0726_capwall_refresh as WR2
|
||
import r0726_piano_5k500 as PL
|
||
|
||
r = DP.deluck_returns()
|
||
gross = CC2.TARGET_EUR_DAY * 365 * CC2.EURUSD / (1 - CC2.TAX_RATE)
|
||
_, _, wall = WR2.perp_and_wall(r, 1.0, gross)
|
||
rng = np.random.default_rng(PL.SEED)
|
||
paths = CC2._boot_paths(r, 1500, 365 * 25, PL.BLOCK, rng)
|
||
res = PL.run(paths, PL.CONTO_OGGI, 500.0 * CC2.EURUSD, wall)
|
||
fin = res["yrs"][~np.isnan(res["yrs"])]
|
||
assert 11.5 < float(np.percentile(fin, 50)) < 13.5
|
||
|
||
|
||
def test_il_versamento_iniziale_anticipa_il_traguardo():
|
||
"""Direzione: partire da piu' in alto non puo' allungare il tempo."""
|
||
import r0725_capcurve as CC2
|
||
import r0726_capwall_refresh as WR2
|
||
import r0726_piano_5k500 as PL
|
||
|
||
r = DP.deluck_returns()
|
||
gross = CC2.TARGET_EUR_DAY * 365 * CC2.EURUSD / (1 - CC2.TAX_RATE)
|
||
_, _, wall = WR2.perp_and_wall(r, 1.0, gross)
|
||
rng = np.random.default_rng(PL.SEED)
|
||
paths = CC2._boot_paths(r, 1500, 365 * 25, PL.BLOCK, rng)
|
||
dep = 500.0 * CC2.EURUSD
|
||
senza = PL.run(paths, PL.CONTO_OGGI, dep, wall)["yrs"]
|
||
con = PL.run(paths, PL.CONTO_OGGI + 5_000 * CC2.EURUSD, dep, wall)["yrs"]
|
||
m_senza = float(np.nanpercentile(senza, 50))
|
||
m_con = float(np.nanpercentile(con, 50))
|
||
assert m_con < m_senza
|
||
|
||
|
||
def test_il_cap_fisso_diventa_una_strozzatura_dopo_un_deposito():
|
||
"""⚠️ Il punto operativo trovato col piano: `_cap` ripiega su max_notional_per_asset_usd
|
||
quando l'equity reale non e' leggibile. A $600 era INERTE (equity/2 = $300 = cap fisso);
|
||
dopo un deposito da €5.000 il fallback strozzerebbe il book a ~10% del target.
|
||
Questo test FALLISCE se qualcuno deposita senza adeguare il cap fisso — che e' l'azione
|
||
gia' pre-registrata il 2026-07-02 ('al deposito alzare il cap a equity/2')."""
|
||
import json
|
||
|
||
cfg = json.loads((ROOT / "config" / "live.json").read_text())
|
||
fisso = float(cfg["max_notional_per_asset_usd"])
|
||
frac = float(cfg["max_notional_per_asset_frac"])
|
||
equity_attuale = 596.92
|
||
assert fisso >= equity_attuale * frac * 0.95, (
|
||
f"cap fisso ${fisso:.0f} molto sotto equity/2 = ${equity_attuale*frac:.0f}: se l'equity "
|
||
"reale non e' leggibile il book gira strozzato. Alzare max_notional_per_asset_usd.")
|