feat(live): loop di esecuzione GATED di TP01 (execution_enabled + --execute, default OFF)

scripts/live/live_execute.py porta il conto reale al target di TP01 (min(0.5*frazione*equity,
cap/asset)): apre/riduce/chiude via DeribitTrader.rebalance_to(). DOPPIO GATE: config/live.json
execution_enabled=true (master, default false) E flag --execute; senza entrambi e' dry-run.
Reconciliation post-ordine + log in data/live/executions.jsonl. TP01 flat -> 0 azioni.

- execution.py: rebalance_to() (open/reduce/close al target); MAX_AMOUNT alzato a tetto hard
  anti-fat-finger (~$630/$430 su conto ~$600), il sizing operativo lo decide config max_notional.
- config/live.json: master switch + cap/asset $300 + min ordine $5 + disaster_sl_pct.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Adriano Dal Pastro
2026-06-20 15:37:51 +00:00
parent a3d6b97db6
commit bc9e322d0d
3 changed files with 148 additions and 2 deletions
+27 -2
View File
@@ -17,9 +17,11 @@ from dataclasses import dataclass
from src.live.deribit import DeribitRead, notional_to_amount, quantize_price
# Conto USDC -> perp LINEARE USDC (amount in base-coin). Cap micro-test: ~$13.
# Conto USDC -> perp LINEARE USDC (amount in base-coin). MAX_AMOUNT = TETTO HARD anti-fat-finger
# (~$630/$430 su un conto ~$600): backstop sopra il sizing di TP01, non il sizing operativo (quello
# lo decide config/live.json max_notional_per_asset_usd). Il micro-test invia comunque size fissa minima.
ALLOWED = {"BTC_USDC-PERPETUAL", "ETH_USDC-PERPETUAL"}
MAX_AMOUNT = {"BTC_USDC-PERPETUAL": 0.0002, "ETH_USDC-PERPETUAL": 0.005}
MAX_AMOUNT = {"BTC_USDC-PERPETUAL": 0.01, "ETH_USDC-PERPETUAL": 0.25}
FLAT_USD = 1.0 # |notional| < $1 = posizione considerata flat
@@ -115,6 +117,29 @@ class DeribitTrader(DeribitRead):
side = "sell" if pos_usd > 0 else "buy"
return self._submit(instrument, side, amount, reduce_only=True, label=label)
# --- RIBILANCIO al target (long-only TP01): apre / riduce / chiude ---
def rebalance_to(self, instrument: str, target_notional_usd: float, mark: float,
min_usd: float = 5.0) -> list[Fill]:
"""Porta la posizione su `instrument` al target (USD notional). Long-only: target>=0.
- delta < min_usd -> niente (gia' a target);
- target ~0 & posizione -> close() (uscita piena, reduce_only);
- delta > 0 -> open buy (aumenta);
- delta < 0 (resta long) -> sell reduce_only del delta (riduce).
Ritorna i Fill eseguiti."""
cur = self.position_usd(instrument)
delta = target_notional_usd - cur
if abs(delta) < min_usd:
return []
if target_notional_usd < FLAT_USD and cur > FLAT_USD:
f = self.close(instrument, label="tp01-exit")
return [f] if f else []
amount = notional_to_amount(instrument, abs(delta), price=mark)
if amount <= 0:
return []
if delta > 0:
return [self.open(instrument, "buy", amount, label="tp01-buy")]
return [self._submit(instrument, "sell", amount, reduce_only=True, label="tp01-reduce")]
# --- DISASTER BRACKET (assicurazione on-book per outage; da Old) ---
def place_disaster_sl(self, instrument: str, side_held: str, amount: float,
stop_price: float, label: str = "disaster-sl") -> Fill: