feat(V2): IBKR simple write tools (place/amend/cancel/close)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -4,10 +4,11 @@ from __future__ import annotations
|
||||
import asyncio
|
||||
from typing import Any
|
||||
|
||||
from fastapi import HTTPException
|
||||
from pydantic import BaseModel
|
||||
|
||||
from cerbero_mcp.exchanges.ibkr.client import _SEC_TYPE_MAP, IBKRClient, IBKRError
|
||||
from cerbero_mcp.exchanges.ibkr.leverage_cap import enforce_leverage, get_max_leverage # noqa: F401
|
||||
from cerbero_mcp.exchanges.ibkr.leverage_cap import get_max_leverage
|
||||
from cerbero_mcp.exchanges.ibkr.ws import IBKRWebSocket
|
||||
|
||||
# === Schemas: reads ===
|
||||
@@ -247,3 +248,84 @@ async def unsubscribe(
|
||||
conid = await client.resolve_conid(params.symbol, sec)
|
||||
await ws.unsubscribe(conid)
|
||||
return {"symbol": params.symbol, "conid": conid, "unsubscribed": True}
|
||||
|
||||
|
||||
# === Write tools: simple ===
|
||||
|
||||
|
||||
async def place_order(
|
||||
client: IBKRClient, params: PlaceOrderReq,
|
||||
*, creds: dict, last_price: float | None = None,
|
||||
) -> dict:
|
||||
cap = get_max_leverage(creds)
|
||||
if last_price is None:
|
||||
try:
|
||||
ticker = await client.get_ticker(params.symbol, params.asset_class)
|
||||
last_price = ticker.get("last_price") or ticker.get("ask")
|
||||
except Exception:
|
||||
last_price = None
|
||||
if last_price:
|
||||
notional = params.qty * float(last_price)
|
||||
try:
|
||||
account = await client.get_account()
|
||||
equity = float(
|
||||
(account.get("netliquidation") or {}).get("amount") or 0
|
||||
)
|
||||
except Exception:
|
||||
equity = 0.0
|
||||
if equity > 0 and notional / equity > cap:
|
||||
raise HTTPException(
|
||||
status_code=403,
|
||||
detail={
|
||||
"error": "LEVERAGE_CAP_EXCEEDED",
|
||||
"exchange": "ibkr",
|
||||
"requested_ratio": notional / equity,
|
||||
"max": cap,
|
||||
},
|
||||
)
|
||||
|
||||
return await client.place_order(
|
||||
symbol=params.symbol,
|
||||
side=params.side,
|
||||
qty=params.qty,
|
||||
order_type=params.order_type,
|
||||
limit_price=params.limit_price,
|
||||
stop_price=params.stop_price,
|
||||
tif=params.tif,
|
||||
asset_class=params.asset_class,
|
||||
sec_type=params.sec_type,
|
||||
exchange=params.exchange,
|
||||
outside_rth=params.outside_rth,
|
||||
)
|
||||
|
||||
|
||||
async def amend_order(client: IBKRClient, params: AmendOrderReq) -> dict:
|
||||
return await client.amend_order(
|
||||
params.order_id,
|
||||
qty=params.qty,
|
||||
limit_price=params.limit_price,
|
||||
stop_price=params.stop_price,
|
||||
tif=params.tif,
|
||||
)
|
||||
|
||||
|
||||
async def cancel_order(client: IBKRClient, params: CancelOrderReq) -> dict:
|
||||
return await client.cancel_order(params.order_id)
|
||||
|
||||
|
||||
async def cancel_all_orders(
|
||||
client: IBKRClient, params: CancelAllOrdersReq
|
||||
) -> dict:
|
||||
return {"canceled": await client.cancel_all_orders()}
|
||||
|
||||
|
||||
async def close_position(
|
||||
client: IBKRClient, params: ClosePositionReq
|
||||
) -> dict:
|
||||
return await client.close_position(params.symbol, params.qty)
|
||||
|
||||
|
||||
async def close_all_positions(
|
||||
client: IBKRClient, params: CloseAllPositionsReq
|
||||
) -> dict:
|
||||
return {"closed": await client.close_all_positions()}
|
||||
|
||||
Reference in New Issue
Block a user