Skip to main content

futu_mcp/tools/
market.rs

1//! MCP core market-data tools (quote, snapshot, K-line, ticker, RT, static, broker, plates).
2
3use crate::handlers;
4use crate::tool_args::{
5    KLineReq, OrderBookReq, PlateListReq, PlateStocksReq, SymbolListReq, SymbolReq, TickerReq,
6};
7use rmcp::{
8    RoleServer, handler::server::wrapper::Parameters, service::RequestContext, tool, tool_router,
9};
10
11use super::FutuServer;
12
13#[tool_router(router = market_tool_router, vis = "pub(crate)")]
14impl FutuServer {
15    #[tool(
16        description = "Get real-time basic quote (price, volume, turnover) for a security. Auto-subscribes SubType::Basic on first call."
17    )]
18    async fn futu_get_quote(
19        &self,
20        Parameters(req): Parameters<SymbolReq>,
21        req_ctx: RequestContext<RoleServer>,
22    ) -> std::result::Result<String, String> {
23        tracing::info!(tool = "futu_get_quote", symbol = %req.symbol);
24        let client = self
25            .read_client_or_err("futu_get_quote", &req_ctx, None, None)
26            .await?;
27        Self::wrap_result(handlers::core::get_quote(&client, &req.symbol).await)
28    }
29
30    #[tool(
31        description = "Get a security snapshot (one-shot, no subscription) with extended fields: 52-week high/low, avg price, volume ratio, amplitude, bid/ask."
32    )]
33    async fn futu_get_snapshot(
34        &self,
35        Parameters(req): Parameters<SymbolReq>,
36        req_ctx: RequestContext<RoleServer>,
37    ) -> std::result::Result<String, String> {
38        tracing::info!(tool = "futu_get_snapshot", symbol = %req.symbol);
39        let client = self
40            .read_client_or_err("futu_get_snapshot", &req_ctx, None, None)
41            .await?;
42        Self::wrap_result(handlers::core::get_snapshot(&client, &req.symbol).await)
43    }
44
45    #[tool(
46        description = "Get historical K-line (OHLCV). Supports day/week/month/quarter/year plus 1/3/5/15/30/60 minute bars."
47    )]
48    async fn futu_get_kline(
49        &self,
50        Parameters(req): Parameters<KLineReq>,
51        req_ctx: RequestContext<RoleServer>,
52    ) -> std::result::Result<String, String> {
53        req.validate()?;
54        tracing::info!(
55            tool = "futu_get_kline",
56            symbol = %req.symbol,
57            kl_type = %req.kl_type,
58            // v1.4.90 P2-C: Option<i32> -> f64 NaN sentinel, JSON layer 输出 number / null
59            count = crate::state::audit_fmt::opt_i32(req.count)
60        );
61        let client = self
62            .read_client_or_err("futu_get_kline", &req_ctx, None, None)
63            .await?;
64        Self::wrap_result(
65            handlers::market::get_kline(
66                &client,
67                &req.symbol,
68                &req.kl_type,
69                req.count,
70                req.begin.as_deref(),
71                req.end.as_deref(),
72            )
73            .await,
74        )
75    }
76
77    #[tool(
78        description = "Get the order book (bids and asks with price, volume, order count). Auto-subscribes OrderBook; set odd_lot=true for SG/MY odd-lot orderbook."
79    )]
80    async fn futu_get_orderbook(
81        &self,
82        Parameters(req): Parameters<OrderBookReq>,
83        req_ctx: RequestContext<RoleServer>,
84    ) -> std::result::Result<String, String> {
85        req.validate()?;
86        tracing::info!(
87            tool = "futu_get_orderbook",
88            symbol = %req.symbol,
89            depth = req.depth,
90            odd_lot = req.odd_lot
91        );
92        let client = self
93            .read_client_or_err("futu_get_orderbook", &req_ctx, None, None)
94            .await?;
95        Self::wrap_result(
96            handlers::market::get_orderbook(&client, &req.symbol, req.depth, req.odd_lot).await,
97        )
98    }
99
100    #[tool(description = "Get recent ticker (trade-by-trade). Auto-subscribes Ticker.")]
101    async fn futu_get_ticker(
102        &self,
103        Parameters(req): Parameters<TickerReq>,
104        req_ctx: RequestContext<RoleServer>,
105    ) -> std::result::Result<String, String> {
106        req.validate()?;
107        tracing::info!(tool = "futu_get_ticker", symbol = %req.symbol, count = req.count);
108        let client = self
109            .read_client_or_err("futu_get_ticker", &req_ctx, None, None)
110            .await?;
111        Self::wrap_result(handlers::market::get_ticker(&client, &req.symbol, req.count).await)
112    }
113
114    #[tool(
115        description = "Get intraday (RT / time-sharing) minute-by-minute price series. Auto-subscribes RT."
116    )]
117    async fn futu_get_rt(
118        &self,
119        Parameters(req): Parameters<SymbolReq>,
120        req_ctx: RequestContext<RoleServer>,
121    ) -> std::result::Result<String, String> {
122        tracing::info!(tool = "futu_get_rt", symbol = %req.symbol);
123        let client = self
124            .read_client_or_err("futu_get_rt", &req_ctx, None, None)
125            .await?;
126        Self::wrap_result(handlers::market::get_rt(&client, &req.symbol).await)
127    }
128
129    #[tool(
130        description = "Get static info (name, lot size, listing date) for one or more securities. No subscription needed."
131    )]
132    async fn futu_get_static(
133        &self,
134        Parameters(req): Parameters<SymbolListReq>,
135        req_ctx: RequestContext<RoleServer>,
136    ) -> std::result::Result<String, String> {
137        tracing::info!(tool = "futu_get_static", symbols = ?req.symbols);
138        let client = self
139            .read_client_or_err("futu_get_static", &req_ctx, None, None)
140            .await?;
141        Self::wrap_result(handlers::market::get_static(&client, &req.symbols).await)
142    }
143
144    #[tool(description = "Get the broker queue (HK only). Auto-subscribes Broker.")]
145    async fn futu_get_broker(
146        &self,
147        Parameters(req): Parameters<SymbolReq>,
148        req_ctx: RequestContext<RoleServer>,
149    ) -> std::result::Result<String, String> {
150        tracing::info!(tool = "futu_get_broker", symbol = %req.symbol);
151        let client = self
152            .read_client_or_err("futu_get_broker", &req_ctx, None, None)
153            .await?;
154        Self::wrap_result(handlers::market::get_broker(&client, &req.symbol).await)
155    }
156
157    #[tool(description = "List plates by market and set type (industry / region / concept / all).")]
158    async fn futu_list_plates(
159        &self,
160        Parameters(req): Parameters<PlateListReq>,
161        req_ctx: RequestContext<RoleServer>,
162    ) -> std::result::Result<String, String> {
163        tracing::info!(tool = "futu_list_plates", market = %req.market, set = %req.plate_set);
164        let client = self
165            .read_client_or_err("futu_list_plates", &req_ctx, None, None)
166            .await?;
167        Self::wrap_result(handlers::plate::list_plates(&client, &req.market, &req.plate_set).await)
168    }
169
170    #[tool(description = "List constituent securities of a plate.")]
171    async fn futu_plate_stocks(
172        &self,
173        Parameters(req): Parameters<PlateStocksReq>,
174        req_ctx: RequestContext<RoleServer>,
175    ) -> std::result::Result<String, String> {
176        tracing::info!(tool = "futu_plate_stocks", plate = %req.plate);
177        let client = self
178            .read_client_or_err("futu_plate_stocks", &req_ctx, None, None)
179            .await?;
180        Self::wrap_result(handlers::plate::plate_stocks(&client, &req.plate).await)
181    }
182}