"""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.")