881bc8a1bf
- pyproject.toml with uv, deps for runtime + gui + backtest + dev - ruff/mypy strict config, pre-commit hooks for ruff/mypy/pytest - src/cerbero_bite/ layout with empty modules ready for Phase 1+ - structlog JSONL logger with daily rotation - click CLI with placeholder subcommands (status, start, kill-switch, gui, replay, config hash, audit verify) - 6 smoke tests passing, mypy --strict clean, ruff clean Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
244 lines
8.4 KiB
Markdown
244 lines
8.4 KiB
Markdown
# 04 — MCP Integration
|
|
|
|
Tutti i server MCP sono già configurati a livello di `CerberoSuite` (vedi
|
|
`Cerbero_Office/.mcp.json`). Cerbero Bite vi si connette come **client
|
|
MCP** usando l'SDK ufficiale `mcp` per Python.
|
|
|
|
## Configurazione di connessione
|
|
|
|
Cerbero Bite legge `~/.config/cerbero-suite/mcp.json` (o, in dev, da
|
|
`.mcp.json` locale puntato via env var `CERBERO_BITE_MCP_CONFIG`). I
|
|
server vengono risolti **per nome** dichiarato nel file di config.
|
|
|
|
```python
|
|
# clients/_base.py — abstract
|
|
class McpClient:
|
|
name: str # "cerbero-deribit", ecc.
|
|
timeout_s: float = 8.0
|
|
retry_max: int = 3
|
|
retry_base_delay: float = 1.0 # esponenziale
|
|
|
|
async def call(self, tool: str, **params) -> dict: ...
|
|
```
|
|
|
|
Ogni wrapper concreto eredita `McpClient` ed espone metodi tipizzati.
|
|
La logica di retry e timeout è centralizzata.
|
|
|
|
## Server MCP usati
|
|
|
|
### `cerbero-deribit`
|
|
|
|
Tool consumati:
|
|
|
|
| Tool | Uso | Frequenza |
|
|
|---|---|---|
|
|
| `get_index_price(asset="ETH")` | Spot ETH per calcolo strike | Ogni ciclo entry + monitor |
|
|
| `get_dvol()` | Volatilità implicita aggregata ETH | Ogni ciclo entry + monitor |
|
|
| `get_options_chain(asset, expiry_window)` | Lista strumenti per dato DTE | Solo entry |
|
|
| `get_instrument(instrument_name)` | Mid, bid, ask, greche su singolo strumento | Entry + monitor |
|
|
| `get_orderbook(instrument_name, depth=5)` | Profondità per liquidity_gate e slippage | Solo entry |
|
|
| `get_combo_mark(legs)` | Mark price del combo (debito di chiusura) | Solo monitor |
|
|
| `get_account_summary(currency="USDC")` | Equity Deribit, margin libero | Periodico |
|
|
|
|
Wrapper:
|
|
|
|
```python
|
|
# clients/deribit.py
|
|
class DeribitClient(McpClient):
|
|
name = "cerbero-deribit"
|
|
|
|
async def index_price(self, asset: str) -> Decimal: ...
|
|
async def dvol(self) -> Decimal: ...
|
|
async def options_chain(self, asset: str, dte_min: int, dte_max: int) -> list[InstrumentSnapshot]: ...
|
|
async def instrument(self, name: str) -> InstrumentSnapshot: ...
|
|
async def orderbook(self, name: str, depth: int = 5) -> OrderbookSnapshot: ...
|
|
async def combo_mark(self, legs: list[OptionLeg]) -> Decimal: ...
|
|
async def account_summary(self) -> AccountSummary: ...
|
|
```
|
|
|
|
**Note:**
|
|
- Tutti i prezzi sono ricevuti come float dal MCP, convertiti in
|
|
`Decimal` con `quantize` a 6 cifre nel wrapper.
|
|
- Greche convertite con la stessa quantizzazione.
|
|
- Se `mark_iv = 7%` o `300%` o `bid = 0` su orderbook ATM su tutti gli
|
|
strumenti → wrapper solleva `DeribitDataAnomalyError` (probabile
|
|
testnet o feed rotto). Il decision orchestrator cattura, alert,
|
|
skippa il ciclo.
|
|
|
|
### `cerbero-hyperliquid`
|
|
|
|
| Tool | Uso |
|
|
|---|---|
|
|
| `get_perp_funding_rate(asset="ETH")` | Filtro entry §2.6 |
|
|
| `get_perp_summary(asset="ETH")` | Volume 24h, conferma liquidità correlata |
|
|
| `get_account_summary()` | Solo per coerenza, non usato in decision loop |
|
|
|
|
```python
|
|
class HyperliquidClient(McpClient):
|
|
async def funding_rate_annualized(self, asset: str) -> Decimal: ...
|
|
async def perp_summary(self, asset: str) -> PerpSummary: ...
|
|
```
|
|
|
|
### `cerbero-sentiment`
|
|
|
|
| Tool | Uso |
|
|
|---|---|
|
|
| `get_funding_cross_exchange(asset="ETH")` | Bias direzionale §3.1 (mediana 4 maggiori) |
|
|
|
|
Le news qualitative **non sono usate** nel decision loop (no LLM).
|
|
Vengono eventualmente lette da Adriano in occasione del report
|
|
settimanale.
|
|
|
|
```python
|
|
class SentimentClient(McpClient):
|
|
async def funding_cross_median(self, asset: str) -> Decimal: ...
|
|
```
|
|
|
|
### `cerbero-macro`
|
|
|
|
| Tool | Uso |
|
|
|---|---|
|
|
| `get_calendar(days_ahead=18)` | Filtro eventi macro pre-entry |
|
|
|
|
Eventi rilevanti (filtra per `severity = high` e `country in {US, EU}`):
|
|
FOMC, FED minutes, CPI, NFP, ECB, GDP, Powell speech, Lagarde speech.
|
|
|
|
```python
|
|
class MacroClient(McpClient):
|
|
async def upcoming_events(self, days_ahead: int) -> list[MacroEvent]: ...
|
|
async def first_high_severity_within(self, days: int) -> int | None:
|
|
"""Days until first high-severity event, None if none in window."""
|
|
```
|
|
|
|
### `cerbero-portfolio`
|
|
|
|
| Tool | Uso |
|
|
|---|---|
|
|
| `get_holdings()` | Capitale corrente complessivo |
|
|
| `get_holdings_by_asset()` | Filtro entry §2.7 (ETH < 30% portfolio) |
|
|
| `get_correlation()` | Sanity check, non bloccante |
|
|
|
|
```python
|
|
class PortfolioClient(McpClient):
|
|
async def total_equity_usd(self) -> Decimal: ...
|
|
async def asset_pct(self, asset: str) -> Decimal: ...
|
|
```
|
|
|
|
### `cerbero-memory`
|
|
|
|
| Tool | Uso |
|
|
|---|---|
|
|
| `push_user_instruction(payload, source="cerbero-bite")` | Invio istruzione apertura/chiusura a Cerbero core |
|
|
| `get_pending(source="cerbero-bite")` | Verifica ack di Cerbero core |
|
|
|
|
```python
|
|
class MemoryClient(McpClient):
|
|
async def push_instruction(self, instruction: CerberoInstruction) -> str:
|
|
"""Returns instruction_id."""
|
|
async def is_acknowledged(self, instruction_id: str) -> bool: ...
|
|
```
|
|
|
|
**Payload `CerberoInstruction`** (schema condiviso con Cerbero core,
|
|
documentato in `Cerbero/prompt.base v4`):
|
|
|
|
```json
|
|
{
|
|
"source": "cerbero-bite",
|
|
"kind": "open_combo" | "close_combo",
|
|
"exchange": "deribit",
|
|
"asset": "ETH",
|
|
"proposal_id": "uuid-...",
|
|
"legs": [
|
|
{"instrument": "ETH-13MAY26-1900-P", "side": "SELL", "size": 2,
|
|
"limit_price_eth": "0.0048"},
|
|
{"instrument": "ETH-13MAY26-1810-P", "side": "BUY", "size": 2,
|
|
"limit_price_eth": "0.0021"}
|
|
],
|
|
"limit_combo_eth": "0.0027",
|
|
"tif": "GTC",
|
|
"expires_at": "2026-04-27T16:00:00Z",
|
|
"max_slippage_eth": "0.0005",
|
|
"reason": "weekly_open" | "profit_take" | "stop_loss" | "vol_stop" |
|
|
"time_stop" | "delta_breach" | "adverse_move",
|
|
"milestone": "advisory_only" | "approved_by_user"
|
|
}
|
|
```
|
|
|
|
Cerbero core deduplica per `proposal_id` (idempotenza in caso di retry).
|
|
|
|
### `cerbero-telegram`
|
|
|
|
| Tool | Uso |
|
|
|---|---|
|
|
| `send_message(text, parse_mode="MarkdownV2")` | Report pre/post trade, alert |
|
|
| `send_with_buttons(text, buttons)` | Conferma ad Adriano (yes/no) |
|
|
|
|
Le conferme devono ritornare entro 60 minuti (entry) o 30 minuti (exit).
|
|
Implementazione: l'engine si mette in `await` su una coda interna
|
|
alimentata dal callback Telegram via webhook locale.
|
|
|
|
```python
|
|
class TelegramClient(McpClient):
|
|
async def send(self, text: str, parse_mode: str = "MarkdownV2") -> int: ...
|
|
async def request_confirmation(self, text: str, timeout_s: int) -> bool: ...
|
|
```
|
|
|
|
### `cerbero-brain-bridge`
|
|
|
|
| Tool | Uso |
|
|
|---|---|
|
|
| `kb_search(query)` | Lookup pre-trade su pattern simili (consultivo) |
|
|
| `kb_read(path)` | Lettura nota wiki specifica |
|
|
| `kb_write(path, content)` | Salvataggio learning post-trade |
|
|
|
|
**Importante:** il brain-bridge non partecipa al decision loop. Le
|
|
chiamate `kb_search` sono **consultive** e i risultati allegati al
|
|
report di Adriano per contesto, mai consumati come input ai filtri
|
|
deterministici.
|
|
|
|
```python
|
|
class BrainBridgeClient(McpClient):
|
|
async def search(self, query: str, limit: int = 5) -> list[KbHit]: ...
|
|
async def write_note(self, path: str, content: str) -> None: ...
|
|
```
|
|
|
|
### `cerbero-scheduler`
|
|
|
|
**Non usato** dal decision loop. Cerbero Bite ha il proprio scheduler
|
|
APScheduler interno. Il MCP scheduler resta a disposizione del core
|
|
Cerbero per altre routine.
|
|
|
|
## Errori e degradation
|
|
|
|
| Server down | Comportamento |
|
|
|---|---|
|
|
| `cerbero-deribit` | Skip ciclo entry; per monitor → alert e marca posizione come `unknown_state` (non chiude alla cieca) |
|
|
| `cerbero-hyperliquid` | Skip filtro funding §2.6 con warning; entry può proseguire se altre condizioni soddisfatte |
|
|
| `cerbero-sentiment` | Bias §3.1 cade in `no_entry` per default (no funding cross → niente direzione) |
|
|
| `cerbero-macro` | **Hard fail**: senza calendar non si apre. È un filtro irrinunciabile |
|
|
| `cerbero-portfolio` | Skip filtro §2.7 con warning; sizing usa ultimo capitale noto da SQLite con warning |
|
|
| `cerbero-memory` | Hard fail per esecuzione: senza push_user_instruction non si può aprire/chiudere |
|
|
| `cerbero-telegram` | Skip ciclo: senza canale di conferma niente proposta |
|
|
| `cerbero-brain-bridge` | Skip lookup, log warning. Mai bloccante |
|
|
|
|
Ogni "hard fail" → alert sonoro su Telegram via canale di backup
|
|
(BotPapà), kill switch armato fino al ripristino.
|
|
|
|
## Versioning
|
|
|
|
Cerbero Bite verifica all'avvio la versione di ciascun MCP via
|
|
`get_version()` (tool standard). Schema di versioning attesa:
|
|
|
|
```python
|
|
EXPECTED_MCP_VERSIONS = {
|
|
"cerbero-deribit": "^2.0.0",
|
|
"cerbero-hyperliquid": "^1.5.0",
|
|
"cerbero-memory": "^4.0.0",
|
|
"cerbero-portfolio": "^1.2.0",
|
|
...
|
|
}
|
|
```
|
|
|
|
Mismatch → kill switch e alert manuale. Mai partire con MCP a versione
|
|
incompatibile.
|