feat(V2): shared Candle validator + uniform 'candles' response key
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>
This commit is contained in:
@@ -20,6 +20,7 @@ from typing import Any
|
||||
|
||||
import httpx
|
||||
|
||||
from cerbero_mcp.common.candles import validate_candles
|
||||
from cerbero_mcp.common.http import async_client
|
||||
|
||||
# ── Endpoint base ────────────────────────────────────────────────
|
||||
@@ -301,9 +302,17 @@ class AlpacaClient:
|
||||
|
||||
bars_dict = (data or {}).get("bars") or {}
|
||||
rows = bars_dict.get(symbol) or []
|
||||
bars = [
|
||||
|
||||
def _iso_to_ms(ts: str | int | None) -> int | None:
|
||||
if ts is None or isinstance(ts, int):
|
||||
return ts
|
||||
return int(_dt.datetime.fromisoformat(
|
||||
ts.replace("Z", "+00:00")
|
||||
).timestamp() * 1000)
|
||||
|
||||
candles = validate_candles([
|
||||
{
|
||||
"timestamp": b.get("t"),
|
||||
"timestamp": _iso_to_ms(b.get("t")),
|
||||
"open": b.get("o"),
|
||||
"high": b.get("h"),
|
||||
"low": b.get("l"),
|
||||
@@ -311,12 +320,12 @@ class AlpacaClient:
|
||||
"volume": b.get("v"),
|
||||
}
|
||||
for b in rows
|
||||
]
|
||||
])
|
||||
return {
|
||||
"symbol": symbol,
|
||||
"asset_class": ac,
|
||||
"interval": interval,
|
||||
"bars": bars,
|
||||
"candles": candles,
|
||||
}
|
||||
|
||||
async def get_snapshot(self, symbol: str) -> dict:
|
||||
|
||||
Reference in New Issue
Block a user