Files
Cerbero-Bite/docs/12-mcp-deribit-changes.md
T
Adriano 466e63dc19 Phase 3: MCP HTTP clients + Dockerization
Wrapper async tipizzati sui sei servizi MCP HTTP che Cerbero Bite
consuma in autonomia. 277 test pass, copertura clients 93%, mypy
strict pulito, ruff clean.

Base layer:
- clients/_base.py: HttpToolClient con httpx + tenacity (retry
  esponenziale 3x, timeout 8s, mapping HTTP→eccezioni tipizzate).
- clients/_exceptions.py: McpAuthError, McpServerError, McpToolError,
  McpDataAnomalyError, McpNotFoundError, McpTimeoutError.
- config/mcp_endpoints.py: risoluzione URL via Docker DNS
  (mcp-deribit:9011, ...) con override per servizio via env var;
  caricamento bearer token da secrets/core.token o
  CERBERO_BITE_CORE_TOKEN_FILE.

Wrapper:
- clients/macro.py: next_high_severity_within() per filtro entry §2.5.
- clients/sentiment.py: funding_cross_median_annualized() con
  annualizzazione per period nativo per exchange (Binance/Bybit/OKX
  1095, Hyperliquid 8760).
- clients/hyperliquid.py: funding_rate_annualized() per filtro §2.6.
- clients/portfolio.py: total_equity_eur(), asset_pct_of_portfolio()
  per sizing engine + filtro §2.7.
- clients/telegram.py: notify-only (no callback queue, no
  conferme — Bite auto-execute).
- clients/deribit.py: environment_info, index_price_eth,
  latest_dvol, options_chain, get_tickers, orderbook_depth_top3,
  get_account_summary, get_positions, place_combo_order (combo
  atomico), cancel_order.

CLI:
- cerbero-bite ping: health-check parallelo di tutti gli MCP con
  tabella rich (OK/FAIL/SKIPPED).

Docker:
- Dockerfile multi-stage Python 3.13 + uv, user non-root.
- docker-compose.yml con rete external "cerbero-suite", secret
  core_token montato a /run/secrets/core_token, env per ogni MCP.
- secrets/README.md documenta il setup del token.

Documentazione di intervento:
- docs/12-mcp-deribit-changes.md: spec delle modifiche apportate
  al server mcp-deribit (place_combo_order + override testnet via
  DERIBIT_TESTNET).

