docs(V2): README riscritto per architettura V2.0.0
- Singola immagine Docker, routing testnet/mainnet via bearer token - Configurazione interamente in .env - Swagger /apidocs - Sezione migrazione V1 → V2 con tabella mapping campi - Riferimento alla spec di design Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,153 +1,215 @@
|
|||||||
# Cerbero_mcp
|
# Cerbero MCP — V2.0.0
|
||||||
|
|
||||||
Server MCP riusabili (exchange + market data) per la suite Cerbero.
|
Server MCP unificato multi-exchange per la suite Cerbero. Distribuito come
|
||||||
Spinta da `Cerbero/` (commit `pre-split-2026-04-27`) come parte dello
|
singola immagine Docker; testnet e mainnet sono raggiungibili
|
||||||
split documentato in `docs/superpowers/specs/2026-04-27-split-mcp-core-design.md`
|
contemporaneamente attraverso un meccanismo di routing per-request basato
|
||||||
(nel repo storico).
|
sul token bearer fornito dal client.
|
||||||
|
|
||||||
## Servizi
|
## Caratteristiche
|
||||||
- `mcp-alpaca`, `mcp-bybit`, `mcp-deribit`, `mcp-hyperliquid` — exchange
|
|
||||||
con `place_order`, `environment_info`, leverage cap server-side
|
|
||||||
- `mcp-deribit` e `mcp-bybit` espongono inoltre `place_combo_order`:
|
|
||||||
- Deribit: `private/create_combo` + ordine sul combo → 1 sola crociata
|
|
||||||
di spread invece di N (slippage atteso ridotto su strutture liquide).
|
|
||||||
- Bybit: `place_batch_order` su `category=option` → multi-leg atomico
|
|
||||||
in un solo round-trip API (no sconto fee, solo atomicità + latenza).
|
|
||||||
- `mcp-macro`, `mcp-sentiment` — read-only market data
|
|
||||||
|
|
||||||
## Indicatori quantitativi disponibili
|
- **Una singola immagine Docker** (`cerbero-mcp`) ospita tutti i router
|
||||||
|
exchange in un unico processo FastAPI
|
||||||
|
- **Quattro exchange** (Deribit, Bybit, Hyperliquid, Alpaca) e **due data
|
||||||
|
provider** read-only (Macro, Sentiment)
|
||||||
|
- **Switch testnet/mainnet per-request** tramite header
|
||||||
|
`Authorization: Bearer <TOKEN>`: lo stesso container serve entrambi gli
|
||||||
|
ambienti senza riavvii
|
||||||
|
- **Configurazione interamente in `.env`**: nessun file JSON di credenziali
|
||||||
|
separato
|
||||||
|
- **Documentazione interattiva** OpenAPI/Swagger esposta a `/apidocs`
|
||||||
|
|
||||||
### Common (`mcp_common.indicators` + `options` + `microstructure` + `stats`)
|
## Avvio rapido (sviluppo, senza Docker)
|
||||||
- Tecnici: `sma`, `rsi`, `macd`, `atr`, `adx`
|
|
||||||
- Volatilità: `vol_cone` (RV multi-window con percentili), `garch11_forecast`
|
|
||||||
- Statistici: `hurst_exponent`, `half_life_mean_reversion`, `autocorrelation`,
|
|
||||||
`cointegration_test` (Engle-Granger)
|
|
||||||
- Risk: `rolling_sharpe` (Sharpe + Sortino), `var_cvar` (historical VaR/ES)
|
|
||||||
- Microstructure: `orderbook_imbalance` (ratio + microprice + slope)
|
|
||||||
- Options: `oi_weighted_skew`, `smile_asymmetry`, `atm_vs_wings_vol`,
|
|
||||||
`dealer_gamma_profile`, `vanna_charm_aggregate`
|
|
||||||
|
|
||||||
### Deribit (esposti come tool MCP)
|
1. Copiare il template di configurazione e compilarlo:
|
||||||
|
```bash
|
||||||
|
cp .env.example .env
|
||||||
|
# editare .env con le proprie credenziali e i due token
|
||||||
|
```
|
||||||
|
2. Generare i token bearer:
|
||||||
|
```bash
|
||||||
|
python -c 'import secrets; print("TESTNET_TOKEN=" + secrets.token_urlsafe(32))'
|
||||||
|
python -c 'import secrets; print("MAINNET_TOKEN=" + secrets.token_urlsafe(32))'
|
||||||
|
```
|
||||||
|
3. Installare le dipendenze e avviare:
|
||||||
|
```bash
|
||||||
|
uv sync
|
||||||
|
uv run cerbero-mcp
|
||||||
|
```
|
||||||
|
4. Aprire la documentazione interattiva: <http://localhost:9000/apidocs>
|
||||||
|
|
||||||
|
## Avvio con Docker
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cp .env.example .env # compilare valori
|
||||||
|
docker compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
Il container espone la porta indicata da `PORT` in `.env` (default 9000).
|
||||||
|
|
||||||
|
## Token bearer e ambienti
|
||||||
|
|
||||||
|
| Token usato | Ambiente upstream |
|
||||||
|
|---|---|
|
||||||
|
| `Authorization: Bearer $TESTNET_TOKEN` | URL testnet di ciascun exchange |
|
||||||
|
| `Authorization: Bearer $MAINNET_TOKEN` | URL mainnet (live) |
|
||||||
|
| Nessun token / token sconosciuto | 401 Unauthorized |
|
||||||
|
|
||||||
|
Le tool puramente read-only (`/mcp-macro/*` e `/mcp-sentiment/*`)
|
||||||
|
richiedono comunque un bearer valido, ma il valore (testnet o mainnet) è
|
||||||
|
indifferente perché non hanno endpoint testnet.
|
||||||
|
|
||||||
|
## Endpoint principali
|
||||||
|
|
||||||
|
| Path | Descrizione |
|
||||||
|
|---|---|
|
||||||
|
| `GET /health` | Healthcheck (no auth) |
|
||||||
|
| `GET /apidocs` | Swagger UI (no auth) |
|
||||||
|
| `GET /openapi.json` | Schema OpenAPI 3.1 (no auth) |
|
||||||
|
| `POST /mcp-deribit/tools/{tool}` | Tool exchange Deribit |
|
||||||
|
| `POST /mcp-bybit/tools/{tool}` | Tool exchange Bybit |
|
||||||
|
| `POST /mcp-hyperliquid/tools/{tool}` | Tool exchange Hyperliquid |
|
||||||
|
| `POST /mcp-alpaca/tools/{tool}` | Tool exchange Alpaca |
|
||||||
|
| `POST /mcp-macro/tools/{tool}` | Tool macro/market data |
|
||||||
|
| `POST /mcp-sentiment/tools/{tool}` | Tool sentiment/news |
|
||||||
|
|
||||||
|
## Tool disponibili
|
||||||
|
|
||||||
|
### Common (`cerbero_mcp.common.indicators` + `options` + `microstructure` + `stats`)
|
||||||
|
Tecnici (`sma`, `rsi`, `macd`, `atr`, `adx`), volatilità (`vol_cone`,
|
||||||
|
`garch11_forecast`), statistici (`hurst_exponent`,
|
||||||
|
`half_life_mean_reversion`, `cointegration_test`), risk (`rolling_sharpe`,
|
||||||
|
`var_cvar`), microstructure (`orderbook_imbalance`), options
|
||||||
|
(`oi_weighted_skew`, `smile_asymmetry`, `dealer_gamma_profile`,
|
||||||
|
`vanna_charm_aggregate`).
|
||||||
|
|
||||||
|
### Deribit
|
||||||
DVOL, GEX, P/C ratio, skew_25d, term_structure, iv_rank, realized_vol,
|
DVOL, GEX, P/C ratio, skew_25d, term_structure, iv_rank, realized_vol,
|
||||||
indicatori tecnici, find_by_delta, calculate_spread_payoff.
|
indicatori tecnici, find_by_delta, calculate_spread_payoff,
|
||||||
**Nuovi**: `get_dealer_gamma_profile`, `get_vanna_charm`,
|
get_dealer_gamma_profile, get_vanna_charm, get_oi_weighted_skew,
|
||||||
`get_oi_weighted_skew`, `get_smile_asymmetry`, `get_atm_vs_wings_vol`,
|
get_smile_asymmetry, get_atm_vs_wings_vol, get_orderbook_imbalance,
|
||||||
`get_orderbook_imbalance`.
|
place_combo_order.
|
||||||
|
|
||||||
### Bybit
|
### Bybit
|
||||||
Ticker, orderbook, OHLCV, funding rate (current+history), open interest,
|
Ticker, orderbook, OHLCV, funding rate, open interest, basis spot/perp,
|
||||||
basis spot/perp, indicatori tecnici. **Nuovi**: `get_orderbook_imbalance`,
|
indicatori tecnici, place_batch_order, get_orderbook_imbalance,
|
||||||
`get_basis_term_structure`.
|
get_basis_term_structure.
|
||||||
|
|
||||||
|
### Hyperliquid
|
||||||
|
Account summary, positions, orderbook, historical, indicators, funding
|
||||||
|
rate, basis spot/perp, place_order, set_stop_loss, set_take_profit.
|
||||||
|
|
||||||
|
### Alpaca
|
||||||
|
Account, positions, bars, snapshot, option chain, place_order,
|
||||||
|
amend_order, cancel_order, close_position.
|
||||||
|
|
||||||
### Macro
|
### Macro
|
||||||
Treasury yields, FRED indicators, equity futures, asset prices, calendar.
|
Treasury yields, FRED indicators, equity futures, asset prices, calendar,
|
||||||
**Nuovi**: `get_yield_curve_slope` (slope 2y10y/5y30y + butterfly + regime),
|
get_yield_curve_slope, get_breakeven_inflation, get_cot_tff,
|
||||||
`get_breakeven_inflation` (T5YIE/T10YIE/T5YIFR), `get_cot_tff` (TFF report
|
get_cot_disaggregated, get_cot_extreme_positioning.
|
||||||
CFTC equity/financial: ES/NQ/RTY/ZN/ZB/6E/6J/DX), `get_cot_disaggregated`
|
|
||||||
(Disaggregated report CFTC commodities: CL/GC/SI/HG/ZW/ZC/ZS),
|
|
||||||
`get_cot_extreme_positioning` (scanner percentile ≤5/≥95 su watchlist).
|
|
||||||
|
|
||||||
### Sentiment
|
### Sentiment
|
||||||
News (CryptoPanic/CoinDesk), social (LunarCrush), funding multi-exchange,
|
News (CryptoPanic/CoinDesk), social (LunarCrush), funding multi-exchange,
|
||||||
OI history. **Nuovi**: `get_funding_arb_spread` (opportunità arb compatte),
|
OI history, get_funding_arb_spread, get_liquidation_heatmap,
|
||||||
`get_liquidation_heatmap` (heuristic da OI delta + funding extreme),
|
get_cointegration_pairs.
|
||||||
`get_cointegration_pairs` (Engle-Granger su coppie crypto).
|
|
||||||
|
## Deploy su VPS con Traefik
|
||||||
|
|
||||||
|
Sul VPS la rete pubblica (TLS, allowlist IP, rate limit) è gestita da
|
||||||
|
Traefik esterno a questo repository. Il container `cerbero-mcp` non
|
||||||
|
espone porte all'esterno: si registra alla rete docker di Traefik tramite
|
||||||
|
label aggiunte da un override compose esterno (es.
|
||||||
|
`docker-compose.override.yml` versionato fuori da questo repo). La policy
|
||||||
|
di sicurezza pubblica (allowlist IP per gli endpoint write) è
|
||||||
|
responsabilità di Traefik.
|
||||||
|
|
||||||
|
Esempio label minime per Traefik:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
labels:
|
||||||
|
- "traefik.enable=true"
|
||||||
|
- "traefik.http.routers.cerbero.rule=Host(`cerbero-mcp.tielogic.xyz`)"
|
||||||
|
- "traefik.http.routers.cerbero.entrypoints=websecure"
|
||||||
|
- "traefik.http.routers.cerbero.tls.certresolver=letsencrypt"
|
||||||
|
- "traefik.http.services.cerbero.loadbalancer.server.port=9000"
|
||||||
|
```
|
||||||
|
|
||||||
## Build & deploy pipeline
|
## Build & deploy pipeline
|
||||||
|
|
||||||
Niente CI/CD su Gitea: la build delle 8 image è responsabilità della
|
Build dell'immagine eseguita sulla macchina di sviluppo:
|
||||||
macchina di sviluppo, fatta da `scripts/build-push.sh`. Il flusso è:
|
|
||||||
|
|
||||||
1. **Quality gate locale** (sul laptop, prima di pushare):
|
|
||||||
- `uv run ruff check services/`
|
|
||||||
- `uv run mypy services/common/src/mcp_common`
|
|
||||||
- `uv run pytest services/`
|
|
||||||
- `docker compose -f docker-compose.prod.yml config -q`
|
|
||||||
2. **Build & push** (sul laptop):
|
|
||||||
```bash
|
|
||||||
export GITEA_PAT='<PAT_write:package>'
|
|
||||||
./scripts/build-push.sh # tutte le 8 image
|
|
||||||
./scripts/build-push.sh base mcp-bybit # solo specifiche
|
|
||||||
```
|
|
||||||
Tagga `:latest` + `:sha-<short_HEAD>` per rollback puntuali. Cache
|
|
||||||
buildx via registry stesso (run successivi 5-10× più veloci).
|
|
||||||
3. **Auto-rollover su VPS**: Watchtower polla il registry ogni 5 min e
|
|
||||||
aggiorna i container quando il digest del tag `:latest` cambia.
|
|
||||||
|
|
||||||
Vedi [`DEPLOYMENT.md`](DEPLOYMENT.md) per build & push, deploy VPS
|
|
||||||
no-clone (`scripts/deploy-noclone.sh`), smoke test, rollback.
|
|
||||||
|
|
||||||
## Avvio locale (dev)
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker compose up -d
|
export GITEA_PAT='<PAT con scope write:package>'
|
||||||
bash tests/smoke/run.sh
|
./scripts/build-push.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
## Configurazione
|
Lo script tagga `:2.0.0`, `:latest` e `:sha-<short>` per rollback puntuali
|
||||||
|
e pubblica al registry Gitea. Sul VPS Watchtower polla `:latest` e
|
||||||
|
aggiorna il container automaticamente.
|
||||||
|
|
||||||
Vedi `secrets/*.json` e variabili `*_TESTNET` / `ALPACA_PAPER` in
|
Smoke test post-deploy:
|
||||||
`docker-compose.yml` per override ambiente.
|
|
||||||
|
|
||||||
### Deploy su VPS pubblica (`cerbero-mcp.tielogic.xyz`)
|
|
||||||
|
|
||||||
Vedi [`DEPLOYMENT.md`](DEPLOYMENT.md) per il runbook completo end-to-end.
|
|
||||||
|
|
||||||
Il gateway Caddy è configurato per:
|
|
||||||
- TLS automatico via Let's Encrypt (richiede DNS A/AAAA che punti al
|
|
||||||
VPS e porte 80+443 raggiungibili).
|
|
||||||
- HSTS preload, header di sicurezza (`X-Content-Type-Options`,
|
|
||||||
`X-Frame-Options`, `Referrer-Policy`).
|
|
||||||
- Rate limit per IP (60 req/min su read, 10 req/min su write) tramite
|
|
||||||
plugin `mholt/caddy-ratelimit`.
|
|
||||||
- Allowlist IP sui write endpoint (`place_*`, `cancel_*`, `set_*`,
|
|
||||||
`close_*`, `transfer_*`, `amend_*`, `switch_*`): IP non presenti in
|
|
||||||
`WRITE_ALLOWLIST` ricevono `403 forbidden`.
|
|
||||||
|
|
||||||
Variabili d'ambiente per il deploy:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# .env (su VPS)
|
PORT=9000 TESTNET_TOKEN="$TESTNET_TOKEN" bash tests/smoke/run.sh
|
||||||
ACME_EMAIL=adrianodalpastro@tielogic.com
|
|
||||||
GATEWAY_HTTP_PORT=80
|
|
||||||
GATEWAY_HTTPS_PORT=443
|
|
||||||
|
|
||||||
# Allowlist write endpoint (CIDR space-separated). Default copre:
|
|
||||||
# - loopback IPv4/IPv6 (bot sull'host VPS chiama http://localhost)
|
|
||||||
# - Docker bridge 172.16.0.0/12 (bot in container nella stessa compose network)
|
|
||||||
# Aggiungi gli IP pubblici dei tuoi bot esterni se li hai.
|
|
||||||
WRITE_ALLOWLIST="127.0.0.1/32 ::1/128 172.16.0.0/12 1.2.3.4/32"
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Tre scenari per il trading bot:
|
## Sviluppo
|
||||||
1. Bot container nella stessa compose network → chiama `http://gateway:80`
|
|
||||||
internamente. Source IP = Docker bridge → coperto dalla default.
|
|
||||||
2. Bot processo sull'host VPS → chiama `http://localhost`. Source IP =
|
|
||||||
`127.0.0.1` → coperto dalla default.
|
|
||||||
3. Bot esterno (laptop, altro server) → chiama
|
|
||||||
`https://cerbero-mcp.tielogic.xyz` con TLS. Devi aggiungere l'IP
|
|
||||||
pubblico del bot in `WRITE_ALLOWLIST`.
|
|
||||||
|
|
||||||
Senza configurare `WRITE_ALLOWLIST` la default è loopback + Docker bridge:
|
|
||||||
nessun IP pubblico esterno può triggerare ordini.
|
|
||||||
|
|
||||||
Sull'host VPS i secret devono avere permessi restrittivi:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
chmod 600 secrets/*.json secrets/*.token
|
uv sync
|
||||||
|
uv run pytest # tutta la suite
|
||||||
|
uv run pytest tests/unit -v # solo unit
|
||||||
|
uv run ruff check src/
|
||||||
|
uv run mypy src/cerbero_mcp
|
||||||
```
|
```
|
||||||
|
|
||||||
### Risoluzione environment (testnet/mainnet)
|
## Migrazione da V1 (1.x → 2.0.0)
|
||||||
|
|
||||||
Ogni servizio exchange usa `mcp_common.environment.resolve_environment()`
|
Per chi è in produzione su V1:
|
||||||
che applica la precedenza:
|
|
||||||
|
|
||||||
1. env var di override (`DERIBIT_TESTNET`, `BYBIT_TESTNET`,
|
1. Backup `secrets/` (V2 non li userà ma servono come fonte di copia).
|
||||||
`HYPERLIQUID_TESTNET`, `ALPACA_PAPER`)
|
2. Generare i due nuovi token bearer (vedi sopra).
|
||||||
2. flag nel secret JSON (`testnet` o `paper` per alpaca)
|
3. Compilare `.env` mappando i campi V1 ai campi V2:
|
||||||
3. default `testnet`
|
|
||||||
|
|
||||||
Gli URL canonici live/testnet sono passati come kwargs
|
| V1 (file JSON) | V2 (variabile `.env`) |
|
||||||
`default_base_url_live` / `default_base_url_testnet` direttamente al
|
|---|---|
|
||||||
resolver — non serve duplicarli nel secret JSON, ma se presenti
|
| `secrets/deribit.json` `client_id` / `client_secret` | `DERIBIT_CLIENT_ID` / `DERIBIT_CLIENT_SECRET` |
|
||||||
prevalgono sui default del codice.
|
| `secrets/bybit.json` `api_key` / `api_secret` | `BYBIT_API_KEY` / `BYBIT_API_SECRET` |
|
||||||
|
| `secrets/hyperliquid.json` `wallet_address` / `private_key` | `HYPERLIQUID_WALLET_ADDRESS` / `HYPERLIQUID_PRIVATE_KEY` |
|
||||||
|
| `secrets/alpaca.json` `api_key_id` / `secret_key` | `ALPACA_API_KEY_ID` / `ALPACA_SECRET_KEY` |
|
||||||
|
| `secrets/macro.json` `fred_api_key` / `finnhub_api_key` | `FRED_API_KEY` / `FINNHUB_API_KEY` |
|
||||||
|
| `secrets/sentiment.json` `cryptopanic_key` / `lunarcrush_key` | `CRYPTOPANIC_KEY` / `LUNARCRUSH_KEY` |
|
||||||
|
|
||||||
|
4. Aggiornare i client bot:
|
||||||
|
- i path API restano identici (`/mcp-{exchange}/tools/{tool}`)
|
||||||
|
- sostituire `core.token` / `observer.token` con `TESTNET_TOKEN` o
|
||||||
|
`MAINNET_TOKEN` a seconda dell'ambiente desiderato per la chiamata
|
||||||
|
5. Spegnere V1 (`docker compose -f <vecchio compose> down`) e avviare V2
|
||||||
|
(`docker compose up -d`).
|
||||||
|
6. Verificare `/health` e `/apidocs`.
|
||||||
|
|
||||||
|
In caso di necessità è possibile fare rollback pullando i tag immagine V1
|
||||||
|
(`cerbero-mcp-*:1.x`); si ricordi però che `.env` e `secrets/` sono
|
||||||
|
formati incompatibili tra V1 e V2 — tenere backup separati.
|
||||||
|
|
||||||
|
## Architettura
|
||||||
|
|
||||||
|
Spec di progettazione completa in
|
||||||
|
[`docs/superpowers/specs/2026-04-30-V2.0.0-unified-image-token-routing-design.md`](docs/superpowers/specs/2026-04-30-V2.0.0-unified-image-token-routing-design.md).
|
||||||
|
|
||||||
|
Riepilogo:
|
||||||
|
|
||||||
|
```
|
||||||
|
Bot → Authorization: Bearer <TESTNET|MAINNET>_TOKEN
|
||||||
|
↓
|
||||||
|
FastAPI middleware auth → request.state.environment ∈ {testnet, mainnet}
|
||||||
|
↓
|
||||||
|
Router /mcp-{exchange}/tools/{tool}
|
||||||
|
↓
|
||||||
|
ClientRegistry.get(exchange, env) → client cached lazy (HTTP/WS pool riusato)
|
||||||
|
↓
|
||||||
|
Tool function (logica pura) → exchange API
|
||||||
|
```
|
||||||
|
|
||||||
|
## Licenza
|
||||||
|
|
||||||
|
Privato.
|
||||||
|
|||||||
Reference in New Issue
Block a user