chore(reset): v2.0.0 — storico certificato Deribit mainnet, ripartenza pulita

Reset del progetto su fondamenta verificate dopo la scoperta che l'intera
libreria "validata OOS" era artefatto di feed contaminato (print fantasma del
feed Cerbero TESTNET + storico Binance/USDT).

- Storico ricostruito da Deribit MAINNET (ccxt pubblico, tokenless) e
  CERTIFICATO (certify_feed.py): BTC/ETH puliti su TUTTA la storia
  (mediana 2-6 bps vs Coinbase USD), integrita' OHLC + coerenza resample
  (maxΔ 0.00) + cross-venue OK. Alt esclusi (illiquidi/divergenti: LTC/DOGE
  50-82% barre flat; XRP/BNB non certificabili).
- Verdetto sul feed pulito: FADE / PAIRS / XS01 / TSM01 morti (ogni
  portafoglio Sharpe -2.3..-3.0, DD ~40%); solo SH01 e frammenti HONEST
  con segnale residuo, da ri-validare in isolamento.
- Cleanup "restart pulito": strategie, stack live (src/live, src/portfolio,
  runner/executor, yml, docker), ~100 script ricerca/gate, waste/games/
  portfolios, dati non certificati + cache e 60+ diari -> archiviati in Old/
  (preservati, non cancellati). Diario consolidato in un unico documento.
- Skeleton ricerca tenuto: Strategy ABC + indicatori + src/fractal +
  src/backtest/engine + load_data; tool dati certificati (rebuild_history,
  certify_feed, audit_feed, multi_source_check).
- Universo dati ATTIVO: solo BTC/ETH (5m/15m/1h); guardrail fisico
  (load_data su alt -> FileNotFoundError). Esecuzione DISABILITATA, conto flat.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Adriano Dal Pastro
