feat(live): conto USDC -> strumenti lineari; entrata/uscita da Old; dashboard LIVE separato da PAPER

Correzione post-micro-test (il conto e' USDC, non BTC/ETH):
- deribit.py: INSTRUMENT -> BTC/ETH_USDC-PERPETUAL (lineari, gli unici eseguibili sul conto USDC);
  notional_to_amount gestisce i lineari (amount in base-coin = notional/price); + quantize_price;
  trade_history (read-only) per i trade reali. build_rebalance_order passa il prezzo.
- shadow.py: sizing col prezzo; espone live_trades (trade reali eseguiti su Deribit).

Entrata/uscita verificate (logica presa da Old/src/live/execution.py):
- execution.py: open() market verificato (state=='filled' + trade, fill/fee reali, filled_amount
  autorevole), close() market reduce_only (le CHIUSURE si tentano SEMPRE, senza cap), disaster-SL
  STOP_MARKET reduce_only. Cap di size SOLO sulle aperture. Fill dataclass.
- microtest.py: usa open()/close(); safe-close se l'apertura non e' verificata.

Dashboard: sezione PAPER (backtest+forward) separata da sezione LIVE (conto reale Deribit: shadow
TP01 + Trades REALI eseguiti). Test 27/27.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Adriano Dal Pastro
2026-06-20 15:15:45 +00:00
parent c00f6016df
commit cddea50c5a
6 changed files with 238 additions and 133 deletions
+38 -35
View File
@@ -1,9 +1,9 @@
"""Test deterministici dello SHADOW MODE di TP01 (src/live/deribit.py).
"""Test deterministici dello SHADOW/esecuzione 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.
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
@@ -14,67 +14,70 @@ 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_to_amount_step_and_min():
# BTC step/min $10
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", 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
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():
# _quantize_step usa Decimal: nessun 0.07200000000000001 & co.
v = notional_to_amount("ETH-PERPETUAL", 72.0)
assert v == 72 and float(v).is_integer()
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():
# 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
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("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
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("BTC-PERPETUAL", 1.0, 0.5, 2000, current_pos_usd=1000.0)
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 $3 su BTC (< mezzo step $5) -> niente ordine
o = build_rebalance_order("BTC-PERPETUAL", 0.5015, 0.5, 2000, current_pos_usd=498.5)
# 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():
# 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
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():
# 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"))