"""Guardia a DUE LATI sul cap di fallback (src/live/book._cap) — codice di PRODUZIONE, soldi veri. Il cap fisso e' sbagliato in entrambi i versi se mantenuto a mano: * troppo BASSO dopo un deposito -> il book gira strozzato (a $6k col cap a $300: ~10% del target); * troppo ALTO prima del deposito -> il fallback (equity reale non leggibile) sizza su `paper_cap` ($2.000 nominali) e su un conto da $597 diventa **3.35x di leva** proprio quando non si sa quanto c'e' sul conto. Dal 2026-07-26 il fallback e' legato all'ULTIMA EQUITY REALE OSSERVATA (watermark), quindi il cap di config e' un tetto dichiarato e non una manopola da ricordare. """ from __future__ import annotations import json import sys from pathlib import Path import pytest ROOT = Path(__file__).resolve().parents[1] sys.path.insert(0, str(ROOT)) from src.live import book as B # noqa: E402 @pytest.fixture def wm(tmp_path, monkeypatch): """Redirige il watermark su tmp: i test non devono MAI toccare lo stato reale del book.""" p = tmp_path / "equity_seen.json" monkeypatch.setattr(B, "EQUITY_WATERMARK", p) return p # =========================================================================== # il lato pericoloso: cap alto, conto piccolo # =========================================================================== def test_il_fallback_non_puo_superare_il_conto_reale_osservato(wm): """IL test. Cap di config $3.000 ma il conto vale $597 -> il fallback deve restare a ~$298.""" B.write_equity_watermark(596.92) cap = B._cap(equity=2000.0, real_equity=None, eq_fallback="equity non leggibile") assert cap == pytest.approx(596.92 * 0.5, rel=1e-6) assert cap < 300.0, "il fallback supera il conto reale: leva nascosta" def test_la_leva_di_fallback_resta_sotto_1x(wm): """Invariante che il progetto usa altrove (analisi liquidation fee 26/07): il nozionale LORDO non deve superare l'equity reale. Due asset, quindi 2 x cap <= equity.""" for eq in (596.92, 2_000.0, 6_047.0, 50_000.0): B.write_equity_watermark(eq) cap = B._cap(equity=2_000.0, real_equity=None, eq_fallback="fallback") assert 2 * cap <= eq * 1.0 + 1e-6, f"a equity ${eq:,.0f} il fallback fa leva {2*cap/eq:.2f}x" def test_senza_watermark_si_usa_la_taglia_sicura_non_il_cap_di_config(wm): """Primo avvio / file cancellato: non si sa niente del conto -> taglia storicamente sicura.""" assert not wm.exists() cap = B._cap(equity=2_000.0, real_equity=None, eq_fallback="fallback") assert cap == pytest.approx(B.CAP_UNKNOWN_USD) def test_watermark_corrotto_degrada_alla_taglia_sicura(wm): wm.write_text("{ non e' json") assert B._cap(equity=2_000.0, real_equity=None, eq_fallback="x") == pytest.approx(B.CAP_UNKNOWN_USD) def test_watermark_assurdo_viene_ignorato(wm): wm.write_text(json.dumps({"real_equity": -5.0})) assert B._cap(equity=2_000.0, real_equity=None, eq_fallback="x") == pytest.approx(B.CAP_UNKNOWN_USD) # =========================================================================== # il lato della strozzatura: cap basso, conto cresciuto # =========================================================================== def test_dopo_il_deposito_il_fallback_segue_il_conto(wm): """Il motivo per cui il cap e' stato alzato: a $6.047 il fallback non deve restare a $300.""" B.write_equity_watermark(6_047.0) cap = B._cap(equity=2_000.0, real_equity=None, eq_fallback="fallback") assert cap == pytest.approx(min(3000.0, 6_047.0 * 0.5), rel=1e-6) assert cap > 1_000.0 def test_il_cap_di_config_resta_un_tetto(wm): """Anche con un conto enorme il cap non supera il valore dichiarato in config.""" B.write_equity_watermark(1_000_000.0) cap = B._cap(equity=2_000.0, real_equity=None, eq_fallback="fallback") fixed = float(json.loads((ROOT / "config" / "live.json").read_text())["max_notional_per_asset_usd"]) assert cap == pytest.approx(fixed) # =========================================================================== # il percorso normale non cambia # =========================================================================== def test_con_equity_reale_leggibile_il_cap_resta_dinamico(wm): """Il caso di tutti i giorni: nessuna regressione dal cambio.""" B.write_equity_watermark(100.0) # watermark vecchio e basso: non deve interferire cap = B._cap(equity=6_047.0, real_equity=6_047.0, eq_fallback=None) assert cap == pytest.approx(6_047.0 * 0.5) def test_il_watermark_viene_scritto_solo_con_equity_valida(wm): B.write_equity_watermark(None) assert not wm.exists() B.write_equity_watermark(0.0) assert not wm.exists() B.write_equity_watermark(123.45) assert json.loads(wm.read_text())["real_equity"] == pytest.approx(123.45) # =========================================================================== # config # =========================================================================== def test_il_cap_di_config_copre_il_versamento_previsto(): """€5.000 + €500/mese -> equity ~$6.050 -> equity/2 ~$3.025. Il tetto deve permetterlo.""" fixed = float(json.loads((ROOT / "config" / "live.json").read_text())["max_notional_per_asset_usd"]) assert fixed >= 3000.0 def test_la_frazione_di_config_tiene_la_leva_a_1x(): """2 asset x frac <= 1.0: e' l'ipotesi su cui poggia 'la liquidation fee non ci tocca' (26/07).""" cfg = json.loads((ROOT / "config" / "live.json").read_text()) assert float(cfg["max_notional_per_asset_frac"]) * 2 <= 1.0