"""Test deterministici dello SHADOW/esecuzione TP01 (src/live/deribit.py). Coprono la logica a rischio zero che NON tocca la rete: quantizzazione notional->contratti (INVERSE e LINEARE USDC), sizing target, costruzione ordine di ribilancio (buy/sell/exit/None), e PARITA' col backtest. Il conto reale e' USDC -> il path principale e' il LINEARE BTC_USDC-PERPETUAL. Il fill reale (slippage/fee) NON e' qui: si valida solo col micro-test mainnet. """ import sys from pathlib import Path PROJECT_ROOT = Path(__file__).resolve().parents[1] sys.path.insert(0, str(PROJECT_ROOT)) from src.live.deribit import (build_rebalance_order, notional_to_amount, target_notional_usd) from src.strategies.trend_portfolio import CANONICAL, TrendPortfolio, resample_1d LIN = "BTC_USDC-PERPETUAL" # lineare USDC: amount in BTC, step 0.0001 (path reale del conto) PX = 64000.0 def test_notional_linear_usdc(): # amount in base-coin = notional/price, quantizzato a 0.0001, clamp al minimo assert notional_to_amount(LIN, 6.4, price=PX) == 0.0001 # 6.4/64000 = 0.0001 assert notional_to_amount(LIN, 12.8, price=PX) == 0.0002 assert notional_to_amount(LIN, 3.0, price=PX) == 0.0 # < mezzo step ($3.2) -> niente assert notional_to_amount(LIN, 100, price=None) == 0.0 # lineare senza prezzo -> 0 assert notional_to_amount(LIN, -6.4, price=PX) == 0.0001 # usa il valore assoluto def test_notional_inverse_still_supported(): # l'helper regge ancora gli inverse (amount in USD), senza prezzo assert notional_to_amount("BTC-PERPETUAL", 1000) == 1000 assert notional_to_amount("BTC-PERPETUAL", 7) == 10 # clamp al minimo assert notional_to_amount("BTC-PERPETUAL", 3) == 0.0 def test_no_float_artifacts(): v = notional_to_amount(LIN, 0.0001 * PX * 72, price=PX) # 72 step esatti assert v == 0.0072 and abs(v - 0.0072) < 1e-12 def test_target_notional_5050_weight(): assert target_notional_usd(1.0, 0.5, 2000) == 1000 assert target_notional_usd(2.0, 0.5, 2000) == 2000 # leva-cap 2x -> piena equity sull'asset assert target_notional_usd(0.0, 0.5, 2000) == 0.0 def test_build_order_entry_linear(): o = build_rebalance_order(LIN, target_fraction=1.0, weight=0.5, equity_usd=2000, current_pos_usd=0.0, price=PX) assert o["side"] == "buy" and o["reduce_only"] is False assert o["target_notional"] == 1000 and o["delta_notional"] == 1000 assert abs(o["amount"] - 0.0156) < 1e-9 # 1000/64000=0.015625 -> 0.0156 def test_build_order_exit_is_reduce_only(): o = build_rebalance_order(LIN, target_fraction=0.0, weight=0.5, equity_usd=2000, current_pos_usd=1000.0, price=PX) assert o["side"] == "sell" and o["reduce_only"] is True and o["amount"] > 0 def test_build_order_already_at_target_is_none(): o = build_rebalance_order(LIN, 1.0, 0.5, 2000, current_pos_usd=1000.0, price=PX) assert o is None # delta 0 -> nessun ordine def test_build_order_subthreshold_is_none(): # delta $2 (< mezzo step in notional, $3.2 a 64k) -> niente ordine o = build_rebalance_order(LIN, 1.0, 0.5, 2000, current_pos_usd=998.0, price=PX) assert o is None def test_partial_rebalance_direction(): up = build_rebalance_order(LIN, 1.0, 0.5, 2000, 600.0, price=PX) # compra il delta dn = build_rebalance_order(LIN, 1.0, 0.5, 2000, 1400.0, price=PX) # vende il delta assert up["side"] == "buy" and up["delta_notional"] == 400 assert dn["side"] == "sell" and dn["delta_notional"] == -400 and dn["reduce_only"] is False def test_parity_live_target_equals_backtest(): from src.backtest.harness import load tp = TrendPortfolio(**CANONICAL) df = resample_1d(load("BTC", "1h")) assert tp.current_target(df) == tp.target_series(df)[-1]