refactor(V2): IBKR client read methods — defensive conid + sec_type DRY
Code review fixes (commit 611a269):
- resolve_conid validates conid key presence (was raw KeyError on malformed)
- _SEC_TYPE_MAP module constant — reused in get_ticker + get_bars
(also fixes get_bars previously missing "forex": "CASH")
- New tests: empty response + malformed response error paths
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -25,6 +25,14 @@ class IBKRError(Exception):
|
||||
|
||||
_TICKLE_INTERVAL_S = 240.0 # tickle if last call > 4min ago
|
||||
|
||||
# Mapping asset_class (cerbero/MCP convention) → IBKR secType.
|
||||
_SEC_TYPE_MAP: dict[str, str] = {
|
||||
"stocks": "STK",
|
||||
"options": "OPT",
|
||||
"futures": "FUT",
|
||||
"forex": "CASH",
|
||||
}
|
||||
|
||||
|
||||
@dataclass
|
||||
class IBKRClient:
|
||||
@@ -148,7 +156,12 @@ class IBKRClient:
|
||||
)
|
||||
if not result or not isinstance(result, list):
|
||||
raise IBKRError(f"IBKR_CONID_NOT_FOUND: {symbol}/{sec_type}")
|
||||
conid = int(result[0]["conid"])
|
||||
first = result[0]
|
||||
if not isinstance(first, dict) or "conid" not in first:
|
||||
raise IBKRError(
|
||||
f"IBKR_CONID_NOT_FOUND: {symbol}/{sec_type} (malformed response)"
|
||||
)
|
||||
conid = int(first["conid"])
|
||||
self._conid_cache[key] = conid
|
||||
if len(self._conid_cache) > self._CONID_CACHE_MAX:
|
||||
self._conid_cache.popitem(last=False)
|
||||
@@ -183,9 +196,7 @@ class IBKRClient:
|
||||
_SNAPSHOT_FIELDS = "31,84,86,7295,7296" # last,bid,ask,bid_size,ask_size
|
||||
|
||||
async def get_ticker(self, symbol: str, asset_class: str = "stocks") -> dict:
|
||||
sec_type = {"stocks": "STK", "options": "OPT", "futures": "FUT", "forex": "CASH"}.get(
|
||||
asset_class.lower(), "STK"
|
||||
)
|
||||
sec_type = _SEC_TYPE_MAP.get(asset_class.lower(), "STK")
|
||||
conid = await self.resolve_conid(symbol, sec_type)
|
||||
data = await self._request(
|
||||
"GET", "/iserver/marketdata/snapshot",
|
||||
@@ -216,9 +227,7 @@ class IBKRClient:
|
||||
self, symbol: str, asset_class: str = "stocks",
|
||||
period: str = "1d", bar: str = "5min",
|
||||
) -> dict:
|
||||
sec_type = {"stocks": "STK", "options": "OPT", "futures": "FUT"}.get(
|
||||
asset_class.lower(), "STK"
|
||||
)
|
||||
sec_type = _SEC_TYPE_MAP.get(asset_class.lower(), "STK")
|
||||
conid = await self.resolve_conid(symbol, sec_type)
|
||||
data = await self._request(
|
||||
"GET", "/iserver/marketdata/history",
|
||||
|
||||
Reference in New Issue
Block a user