feat(live): esecuzione REALE su Deribit testnet (shadow) per i 6 fade sui lineari USDC

- ExecutionClient: notional->amount (lineare USDC + inverse), open/close_amount
  reduce-only, verifica sul trade (order_id), fee reali lette dai trades[]
- CerberoClient: place_order market + reduce_only, get_trade_history
- StrategyWorker: shadow (REAL_OPEN/REAL_CLOSE accanto al sim), ledger reale
  parallelo persistito, confronto slippage/fee sim-vs-reale
- runner+portfolios.yml: config execution (6 fade MR01/MR02/MR07 x BTC/ETH su
  BTC_USDC/ETH_USDC-PERPETUAL), capitale 2000
- smoke: live_exec_smoke (layer) + live_shadow_smoke (catena worker), provati su testnet

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Adriano Dal Pastro
2026-06-03 10:11:26 +00:00
parent 1f0c1ab02a
commit cb1b6ea46a
7 changed files with 563 additions and 6 deletions
+80
View File
@@ -0,0 +1,80 @@
"""Smoke end-to-end dell'esecuzione REALE su Deribit testnet.
Dimostra il giro completo che serve al live:
account → OPEN (ordine reale minimo) → VERIFICA posizione su Deribit →
FEE reale dai trade → CLOSE → VERIFICA flat → riepilogo.
Usa il sizing MINIMO del contratto (BTC $10, ETH $1): costo testnet = €0.
NON tocca il runner: e' solo una prova della macchina di esecuzione.
uv run python scripts/analysis/live_exec_smoke.py # ETH + BTC
uv run python scripts/analysis/live_exec_smoke.py ETH-PERPETUAL
"""
from __future__ import annotations
import sys
from src.live.cerbero_client import CerberoClient
from src.live.execution import ExecutionClient, contract_spec, settlement_currency
def smoke_one(ex: ExecutionClient, instrument: str, notional: float = 10.0) -> bool:
spec = contract_spec(instrument)
cur = settlement_currency(instrument)
kind = "lineare USDC" if spec.get("linear") else "inverse"
print(f"\n===== {instrument} ({kind}, ~${notional:.0f} notional) =====")
pre = ex._position_size(instrument)
print(f" posizione pre: {pre} USD (atteso 0)")
print(f" → OPEN buy ${notional:.0f} notional ...")
f = ex.open(instrument, "buy", notional, label="smoke-exec")
print(f" order_id={f.order_id} state={f.order_state} verified={f.verified}")
print(f" fill_price={f.fill_price} amount={f.amount} ({'BTC' if spec.get('linear') else 'USD'})")
print(f" FEE reale: {f.fee_coin:.10f} {cur} (~${f.fee_usd:.4f})")
if f.notes:
print(f" note: {f.notes}")
if not f.verified:
print(" ✗ OPEN NON verificato — interrompo questo strumento")
return False
print(" ✓ posizione confermata su Deribit (riletta da get_positions)")
print(" → CLOSE ...")
c = ex.close(instrument, label="smoke-exec")
print(f" order_id={c.order_id} state={c.order_state} verified(flat)={c.verified}")
print(f" fill_price={c.fill_price} FEE reale: {c.fee_coin:.10f} {cur} (~${c.fee_usd:.4f})")
if c.notes:
print(f" note: {c.notes}")
post = ex._position_size(instrument)
ok = f.verified and c.verified and post == 0
# fee RT reale osservata vs assunto 0.10% RT sul notional
fee_usd_rt = f.fee_usd + c.fee_usd
assumed_rt = notional * 0.001
print(f" posizione post: {post} USD (atteso 0)")
print(f" FEE RT reale ~${fee_usd_rt:.4f} vs assunto 0.10% RT ~${assumed_rt:.4f}")
print(f" {'✓ OK' if ok else '✗ FALLITO'} — giro completo {instrument}")
return ok
def main() -> None:
instruments = sys.argv[1:] or ["ETH-PERPETUAL", "BTC-PERPETUAL"]
client = CerberoClient()
acct = client.get_account_summary(currency="USDC")
print(f"Account testnet: equity={acct.get('equity')} USDC testnet={acct.get('testnet')}")
if not acct.get("testnet"):
print("✗ ABORT: non e' testnet — niente ordini reali su mainnet.")
sys.exit(1)
ex = ExecutionClient(client=client)
results = {inst: smoke_one(ex, inst) for inst in instruments}
print("\n===== RIEPILOGO =====")
for inst, ok in results.items():
print(f" {inst:16s} {'✓ OK' if ok else '✗ FALLITO'}")
sys.exit(0 if all(results.values()) else 1)
if __name__ == "__main__":
main()
+67
View File
@@ -0,0 +1,67 @@
"""Smoke della catena SHADOW dentro lo StrategyWorker (testnet, ordini reali minimi).
Apre e chiude la quota di UN worker fade come farebbe il runner:
_open_position (sim + REAL_OPEN reale su BTC_USDC) → _close_position (sim +
REAL_CLOSE reduce-only) → controlla che real_capital sia aggiornato dal fill reale.
Non tocca lo stato di produzione (data_dir temporanea). Costo testnet = €0.
uv run python scripts/analysis/live_shadow_smoke.py
"""
from __future__ import annotations
import tempfile
from pathlib import Path
from src.live.cerbero_client import CerberoClient
from src.live.execution import ExecutionClient
from src.live.strategy_loader import load_strategy
from src.live.strategy_worker import StrategyWorker
from src.strategies.base import Signal
def main() -> None:
client = CerberoClient()
acct = client.get_account_summary(currency="USDC")
print(f"Account testnet equity={acct.get('equity')} USDC testnet={acct.get('testnet')}")
if not acct.get("testnet"):
raise SystemExit("ABORT: non testnet")
ex = ExecutionClient(client=client)
instrument = "BTC_USDC-PERPETUAL"
price = ex._mark_price(instrument)
print(f"{instrument} mark={price}")
with tempfile.TemporaryDirectory() as tmp:
w = StrategyWorker(
strategy=load_strategy("MR01_bollinger_fade"),
asset="BTC", tf="1h", capital=100.0, position_size=0.15, leverage=2.0,
data_dir=Path(tmp), executor=ex, exec_instrument=instrument,
)
print(f"execution_enabled={w.execution_enabled} notional atteso=${100*0.15*2:.0f}")
# OPEN long (sim + reale)
sig = Signal(idx=0, direction=1, entry_price=price, metadata={})
w._open_position(sig, price)
print(f" real_in_position={w.real_in_position} side={w.real_side} "
f"amount={w.real_amount} entry={w.real_entry_price} "
f"entry_fee=${w.real_entry_fee_usd:.5f} notional=${w.real_entry_notional:.2f}")
assert w.real_in_position, "OPEN reale non verificato"
# CLOSE (sim + reale reduce-only) a un prezzo leggermente diverso
exit_price = (w.entry_price or price) * 1.001
cap_before = w.real_capital
w._close_position(exit_price, "smoke_close")
print(f" real_capital {cap_before:.4f} -> {w.real_capital:.4f} "
f"{w.real_capital - cap_before:+.4f}) real_trades={w.real_trades}")
assert not w.real_in_position, "posizione reale non chiusa"
# verifica finale: il conto e' flat sullo strumento (nessuna quota residua del worker)
pos = ex._position_size(instrument)
print(f" posizione netta {instrument}: {pos}")
print("✓ catena shadow OK — ordine reale aperto, verificato, chiuso reduce-only, "
"fee reali nel ledger reale")
if __name__ == "__main__":
main()