diff --git a/src/cerbero_mcp/__main__.py b/src/cerbero_mcp/__main__.py index 576dc31..8f554e4 100644 --- a/src/cerbero_mcp/__main__.py +++ b/src/cerbero_mcp/__main__.py @@ -9,6 +9,7 @@ Boot: """ from __future__ import annotations +import contextlib from contextlib import asynccontextmanager from typing import Literal, cast @@ -54,6 +55,11 @@ def _make_app(settings: Settings) -> FastAPI: try: yield finally: + # Stop any IBKR WebSocket singletons before closing client registry + ibkr_ws_dict = getattr(app.state, "ibkr_ws", {}) or {} + for ws in ibkr_ws_dict.values(): + with contextlib.suppress(Exception): + await ws.stop() await app.state.registry.aclose() app.router.lifespan_context = lifespan diff --git a/src/cerbero_mcp/exchanges/ibkr/client.py b/src/cerbero_mcp/exchanges/ibkr/client.py index 304fda6..1768980 100644 --- a/src/cerbero_mcp/exchanges/ibkr/client.py +++ b/src/cerbero_mcp/exchanges/ibkr/client.py @@ -392,10 +392,17 @@ class IBKRClient: async def close_position( self, symbol: str, qty: float | None = None ) -> dict: + # Resolve symbol → conid, then match positions on conid (positions + # return `contractDesc` as a long display string, not ticker). + sec_type = _SEC_TYPE_MAP.get("stocks", "STK") + conid = await self.resolve_conid(symbol, sec_type) positions = await self.get_positions() - target = next((p for p in positions if p.get("contractDesc") == symbol), None) + target = next( + (p for p in positions if int(p.get("conid", 0)) == conid), + None, + ) if not target: - raise IBKRError(f"IBKR_NO_POSITION: {symbol}") + raise IBKRError(f"IBKR_NO_POSITION: {symbol} (conid={conid})") position_qty = float(target.get("position", 0)) close_qty = abs(qty if qty is not None else position_qty) side = "SELL" if position_qty > 0 else "BUY" @@ -407,7 +414,7 @@ class IBKRClient: positions = await self.get_positions() results = [] for p in positions: - sym = p.get("contractDesc") + sym = p.get("ticker") or p.get("contractDesc") if not sym: continue try: diff --git a/src/cerbero_mcp/exchanges/ibkr/tools.py b/src/cerbero_mcp/exchanges/ibkr/tools.py index 79914dc..f18b2c3 100644 --- a/src/cerbero_mcp/exchanges/ibkr/tools.py +++ b/src/cerbero_mcp/exchanges/ibkr/tools.py @@ -192,6 +192,12 @@ async def get_clock(client: IBKRClient, params: GetClockReq) -> dict: "timestamp": now.isoformat(), "is_open": _dt.time(13, 30) <= now.time() <= _dt.time(20, 0) and now.weekday() < 5, + "approximate": True, + "note": ( + "is_open is a UTC-based approximation; does not account for " + "US market holidays or half-days. Use IBKR /trsrv/marketdata/calendar " + "for authoritative schedule." + ), }