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
+31 -7
View File
@@ -30,7 +30,8 @@ _CONTRACT = {
"BTC_USDC-PERPETUAL": {"min": 0.0001, "step": 0.0001, "tick": 0.5, "settle": "USDC", "linear": True},
"ETH_USDC-PERPETUAL": {"min": 0.001, "step": 0.001, "tick": 0.05, "settle": "USDC", "linear": True},
}
INSTRUMENT = {"BTC": "BTC-PERPETUAL", "ETH": "ETH-PERPETUAL"}
# Il conto reale e' USDC -> mappiamo gli asset sui perp LINEARI USDC (gli unici eseguibili qui).
INSTRUMENT = {"BTC": "BTC_USDC-PERPETUAL", "ETH": "ETH_USDC-PERPETUAL"}
# ----------------------------- costruzione ordini (pura, testabile, NIENTE rete) -----------------------------
@@ -41,16 +42,32 @@ def _quantize_step(value: float, step: float, mn: float) -> float:
return float(max(n * Decimal(str(step)), Decimal(str(mn))))
def notional_to_amount(instrument: str, notional_usd: float) -> float:
"""USD notional -> `amount` Deribit (inverse: amount in USD), arrotondato allo step e clampato
al minimo. Ritorna 0.0 se |notional| < mezzo step (sotto-soglia: niente ordine)."""
def notional_to_amount(instrument: str, notional_usd: float, price: float | None = None) -> float:
"""USD notional -> `amount` Deribit, arrotondato allo step e clampato al minimo. Ritorna 0.0 se
sotto mezzo step. INVERSE: amount in USD (price ignorato). LINEAR USDC: amount in base-coin
(units = notional/price -> serve il `price`; senza price ritorna 0.0)."""
spec = _CONTRACT[instrument]
step, mn = spec["step"], spec["min"]
if spec.get("linear"):
if not price:
return 0.0
units = abs(notional_usd) / price
if units < step / 2:
return 0.0
return _quantize_step(units, step, mn)
if abs(notional_usd) < step / 2:
return 0.0
return _quantize_step(abs(notional_usd), step, mn)
def quantize_price(instrument: str, price: float) -> float:
"""Arrotonda il prezzo al tick dello strumento (per gli ordini stop/limit)."""
tick = _CONTRACT[instrument].get("tick")
if not tick or price <= 0:
return price
return float(round(price / tick) * Decimal(str(tick)))
def target_notional_usd(target_fraction: float, weight: float, equity_usd: float) -> float:
"""Notional bersaglio (USD) di un asset = peso nel book * frazione-di-equity TP01 * equity.
Coerente col paper trader (esposizione asset = WEIGHT * target * equity)."""
@@ -58,12 +75,13 @@ def target_notional_usd(target_fraction: float, weight: float, equity_usd: float
def build_rebalance_order(instrument: str, target_fraction: float, weight: float,
equity_usd: float, current_pos_usd: float) -> dict | None:
equity_usd: float, current_pos_usd: float, price: float | None = None) -> dict | None:
"""COSTRUISCE (non invia) l'ordine di ribilancio verso il target. Ritorna un dict-ordine o None
se sotto-soglia. Long-only TP01 -> target_notional >= 0; delta = target - posizione corrente."""
se sotto-soglia. Long-only TP01 -> target_notional >= 0; delta = target - posizione corrente.
`price` (mark) serve a convertire il notional in base-coin per gli strumenti LINEARI USDC."""
tgt = target_notional_usd(target_fraction, weight, equity_usd)
delta = tgt - current_pos_usd
amount = notional_to_amount(instrument, delta)
amount = notional_to_amount(instrument, delta, price=price)
if amount == 0.0:
return None
is_exit = abs(tgt) < 1e-9 and abs(current_pos_usd) > 0
@@ -141,3 +159,9 @@ class DeribitRead:
if p.get("instrument_name") == instrument or p.get("instrument") == instrument:
return float(p.get("size") or p.get("size_currency") or 0.0)
return 0.0
def trade_history(self, instrument: str, limit: int = 20) -> list[dict]:
"""Trade REALMENTE eseguiti sul conto per `instrument` (fonte autorevole fee/fill)."""
out = self._unwrap(self._post("/mcp-deribit/tools/get_trade_history",
{"limit": limit, "instrument_name": instrument}))
return out if isinstance(out, list) else (out.get("trades", []) if isinstance(out, dict) else [])