9c48cdd884
Validazione esecuzione di TP01 a RISCHIO ZERO: gira il loop live contro dati/conto/posizioni REALI del mainnet, costruisce gli ordini di ribilancio esatti e li STAMPA invece di inviarli. Niente testnet (e' la causa del reset v2.0.0: feed farlocco) -> shadow su mainnet reale + micro-test a size minima come unica via per il fill (passo successivo). - src/live/deribit.py : client Deribit mainnet SOLA LETTURA (ticker/conto/posizioni via Cerbero MCP) + costruttore ordini deterministico (notional->contratti, step BTC $10/ETH $1, quantizzazione, delta vs posizione). Nessun metodo di trading, by design. - src/live/shadow.py : shadow_report() condiviso CLI+dashboard (niente drift); degrada con grazia se il mainnet non risponde. - scripts/live/live_trend.py : CLI shadow (--no-net offline, --equity override). Verificato su mainnet reale: conto $598.07, posizioni flat, TP01 flat -> 0 ordini, parita' col paper OK. - src/live/dashboard.py : box "Shadow live" + titolo/note al 3-way (TP01+XS01+VRP01). - tests/test_live_shadow.py : 9 test deterministici (quantizzazione, sizing 50/50, entry/exit/None, parita' live==backtest). Suite 26/26. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
82 lines
3.6 KiB
Python
82 lines
3.6 KiB
Python
"""Test deterministici dello SHADOW MODE di TP01 (src/live/deribit.py).
|
|
|
|
Coprono la logica a rischio zero che NON tocca la rete: quantizzazione notional->contratti, sizing
|
|
target, costruzione ordine di ribilancio (buy/sell/exit/None), e PARITA' col backtest (il target
|
|
live = ultimo target della serie causale). 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
|
|
|
|
|
|
def test_notional_to_amount_step_and_min():
|
|
# BTC step/min $10
|
|
assert notional_to_amount("BTC-PERPETUAL", 1000) == 1000
|
|
assert notional_to_amount("BTC-PERPETUAL", 1006) == 1010 # round allo step
|
|
assert notional_to_amount("BTC-PERPETUAL", 7) == 10 # clamp al minimo (>= mezzo step)
|
|
assert notional_to_amount("BTC-PERPETUAL", 3) == 0.0 # < mezzo step -> niente ordine
|
|
# ETH step/min $1
|
|
assert notional_to_amount("ETH-PERPETUAL", 33.7) == 34
|
|
assert notional_to_amount("ETH-PERPETUAL", 0.4) == 0.0
|
|
# usa il valore assoluto (il segno lo decide il delta a monte)
|
|
assert notional_to_amount("BTC-PERPETUAL", -1000) == 1000
|
|
|
|
|
|
def test_no_float_artifacts():
|
|
# _quantize_step usa Decimal: nessun 0.07200000000000001 & co.
|
|
v = notional_to_amount("ETH-PERPETUAL", 72.0)
|
|
assert v == 72 and float(v).is_integer()
|
|
|
|
|
|
def test_target_notional_5050_weight():
|
|
# 50/50 book: notional asset = 0.5 * frazione * equity
|
|
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():
|
|
o = build_rebalance_order("BTC-PERPETUAL", target_fraction=1.0, weight=0.5,
|
|
equity_usd=2000, current_pos_usd=0.0)
|
|
assert o["side"] == "buy" and o["amount"] == 1000 and o["reduce_only"] is False
|
|
assert o["target_notional"] == 1000 and o["delta_notional"] == 1000
|
|
|
|
|
|
def test_build_order_exit_is_reduce_only():
|
|
o = build_rebalance_order("ETH-PERPETUAL", target_fraction=0.0, weight=0.5,
|
|
equity_usd=2000, current_pos_usd=1000.0)
|
|
assert o["side"] == "sell" and o["reduce_only"] is True and o["amount"] == 1000
|
|
|
|
|
|
def test_build_order_already_at_target_is_none():
|
|
o = build_rebalance_order("BTC-PERPETUAL", 1.0, 0.5, 2000, current_pos_usd=1000.0)
|
|
assert o is None # delta 0 -> nessun ordine
|
|
|
|
|
|
def test_build_order_subthreshold_is_none():
|
|
# delta $3 su BTC (< mezzo step $5) -> niente ordine
|
|
o = build_rebalance_order("BTC-PERPETUAL", 0.5015, 0.5, 2000, current_pos_usd=498.5)
|
|
assert o is None
|
|
|
|
|
|
def test_partial_rebalance_direction():
|
|
# target $1000, ho $600 -> compro $400; ho $1400 -> vendo $400
|
|
up = build_rebalance_order("BTC-PERPETUAL", 1.0, 0.5, 2000, 600.0)
|
|
dn = build_rebalance_order("BTC-PERPETUAL", 1.0, 0.5, 2000, 1400.0)
|
|
assert up["side"] == "buy" and up["amount"] == 400
|
|
assert dn["side"] == "sell" and dn["amount"] == 400 and dn["reduce_only"] is False
|
|
|
|
|
|
def test_parity_live_target_equals_backtest():
|
|
# il target live (current_target) DEVE essere l'ultimo della serie causale del 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]
|