Files
Cerbero-Bite/docs/04-mcp-integration.md
T
Adriano abf5a140e2 refactor: telegram + portfolio in-process (drop shared MCP)
Each bot now manages its own notification + portfolio aggregation:

* TelegramClient calls the public Bot API directly via httpx, reading
  CERBERO_BITE_TELEGRAM_BOT_TOKEN / CERBERO_BITE_TELEGRAM_CHAT_ID from
  env. No credentials → silent disabled mode.
* PortfolioClient composes DeribitClient + HyperliquidClient + the new
  MacroClient.get_asset_price/eur_usd_rate to expose equity (EUR) and
  per-asset exposure as the bot's own slice (no cross-bot view).
* mcp-telegram and mcp-portfolio removed from MCP_SERVICES / McpEndpoints
  and the cerbero-bite ping CLI; health_check no longer probes portfolio.

Docs (02/04/06/07) and docker-compose updated to reflect the new
architecture.

353/353 tests pass; ruff clean; mypy src clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-30 00:31:20 +02:00

193 lines
9.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 04 — MCP Integration
Cerbero Bite consuma quattro servizi MCP HTTP della suite (`Cerbero_mcp`):
`cerbero-deribit`, `cerbero-hyperliquid`, `cerbero-macro`,
`cerbero-sentiment`. Non utilizza l'SDK Python `mcp`: ogni server
espone gli endpoint REST `POST <base_url>/tools/<tool_name>` con
autenticazione Bearer, e Cerbero Bite vi si collega tramite
`httpx.AsyncClient` long-lived (`clients/_base.py`).
Telegram e Portfolio, in passato esposti come servizi MCP condivisi,
sono stati rimossi dal layer MCP e gestiti **in-process** da ogni bot
della suite: il client Telegram chiama direttamente la Bot API
pubblica e l'aggregatore di portafoglio compone equity ed esposizioni
dai client di scambio (Deribit + Hyperliquid) convertendo in EUR
attraverso `cerbero-macro.get_asset_price("EURUSD")`.
## Configurazione di connessione
Le URL sono risolte da `cerbero_bite.config.mcp_endpoints.load_endpoints`,
con default che corrispondono al DNS della rete Docker
`cerbero-suite` (`http://mcp-deribit:9011`, `http://mcp-macro:9013`,
ecc.). Ogni servizio può essere sovrascritto da una variabile
d'ambiente dedicata, utile in sviluppo:
| Servizio | Variabile d'ambiente | Default Docker DNS |
|---|---|---|
| Deribit | `CERBERO_BITE_MCP_DERIBIT_URL` | `http://mcp-deribit:9011` |
| Hyperliquid | `CERBERO_BITE_MCP_HYPERLIQUID_URL` | `http://mcp-hyperliquid:9012` |
| Macro | `CERBERO_BITE_MCP_MACRO_URL` | `http://mcp-macro:9013` |
| Sentiment | `CERBERO_BITE_MCP_SENTIMENT_URL` | `http://mcp-sentiment:9014` |
Telegram (notify-only) viene configurato direttamente via due
variabili d'ambiente, lette al boot dal client in-process:
| Variabile | Uso |
|---|---|
| `CERBERO_BITE_TELEGRAM_BOT_TOKEN` | Token del bot fornito da BotFather |
| `CERBERO_BITE_TELEGRAM_CHAT_ID` | Identificativo della chat o del gruppo destinatario |
Quando una delle due manca, il client Telegram entra in modalità
**disabled** e ogni `notify_*` diventa un no-op a livello di DEBUG.
Il bearer token per le chiamate è il token con capability `core` letto
da `secrets/core.token` (path configurabile via
`CERBERO_BITE_CORE_TOKEN_FILE`, default `/run/secrets/core_token` nel
container). Non è loggato.
```python
# clients/_base.py — sintesi
class HttpToolClient:
service: str # "deribit", "macro", ...
base_url: str # "http://mcp-deribit:9011"
token: str # bearer
timeout_s: float = 8.0
retry_max: int = 3 # esponenziale 1s/5s/30s
client: httpx.AsyncClient | None # condiviso dal RuntimeContext
async def call(self, tool: str, body: dict | None = None) -> Any: ...
```
Ogni wrapper concreto compone un `HttpToolClient` e ritorna i record
Pydantic consumati direttamente dagli algoritmi `core/`.
## Server MCP usati
### `cerbero-deribit`
Sorgente di tutti i dati di mercato sulle opzioni e canale di
esecuzione: Cerbero Bite invia gli ordini combo direttamente al broker
attraverso questo MCP, senza intermediazioni.
| Tool | Uso | Frequenza |
|---|---|---|
| `environment_info` | Verifica al boot: testnet/mainnet, base_url, max_leverage | Boot + ogni ciclo health |
| `get_ticker(instrument_name)` | Spot proxy via `ETH-PERPETUAL.mark_price`, mid/bid/ask + greche per le leg | Ogni ciclo entry + monitor |
| `get_ticker_batch(instrument_names)` | Quotes in batch per la chain candidata (max 20) | Solo entry |
| `get_dvol(currency="ETH", start_date, end_date)` | Latest DVOL per filtro §2.3 e bias §3.1 | Ogni ciclo entry + monitor |
| `get_instruments(currency, kind="option", expiry_from, expiry_to, min_open_interest)` | Lista strike per il DTE window | Solo entry |
| `get_orderbook(instrument_name, depth=3)` | `book_depth_top3` per liquidity gate | Solo entry |
| `get_historical(instrument, start_date, end_date, resolution)` | Spot 30g fa per bias direzionale + bootstrap return_4h | Entry + monitor (fallback) |
| `get_technical_indicators(instrument, indicators=["adx"], ...)` | ADX(14) per il filtro Iron Condor §3.1 | Solo entry |
| `get_account_summary(currency="USDC")` | Equity Deribit, margin libero (informativo) | Boot + monitor |
| `get_positions(currency="USDC")` | Riconciliazione stato dopo crash | Boot |
| `place_combo_order(legs, side, amount, type, price, label)` | **Esecuzione**: combo atomico via `private/create_combo` + `private/buy/sell` sul combo creato | Entry + monitor (close) |
| `cancel_order(order_id)` | Repricing e annullamenti | Solo monitor |
Note operative:
- Tutti i prezzi e le greche sono restituiti come `float` dal server e
convertiti in `Decimal` ad alta precisione nel wrapper, mai usati
come `float` nel motore decisionale.
- Se la chain risponde con `mark_iv` palesemente fuori range
(es. 7% o 300%) o tutti i `bid == 0` la chiamata viene segnalata come
`McpDataAnomalyError`; l'orchestrator emette un alert e salta il
ciclo.
- L'invio di `place_combo_order` è atomico: la creazione del combo e
l'ordine eseguito sul combo viaggiano in sequenza ma all'interno di
un'unica chiamata MCP, senza esposizione a leg risk.
### `cerbero-hyperliquid`
| Tool | Uso |
|---|---|
| `get_funding_rate(instrument="ETH")` | Funding rate ETH-PERP (annualizzato × 8760) per il filtro entry §2.6 |
### `cerbero-sentiment`
| Tool | Uso |
|---|---|
| `get_cross_exchange_funding(assets=["ETH"])` | Mediana funding annualizzato (Binance/Bybit/OKX 1095, Hyperliquid 8760) per bias direzionale §3.1 |
Le news qualitative non vengono consumate dal decision loop:
Cerbero Bite è deterministico e non interpreta testi liberi.
### `cerbero-macro`
| Tool | Uso |
|---|---|
| `get_macro_calendar(days, country_filter, importance_min)` | Filtro entry §2.5: zero eventi `high` in `country_filter` (default `["US","EU"]`) entro la finestra DTE |
| `get_asset_price(ticker="EURUSD")` | Tasso di cambio EUR/USD usato dall'aggregatore di portafoglio per convertire l'equity USD degli scambi in EUR |
## Componenti in-process
### Portfolio aggregator (`clients/portfolio.py`)
Il client `PortfolioClient` non chiama più un servizio MCP dedicato;
compone i dati dei due exchange usati dal bot e applica il cambio
EUR/USD letto da `cerbero-macro`.
| Metodo | Comportamento |
|---|---|
| `total_equity_eur()` | Somma `equity` USD di Deribit (USDC) e Hyperliquid, divide per `EURUSD` per ottenere il capitale in EUR consumato dal sizing engine |
| `asset_pct_of_portfolio(ticker)` | Somma il notional USD assoluto delle posizioni aperte su entrambi gli scambi il cui `instrument`/`coin` contiene `ticker`, e lo divide per l'equity totale USD. Usato dal filtro §2.7 (`eth_holdings_pct_max`) |
**Nota di scope**: la vista è la *slice* del singolo bot. Holdings su
exchange esterni, in cold storage, o gestiti da altri bot della suite
non vengono contati. Il filtro §2.7 va quindi inteso come cap
per-bot, non come cap suite-wide.
### Telegram client (`clients/telegram.py`)
Cerbero Bite usa Telegram in modalità **notify-only**: nessuna
conferma manuale, nessun callback. L'engine apre e chiude le
posizioni automaticamente quando le regole sono soddisfatte; il
client invia il messaggio al `chat_id` configurato chiamando
direttamente `https://api.telegram.org/bot<TOKEN>/sendMessage`.
| Metodo | Uso |
|---|---|
| `notify(message, priority, tag)` | Alert MEDIUM o messaggi informativi |
| `notify_position_opened(instrument, side, size, strategy, greeks, expected_pnl)` | Notifica di entry placed |
| `notify_position_closed(instrument, realized_pnl, reason)` | Notifica di exit filled |
| `notify_alert(source, message, priority)` | Alert HIGH (kill switch) |
| `notify_system_error(message, component, priority)` | Alert CRITICAL |
Quando le credenziali env non sono configurate, il client è in
modalità disabled e ogni invio diventa un no-op silente: il ciclo
decisionale non viene bloccato.
## Errori e degradation
| Componente fuori uso | Comportamento |
|---|---|
| `cerbero-deribit` | **Hard fail**: senza dati di mercato e canale di esecuzione il ciclo viene saltato; in monitor le posizioni esistenti restano nello stato corrente, alert HIGH e kill switch |
| `cerbero-hyperliquid` | Skip del filtro funding §2.6 con warning; il ciclo prosegue se le altre condizioni sono soddisfatte |
| `cerbero-sentiment` | Bias §3.1 cade su `no_entry` per default (senza funding cross il bias non può fissare la direzione) |
| `cerbero-macro` | Hard fail per il filtro §2.5 e per la conversione EUR/USD del portfolio aggregator; senza calendar/FX non si apre |
| Portfolio aggregator (deribit o hyperliquid down) | I metodi di `PortfolioClient` propagano l'eccezione dell'exchange sottostante; il sizing engine si comporta come per un guasto MCP del livello inferiore |
| Telegram client | Errore HTTP o `ok=false` dalla Bot API → `TelegramError` propagata dal chiamante. In modalità disabled (env mancanti) tutti i `notify_*` sono no-op silenti e il ciclo decisionale prosegue |
I trigger HIGH e CRITICAL armano il kill switch e propagano un alert
in audit chain.
## Verifica ambiente al boot
All'avvio l'orchestrator (`runtime/orchestrator.boot`) chiama
`cerbero-deribit.environment_info` e confronta il campo `environment`
con `strategy.execution.environment`. Un `mismatch` (per esempio engine
configurato per `testnet` ma server agganciato a `mainnet`) produce un
alert CRITICAL e arma il kill switch prima che qualsiasi ciclo
trading parta. La stessa verifica viene ripetuta dal probe periodico
(ogni 5 minuti) di `runtime/health_check.HealthCheck`.
## Versioning
I server MCP non espongono attualmente un endpoint `get_version()`
formale; il check di compatibilità si limita a `environment_info` per
Deribit e a un round-trip lightweight sui tool read-only degli altri
servizi nel job di health check. Quando i server pubblicheranno il
versionamento esplicito, l'orchestrator confronterà al boot le
versioni con la tabella `EXPECTED_MCP_VERSIONS` e armerà il kill
switch su mismatch.