Skip to main content
Glama
trading_tools.py•29 kB
""" Trading MCP Tools for Tiger Brokers API. Provides MCP tools for trading operations including account information, positions, orders, and trade execution through the Tiger API process pool. """ import sys from datetime import datetime from typing import Any, Dict, List, Optional # Add paths for imports sys.path.insert( 0, "/Volumes/extdisk/MyRepos/cctrading-ws/tiger-mcp/packages/shared/src" ) from fastmcp import FastMCP from loguru import logger from pydantic import BaseModel, Field from shared.account_manager import get_account_manager from shared.account_router import OperationType, get_account_router from ..process_manager import get_process_manager # Initialize FastMCP instance for trading tools mcp = FastMCP("Tiger Trading Tools") class PositionsResponse(BaseModel): """Positions response model.""" success: bool account_id: str = "" positions: List[Dict[str, Any]] = Field(default_factory=list) total_market_value: Optional[float] = None total_pnl: Optional[float] = None error: Optional[str] = None timestamp: str = Field(default_factory=lambda: datetime.utcnow().isoformat()) class AccountInfoResponse(BaseModel): """Account info response model.""" success: bool account_id: str = "" account_info: Optional[Dict[str, Any]] = None error: Optional[str] = None timestamp: str = Field(default_factory=lambda: datetime.utcnow().isoformat()) class OrdersResponse(BaseModel): """Orders response model.""" success: bool account_id: str = "" orders: List[Dict[str, Any]] = Field(default_factory=list) total_count: int = 0 error: Optional[str] = None timestamp: str = Field(default_factory=lambda: datetime.utcnow().isoformat()) class OrderResponse(BaseModel): """Single order response model.""" success: bool account_id: str = "" order: Optional[Dict[str, Any]] = None order_id: Optional[str] = None error: Optional[str] = None timestamp: str = Field(default_factory=lambda: datetime.utcnow().isoformat()) # Service instance for trading operations class TradingToolsService: """Service class for trading tools with account routing.""" def __init__(self): self.process_manager = get_process_manager() self.account_manager = get_account_manager() self.account_router = get_account_router() async def _route_trading_account( self, account_id: Optional[str], operation_type: OperationType = OperationType.ACCOUNT_INFO, ) -> str: """Route request to appropriate trading account.""" if account_id: # Use specified account return account_id else: # Use default trading account or router default_account = await self.account_manager.get_default_trading_account() if default_account: return str(default_account.id) else: # Use account router for trading operations routed_account = await self.account_router.route_trading_operation( operation_type ) return str(routed_account.id) async def ensure_started(self): """Ensure the process manager is started.""" if ( not hasattr(self.process_manager, "_started") or not self.process_manager._started ): await self.process_manager.start() # Global service instance _trading_service = TradingToolsService() @mcp.tool() async def tiger_get_positions(account_id: Optional[str] = None) -> PositionsResponse: """ Get current positions for a Tiger account. Retrieves all current positions including stocks, options, and other securities held in the specified account with real-time market values and P&L information. Args: account_id: Optional specific account ID to use. If not provided, uses default trading account or router. Returns: PositionsResponse containing positions data: - symbol: Security symbol - quantity: Number of shares/contracts held - market_value: Current market value of position - average_cost: Average cost basis - unrealized_pnl: Unrealized profit/loss - realized_pnl: Realized profit/loss - position_side: LONG or SHORT - security_type: STK, OPT, etc. Example: ```python # Get positions for default trading account response = await tiger_get_positions() if response.success: for position in response.positions: print(f"{position['symbol']}: {position['quantity']} shares, " f"P&L: ${position['unrealized_pnl']:.2f}") # Get positions for specific account response = await tiger_get_positions("account-uuid-here") ``` """ try: await _trading_service.ensure_started() # Route to appropriate account target_account_id = await _trading_service._route_trading_account( account_id, OperationType.POSITIONS ) # Execute API call result = await _trading_service.process_manager.execute_api_call( account_id=target_account_id, method="trade.get_positions", timeout=15.0 ) # Process positions data positions = [] total_market_value = 0.0 total_pnl = 0.0 if result: for position in result: position_data = { "symbol": getattr(position, "symbol", ""), "quantity": getattr(position, "quantity", 0), "market_value": getattr(position, "market_value", 0.0), "average_cost": getattr(position, "average_cost", 0.0), "unrealized_pnl": getattr(position, "unrealized_pnl", 0.0), "realized_pnl": getattr(position, "realized_pnl", 0.0), "position_side": getattr(position, "position_side", "LONG"), "security_type": getattr(position, "security_type", "STK"), "currency": getattr(position, "currency", "USD"), "local_symbol": getattr(position, "local_symbol", ""), "multiplier": getattr(position, "multiplier", 1), "strike": getattr(position, "strike", None), "expiry": getattr(position, "expiry", None), "right": getattr(position, "right", None), } positions.append(position_data) # Add to totals total_market_value += position_data["market_value"] total_pnl += position_data["unrealized_pnl"] return PositionsResponse( success=True, account_id=target_account_id, positions=positions, total_market_value=total_market_value, total_pnl=total_pnl, ) except Exception as e: logger.error(f"Failed to get positions: {e}") return PositionsResponse( success=False, account_id=account_id or "", error=str(e) ) @mcp.tool() async def tiger_get_account_info( account_id: Optional[str] = None, ) -> AccountInfoResponse: """ Get account balance and information for a Tiger account. Retrieves comprehensive account information including cash balances, buying power, margin information, and account summary data. Args: account_id: Optional specific account ID to use. If not provided, uses default trading account or router. Returns: AccountInfoResponse containing account information: - total_cash: Total cash available - buying_power: Available buying power - net_liquidation: Net liquidation value - total_market_value: Total market value of positions - unrealized_pnl: Total unrealized P&L - realized_pnl: Total realized P&L - margin_used: Margin currently used - maintenance_margin: Maintenance margin requirement - currency: Account base currency Example: ```python # Get account info for default account response = await tiger_get_account_info() if response.success: info = response.account_info print(f"Cash: ${info['total_cash']:.2f}") print(f"Buying Power: ${info['buying_power']:.2f}") print(f"P&L: ${info['unrealized_pnl']:.2f}") ``` """ try: await _trading_service.ensure_started() # Route to appropriate account target_account_id = await _trading_service._route_trading_account( account_id, OperationType.ACCOUNT_INFO ) # Execute API call result = await _trading_service.process_manager.execute_api_call( account_id=target_account_id, method="trade.get_account", timeout=15.0 ) # Process account info account_info = None if result: account_info = { "account_number": getattr(result, "account_number", ""), "total_cash": getattr(result, "total_cash", 0.0), "buying_power": getattr(result, "buying_power", 0.0), "net_liquidation": getattr(result, "net_liquidation", 0.0), "total_market_value": getattr(result, "total_market_value", 0.0), "unrealized_pnl": getattr(result, "unrealized_pnl", 0.0), "realized_pnl": getattr(result, "realized_pnl", 0.0), "margin_used": getattr(result, "margin_used", 0.0), "maintenance_margin": getattr(result, "maintenance_margin", 0.0), "available_funds": getattr(result, "available_funds", 0.0), "excess_liquidity": getattr(result, "excess_liquidity", 0.0), "currency": getattr(result, "currency", "USD"), "account_type": getattr(result, "account_type", ""), "trading_status": getattr(result, "trading_status", ""), "last_updated": getattr( result, "last_updated", datetime.utcnow().isoformat() ), } return AccountInfoResponse( success=True, account_id=target_account_id, account_info=account_info ) except Exception as e: logger.error(f"Failed to get account info: {e}") return AccountInfoResponse( success=False, account_id=account_id or "", error=str(e) ) @mcp.tool() async def tiger_get_orders( account_id: Optional[str] = None, status: Optional[str] = None, symbol: Optional[str] = None, start_date: Optional[str] = None, end_date: Optional[str] = None, limit: int = 100, ) -> OrdersResponse: """ Get orders for a Tiger account with optional filtering. Retrieves orders from the account with optional filtering by status, symbol, and date range. Useful for monitoring order execution and history. Args: account_id: Optional specific account ID to use. If not provided, uses default trading account or router. status: Optional filter by order status: - 'PENDING': Orders awaiting execution - 'FILLED': Completely executed orders - 'PARTIAL_FILLED': Partially executed orders - 'CANCELLED': Cancelled orders - 'REJECTED': Rejected orders symbol: Optional filter by symbol (e.g., 'AAPL', 'TSLA') start_date: Optional start date filter (YYYY-MM-DD format) end_date: Optional end date filter (YYYY-MM-DD format) limit: Maximum number of orders to return (default: 100, max: 500) Returns: OrdersResponse containing list of orders with details: - order_id: Unique order identifier - symbol: Trading symbol - side: BUY or SELL - quantity: Number of shares/contracts - order_type: MARKET, LIMIT, STOP, etc. - price: Order price (for limit orders) - status: Current order status - filled_quantity: Quantity already executed - average_fill_price: Average execution price - created_time: Order creation timestamp Example: ```python # Get all recent orders response = await tiger_get_orders() if response.success: for order in response.orders: print(f"{order['symbol']} {order['side']} {order['quantity']} @ " f"{order['price']} - Status: {order['status']}") # Get pending orders for AAPL response = await tiger_get_orders(status="PENDING", symbol="AAPL") ``` """ try: await _trading_service.ensure_started() # Validate limit limit = min(max(limit, 1), 500) # Route to appropriate account target_account_id = await _trading_service._route_trading_account( account_id, OperationType.ORDERS ) # Prepare filter parameters filters = {} if status: filters["status"] = status.upper() if symbol: filters["symbol"] = symbol.upper() if start_date: filters["start_date"] = start_date if end_date: filters["end_date"] = end_date filters["limit"] = limit # Execute API call result = await _trading_service.process_manager.execute_api_call( account_id=target_account_id, method="trade.get_orders", kwargs=filters, timeout=15.0, ) # Process orders orders = [] if result: for order in result: order_data = { "order_id": getattr(order, "order_id", ""), "symbol": getattr(order, "symbol", ""), "side": getattr(order, "side", ""), "quantity": getattr(order, "quantity", 0), "order_type": getattr(order, "order_type", ""), "price": getattr(order, "price", None), "stop_price": getattr(order, "stop_price", None), "status": getattr(order, "status", ""), "filled_quantity": getattr(order, "filled_quantity", 0), "remaining_quantity": getattr(order, "remaining_quantity", 0), "average_fill_price": getattr(order, "average_fill_price", None), "created_time": getattr(order, "created_time", None), "updated_time": getattr(order, "updated_time", None), "time_in_force": getattr(order, "time_in_force", "DAY"), "security_type": getattr(order, "security_type", "STK"), "currency": getattr(order, "currency", "USD"), "commission": getattr(order, "commission", 0.0), "realized_pnl": getattr(order, "realized_pnl", 0.0), } orders.append(order_data) return OrdersResponse( success=True, account_id=target_account_id, orders=orders, total_count=len(orders), ) except Exception as e: logger.error(f"Failed to get orders: {e}") return OrdersResponse(success=False, account_id=account_id or "", error=str(e)) @mcp.tool() async def tiger_place_order( symbol: str, side: str, quantity: int, order_type: str, price: Optional[float] = None, stop_price: Optional[float] = None, time_in_force: str = "DAY", account_id: Optional[str] = None, ) -> OrderResponse: """ Place a new order on Tiger Brokers. Submit a new trading order with specified parameters. Supports various order types including market, limit, stop, and stop-limit orders. Args: symbol: Trading symbol (e.g., 'AAPL', 'TSLA', 'SPY') side: Order side - 'BUY' or 'SELL' quantity: Number of shares to trade (must be positive integer) order_type: Type of order: - 'MARKET': Execute at current market price - 'LIMIT': Execute at specified price or better - 'STOP': Stop-loss order (becomes market order when triggered) - 'STOP_LIMIT': Stop order that becomes limit order when triggered price: Limit price (required for LIMIT and STOP_LIMIT orders) stop_price: Stop trigger price (required for STOP and STOP_LIMIT orders) time_in_force: Order duration: - 'DAY': Valid for current trading day - 'GTC': Good till cancelled - 'IOC': Immediate or cancel - 'FOK': Fill or kill account_id: Optional specific account ID to use. If not provided, uses default trading account. Returns: OrderResponse containing the placed order details or error information. Example: ```python # Place a market buy order response = await tiger_place_order( symbol="AAPL", side="BUY", quantity=100, order_type="MARKET" ) # Place a limit sell order response = await tiger_place_order( symbol="TSLA", side="SELL", quantity=50, order_type="LIMIT", price=250.00, time_in_force="GTC" ) # Place a stop-loss order response = await tiger_place_order( symbol="SPY", side="SELL", quantity=100, order_type="STOP", stop_price=400.00 ) ``` Note: - This will execute real trades in production accounts - Always verify symbol, quantity, and prices before placing orders - Paper trading accounts can be used for testing """ try: await _trading_service.ensure_started() # Validate inputs if not symbol or not symbol.strip(): return OrderResponse(success=False, error="Symbol is required") side = side.upper().strip() if side not in ["BUY", "SELL"]: return OrderResponse(success=False, error="Side must be 'BUY' or 'SELL'") if quantity <= 0: return OrderResponse(success=False, error="Quantity must be positive") order_type = order_type.upper().strip() valid_order_types = ["MARKET", "LIMIT", "STOP", "STOP_LIMIT"] if order_type not in valid_order_types: return OrderResponse( success=False, error=f"Order type must be one of: {', '.join(valid_order_types)}", ) # Validate price requirements if order_type in ["LIMIT", "STOP_LIMIT"] and price is None: return OrderResponse( success=False, error=f"{order_type} order requires a price" ) if order_type in ["STOP", "STOP_LIMIT"] and stop_price is None: return OrderResponse( success=False, error=f"{order_type} order requires a stop_price" ) # Route to appropriate account target_account_id = await _trading_service._route_trading_account( account_id, OperationType.PLACE_ORDER ) # Prepare order parameters order_params = { "symbol": symbol.upper(), "side": side, "quantity": quantity, "order_type": order_type, "time_in_force": time_in_force.upper(), } if price is not None: order_params["price"] = price if stop_price is not None: order_params["stop_price"] = stop_price # Execute order placement result = await _trading_service.process_manager.execute_api_call( account_id=target_account_id, method="trade.place_order", kwargs=order_params, timeout=30.0, ) # Process order response order_info = None order_id = None if result: order_id = getattr(result, "order_id", None) or getattr(result, "id", None) order_info = { "order_id": order_id, "symbol": getattr(result, "symbol", symbol), "side": getattr(result, "side", side), "quantity": getattr(result, "quantity", quantity), "order_type": getattr(result, "order_type", order_type), "price": getattr(result, "price", price), "stop_price": getattr(result, "stop_price", stop_price), "time_in_force": getattr(result, "time_in_force", time_in_force), "status": getattr(result, "status", "PENDING"), "created_time": getattr( result, "created_time", datetime.utcnow().isoformat() ), "account_id": target_account_id, } logger.info( f"Successfully placed order: {symbol} {side} {quantity} shares (ID: {order_id})" ) return OrderResponse( success=True, account_id=target_account_id, order=order_info, order_id=order_id, ) except Exception as e: logger.error(f"Failed to place order: {e}") return OrderResponse(success=False, account_id=account_id or "", error=str(e)) @mcp.tool() async def tiger_cancel_order( order_id: str, account_id: Optional[str] = None ) -> OrderResponse: """ Cancel an existing order. Attempts to cancel a pending or partially filled order. Only orders that are not yet fully executed can be cancelled. Args: order_id: Unique identifier of the order to cancel account_id: Optional specific account ID to use. If not provided, uses default trading account. Returns: OrderResponse indicating success or failure of the cancellation. Example: ```python response = await tiger_cancel_order("order-id-12345") if response.success: print("Order cancelled successfully") print(f"Final status: {response.order['status']}") ``` Note: - Only pending or partially filled orders can be cancelled - Market orders may execute before cancellation can be processed - The order status will be updated to 'CANCELLED' if successful """ try: await _trading_service.ensure_started() if not order_id or not order_id.strip(): return OrderResponse(success=False, error="Order ID is required") # Route to appropriate account target_account_id = await _trading_service._route_trading_account( account_id, OperationType.CANCEL_ORDER ) # Execute order cancellation result = await _trading_service.process_manager.execute_api_call( account_id=target_account_id, method="trade.cancel_order", kwargs={"order_id": order_id}, timeout=15.0, ) # Process cancellation response order_info = None if result: order_info = { "order_id": getattr(result, "order_id", order_id), "symbol": getattr(result, "symbol", ""), "side": getattr(result, "side", ""), "quantity": getattr(result, "quantity", 0), "status": getattr(result, "status", "CANCELLED"), "cancelled_time": getattr( result, "cancelled_time", datetime.utcnow().isoformat() ), "filled_quantity": getattr(result, "filled_quantity", 0), "remaining_quantity": getattr(result, "remaining_quantity", 0), } logger.info(f"Successfully cancelled order: {order_id}") return OrderResponse( success=True, account_id=target_account_id, order=order_info, order_id=order_id, ) except Exception as e: logger.error(f"Failed to cancel order {order_id}: {e}") return OrderResponse( success=False, account_id=account_id or "", order_id=order_id, error=str(e) ) @mcp.tool() async def tiger_modify_order( order_id: str, new_quantity: Optional[int] = None, new_price: Optional[float] = None, new_stop_price: Optional[float] = None, account_id: Optional[str] = None, ) -> OrderResponse: """ Modify an existing order. Updates the parameters of a pending or partially filled order. At least one parameter (quantity, price, or stop_price) must be provided. Args: order_id: Unique identifier of the order to modify new_quantity: New quantity for the order (must be positive) new_price: New limit price (for limit and stop-limit orders) new_stop_price: New stop trigger price (for stop and stop-limit orders) account_id: Optional specific account ID to use. If not provided, uses default trading account. Returns: OrderResponse containing the modified order details or error information. Example: ```python # Modify order quantity response = await tiger_modify_order("order-id-12345", new_quantity=150) # Modify limit price response = await tiger_modify_order("order-id-12345", new_price=101.50) # Modify both quantity and price response = await tiger_modify_order( order_id="order-id-12345", new_quantity=200, new_price=102.00 ) ``` Note: - Only pending or partially filled orders can be modified - Market orders typically cannot be modified - For partially filled orders, the new quantity must be >= filled quantity - Some brokers may cancel and replace the order instead of modifying in place """ try: await _trading_service.ensure_started() if not order_id or not order_id.strip(): return OrderResponse(success=False, error="Order ID is required") # Validate at least one modification parameter is provided if new_quantity is None and new_price is None and new_stop_price is None: return OrderResponse( success=False, error="At least one modification parameter (new_quantity, new_price, new_stop_price) must be provided", ) # Validate new_quantity if provided if new_quantity is not None and new_quantity <= 0: return OrderResponse(success=False, error="New quantity must be positive") # Route to appropriate account target_account_id = await _trading_service._route_trading_account( account_id, OperationType.MODIFY_ORDER ) # Prepare modification parameters modify_params = {"order_id": order_id} if new_quantity is not None: modify_params["quantity"] = new_quantity if new_price is not None: modify_params["price"] = new_price if new_stop_price is not None: modify_params["stop_price"] = new_stop_price # Execute order modification result = await _trading_service.process_manager.execute_api_call( account_id=target_account_id, method="trade.modify_order", kwargs=modify_params, timeout=15.0, ) # Process modification response order_info = None if result: order_info = { "order_id": getattr(result, "order_id", order_id), "symbol": getattr(result, "symbol", ""), "side": getattr(result, "side", ""), "quantity": getattr(result, "quantity", new_quantity), "price": getattr(result, "price", new_price), "stop_price": getattr(result, "stop_price", new_stop_price), "status": getattr(result, "status", "PENDING"), "modified_time": getattr( result, "modified_time", datetime.utcnow().isoformat() ), "filled_quantity": getattr(result, "filled_quantity", 0), "remaining_quantity": getattr(result, "remaining_quantity", 0), } logger.info(f"Successfully modified order: {order_id}") return OrderResponse( success=True, account_id=target_account_id, order=order_info, order_id=order_id, ) except Exception as e: logger.error(f"Failed to modify order {order_id}: {e}") return OrderResponse( success=False, account_id=account_id or "", order_id=order_id, error=str(e) )

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/luxiaolei/tiger-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server