Skip to main content
Glama
clsung

Taiwan Stock Agent

by clsung

get_realtime_data

Retrieve current stock prices and trading data for Taiwan-listed companies to monitor market movements and inform investment decisions.

Instructions

Get real-time data for a specific stock.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
stock_codeYes

Implementation Reference

  • MCP tool handler implementation for 'get_realtime_data'. This is the entry point decorated with @mcp.tool that handles the tool execution, error handling, and response formatting using RealtimeDataResponse model.
    @mcp.tool(name="get_realtime_data",
              description="Get real-time data for a specific stock.",
    )
    async def get_realtime_data_tool(stock_code: str) -> RealtimeDataResponse:
        """Get real-time data for a specific stock."""
        try:
            raw_data = await get_realtime_data(stock_code)
            # Extract clean data without _metadata for Pydantic model
            from tw_stock_agent.utils.mcp_error_handler import MCPResponseFormatter
            clean_data = MCPResponseFormatter.extract_metadata_for_model(raw_data)
            return RealtimeDataResponse(**clean_data)
        except TwStockAgentError as e:
            return RealtimeDataResponse(
                stock_code=stock_code,
                updated_at=e.context.timestamp.isoformat(),
                error=e.message
            )
        except Exception as e:
            return RealtimeDataResponse(
                stock_code=stock_code,
                updated_at=datetime.now().isoformat(),
                error=f"Unexpected error: {str(e)}"
            )
  • Pydantic model (schema) defining the structure and validation for the realtime data response, including fields like current_price, volume, bid/ask prices, with computed fields for analysis.
    class RealtimeDataResponse(BaseStockResponse):
        """Enhanced response model for realtime stock data."""
        
        company_name: Optional[str] = Field(
            None,
            description="Company name (股票名稱)",
            alias="name"
        )
        current_price: Optional[TWDAmount] = Field(
            None,
            description="Current price (現在價格)",
            alias="currentPrice"
        )
        opening_price: Optional[TWDAmount] = Field(
            None,
            description="Opening price (開盤價)",
            alias="open"
        )
        highest_price: Optional[TWDAmount] = Field(
            None,
            description="Highest price (最高價)",
            alias="high"
        )
        lowest_price: Optional[TWDAmount] = Field(
            None,
            description="Lowest price (最低價)",
            alias="low"
        )
        trading_volume: Optional[int] = Field(
            None,
            description="Trading volume (成交量)",
            alias="volume",
            ge=0
        )
        bid_price: Optional[TWDAmount] = Field(
            None,
            description="Best bid price (最佳買價)",
            alias="bidPrice"
        )
        ask_price: Optional[TWDAmount] = Field(
            None,
            description="Best ask price (最佳賣價)",
            alias="askPrice"
        )
        bid_size: Optional[int] = Field(
            None,
            description="Bid size (買張數)",
            alias="bidSize",
            ge=0
        )
        ask_size: Optional[int] = Field(
            None,
            description="Ask size (賣張數)",
            alias="askSize",
            ge=0
        )
        market_status: Optional[str] = Field(
            None,
            description="Market status (市場狀態)",
            alias="marketStatus"
        )
        
        def __init__(self, **data):
            # Handle backward compatibility
            for price_field in ['current_price', 'open', 'high', 'low', 'bid_price', 'ask_price']:
                field_name = price_field
                if price_field == 'open':
                    field_name = 'opening_price'
                elif price_field == 'high':
                    field_name = 'highest_price'
                elif price_field == 'low':
                    field_name = 'lowest_price'
                # current_price, bid_price, ask_price stay the same
                
                if price_field in data and data[price_field] is not None:
                    value = data[price_field]
                    if not isinstance(value, dict):
                        data[field_name] = {'amount': value}
                    if price_field != field_name:  # Only remove if field name changed
                        data.pop(price_field, None)
            
            super().__init__(**data)
            self.metadata.data_type = "realtime_data"
        
        @field_validator("market_status")
        @classmethod
        def validate_market_status(cls, v: Optional[str]) -> Optional[str]:
            """Validate market status."""
            if v is None:
                return None
            
            valid_statuses = {
                'open', 'closed', 'pre_market', 'after_hours', 
                'opening_auction', 'closing_auction', 'trading_halt'
            }
            
            v_lower = v.lower().replace(' ', '_')
            if v_lower not in valid_statuses:
                return 'unknown'
            
            return v_lower
        
        @computed_field
        @property
        def price_change_from_open(self) -> Optional[Dict[str, Any]]:
            """Calculate price change from opening."""
            if not self.current_price or not self.opening_price or self.opening_price.amount == 0:
                return None
            
            change_amount = self.current_price.amount - self.opening_price.amount
            change_percentage = float((change_amount / self.opening_price.amount) * 100)
            
            return {
                "change_amount": float(change_amount),
                "change_percentage": change_percentage,
                "is_positive": change_amount > 0,
                "formatted_change": f"{'+' if change_amount >= 0 else ''}{change_amount:.2f}"
            }
        
        @computed_field
        @property
        def bid_ask_spread(self) -> Optional[Dict[str, Any]]:
            """Calculate bid-ask spread."""
            if not self.bid_price or not self.ask_price:
                return None
            
            spread_amount = self.ask_price.amount - self.bid_price.amount
            mid_price = (self.ask_price.amount + self.bid_price.amount) / 2
            spread_percentage = float((spread_amount / mid_price) * 100) if mid_price > 0 else None
            
            return {
                "spread_amount": float(spread_amount),
                "spread_percentage": spread_percentage,
                "mid_price": float(mid_price)
            }
        
        @computed_field
        @property
        def is_actively_trading(self) -> bool:
            """Check if stock is actively trading."""
            return (
                self.market_status == 'open' and
                self.current_price is not None and
                self.trading_volume is not None and
                self.trading_volume > 0
            )
  • Core helper function in StockService that fetches realtime stock data from twstock API, implements caching, circuit breaking, retry logic, and error handling.
    @with_async_error_handling(operation="get_realtime_data")
    @with_retry(max_retries=1, base_delay=0.5)  # Shorter retry for real-time data
    async def get_realtime_data(self, stock_code: str) -> dict[str, Any]:
        """
        獲取即時股票資訊
        
        Args:
            stock_code: 股票代號
            
        Returns:
            即時股票資訊
            
        Raises:
            InvalidStockCodeError: 股票代號格式錯誤
            StockNotFoundError: 找不到股票
            StockDataUnavailableError: 即時資料無法取得
            StockMarketClosedError: 股市休市
        """
        # Track request for performance monitoring
        self._performance_monitor.record_request()
        
        # Validate stock code
        validated_code = StockCodeValidator.validate_stock_code(stock_code)
        
        cache_key = f"realtime_{validated_code}"
        cached_data = self._get_cached_data(cache_key)
        if cached_data:
            return cached_data
        
        try:
            # Use circuit breaker for external API call
            realtime_data = await self.circuit_breaker.acall(
                asyncio.to_thread, twstock.realtime.get, validated_code
            )
            
            if not realtime_data:
                raise StockNotFoundError(
                    stock_code=validated_code,
                    message=f"No real-time data available for stock '{validated_code}'"
                )
            
            if realtime_data.get('success', False) is False:
                # Check if market is closed
                current_hour = datetime.now().hour
                if current_hour < 9 or current_hour > 14:  # Taiwan market hours: 9:00-13:30
                    from tw_stock_agent.exceptions import StockMarketClosedError
                    raise StockMarketClosedError(
                        stock_code=validated_code,
                        message="Taiwan stock market is closed. Trading hours: 9:00 AM - 1:30 PM (GMT+8)"
                    )
                else:
                    raise StockDataUnavailableError(
                        stock_code=validated_code,
                        data_type="real-time data",
                        message="Real-time data temporarily unavailable"
                    )
            
            realtime_info = realtime_data.get('realtime', {})
            stock_info = realtime_data.get('info', {})
            
            data = {
                "stock_code": validated_code,
                "name": stock_info.get('name'),
                "current_price": realtime_info.get('latest_trade_price'),
                "open": realtime_info.get('open'),
                "high": realtime_info.get('high'),
                "low": realtime_info.get('low'),
                "volume": realtime_info.get('accumulate_trade_volume'),
                "updated_at": datetime.now().isoformat(),
                "market_status": "open" if realtime_data.get('success') else "closed"
            }
            
            # 使用標籤進行分組管理
            tags = ["realtime", f"stock_{validated_code}", "live_data"]
            self._set_cached_data(cache_key, data, self.cache_ttl['realtime'], tags)
            return data
            
        except (StockDataUnavailableError, StockNotFoundError):
            # Track error and re-raise specific stock errors
            self._performance_monitor.record_error()
            raise
        except Exception as e:
            # Track error and convert generic exceptions to appropriate stock errors
            self._performance_monitor.record_error()
            if "connection" in str(e).lower() or "timeout" in str(e).lower():
                raise StockDataUnavailableError(
                    stock_code=validated_code,
                    data_type="real-time data",
                    message=f"Unable to fetch real-time data due to network issues: {str(e)}"
                )
            else:
                raise StockDataUnavailableError(
                    stock_code=validated_code,
                    data_type="real-time data",
                    message=f"Failed to fetch real-time data: {str(e)}"
                )
  • Intermediate helper function that validates input parameters, delegates to StockService, and formats the response for MCP compatibility.
    @mcp_error_handler("get_realtime_data")
    async def get_realtime_data(stock_code: str) -> dict[str, Any]:
        """
        取得即時股票資訊
        
        Args:
            stock_code: 股票代號,例如:2330
            
        Returns:
            即時股票資訊
            
        Raises:
            InvalidStockCodeError: 股票代號格式錯誤
            StockNotFoundError: 找不到股票
            StockDataUnavailableError: 即時資料無法取得
            StockMarketClosedError: 股市休市
        """
        # Validate request parameters
        validated_params = validate_stock_request(stock_code=stock_code)
        
        # Fetch real-time data
        result = await stock_service.get_realtime_data(validated_params["stock_code"])
        
        # Format response for MCP with enhanced realtime structure
        return MCPResponseFormatter.format_realtime_data_response(result)
