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:
@@ -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})
|
||||
Reference in New Issue
Block a user