tests/smoke/run_unified.sh exercises the common interface against a live
server + real upstreams (Deribit/Hyperliquid public endpoints):
get_instruments (single + fan-out, asserting fees/history_start),
get_historical for both venues, and a 422 for an unsupported exchange.
Refresh tests/smoke/README.md (drop stale Bybit/Alpaca references).
Verified green end-to-end.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add a common cross-exchange interface (/mcp) over the integrated venues
(deribit, hyperliquid):
- get_instruments: uniform schema where each row carries its own
`exchange`, `fees` (maker/taker, live from Deribit, null where the
venue has no per-instrument schedule) and `history_start` (listing
date, live from Deribit creation_timestamp), plus type/tick_size and a
lossless `native` blob. Optional `exchange` filter; fan-out otherwise.
- get_historical: generalized to {exchange, instrument, interval,
start_date, end_date}, returning a single chosen venue's candles.
Consensus merge stays available on /mcp-cross.
New: routers/unified.py, exchanges/cross/instruments.py (normalizers),
UnifiedClient in cross/client.py, schemas in cross/tools.py. Deribit
get_instruments now also surfaces maker/taker_commission and
creation_timestamp (additive).
Retire Bybit and Alpaca from the API surface: move clients, routers,
settings classes and their tests under old/ (history preserved via
git mv); drop them from the builder, /mcp-cross dispatch and symbol_map.
Bybit remains a public funding/OI data source in sentiment (not the
trading client). IBKR is intentionally excluded from /mcp for now.
Docs: rewrite API_REFERENCE.md (remove Bybit/Alpaca, document /mcp,
clarify that data_timestamp is injected globally by middleware).
Tests: add unified-interface coverage; update cross/settings/builder/boot
tests for the reduced venue set. Fix a pre-existing flaky assertion in
the Hyperliquid signing test (r/s use eth_utils.to_hex like the official
SDK, so a leading zero byte yields <66 chars ~1/256 of the time).
323 passed, ruff clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
end_date as a bare date now covers the whole UTC day (23:59:59.999)
instead of stopping at midnight, so end_date=today returns intraday up
to the last closed candle. Dates are parsed as explicit UTC and inline
timestamps (YYYY-MM-DDTHH:MM:SS) are honored for precise windows.
Wide ranges are paged in <=5000-candle windows so Deribit's per-call
cap can no longer silently drop start_date and the oldest candles.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add a unified historical endpoint that fans out to every exchange
supporting the requested (asset_class, symbol) pair, then merges the
results into a single consensus candle series with per-bar divergence
metrics:
- candles[i].close = median across sources
- candles[i].sources = count of contributing exchanges
- candles[i].div_pct = (max-min)/median for that bar's close
Crypto routes BTC/ETH/SOL across Bybit + Hyperliquid + Deribit; equities
route to Alpaca for now (IBKR omitted from MVP because its bars endpoint
takes a relative period instead of start/end). Partial failures return a
warning envelope (failed_sources) instead of failing the whole request;
all sources failing → HTTP 502.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Introduce common/candles.py with a Pydantic Candle model enforcing OHLC
consistency (high≥max, low≤min), non-negative volume and positive
timestamp. validate_candles() coerces upstream rows, sorts by timestamp
and raises HTTPException(502) on malformed data — surfacing upstream
data corruption as a retryable envelope instead of silently returning
nonsense.
Wired into all five exchange historical endpoints (Bybit, Hyperliquid,
Deribit, Alpaca, IBKR). BREAKING: Alpaca get_bars and IBKR get_bars now
return 'candles' (was 'bars') to align with the other exchanges.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Cloudflare 5xx pages from Deribit testnet were leaking through the JSON
parser as JSONDecodeError → UNHANDLED_EXCEPTION. Wrap response parsing so
upstream errors surface as a retryable HTTP_502 envelope instead.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
DeribitSettings ora supporta coppie credenziali distinte per testnet e
mainnet via DERIBIT_CLIENT_ID_TESTNET/_LIVE e DERIBIT_CLIENT_SECRET_TESTNET/_LIVE.
Le coppie env-specifiche prevalgono sulla coppia base
DERIBIT_CLIENT_ID/DERIBIT_CLIENT_SECRET (mantenuta per backward compat).
build_client risolve la coppia giusta tramite settings.deribit.credentials(env);
ValueError esplicito se nessuna coppia configurata per l'env richiesto.
+4 test (legacy single, per-env, override, missing). Fix anche isolation
da .env reale via monkeypatch.chdir(tmp_path).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Su errore di Deribit (auth fallita, ecc.) i campi equity/balance/margin/
available/unrealized_pnl/total_pnl ora sono None: signal chiaro di "valore
ignoto" vs "saldo realmente a zero". Risolve ambiguità lato client che
leggevano equity=0 senza accorgersi del campo error.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Quando Deribit risponde con {"error": {...}} su public/auth (creds errate,
scope mancante, env mismatch), il client esplodeva con KeyError: 'result' →
500 UNHANDLED_EXCEPTION sui tool privati (get_account_summary, get_positions).
Ora _authenticate solleva DeribitAuthError tipizzata, _request la converte
in error envelope coerente con il resto del flusso.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- /health/ready: ping di tutti i client (exchange, env) cached con
timeout 2s, status ready|degraded|not_ready, opt-in 503 via
READY_FAILS_ON_DEGRADED.
- Middleware mcp.request: 1 riga JSON per HTTP request con request_id,
method, path, status_code, duration_ms, actor, bot_tag, exchange,
tool, client_ip, user_agent.
- request_id propagato in request.state, audit log e error envelope per
correlazione cross-cutting.
- Aggiunto async health() come probe minimo a bybit/alpaca/macro/
sentiment/deribit (hyperliquid lo aveva già).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Riscritto interamente HyperliquidClient su httpx puro + eth-account per la
firma EIP-712 L1 (chainId 1337, phantom agent source 'a'/'b' per
mainnet/testnet). Bit-parity verificata contro hyperliquid.utils.signing
in test_signing_parity_with_canonical_sdk.
16 metodi pubblici, 26 test passanti. Aggiunte deps: eth-account, msgpack,
eth-utils. hyperliquid-python-sdk ancora presente nel pyproject; rimossa
nel sweep finale.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Riscrive `AlpacaClient` su `httpx.AsyncClient` rimuovendo ogni dipendenza
runtime da `alpaca-py`. 4 endpoint base distinti (trading paper/live,
stock data, crypto data, options data) gestiti via helper `_request`
con header `APCA-API-KEY-ID` / `APCA-API-SECRET-KEY`. Firma costruttore
e attributi pubblici (`paper`, `base_url`) invariati; `base_url` override
applica al solo trading endpoint. Nuovo `aclose()` per cleanup connessioni.
Test riscritti su `pytest-httpx` (29 test alpaca + leverage cap).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- ruff: contextlib.suppress al posto di try/except/pass (client_registry, test_env_routing)
- rimozione services/ legacy (residuo da git rm)
- fix integration test fixture: rimosso sys.modules.pop che inquinava module references nei test successivi (test_audit, test_client_init_default_http)
254 test passano. Ruff: clean. Mypy: 68 warning preesistenti dal codice V1 migrato (strict=false).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Task 6.7: porting alpaca da services/mcp-alpaca a src/cerbero_mcp.
client.py + leverage_cap.py copiati 1:1 (default cap 1 cash).
tools.py: 17 tool senza ACL/Principal/audit. Router /mcp-alpaca con 18
route (env_info + 17 tool). Builder branch alpaca: paper=(env=="testnet"),
api_key viene da settings.alpaca.api_key_id. Test client + leverage_cap
migrati (15 test alpaca pass). Test builder con stub SDK alpaca-py.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Router /mcp-deribit/* monta 34 tool (28 read + 6 write) come endpoint
POST /mcp-deribit/tools/{tool_name}, con DI per env (request.state) e
client (ClientRegistry). Write tools costruiscono creds minimale
{max_leverage, client_id} da settings per leverage cap enforcement.
Test deribit migrati: test_client.py + test_leverage_cap.py riassegnati
sotto tests/unit/exchanges/deribit/ con import rewrite mcp_* -> cerbero_mcp.*.
Skip dei legacy V1-only test_environment_info / test_server_acl / test_env_validation
(ACL e resolve_environment eliminati in V2).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copiati e aggiornati i test da services/common/tests/ a tests/unit/common/.
Import aggiornati da mcp_common a cerbero_mcp.common. Eliminati test di
funzionalità V1-only (app_factory, environment, auth/Principal, server_base).
Refactored test_audit.py (principal→actor str) e test_mcp_bridge.py
(TokenStore→valid_tokens set). 71/71 test passano.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>