Phase 0: project skeleton

- 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>
This commit is contained in:
2026-04-26 23:10:30 +02:00
commit 881bc8a1bf
40 changed files with 6018 additions and 0 deletions
+243
View File
@@ -0,0 +1,243 @@
# 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.