Behavior2/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations are provided, so the description carries the full burden of behavioral disclosure. It mentions 'real-time data' but does not specify details like data freshness, rate limits, authentication needs, or error handling. This leaves significant gaps in understanding how the tool behaves beyond its basic action.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is a single, efficient sentence that directly states the tool's function without unnecessary words. It is front-loaded and appropriately sized, making it easy to parse quickly.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness2/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the tool's complexity (a data retrieval operation with no annotations or output schema), the description is incomplete. It lacks details on return values, error conditions, or behavioral traits, making it insufficient for an AI agent to fully understand how to use the tool effectively in context.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters2/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The input schema has 0% description coverage, with only a parameter 'stock_code' documented structurally. The description adds minimal semantics by implying the parameter is for a stock, but it does not explain format (e.g., ticker symbol), constraints, or examples, failing to compensate for the low schema coverage.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the verb 'Get' and the resource 'real-time data for a specific stock,' making the purpose understandable. However, it does not distinguish this tool from siblings like 'get_stock_data' or 'get_price_history,' which might also retrieve stock-related information, leaving some ambiguity about uniqueness.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides no guidance on when to use this tool versus alternatives such as 'get_stock_data' or 'get_price_history.' It lacks context about prerequisites, exclusions, or specific scenarios, offering only a basic statement of function without usage instructions.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

Latest Blog Posts

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/clsung/tw-stock-agent'

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