"""Lock del 5o sleeve GTAA01 (promosso 2026-07-01) e del registry a 5 sleeve. Pin degli invarianti decisi nel diario 2026-07-01-strategy-wave-6threads.md (addendum GTAA): * registry = 5 sleeve, pesi 33/15/12/20/20 (somma 1), GTAA01 presente @0.20; * convenzione cross-venue: weekend/festivi = 0.0 (capitale IB fermo, NON rinormalizzato); * attivazione nel book all'era crypto (2019-03-14) — niente 1996-2019 di solo-GTAA; * il book live Deribit (deribit_book_sleeves) NON include GTAA (resta TP01+SKH01 75/25). """ import sys from pathlib import Path import numpy as np import pandas as pd import pytest sys.path.insert(0, str(Path(__file__).resolve().parents[1])) from src.portfolio.sleeves import GTAA_BOOK_ACTIVATION, _gtaa_daily_returns, active_sleeves, deribit_book_sleeves def test_registry_5_sleeve_pesi(): sl = active_sleeves() w = {s.name: s.weight for s in sl} assert len(sl) == 5 assert w == {"TP01_trend_1d": 0.33, "XS01_xsec_hl": 0.15, "VRP01_shortvol": 0.12, "SKH01_skyhook": 0.20, "GTAA01_eq_trend": 0.20} assert sum(w.values()) == pytest.approx(1.0) def test_gtaa_weekend_zero_e_attivazione(): r = _gtaa_daily_returns() assert r.index.min() == GTAA_BOOK_ACTIVATION # niente storia pre-book nel registry assert r.index.freqstr in ("D", "1D") or (r.index[1:] - r.index[:-1] == pd.Timedelta("1D")).all() wknd = r[r.index.dayofweek >= 5] # sab/dom: capitale equity fermo assert len(wknd) > 100 and float(np.abs(wknd.values).max()) == 0.0 wk = r[r.index.dayofweek < 5] assert float(np.abs(wk.values).sum()) > 0 # nei feriali la strategia vive def test_book_live_deribit_non_include_gtaa(): names = [s.name for s in deribit_book_sleeves()] assert names == ["TP01_trend_1d", "SKH01_skyhook"] # esecuzione live invariata # --------------------------------------------------------------------------- # COSTO IB REALE + BANDA (fix 2026-07-25, diario 2026-07-25-rendita-capcurve-prop.md §1) # --------------------------------------------------------------------------- from src.portfolio.gtaa import (GTAA_DEFAULT_CAPITAL, GTAA_MIN_CAPITAL, gtaa_is_deployable, gtaa_rebalance_plan, gtaa_returns, ib_commission) from src.portfolio.portfolio import metrics, to_daily def test_commissione_ib_ha_un_PAVIMENTO_non_e_proporzionale(): """Il difetto originale: modellare IB come fee proporzionale. Su un ordine piccolo la commissione e' il MINIMO FISSO ($0.35), cioe' una percentuale enorme; su uno grande e' per-azione. Se questo test cade, il modello e' tornato proporzionale.""" c_small = ib_commission(20.0, 100.0) # ordine da $20 c_big = ib_commission(20_000.0, 100.0) # ordine da $20.000 assert c_small == pytest.approx(0.20) # cap 1% del controvalore morde sui micro-ordini assert c_small / 20.0 > 0.005 # >0.5% dell'ordine: il piccolo paga carissimo assert c_big / 20_000.0 < 0.001 # il grande paga pochi bps assert ib_commission(200.0, 100.0) == pytest.approx(0.35) # pavimento fisso def test_lo_sleeve_dipende_dal_CAPITALE_e_non_e_deployabile_sotto_soglia(): """Il modello vecchio era CIECO AL CAPITALE: dava lo stesso Sharpe a $600 e a $50k. Ora no. E sotto GTAA_MIN_CAPITAL lo sleeve si dichiara non deployabile.""" assert not gtaa_is_deployable(600.0) assert not gtaa_is_deployable(GTAA_MIN_CAPITAL - 1) assert gtaa_is_deployable(GTAA_DEFAULT_CAPITAL) piccolo = metrics(to_daily(gtaa_returns(capital=600.0))) grande = metrics(to_daily(gtaa_returns(capital=50_000.0))) assert grande["cagr"] > piccolo["cagr"], "il pavimento fisso deve penalizzare il capitale piccolo" def test_senza_banda_a_basso_capitale_lo_sleeve_e_DISTRUTTIVO(): """La ragione per cui la banda esiste: a $600, ribilanciando in continuo (banda 0, ogni giorno), il pavimento di commissione rende lo sleeve NEGATIVO. E' il difetto trovato.""" nudo = metrics(to_daily(gtaa_returns(capital=600.0, band_usd=0.0, every=1))) bandato = metrics(to_daily(gtaa_returns(capital=600.0))) assert nudo["cagr"] < 0.0, "senza banda a $600 lo sleeve deve perdere (pavimento IB)" assert bandato["cagr"] > 0.0, "con banda+cadenza a $600 deve tornare positivo" def test_piano_di_ribilanciamento_salta_le_gambe_sotto_banda(): """L'esecutore deve passare da qui: le gambe il cui nozionale non supera la banda NON si toccano (un ordine da $12 costa $0.35 = 2.9%).""" grande = gtaa_rebalance_plan({}, 10_000.0) assert grande["deployable"] and grande["orders"], "a $10k da flat si deve poter operare" assert all(o["notional"] >= 50.0 for o in grande["orders"].values()) piccolo = gtaa_rebalance_plan({}, 300.0) assert not piccolo["deployable"] # a $300 su 6 gambe gli ordini sono ~$50 o meno -> quasi tutto sotto banda assert len(piccolo["orders"]) < len(grande["orders"])