1use crate::handlers;
4use crate::tool_args::*;
5use rmcp::{
6 RoleServer, handler::server::wrapper::Parameters, service::RequestContext, tool, tool_router,
7};
8
9use super::FutuServer;
10
11#[tool_router(router = reference_tool_router, vis = "pub(crate)")]
12impl FutuServer {
13 #[tool(
16 description = "Capital flow (net inflow) time series for a security. Python SDK: OpenQuoteContext.get_capital_flow."
17 )]
18 async fn futu_get_capital_flow(
19 &self,
20 Parameters(req): Parameters<CapitalFlowReq>,
21 req_ctx: RequestContext<RoleServer>,
22 ) -> std::result::Result<String, String> {
23 tracing::info!(tool = "futu_get_capital_flow", symbol = %req.symbol);
24 let client = self
25 .read_client_or_err("futu_get_capital_flow", &req_ctx, None, None)
26 .await?;
27 Self::wrap_result(
28 handlers::analysis::get_capital_flow(
29 &client,
30 &req.symbol,
31 req.period_type,
32 req.begin_time,
33 req.end_time,
34 )
35 .await,
36 )
37 }
38
39 #[tool(
40 description = "Capital distribution (super/big/mid/small order in/out flow amounts) snapshot. Python SDK: OpenQuoteContext.get_capital_distribution."
41 )]
42 async fn futu_get_capital_distribution(
43 &self,
44 Parameters(req): Parameters<SymbolReq>,
45 req_ctx: RequestContext<RoleServer>,
46 ) -> std::result::Result<String, String> {
47 tracing::info!(tool = "futu_get_capital_distribution", symbol = %req.symbol);
48 let client = self
49 .read_client_or_err("futu_get_capital_distribution", &req_ctx, None, None)
50 .await?;
51 Self::wrap_result(handlers::analysis::get_capital_distribution(&client, &req.symbol).await)
52 }
53
54 #[tool(
55 description = "Company profile labels/details for a security. Futu API v10.6: OpenQuoteContext.get_company_profile."
56 )]
57 async fn futu_get_company_profile(
58 &self,
59 Parameters(req): Parameters<SymbolReq>,
60 req_ctx: RequestContext<RoleServer>,
61 ) -> std::result::Result<String, String> {
62 tracing::info!(tool = "futu_get_company_profile", symbol = %req.symbol);
63 let client = self
64 .read_client_or_err("futu_get_company_profile", &req_ctx, None, None)
65 .await?;
66 Self::wrap_result(handlers::reference::get_company_profile(&client, &req.symbol).await)
67 }
68
69 #[tool(
70 description = "Company executives / directors for a security. Futu API v10.6: OpenQuoteContext.get_company_executives."
71 )]
72 async fn futu_get_company_executives(
73 &self,
74 Parameters(req): Parameters<SymbolReq>,
75 req_ctx: RequestContext<RoleServer>,
76 ) -> std::result::Result<String, String> {
77 tracing::info!(tool = "futu_get_company_executives", symbol = %req.symbol);
78 let client = self
79 .read_client_or_err("futu_get_company_executives", &req_ctx, None, None)
80 .await?;
81 Self::wrap_result(handlers::reference::get_company_executives(&client, &req.symbol).await)
82 }
83
84 #[tool(
85 description = "Company executive/director background for a security. Futu API v10.6: OpenQuoteContext.get_company_executive_background."
86 )]
87 async fn futu_get_company_executive_background(
88 &self,
89 Parameters(req): Parameters<CompanyExecutiveBackgroundReq>,
90 req_ctx: RequestContext<RoleServer>,
91 ) -> std::result::Result<String, String> {
92 tracing::info!(
93 tool = "futu_get_company_executive_background",
94 symbol = %req.symbol,
95 leader_name = %req.leader_name
96 );
97 let client = self
98 .read_client_or_err(
99 "futu_get_company_executive_background",
100 &req_ctx,
101 None,
102 None,
103 )
104 .await?;
105 Self::wrap_result(
106 handlers::reference::get_company_executive_background(
107 &client,
108 &req.symbol,
109 &req.leader_name,
110 )
111 .await,
112 )
113 }
114
115 #[tool(
116 description = "Query current market state for a list of securities (open/closed/lunch-break etc). Python SDK: OpenQuoteContext.get_market_state."
117 )]
118 async fn futu_get_market_state(
119 &self,
120 Parameters(req): Parameters<MarketStateReq>,
121 req_ctx: RequestContext<RoleServer>,
122 ) -> std::result::Result<String, String> {
123 tracing::info!(tool = "futu_get_market_state", count = req.symbols.len());
124 let client = self
125 .read_client_or_err("futu_get_market_state", &req_ctx, None, None)
126 .await?;
127 Self::wrap_result(handlers::analysis::get_market_state(&client, &req.symbols).await)
128 }
129
130 #[tool(
133 description = "Historical K-line / OHLCV time series with rehab type control (forward/backward/none) and pagination-friendly max_count. Python SDK: OpenQuoteContext.request_history_kline."
134 )]
135 async fn futu_get_history_kline(
136 &self,
137 Parameters(req): Parameters<HistoryKLineReq>,
138 req_ctx: RequestContext<RoleServer>,
139 ) -> std::result::Result<String, String> {
140 let max_count = req.validated_max_count()?;
141 tracing::info!(
142 tool = "futu_get_history_kline",
143 symbol = %req.symbol,
144 kl_type = %req.kl_type,
145 rehab = %req.rehab_type
146 );
147 let client = self
148 .read_client_or_err("futu_get_history_kline", &req_ctx, None, None)
149 .await?;
150 Self::wrap_result(
151 handlers::analysis::get_history_kline(
152 &client,
153 &req.symbol,
154 &req.kl_type,
155 &req.rehab_type,
156 &req.begin,
157 &req.end,
158 max_count,
159 )
160 .await,
161 )
162 }
163
164 #[tool(
165 description = "List plates (industry/concept/region) that contain given stocks. Python SDK: OpenQuoteContext.get_owner_plate."
166 )]
167 async fn futu_get_owner_plate(
168 &self,
169 Parameters(req): Parameters<SymbolListReq>,
170 req_ctx: RequestContext<RoleServer>,
171 ) -> std::result::Result<String, String> {
172 tracing::info!(tool = "futu_get_owner_plate", count = req.symbols.len());
173 let client = self
174 .read_client_or_err("futu_get_owner_plate", &req_ctx, None, None)
175 .await?;
176 Self::wrap_result(handlers::analysis::get_owner_plate(&client, &req.symbols).await)
177 }
178
179 #[tool(
180 description = "Related securities of an underlying: list all warrants/futures/options derived from a given stock. Python SDK: OpenQuoteContext.get_referencestock_list."
181 )]
182 async fn futu_get_reference(
183 &self,
184 Parameters(req): Parameters<ReferenceReq>,
185 req_ctx: RequestContext<RoleServer>,
186 ) -> std::result::Result<String, String> {
187 tracing::info!(
188 tool = "futu_get_reference",
189 symbol = %req.symbol,
190 reference_type = %req.reference_type
191 );
192 let client = self
193 .read_client_or_err("futu_get_reference", &req_ctx, None, None)
194 .await?;
195 Self::wrap_result(
196 handlers::analysis::get_reference(&client, &req.symbol, &req.reference_type).await,
197 )
198 }
199
200 #[tool(
201 description = "Option chain of an underlying stock within an expiry date range, grouped by strike time with call/put symbol lists. Python SDK: OpenQuoteContext.get_option_chain."
202 )]
203 async fn futu_get_option_chain(
204 &self,
205 Parameters(req): Parameters<OptionChainReq>,
206 req_ctx: RequestContext<RoleServer>,
207 ) -> std::result::Result<String, String> {
208 req.validate()?;
209 tracing::info!(
210 tool = "futu_get_option_chain",
211 owner = %req.owner_symbol,
212 begin = %req.begin_time,
213 end = %req.end_time
214 );
215 let client = self
216 .read_client_or_err("futu_get_option_chain", &req_ctx, None, None)
217 .await?;
218 let any_filter = req.delta_min.is_some()
220 || req.delta_max.is_some()
221 || req.iv_min.is_some()
222 || req.iv_max.is_some()
223 || req.oi_min.is_some()
224 || req.oi_max.is_some()
225 || req.gamma_min.is_some()
226 || req.gamma_max.is_some()
227 || req.vega_min.is_some()
228 || req.vega_max.is_some()
229 || req.theta_min.is_some()
230 || req.theta_max.is_some();
231 let data_filter = if any_filter {
232 Some(futu_proto::qot_get_option_chain::DataFilter {
233 implied_volatility_min: req.iv_min,
234 implied_volatility_max: req.iv_max,
235 delta_min: req.delta_min,
236 delta_max: req.delta_max,
237 gamma_min: req.gamma_min,
238 gamma_max: req.gamma_max,
239 vega_min: req.vega_min,
240 vega_max: req.vega_max,
241 theta_min: req.theta_min,
242 theta_max: req.theta_max,
243 rho_min: None,
244 rho_max: None,
245 net_open_interest_min: None,
246 net_open_interest_max: None,
247 open_interest_min: req.oi_min,
248 open_interest_max: req.oi_max,
249 vol_min: None,
250 vol_max: None,
251 })
252 } else {
253 None
254 };
255 Self::wrap_result(
256 handlers::analysis::get_option_chain(
257 &client,
258 handlers::analysis::OptionChainInput {
259 owner_symbol: &req.owner_symbol,
260 begin_time: &req.begin_time,
261 end_time: &req.end_time,
262 option_type_str: req.option_type.as_deref(),
263 data_filter,
264 },
265 )
266 .await,
267 )
268 }
269
270 #[tool(
273 description = "List warrants on an underlying stock (or whole-market when owner_symbol omitted), sorted by volume desc. Python SDK: OpenQuoteContext.get_warrant. For advanced filtering (strike/premium/delta/etc.) use REST /api/warrant directly."
274 )]
275 async fn futu_get_warrant(
276 &self,
277 Parameters(req): Parameters<WarrantReq>,
278 req_ctx: RequestContext<RoleServer>,
279 ) -> std::result::Result<String, String> {
280 req.validate()?;
281 tracing::info!(
282 tool = "futu_get_warrant",
283 owner = %crate::state::audit_fmt::opt_str(req.owner_symbol.as_deref()),
285 begin = req.begin,
286 num = req.num
287 );
288 let client = self
289 .read_client_or_err("futu_get_warrant", &req_ctx, None, None)
290 .await?;
291 Self::wrap_result(
292 handlers::reference::get_warrant(
293 &client,
294 req.owner_symbol.as_deref(),
295 req.begin,
296 req.num,
297 )
298 .await,
299 )
300 }
301
302 #[tool(
303 description = "Upcoming / recent IPOs for a market. Python SDK: OpenQuoteContext.get_ipo_list. market: 1=HK, 2=HK_FUTURE, 11=US, 21=SH/CN, 22=SZ, 31=SG, 41=JP, 61=MY."
304 )]
305 async fn futu_get_ipo_list(
306 &self,
307 Parameters(req): Parameters<IpoListReq>,
308 req_ctx: RequestContext<RoleServer>,
309 ) -> std::result::Result<String, String> {
310 req.validate()?;
311 tracing::info!(tool = "futu_get_ipo_list", market = req.market);
312 let client = self
313 .read_client_or_err("futu_get_ipo_list", &req_ctx, None, None)
314 .await?;
315 Self::wrap_result(handlers::reference::get_ipo_list(&client, req.market).await)
316 }
317
318 #[tool(
319 description = "Future contract info (contract size, last trade date, trading hours). Python SDK: OpenQuoteContext.get_future_info."
320 )]
321 async fn futu_get_future_info(
322 &self,
323 Parameters(req): Parameters<FutureInfoReq>,
324 req_ctx: RequestContext<RoleServer>,
325 ) -> std::result::Result<String, String> {
326 tracing::info!(tool = "futu_get_future_info", count = req.symbols.len());
327 let client = self
328 .read_client_or_err("futu_get_future_info", &req_ctx, None, None)
329 .await?;
330 Self::wrap_result(handlers::reference::get_future_info(&client, &req.symbols).await)
331 }
332
333 #[tool(
334 description = "List the user's custom + system watchlist groups. Python SDK: OpenQuoteContext.get_user_security_group. group_type: 1=custom, 2=system, 3=all."
335 )]
336 async fn futu_get_user_security_group(
337 &self,
338 Parameters(req): Parameters<UserSecurityGroupReq>,
339 req_ctx: RequestContext<RoleServer>,
340 ) -> std::result::Result<String, String> {
341 req.validate()?;
342 tracing::info!(
343 tool = "futu_get_user_security_group",
344 group_type = req.group_type
345 );
346 let client = self
347 .read_client_or_err("futu_get_user_security_group", &req_ctx, None, None)
348 .await?;
349 Self::wrap_result(
350 handlers::reference::get_user_security_group(&client, req.group_type).await,
351 )
352 }
353
354 #[tool(
355 description = "Stock filter / scanner (minimal: market + pagination). Python SDK: OpenQuoteContext.get_stock_filter. For condition-based filters (PE/cap/volume/etc.) use REST /api/stock-filter directly."
356 )]
357 async fn futu_get_stock_filter(
358 &self,
359 Parameters(req): Parameters<StockFilterReq>,
360 req_ctx: RequestContext<RoleServer>,
361 ) -> std::result::Result<String, String> {
362 req.validate()?;
363 tracing::info!(
364 tool = "futu_get_stock_filter",
365 market = req.market,
366 begin = req.begin,
367 num = req.num
368 );
369 let client = self
370 .read_client_or_err("futu_get_stock_filter", &req_ctx, None, None)
371 .await?;
372 Self::wrap_result(
373 handlers::reference::get_stock_filter(&client, req.market, req.begin, req.num).await,
374 )
375 }
376
377 #[tool(
380 description = "Trading days for a market in a date range. Python SDK: OpenQuoteContext.request_trading_days. Note: returns natural-day-minus-weekends-and-holidays, excluding temporary market closures."
381 )]
382 async fn futu_get_trading_days(
383 &self,
384 Parameters(req): Parameters<TradingDaysReq>,
385 req_ctx: RequestContext<RoleServer>,
386 ) -> std::result::Result<String, String> {
387 req.validate()?;
388 tracing::info!(
389 tool = "futu_get_trading_days",
390 market = req.market,
391 begin = %req.begin_time,
392 end = %req.end_time
393 );
394 let client = self
395 .read_client_or_err("futu_get_trading_days", &req_ctx, None, None)
396 .await?;
397 Self::wrap_result(
398 handlers::reference::get_trading_days(
399 &client,
400 req.market,
401 &req.begin_time,
402 &req.end_time,
403 )
404 .await,
405 )
406 }
407
408 #[tool(
409 description = "Rehab (dividend / split / bonus) events and adjustment factors. Required for long-term K-line alignment. Python SDK: OpenQuoteContext.get_rehab."
410 )]
411 async fn futu_get_rehab(
412 &self,
413 Parameters(req): Parameters<SymbolReq>,
414 req_ctx: RequestContext<RoleServer>,
415 ) -> std::result::Result<String, String> {
416 tracing::info!(tool = "futu_get_rehab", symbol = %req.symbol);
417 let client = self
418 .read_client_or_err("futu_get_rehab", &req_ctx, None, None)
419 .await?;
420 Self::wrap_result(handlers::reference::get_rehab(&client, &req.symbol).await)
421 }
422
423 #[tool(
424 description = "Suspend (trading halt) days for securities in a date range. Python SDK: OpenQuoteContext.get_suspend."
425 )]
426 async fn futu_get_suspend(
427 &self,
428 Parameters(req): Parameters<SuspendReq>,
429 req_ctx: RequestContext<RoleServer>,
430 ) -> std::result::Result<String, String> {
431 tracing::info!(
432 tool = "futu_get_suspend",
433 count = req.symbols.len(),
434 begin = %req.begin_time,
435 end = %req.end_time
436 );
437 let client = self
438 .read_client_or_err("futu_get_suspend", &req_ctx, None, None)
439 .await?;
440 Self::wrap_result(
441 handlers::reference::get_suspend(&client, &req.symbols, &req.begin_time, &req.end_time)
442 .await,
443 )
444 }
445
446 #[tool(
447 description = "List securities in a user watchlist group. Python SDK: OpenQuoteContext.get_user_security. Use futu_get_user_security_group to find available group names."
448 )]
449 async fn futu_get_user_security(
450 &self,
451 Parameters(req): Parameters<UserSecurityReq>,
452 req_ctx: RequestContext<RoleServer>,
453 ) -> std::result::Result<String, String> {
454 tracing::info!(tool = "futu_get_user_security", group = %req.group_name);
455 let client = self
456 .read_client_or_err("futu_get_user_security", &req_ctx, None, None)
457 .await?;
458 Self::wrap_result(handlers::reference::get_user_security(&client, &req.group_name).await)
459 }
460
461 #[tool(
464 description = "Get gateway global state: per-market trading status, server version / time, quote & trade login status. Python SDK: OpenContext.get_global_state."
465 )]
466 async fn futu_get_global_state(
467 &self,
468 Parameters(_req): Parameters<NoArgs>,
469 req_ctx: RequestContext<RoleServer>,
470 ) -> std::result::Result<String, String> {
471 tracing::info!(tool = "futu_get_global_state");
472 let client = self
473 .read_client_or_err("futu_get_global_state", &req_ctx, None, None)
474 .await?;
475 Self::wrap_result(handlers::core::get_global_state(&client).await)
476 }
477
478 #[tool(
479 description = "Get user info: nickname, per-market quote permissions, subscribe quota, history-K quota. Python SDK: OpenContext.get_user_info."
480 )]
481 async fn futu_get_user_info(
482 &self,
483 Parameters(_req): Parameters<NoArgs>,
484 req_ctx: RequestContext<RoleServer>,
485 ) -> std::result::Result<String, String> {
486 tracing::info!(tool = "futu_get_user_info");
487 let client = self
488 .read_client_or_err("futu_get_user_info", &req_ctx, None, None)
489 .await?;
490 Self::wrap_result(handlers::core::get_user_info(&client).await)
491 }
492
493 #[tool(
494 description = "Get quote-rights profile grouped like Futu OpenD GUI: HK/US/CN/SG/JP/crypto permissions, raw values, labels and quota. Set refresh=true to trigger request_highest_quote_right first."
495 )]
496 async fn futu_get_quote_rights(
497 &self,
498 Parameters(req): Parameters<QuoteRightsReq>,
499 req_ctx: RequestContext<RoleServer>,
500 ) -> std::result::Result<String, String> {
501 tracing::info!(
502 tool = "futu_get_quote_rights",
503 refresh = req.refresh.unwrap_or(false)
504 );
505 let client = self
506 .read_client_or_err("futu_get_quote_rights", &req_ctx, None, None)
507 .await?;
508 Self::wrap_result(
509 handlers::core::get_quote_rights(&client, req.refresh.unwrap_or(false)).await,
510 )
511 }
512
513 #[tool(
514 description = "Get delay-statistics summary: counts of quote-push / request-reply / place-order samples. Python SDK: OpenContext.get_delay_statistics. For raw per-segment buckets use REST /api/delay-statistics."
515 )]
516 async fn futu_get_delay_statistics(
517 &self,
518 Parameters(_req): Parameters<NoArgs>,
519 req_ctx: RequestContext<RoleServer>,
520 ) -> std::result::Result<String, String> {
521 tracing::info!(tool = "futu_get_delay_statistics");
522 let client = self
523 .read_client_or_err("futu_get_delay_statistics", &req_ctx, None, None)
524 .await?;
525 Self::wrap_result(handlers::core::get_delay_statistics(&client).await)
526 }
527
528 #[tool(
529 description = "Query Futu Token / moomoo Token enable + bind state. Returns 4 fields: \
530 nn_token_enable, nn_token_bind, mm_token_enable, mm_token_bind \
531 (1=enabled/bound, 0=disabled/unbound). \
532 Use case: when /api/unlock-trade fails with -20011 (\"please enable Futu Token\"), \
533 call this tool first to diagnose which side is missing token binding."
534 )]
535 async fn futu_get_token_state(
536 &self,
537 Parameters(_req): Parameters<NoArgs>,
538 req_ctx: RequestContext<RoleServer>,
539 ) -> std::result::Result<String, String> {
540 tracing::info!(tool = "futu_get_token_state");
541 let client = self
542 .read_client_or_err("futu_get_token_state", &req_ctx, None, None)
543 .await?;
544 Self::wrap_result(handlers::core::get_token_state(&client, None).await)
546 }
547
548 #[tool(
549 description = "Risk-free rate for HK / US / JP markets (option pricing baseline, \
550 e.g. Black-Scholes). Returns percent values (e.g. 4.5 means 4.5%) plus raw \
551 uint64 (×10^9). Useful for pricing options or computing implied volatility / \
552 cost of carry."
553 )]
554 async fn futu_get_risk_free_rate(
555 &self,
556 Parameters(_req): Parameters<NoArgs>,
557 req_ctx: RequestContext<RoleServer>,
558 ) -> std::result::Result<String, String> {
559 tracing::info!(tool = "futu_get_risk_free_rate");
560 let client = self
561 .read_client_or_err("futu_get_risk_free_rate", &req_ctx, None, None)
562 .await?;
563 Self::wrap_result(handlers::core::get_risk_free_rate(&client).await)
564 }
565
566 #[tool(
567 description = "Get full spread tables (price tick rules per market). Returns \
568 spread_table_list with spread_code + price intervals (price_from / price_to / \
569 value, in actual decimals). Useful for client-side price validation before \
570 PlaceOrder / ModifyOrder."
571 )]
572 async fn futu_get_spread_table(
573 &self,
574 Parameters(_req): Parameters<NoArgs>,
575 req_ctx: RequestContext<RoleServer>,
576 ) -> std::result::Result<String, String> {
577 tracing::info!(tool = "futu_get_spread_table");
578 let client = self
579 .read_client_or_err("futu_get_spread_table", &req_ctx, None, None)
580 .await?;
581 Self::wrap_result(handlers::core::get_spread_table(&client).await)
582 }
583
584 #[tool(description = "Per-stock ticker statistic \
585 (avg_price / volume / buy_volume / sell_volume / neutral_volume / trade_num). \
586 Symbol format: 'HK.00700' / 'US.AAPL'. Pre-condition: must \
587 subscribe / get_static_info first to populate stock_id in static_cache. \
588 ticker_type: 0=ALL, 1=BUY, 2=SELL, 3=BUY_AND_SELL, 4=NEUTRAL. \
589 stat_type: 0=ALL, 1=BEFORE, 2=TRADING, 3=AFTER (market session).")]
590 async fn futu_get_ticker_statistic(
591 &self,
592 Parameters(req): Parameters<TickerStatisticReq>,
593 req_ctx: RequestContext<RoleServer>,
594 ) -> std::result::Result<String, String> {
595 tracing::info!(tool = "futu_get_ticker_statistic", symbol = %req.symbol);
596 let client = self
597 .read_client_or_err("futu_get_ticker_statistic", &req_ctx, None, None)
598 .await?;
599 Self::wrap_result(
600 handlers::core::get_ticker_statistic(
601 &client,
602 &req.symbol,
603 req.ticker_type,
604 req.stat_type,
605 )
606 .await,
607 )
608 }
609
610 #[tool(
611 description = "Per-stock ticker statistic detail (price-level distribution). \
612 Companion of futu_get_ticker_statistic. Typical flow: \
613 (1) call futu_get_ticker_statistic to get ticker_time + summary stats, \
614 (2) call this tool with same ticker_time to get DetailItem list \
615 (price / buy_volume / sell_volume / volume / ratio / neutral_volume per price level). \
616 Symbol format: 'HK.00700' / 'US.AAPL'. Pre-condition: must subscribe / get_static_info \
617 first to populate stock_id in static_cache. \
618 ticker_type: 0=ALL, 1=BUY, 2=SELL, 3=BUY_AND_SELL, 4=NEUTRAL. \
619 stat_type: 0=ALL, 1=BEFORE, 2=TRADING, 3=AFTER. \
620 select_num: 0=all levels, 1..N=top N (backend max ~100). \
621 data_from / data_max_count: pagination."
622 )]
623 async fn futu_get_ticker_statistic_detail(
624 &self,
625 Parameters(req): Parameters<TickerStatisticDetailReq>,
626 req_ctx: RequestContext<RoleServer>,
627 ) -> std::result::Result<String, String> {
628 req.validate()?;
629 tracing::info!(tool = "futu_get_ticker_statistic_detail", symbol = %req.symbol);
630 let client = self
631 .read_client_or_err("futu_get_ticker_statistic_detail", &req_ctx, None, None)
632 .await?;
633 Self::wrap_result(
634 handlers::core::get_ticker_statistic_detail(
635 &client,
636 handlers::core::TickerStatisticDetailInput {
637 symbol: &req.symbol,
638 ticker_type: req.ticker_type,
639 ticker_time: req.ticker_time,
640 select_num: req.select_num,
641 data_from: req.data_from,
642 data_max_count: req.data_max_count,
643 stat_type: req.stat_type,
644 },
645 )
646 .await,
647 )
648 }
649
650 #[tool(
655 description = "Historical K-line download quota (used / remain). Python SDK: OpenQuoteContext.get_history_kl_quota."
656 )]
657 async fn futu_get_history_kl_quota(
658 &self,
659 Parameters(req): Parameters<HistoryKlQuotaReq>,
660 req_ctx: RequestContext<RoleServer>,
661 ) -> std::result::Result<String, String> {
662 tracing::info!(tool = "futu_get_history_kl_quota", detail = req.get_detail);
663 let client = self
664 .read_client_or_err("futu_get_history_kl_quota", &req_ctx, None, None)
665 .await?;
666 Self::wrap_result(handlers::reference::get_history_kl_quota(&client, req.get_detail).await)
667 }
668
669 #[tool(
670 description = "Top-holder share change list (institution / fund / executive). Python SDK: OpenQuoteContext.get_holding_change_list."
671 )]
672 async fn futu_get_holding_change(
673 &self,
674 Parameters(req): Parameters<HoldingChangeReq>,
675 req_ctx: RequestContext<RoleServer>,
676 ) -> std::result::Result<String, String> {
677 tracing::info!(
678 tool = "futu_get_holding_change",
679 symbol = %req.symbol,
680 category = req.holder_category
681 );
682 let client = self
683 .read_client_or_err("futu_get_holding_change", &req_ctx, None, None)
684 .await?;
685 Self::wrap_result(
686 handlers::reference::get_holding_change(
687 &client,
688 &req.symbol,
689 req.holder_category,
690 req.begin_time.as_deref(),
691 req.end_time.as_deref(),
692 )
693 .await,
694 )
695 }
696
697 #[tool(
698 description = "Modify watchlist group — add / delete / move-out stocks. `op` is an INTEGER (not a string literal): 1=AddInto, 2=Delete-from-group, 3=MoveOut. Python SDK: OpenQuoteContext.modify_user_security."
699 )]
700 async fn futu_modify_user_security(
701 &self,
702 Parameters(req): Parameters<ModifyUserSecurityReq>,
703 req_ctx: RequestContext<RoleServer>,
704 ) -> std::result::Result<String, String> {
705 req.validate()?;
706 tracing::info!(
712 tool = "futu_modify_user_security",
713 group = %req.group_name,
714 op = req.op,
715 count = req.symbols.len()
716 );
717 let client = self
718 .read_client_or_err("futu_modify_user_security", &req_ctx, None, None)
719 .await?;
720 Self::wrap_result(
721 handlers::reference::modify_user_security(
722 &client,
723 &req.group_name,
724 req.op,
725 &req.symbols,
726 )
727 .await,
728 )
729 }
730
731 #[tool(
732 description = "Code change / temporary-ticker info (currently HK market only). Python SDK: OpenQuoteContext.get_code_change."
733 )]
734 async fn futu_get_code_change(
735 &self,
736 Parameters(req): Parameters<CodeChangeReq>,
737 req_ctx: RequestContext<RoleServer>,
738 ) -> std::result::Result<String, String> {
739 tracing::info!(tool = "futu_get_code_change", count = req.symbols.len());
740 let client = self
741 .read_client_or_err("futu_get_code_change", &req_ctx, None, None)
742 .await?;
743 Self::wrap_result(handlers::reference::get_code_change(&client, &req.symbols).await)
744 }
745
746 #[tool(
747 description = "Option implied-volatility analysis. Futu API v10.6: OpenQuoteContext.get_option_volatility."
748 )]
749 async fn futu_get_option_volatility(
750 &self,
751 Parameters(req): Parameters<OptionVolatilityReq>,
752 req_ctx: RequestContext<RoleServer>,
753 ) -> std::result::Result<String, String> {
754 req.validate()?;
755 tracing::info!(
756 tool = "futu_get_option_volatility",
757 symbol = %req.symbol,
758 query_time_period = ?req.query_time_period,
759 hv_time_period = ?req.hv_time_period
760 );
761 let client = self
762 .read_client_or_err("futu_get_option_volatility", &req_ctx, None, None)
763 .await?;
764 Self::wrap_result(
765 handlers::reference::get_option_volatility(
766 &client,
767 &req.symbol,
768 req.query_time_period,
769 req.hv_time_period,
770 )
771 .await,
772 )
773 }
774
775 #[tool(
776 description = "Option exercise probability history. Futu API v10.6: OpenQuoteContext.get_option_exercise_probability."
777 )]
778 async fn futu_get_option_exercise_probability(
779 &self,
780 Parameters(req): Parameters<OptionExerciseProbabilityReq>,
781 req_ctx: RequestContext<RoleServer>,
782 ) -> std::result::Result<String, String> {
783 tracing::info!(tool = "futu_get_option_exercise_probability", symbol = %req.symbol);
784 let client = self
785 .read_client_or_err("futu_get_option_exercise_probability", &req_ctx, None, None)
786 .await?;
787 Self::wrap_result(
788 handlers::reference::get_option_exercise_probability(&client, &req.symbol).await,
789 )
790 }
791}