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:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user