Dipendenze:
- aggiunto pytest-httpx per i test HTTP.
- rimosso mcp>=1.0 (non usiamo l'SDK MCP, parliamo via HTTP REST).

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

11 KiB
Raw Blame History

12 — Modifiche da apportare a mcp-deribit

Premessa

Cerbero Bite, a partire dalla Fase 3, opera in modo completamente autonomo: nessuna conferma interattiva da parte di Adriano nel primo periodo, niente bottoni Telegram, niente coda di approvazione manuale. Quando il rule engine valuta che le condizioni di entrata e di uscita sono soddisfatte, l'engine deve poter inviare l'ordine senza intermediazione umana. Il primo periodo di esercizio sarà condotto su ambiente testnet Deribit, in modo da raccogliere dati di slippage e fill reali senza esporre capitale.

Per realizzare questo flusso sono necessarie due modifiche al servizio mcp-deribit ospitato in CerberoSuite/Cerbero/services/mcp-deribit. Entrambe le modifiche sono additive e non rompono i tool esistenti.

Lo stato corrente del servizio è il seguente:

  • place_order esiste, ma è limitato a un singolo strumento per invocazione (PlaceOrderReq.instrument_name); non è atomico per uno spread a due gambe.
  • client.py espone l'attributo testnet, letto dal file di credenziali (secrets/deribit.json, campo testnet). Il tool is_testnet riporta correttamente lo stato. Non esiste oggi un modo di sovrascrivere quella scelta a livello di environment del container.

Modifica 1 — Nuovo tool place_combo_order

Obiettivo

Inviare a Deribit un singolo ordine combo a due o più gambe (legs) in modo atomico, evitando il rischio di leg risk tipico delle credit spread (la prima gamba si riempie e la seconda no, oppure si riempie a un prezzo che invalida il rapporto credito/larghezza).

Comportamento attesto

L'endpoint riceve la lista di legs, costruisce o riusa lo strumento combo corrispondente sul lato Deribit (API public/create_combo), quindi invia l'ordine di acquisto netto sul combo (private/buy con tipo limit o market). Il prezzo limite è espresso come prezzo netto del combo (in ETH per le opzioni inverse Deribit), cioè la somma algebrica firmata dei mid-price delle gambe.

L'ordine è soggetto agli stessi guard rail di place_order: verifica core capability, enforce_single_notional, enforce_aggregate, enforce_leverage. Per le opzioni il notional è calcolato come width × n_contracts × spot_index (perdita massima del combo).

Schema della richiesta

class PlaceComboOrderReq(BaseModel):
    legs: list[ComboLegReq]               # 2..4 gambe
    side: Literal["buy", "sell"]          # "sell" = incassa credito
    amount: float                         # numero di contratti combo
    type: Literal["limit", "market"] = "limit"
    price: float | None = None            # prezzo netto in ETH; richiesto se type=limit
    label: str | None = None              # propagato a Deribit per riconciliazione
    time_in_force: Literal["good_til_cancelled", "good_til_day", "fill_or_kill", "immediate_or_cancel"] = "good_til_cancelled"
    post_only: bool = False
    reduce_only: bool = False             # vero per combo di chiusura

class ComboLegReq(BaseModel):
    instrument_name: str                  # ETH-15MAY26-2475-P
    direction: Literal["buy", "sell"]     # direzione della singola gamba
    ratio: int = 1                        # multiplo della size combo (Deribit usa direction/ratio)

Vincoli di validazione:

  • len(legs) compreso fra 2 e 4 (esclusi naked e ratio sbilanciati).
  • Tutte le gambe hanno la stessa scadenza (expiry). Il payload deve rifiutare combo a scadenze miste.
  • Se type == "limit", price è obbligatorio.
  • direction di ciascuna gamba e side del combo sono coerenti con la convenzione Deribit (vedi docs Deribit public/create_combo).

Schema della risposta

class PlaceComboOrderResp(BaseModel):
    combo_id: str                         # ETH-15MAY26-2475P_2350P
    order_id: str                         # ID dell'ordine sul combo
    state: str                            # open, filled, rejected, ecc.
    average_price: float | None           # in ETH
    filled_amount: float
    legs: list[ComboLegFill]              # per gamba: instrument, direction, fill price, fees
    raw: dict                             # response Deribit completa per audit

Endpoint

@app.post("/tools/place_combo_order", tags=["writes"])
async def t_place_combo_order(
    body: PlaceComboOrderReq,
    principal: Principal = Depends(require_principal),
):
    _check(principal, core=True)
    return await client.place_combo_order(...)

Estensioni del client (mcp_deribit/client.py)

Aggiungere un metodo place_combo_order che incapsula due chiamate Deribit:

  1. public/create_combo con la lista delle gambe; ritorna combo_id (Deribit gestisce idempotenza per pair identico, perciò chiamate ripetute con gli stessi parametri restituiscono lo stesso id).
  2. private/buy o private/sell (a seconda di side) sul combo appena creato, con amount, price, type, time_in_force, post_only, reduce_only, label.

In aggiunta: cancel_combo_order(order_id) che oggi può semplicemente delegare a cancel_order (i combo sono cancellabili come ordini normali); è opportuno tracciarlo come metodo distinto per rendere il log dell'audit più leggibile.

Tag MCP

Aggiungere alla lista tools di mount_mcp_endpoint la voce place_combo_order con descrizione "Invia un combo order multi-leg atomico (Bull Put, Bear Call, Iron Condor)".

Test

  • Mock del client che simula create_combo + buy e verifica che il body propagato a Deribit rispetti la convenzione direction/ratio.
  • Test 403 quando il principal non ha capability core.
  • Test di rifiuto per legs a scadenze miste, len(legs) < 2, len(legs) > 4, price mancante con type=limit.

Modifica 2 — Override testnet via environment variable

Obiettivo

Permettere di forzare l'ambiente di esecuzione (testnet o mainnet) senza dover riscrivere secrets/deribit.json. Questa flessibilità è essenziale per Cerbero Bite, che gira in un container Docker dedicato e deve poter passare da paper trading a soft launch modificando solo una variabile d'ambiente.

Comportamento attesto

In __main__.py, dopo la lettura del file di credenziali, applicare la seguente precedenza di risoluzione:

  1. Se la variabile d'ambiente DERIBIT_TESTNET è valorizzata, la sua conversione booleana (true|false, 1|0, case-insensitive) sovrascrive il campo testnet del file di credenziali.
  2. Altrimenti vale il campo testnet del JSON.
  3. Se nessuno dei due è presente, default True (precauzione: meglio testnet che mainnet per errore).

Variazione al main

def _resolve_testnet(creds: dict) -> bool:
    env = os.environ.get("DERIBIT_TESTNET")
    if env is not None:
        return env.strip().lower() in {"1", "true", "yes", "on"}
    return bool(creds.get("testnet", True))


client = DeribitClient(
    client_id=creds["client_id"],
    client_secret=creds["client_secret"],
    testnet=_resolve_testnet(creds),
)

Estensione del tool is_testnet

Il tool oggi ritorna {"testnet": bool, "base_url": str}. Aggiungere:

  • source: "env" o "credentials" o "default" per indicare l'origine della scelta.
  • env_value: valore grezzo letto da DERIBIT_TESTNET (utile per diagnosticare typo nel docker-compose).

In questo modo Cerbero Bite, all'avvio, può chiamare is_testnet, loggare il risultato e, se in disaccordo con la propria configurazione attesa (strategy.yaml o equivalente), armare il kill switch prima che parta qualsiasi ciclo di entry.

Documentazione e compose

  • In docker-compose.yml, aggiungere environment: DERIBIT_TESTNET: "true" al servizio mcp-deribit con commento "override secrets/ deribit.json testnet flag".
  • In services/mcp-deribit/README.md (se esiste, altrimenti creare), documentare la precedenza e le tre forme accettate di valore booleano.

Test

  • Test che monta sia il file di credenziali (con testnet:false) sia l'env var (DERIBIT_TESTNET=true) e verifica che is_testnet riporti True con source="env".
  • Test del default a True quando entrambe le sorgenti mancano (file con campo assente, env non settata).

Impatto su Cerbero Bite

Le due modifiche abilitano i seguenti flussi nella Fase 3 di Cerbero Bite:

  • Il wrapper clients/deribit.py espone un metodo place_combo_order(short, long, side, n_contracts, limit_price) che inoltra direttamente al nuovo tool. Niente più sequenza di due place_order.
  • All'avvio, l'orchestrator (Fase 4) chiama cerbero-deribit.is_testnet e:
    • blocca il boot se l'ambiente non corrisponde a quanto previsto in strategy.yaml (campo nuovo proposto: execution.environment: "testnet" | "mainnet");
    • scrive l'esito in system_state.config_version o in un campo dedicato system_state.environment per renderlo visibile alla GUI.
  • Il documento di flusso operativo (docs/06-operational-flow.md) viene aggiornato per rimuovere la fase di conferma utente e introdurre l'auto-execute condizionato alle regole già codificate in core/.

Modifiche correlate ai documenti di Cerbero Bite

Una volta integrate le modifiche al server mcp-deribit, occorrerà allineare i seguenti documenti del progetto Cerbero Bite (sono modifiche editoriali, nessuna implicazione architetturale ulteriore):

  • docs/04-mcp-integration.md: rimuovere le sezioni cerbero-memory e cerbero-brain-bridge; aggiungere la voce place_combo_order nella tabella di cerbero-deribit; aggiornare la matrice di degradation in modo che cerbero-deribit resti l'unico hard-fail di esecuzione.
  • docs/06-operational-flow.md: sostituire il flusso di conferma Telegram con un flusso di sola notifica e auto-execute.
  • docs/02-architecture.md: rimuovere il blocco cerbero-memory ⇆ Cerbero core dal diagramma.
  • docs/05-data-model.md: la tabella instructions diventa il log degli ordini Deribit (combo combo_id, order_id, fill price, fees) anziché il tracking di push_user_instruction.
  • docs/07-risk-controls.md: kill switch trigger per mismatch testnet/mainnet, da aggiungere alla matrice.
  • strategy.yaml: aggiungere il blocco execution.environment: "testnet" con last_review aggiornato.

Out of scope di questo documento

Non sono oggetto di modifica al server mcp-deribit:

  • La gestione di ordini combo a più di quattro gambe (i prodotti Cerbero Bite restano due o quattro gambe per Bull Put, Bear Call, Iron Condor).
  • L'aggiunta di logica di repricing (incremento di un tick verso ask combinato): è responsabilità di Cerbero Bite via cancel_combo_order + place_combo_order con prezzo aggiornato.
  • L'integrazione con cerbero-memory o cerbero-brain-bridge: nessun collegamento, neanche indiretto.

Sequenza di lavoro consigliata

  1. Aprire un branch feat/place-combo-order su CerberoSuite/Cerbero.
  2. Implementare il metodo client.place_combo_order con relativi test unitari sul client (mock di create_combo + buy).
  3. Esporre il tool place_combo_order in server.py con i guard rail e i test FastAPI.
  4. Aprire un branch separato feat/deribit-testnet-env per la modifica 2 (più piccola, indipendente dal resto).
  5. Aggiornare docker-compose.yml con la variabile d'ambiente.
  6. Una volta in main, su Cerbero Bite implementare il wrapper clients/deribit.py con i metodi place_combo_order e cancel_combo_order, scrivere i fake corrispondenti in tests/fixtures/fakes/deribit.py e i test integration in tests/integration/test_deribit_combo.py.