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>
11 KiB
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_orderesiste, ma è limitato a un singolo strumento per invocazione (PlaceOrderReq.instrument_name); non è atomico per uno spread a due gambe.client.pyespone l'attributotestnet, letto dal file di credenziali (secrets/deribit.json, campotestnet). Il toolis_testnetriporta 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. directiondi ciascuna gamba esidedel combo sono coerenti con la convenzione Deribit (vedi docs Deribitpublic/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:
public/create_combocon la lista delle gambe; ritornacombo_id(Deribit gestisce idempotenza per pair identico, perciò chiamate ripetute con gli stessi parametri restituiscono lo stesso id).private/buyoprivate/sell(a seconda diside) sul combo appena creato, conamount,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+buye 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,pricemancante contype=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:
- Se la variabile d'ambiente
DERIBIT_TESTNETè valorizzata, la sua conversione booleana (true|false,1|0, case-insensitive) sovrascrive il campotestnetdel file di credenziali. - Altrimenti vale il campo
testnetdel JSON. - 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 daDERIBIT_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, aggiungereenvironment: DERIBIT_TESTNET: "true"al serviziomcp-deribitcon 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 cheis_testnetriportiTrueconsource="env". - Test del default a
Truequando 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.pyespone un metodoplace_combo_order(short, long, side, n_contracts, limit_price)che inoltra direttamente al nuovo tool. Niente più sequenza di dueplace_order. - All'avvio, l'orchestrator (Fase 4) chiama
cerbero-deribit.is_testnete:- 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_versiono in un campo dedicatosystem_state.environmentper renderlo visibile alla GUI.
- blocca il boot se l'ambiente non corrisponde a quanto previsto in
- 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 incore/.
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 sezionicerbero-memoryecerbero-brain-bridge; aggiungere la voceplace_combo_ordernella tabella dicerbero-deribit; aggiornare la matrice di degradation in modo checerbero-deribitresti 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 bloccocerbero-memory ⇆ Cerbero coredal diagramma.docs/05-data-model.md: la tabellainstructionsdiventa il log degli ordini Deribit (combocombo_id,order_id, fill price, fees) anziché il tracking dipush_user_instruction.docs/07-risk-controls.md: kill switch trigger per mismatch testnet/mainnet, da aggiungere alla matrice.strategy.yaml: aggiungere il bloccoexecution.environment: "testnet"conlast_reviewaggiornato.
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_ordercon prezzo aggiornato. - L'integrazione con
cerbero-memoryocerbero-brain-bridge: nessun collegamento, neanche indiretto.
Sequenza di lavoro consigliata
- Aprire un branch
feat/place-combo-ordersuCerberoSuite/Cerbero. - Implementare il metodo
client.place_combo_ordercon relativi test unitari sul client (mock dicreate_combo+buy). - Esporre il tool
place_combo_orderinserver.pycon i guard rail e i test FastAPI. - Aprire un branch separato
feat/deribit-testnet-envper la modifica 2 (più piccola, indipendente dal resto). - Aggiornare
docker-compose.ymlcon la variabile d'ambiente. - Una volta in main, su Cerbero Bite implementare il wrapper
clients/deribit.pycon i metodiplace_combo_orderecancel_combo_order, scrivere i fake corrispondenti intests/fixtures/fakes/deribit.pye i test integration intests/integration/test_deribit_combo.py.