2026-06-19 15:16:03 +00:00
parent 8401a280b9
commit 14522262e6
383 changed files with 1971 additions and 779 deletions
+164
View File
@@ -0,0 +1,164 @@
"""Client HTTP per Cerbero MCP — Deribit.
Ambiente determinato dal TOKEN (vedi API_REFERENCE): TESTNET_TOKEN -> upstream
testnet, MAINNET_TOKEN -> upstream live. Default = testnet. Per il micro-test
mainnet basta esportare CERBERO_TOKEN (e opzionalmente CERBERO_BOT_TAG) nel `.env`
— NESSUNA modifica di codice. Vedi docs/specs/mainnet-microtest-plan.md."""
from __future__ import annotations
import os
from dataclasses import dataclass, field
from typing import Any
import requests
BASE_URL = os.environ.get("CERBERO_BASE_URL", "https://cerbero-mcp.tielogic.xyz")
# Token TESTNET di default; override via env CERBERO_TOKEN per puntare a mainnet.
TESTNET_TOKEN = "_hm0FkyC67P9OXJTy7R9SE2lfhGz_Wa6i89KqH_uXrk"
TOKEN = os.environ.get("CERBERO_TOKEN", TESTNET_TOKEN)
BOT_TAG = os.environ.get("CERBERO_BOT_TAG", "pythagoras-paper")
TIMEOUT = 15
def is_mainnet() -> bool:
"""True se il token attivo NON è quello testnet di default (= upstream live)."""
return TOKEN != TESTNET_TOKEN
@dataclass
class CerberoClient:
base_url: str = BASE_URL
token: str = TOKEN
bot_tag: str = BOT_TAG
def _headers(self) -> dict[str, str]:
return {
"Authorization": f"Bearer {self.token}",
"X-Bot-Tag": self.bot_tag,
"Content-Type": "application/json",
}
def _post(self, path: str, payload: dict | None = None) -> dict:
resp = requests.post(
f"{self.base_url}{path}",
headers=self._headers(),
json=payload or {},
timeout=TIMEOUT,
)
resp.raise_for_status()
return resp.json()
# --- Market data ---
def get_ticker(self, instrument: str = "ETH-PERPETUAL") -> dict:
return self._post("/mcp-deribit/tools/get_ticker", {"instrument": instrument})
def get_historical(self, instrument: str, start_date: str, end_date: str, resolution: str = "15") -> list[dict]:
data = self._post("/mcp-deribit/tools/get_historical", {
"instrument": instrument,
"start_date": start_date,
"end_date": end_date,
"resolution": resolution,
})
return data.get("candles", [])
def get_historical_v2(self, instrument: str, start_date: str, end_date: str,
interval: str = "1h", exchange: str = "deribit") -> list[dict]:
"""Endpoint unificato v2: /mcp/tools/get_historical (exchange deribit|hyperliquid).
Stesso shape candele del legacy: [{timestamp(ms), open, high, low, close, volume}]."""
data = self._post("/mcp/tools/get_historical", {
"exchange": exchange, "instrument": instrument,
"interval": interval, "start_date": start_date, "end_date": end_date,
})
return data.get("candles", [])
def get_instruments(self, currency: str, kind: str = "future",
exchange: str = "deribit", limit: int = 100) -> list[dict]:
"""Enumera gli strumenti reali (v2). Usato per risolvere il naming senza hardcoding."""
data = self._post("/mcp/tools/get_instruments", {
"exchange": exchange, "currency": currency, "kind": kind, "limit": limit,
})
return data.get("instruments", data if isinstance(data, list) else [])
def get_ticker_batch(self, instruments: list[str]) -> dict:
"""Prezzi correnti di N strumenti in una sola chiamata (v2, Deribit)."""
return self._post("/mcp-deribit/tools/get_ticker_batch", {"instruments": instruments})
# --- Account ---
def get_account_summary(self, currency: str = "USDC") -> dict:
return self._post("/mcp-deribit/tools/get_account_summary", {"currency": currency})
def get_positions(self, currency: str = "ETH") -> list[dict]:
return self._post("/mcp-deribit/tools/get_positions", {"currency": currency})
def get_open_orders(self, currency: str = "USDC", type: str = "all") -> list[dict]:
"""Ordini APERTI sul conto (limit resting + trigger non scattati). Ogni voce:
{order_id, instrument, direction, order_type, order_state, amount,
filled_amount, price, trigger_price, reduce_only, label}. NB Deribit puo'
omettere i trigger untriggered da type='all' -> per i bracket interrogare
anche type='trigger_all' e fare merge per order_id."""
out = self._post("/mcp-deribit/tools/get_open_orders",
{"currency": currency, "type": type})
return out if isinstance(out, list) else out.get("orders", [])
# --- Trading ---
def place_order(
self,
instrument: str,
side: str,
amount: float,
order_type: str = "market",
price: float | None = None,
leverage: int | None = None,
label: str | None = None,
reduce_only: bool = False,
) -> dict:
"""Piazza un ordine REALE su Deribit. `amount`: per i perp inverse
(BTC/ETH-PERPETUAL) e' in USD notional (step BTC $10, ETH $1); per i lineari
USDC (BTC_USDC/ETH_USDC-PERPETUAL) e' nel base-coin (step 0.0001/0.001).
`reduce_only=True` per chiudere solo la propria quota su uno strumento
condiviso (le posizioni si nettano per conto). Ritorna il `result` grezzo
Deribit: {"order": {...}, "trades": [{price, amount, fee, ...}]} → le fee
REALI sono in trades[]."""
payload: dict[str, Any] = {
"instrument_name": instrument,
"side": side,
"amount": amount,
"type": order_type,
}
if price is not None:
payload["price"] = price
if leverage is not None:
payload["leverage"] = leverage
if label:
payload["label"] = label
if reduce_only:
payload["reduce_only"] = True
return self._post("/mcp-deribit/tools/place_order", payload)
def cancel_order(self, order_id: str) -> dict:
"""Cancella un ordine resting (es. limit reduce-only al TP). Ritorna
{order_id, state}; state='error' se l'ordine non e' piu' open (gia'
fillato/cancellato) — il chiamante riconcilia via get_trade_history."""
return self._post("/mcp-deribit/tools/cancel_order", {"order_id": order_id})
def close_position(self, instrument: str) -> dict:
return self._post("/mcp-deribit/tools/close_position", {"instrument_name": instrument})
def get_trade_history(self, limit: int = 100, instrument_name: str | None = None) -> list[dict]:
"""Trade ESEGUITI sul conto (fonte autorevole delle fee reali). Ogni voce:
{instrument, direction, price, amount, fee, timestamp, order_id}."""
payload: dict[str, Any] = {"limit": limit}
if instrument_name:
payload["instrument_name"] = instrument_name
out = self._post("/mcp-deribit/tools/get_trade_history", payload)
return out if isinstance(out, list) else out.get("trades", [])
def set_stop_loss(self, order_id: str, stop_price: float) -> dict:
return self._post("/mcp-deribit/tools/set_stop_loss", {"order_id": order_id, "stop_price": stop_price})
def set_take_profit(self, order_id: str, tp_price: float) -> dict:
return self._post("/mcp-deribit/tools/set_take_profit", {"order_id": order_id, "tp_price": tp_price})