feat(live): cap/asset dinamico = equity/2 — operazionalizza la decisione frontier (inerte a $600)

Il cap notional per-asset non e' piu' fisso a $300: con max_notional_per_asset_frac
in config diventa equity/2, cosi' cresce col capitale e un deposito futuro non resta
strozzato (frontiera 2026-07-03). Guardrail: dinamico SOLO con equity reale fidata;
su fallback/offline si torna al cap fisso (protezione downside quando E e' ignota).
Live dry-run: cap $299 a equity $598 -> inerte oggi. Suite 172 passed (+4 test).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Adriano Dal Pastro
2026-07-03 09:42:13 +00:00
parent eef200cf14
commit cf7de40dc0
4 changed files with 119 additions and 5 deletions
+49
View File
@@ -36,6 +36,55 @@ def test_net_target_clamps_negative():
assert book_net_target(-5.0, 1, 600.0, 300.0) == 75.0
# ---------------------------------------------------------------------------
# CAP DINAMICO (frontiera 2026-07-03): con max_notional_per_asset_frac in config il
# cap = equity*frac (cresce col capitale). GUARDRAIL: dinamico SOLO con equity reale
# fidata; su fallback/offline/no-frac -> cap FISSO (protezione downside).
# ---------------------------------------------------------------------------
import json as _json
def _write_cfg(monkeypatch, tmp_path, **keys):
import src.live.book as book
p = tmp_path / "live.json"
p.write_text(_json.dumps(keys))
monkeypatch.setattr(book, "CONFIG", p)
return book
def test_cap_dynamic_scales_with_trusted_equity(monkeypatch, tmp_path):
book = _write_cfg(monkeypatch, tmp_path, max_notional_per_asset_usd=300, max_notional_per_asset_frac=0.5)
# equity reale fidata -> cap = equity/2 (cresce col capitale)
assert book._cap(equity=5000.0, real_equity=5000.0, eq_fallback=None) == 2500.0
assert book._cap(equity=598.0, real_equity=598.0, eq_fallback=None) == 299.0 # ~inerte a $600
def test_cap_falls_back_to_fixed_on_eq_fallback(monkeypatch, tmp_path):
book = _write_cfg(monkeypatch, tmp_path, max_notional_per_asset_usd=300, max_notional_per_asset_frac=0.5)
# equity paper (real non leggibile) -> NON usare equity/2 ($1000 su conto ignoto = pericoloso)
assert book._cap(equity=2000.0, real_equity=None, eq_fallback="paper_cap") == 300.0
assert book._cap(equity=2000.0, real_equity=None, eq_fallback=None) == 300.0 # real None -> fisso
assert book._cap(equity=0.0, real_equity=0.0, eq_fallback=None) == 300.0 # equity 0 -> fisso
def test_cap_fixed_when_no_frac_in_config(monkeypatch, tmp_path):
book = _write_cfg(monkeypatch, tmp_path, max_notional_per_asset_usd=300) # niente frac
assert book._cap(equity=5000.0, real_equity=5000.0, eq_fallback=None) == 300.0
def test_book_report_dynamic_cap_online(monkeypatch, tmp_path):
"""Integrazione: online con equity reale -> cap_per_asset = equity/2 e la formula lo rispetta."""
book = _write_cfg(monkeypatch, tmp_path, max_notional_per_asset_usd=300, max_notional_per_asset_frac=0.5)
base = book.shadow_report(offline=True, equity_override=5000.0)
monkeypatch.setattr(book, "shadow_report",
lambda **k: {**base, "real_equity": 5000.0, "equity": 5000.0, "eq_fallback": None})
r = book.book_report()
assert r["cap_per_asset"] == 2500.0
for a in r["assets"]:
expect = book_net_target(a["tp_frac"], a["skh_sign"], 5000.0, 2500.0)
assert abs(a["net_target"] - expect) < 1e-6
def test_weights_match_backtest_sleeves():
"""I pesi del book live DEVONO coincidere con quelli del backtest (deribit_book_sleeves)."""
from src.portfolio.sleeves import deribit_book_sleeves