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