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        tracing::info!(
54            tool = "futu_get_kline",
55            symbol = %req.symbol,
56            kl_type = %req.kl_type,
57            // v1.4.90 P2-C: Option<i32> -> f64 NaN sentinel, JSON layer 输出 number / null
58            count = crate::state::audit_fmt::opt_i32(req.count)
59        );
60        let client = self
61            .read_client_or_err("futu_get_kline", &req_ctx, None, None)
62            .await?;
63        Self::wrap_result(
64            handlers::market::get_kline(
65                &client,
66                &req.symbol,
67                &req.kl_type,
68                req.count,
69                req.begin.as_deref(),
70                req.end.as_deref(),
71            )
72            .await,
73        )
74    }
75
76    #[tool(
77        description = "Get the order book (bids and asks with price, volume, order count). Auto-subscribes OrderBook."
78    )]
79    async fn futu_get_orderbook(
80        &self,
81        Parameters(req): Parameters<OrderBookReq>,
82        req_ctx: RequestContext<RoleServer>,
83    ) -> std::result::Result<String, String> {
84        tracing::info!(tool = "futu_get_orderbook", symbol = %req.symbol, depth = req.depth);
85        let client = self
86            .read_client_or_err("futu_get_orderbook", &req_ctx, None, None)
87            .await?;
88        Self::wrap_result(handlers::market::get_orderbook(&client, &req.symbol, req.depth).await)
89    }
90
91    #[tool(description = "Get recent ticker (trade-by-trade). Auto-subscribes Ticker.")]
92    async fn futu_get_ticker(
93        &self,
94        Parameters(req): Parameters<TickerReq>,
95        req_ctx: RequestContext<RoleServer>,
96    ) -> std::result::Result<String, String> {
97        tracing::info!(tool = "futu_get_ticker", symbol = %req.symbol, count = req.count);
98        let client = self
99            .read_client_or_err("futu_get_ticker", &req_ctx, None, None)
100            .await?;
101        Self::wrap_result(handlers::market::get_ticker(&client, &req.symbol, req.count).await)
102    }
103
104    #[tool(
105        description = "Get intraday (RT / time-sharing) minute-by-minute price series. Auto-subscribes RT."
106    )]
107    async fn futu_get_rt(
108        &self,
109        Parameters(req): Parameters<SymbolReq>,
110        req_ctx: RequestContext<RoleServer>,
111    ) -> std::result::Result<String, String> {
112        tracing::info!(tool = "futu_get_rt", symbol = %req.symbol);
113        let client = self
114            .read_client_or_err("futu_get_rt", &req_ctx, None, None)
115            .await?;
116        Self::wrap_result(handlers::market::get_rt(&client, &req.symbol).await)
117    }
118
119    #[tool(
120        description = "Get static info (name, lot size, listing date) for one or more securities. No subscription needed."
121    )]
122    async fn futu_get_static(
123        &self,
124        Parameters(req): Parameters<SymbolListReq>,
125        req_ctx: RequestContext<RoleServer>,
126    ) -> std::result::Result<String, String> {
127        tracing::info!(tool = "futu_get_static", symbols = ?req.symbols);
128        let client = self
129            .read_client_or_err("futu_get_static", &req_ctx, None, None)
130            .await?;
131        Self::wrap_result(handlers::market::get_static(&client, &req.symbols).await)
132    }
133
134    #[tool(description = "Get the broker queue (HK only). Auto-subscribes Broker.")]
135    async fn futu_get_broker(
136        &self,
137        Parameters(req): Parameters<SymbolReq>,
138        req_ctx: RequestContext<RoleServer>,
139    ) -> std::result::Result<String, String> {
140        tracing::info!(tool = "futu_get_broker", symbol = %req.symbol);
141        let client = self
142            .read_client_or_err("futu_get_broker", &req_ctx, None, None)
143            .await?;
144        Self::wrap_result(handlers::market::get_broker(&client, &req.symbol).await)
145    }
146
147    #[tool(description = "List plates by market and set type (industry / region / concept / all).")]
148    async fn futu_list_plates(
149        &self,
150        Parameters(req): Parameters<PlateListReq>,
151        req_ctx: RequestContext<RoleServer>,
152    ) -> std::result::Result<String, String> {
153        tracing::info!(tool = "futu_list_plates", market = %req.market, set = %req.plate_set);
154        let client = self
155            .read_client_or_err("futu_list_plates", &req_ctx, None, None)
156            .await?;
157        Self::wrap_result(handlers::plate::list_plates(&client, &req.market, &req.plate_set).await)
158    }
159
160    #[tool(description = "List constituent securities of a plate.")]
161    async fn futu_plate_stocks(
162        &self,
163        Parameters(req): Parameters<PlateStocksReq>,
164        req_ctx: RequestContext<RoleServer>,
165    ) -> std::result::Result<String, String> {
166        tracing::info!(tool = "futu_plate_stocks", plate = %req.plate);
167        let client = self
168            .read_client_or_err("futu_plate_stocks", &req_ctx, None, None)
169            .await?;
170        Self::wrap_result(handlers::plate::plate_stocks(&client, &req.plate).await)
171    }
172}