Skip to main content

futu_mcp/tool_args/qot/
misc.rs

1//! MCP QOT request schemas split from the tool-args root.
2
3use super::*;
4
5#[derive(Debug, Deserialize, schemars::JsonSchema)]
6#[serde(deny_unknown_fields)]
7pub struct UserSecurityGroupReq {
8    #[schemars(description = "Group type: 1=custom, 2=system, 3=all (default 1)")]
9    #[serde(default = "default_user_security_group_type")]
10    pub group_type: i32,
11}
12
13impl UserSecurityGroupReq {
14    pub fn validate(&self) -> Result<(), String> {
15        if (1..=3).contains(&self.group_type) {
16            return Ok(());
17        }
18        Err(format!(
19            "group_type must be in 1..=3 (1=custom, 2=system, 3=all), got {}",
20            self.group_type
21        ))
22    }
23}
24
25#[derive(Debug, Deserialize, schemars::JsonSchema)]
26#[serde(deny_unknown_fields)]
27pub struct StockFilterReq {
28    #[schemars(
29        description = "Market code accepted by the StockFilter backend: int 1=HK, 2=HK_FUTURE, 11=US, 21=SH/CN, 22=SZ, 31=SG, 41=JP, 61=MY; \
30                       or string HK/HK_FUTURE/US/SH/SZ/CN/SG/JP/MY."
31    )]
32    // v1.4.84 §5 B2 field migration
33    #[serde(deserialize_with = "deser_hk_hkfuture_us_cn_market_as_i32")]
34    pub market: i32,
35    #[schemars(description = "Pagination begin index (default 0); alias: offset / skip")]
36    // v1.4.84 §5 B1
37    #[serde(default, alias = "offset", alias = "skip")]
38    pub begin: i32,
39    #[schemars(description = "Max rows (0-200, default 50); alias: count / max_count / req_count")]
40    #[serde(
41        default = "default_stock_filter_num",
42        alias = "count",
43        alias = "max_count",
44        alias = "req_count"
45    )]
46    pub num: i32,
47}
48
49impl StockFilterReq {
50    pub fn validate(&self) -> Result<(), String> {
51        if !matches!(self.market, 1 | 2 | 11 | 21 | 22 | 31 | 41 | 61) {
52            return Err(format!(
53                "futu_get_stock_filter market must be 1, 2, 11, 21, 22, 31, 41, or 61 (HK/HK_FUTURE/US/SH/SZ/SG/JP/MY), got {}",
54                self.market
55            ));
56        }
57        futu_qot::page_bounds::validate_begin_num(self.begin, self.num, 200, "stock_filter")
58            .map(|_| ())
59            .map_err(|err| err.to_string())
60    }
61}
62
63#[derive(Debug, Deserialize, schemars::JsonSchema)]
64#[serde(deny_unknown_fields)]
65pub struct TradingDaysReq {
66    #[schemars(
67        description = "Market code — **Qot_Common.TradeDateMarket enum** (i32, NOT QotMarket!): \
68                       1=HK, 2=US, 3=CN, 4=NorthboundSZ/SH, 5=SouthboundHK, \
69                       6=JP_Future, 7=SG_Future, 8=SG, 9=MY, 10=JP. \
70                       Different from QotMarket (ipo_list/stock_filter use 1=HK 2=HK_FUTURE 11=US 21=SH 22=SZ). \
71                       Legacy QotMarket aliases 11=US, 21=SH, 22=SZ are accepted for backward compatibility."
72    )]
73    pub market: i32,
74    #[schemars(description = "Begin date (yyyy-MM-dd); alias: begin / start_time / from")]
75    // v1.4.84 §5 B1
76    #[serde(alias = "begin", alias = "start_time", alias = "from")]
77    pub begin_time: String,
78    #[schemars(description = "End date (yyyy-MM-dd); alias: end / to")]
79    #[serde(alias = "end", alias = "to")]
80    pub end_time: String,
81}
82
83impl TradingDaysReq {
84    pub fn validate(&self) -> Result<(), String> {
85        if matches!(self.market, 1..=10 | 11 | 21 | 22) {
86            return Ok(());
87        }
88        Err(format!(
89            "futu_get_trading_days market must be TradeDateMarket 1..=10 \
90             (legacy QotMarket aliases 11/21/22 are also accepted), got {}",
91            self.market
92        ))
93    }
94}
95
96#[derive(Debug, Deserialize, schemars::JsonSchema)]
97#[serde(deny_unknown_fields)]
98pub struct SuspendReq {
99    #[schemars(description = "Array of security symbols in MARKET.CODE format \
100                       (e.g. [\"HK.00700\", \"HK.09988\"]). Alias: stocks / code_list / symbol_list / security_list")]
101    // v1.4.84 §5 B1
102    #[serde(
103        alias = "stocks",
104        alias = "code_list",
105        alias = "symbol_list",
106        alias = "security_list"
107    )]
108    pub symbols: Vec<String>,
109    #[schemars(description = "Begin date (yyyy-MM-dd); alias: begin / start_time / from")]
110    #[serde(alias = "begin", alias = "start_time", alias = "from")]
111    pub begin_time: String,
112    #[schemars(description = "End date (yyyy-MM-dd); alias: end / to")]
113    #[serde(alias = "end", alias = "to")]
114    pub end_time: String,
115}
116
117#[derive(Debug, Deserialize, schemars::JsonSchema)]
118#[serde(deny_unknown_fields)]
119pub struct UserSecurityReq {
120    #[schemars(
121        description = "Watchlist group name (use futu_get_user_security_group to list groups); alias: group / name"
122    )]
123    // v1.4.84 §5 B1
124    #[serde(alias = "group", alias = "name")]
125    pub group_name: String,
126}
127
128#[derive(Debug, Deserialize, schemars::JsonSchema)]
129#[serde(deny_unknown_fields)]
130pub struct HistoryKlQuotaReq {
131    #[schemars(
132        description = "Whether to fetch detailed per-symbol download history (default false)"
133    )]
134    #[serde(default)]
135    pub get_detail: bool,
136}
137
138#[derive(Debug, Deserialize, schemars::JsonSchema)]
139#[serde(deny_unknown_fields)]
140pub struct HoldingChangeReq {
141    #[schemars(
142        description = "Underlying stock symbol (e.g. HK.00700, US.AAPL); alias: code / stock"
143    )]
144    // v1.4.84 §5 B1
145    #[serde(alias = "code", alias = "stock")]
146    pub symbol: String,
147    #[schemars(
148        description = "Holder category: 1=Institution, 2=Fund, 3=Executive; alias: category"
149    )]
150    #[serde(alias = "category")]
151    pub holder_category: i32,
152    #[schemars(
153        description = "Begin time YYYY-MM-DD HH:MM:SS (optional); alias: begin / start_time / from"
154    )]
155    #[serde(default, alias = "begin", alias = "start_time", alias = "from")]
156    pub begin_time: Option<String>,
157    #[schemars(description = "End time YYYY-MM-DD HH:MM:SS (optional); alias: end / to")]
158    #[serde(default, alias = "end", alias = "to")]
159    pub end_time: Option<String>,
160}
161
162#[derive(Debug, Deserialize, schemars::JsonSchema)]
163#[serde(deny_unknown_fields)]
164pub struct ModifyUserSecurityReq {
165    #[schemars(description = "Watchlist group name; alias: group / name")]
166    // v1.4.84 §5 B1
167    #[serde(alias = "group", alias = "name")]
168    pub group_name: String,
169    #[schemars(
170        description = "Op: 1=AddInto, 2=Delete (from this group), 3=MoveOut; alias: op_type / operation"
171    )]
172    #[serde(alias = "op_type", alias = "operation")]
173    pub op: i32,
174    #[schemars(
175        description = "Security symbols to add/delete/move; alias: stocks / code_list / symbol_list / security_list"
176    )]
177    #[serde(
178        alias = "stocks",
179        alias = "code_list",
180        alias = "symbol_list",
181        alias = "security_list"
182    )]
183    pub symbols: Vec<String>,
184}
185
186impl ModifyUserSecurityReq {
187    pub fn validate(&self) -> Result<(), String> {
188        if (1..=3).contains(&self.op) {
189            return Ok(());
190        }
191        Err(format!(
192            "op must be in 1..=3 (1=add, 2=delete, 3=move out), got {}",
193            self.op
194        ))
195    }
196}
197
198#[derive(Debug, Deserialize, schemars::JsonSchema)]
199#[serde(deny_unknown_fields)]
200pub struct CodeChangeReq {
201    #[schemars(
202        description = "Security symbols to query (currently HK only); alias: stocks / code_list / symbol_list / security_list"
203    )]
204    // v1.4.84 §5 B1
205    #[serde(
206        alias = "stocks",
207        alias = "code_list",
208        alias = "symbol_list",
209        alias = "security_list"
210    )]
211    pub symbols: Vec<String>,
212}
213
214#[derive(Debug, Deserialize, schemars::JsonSchema)]
215#[serde(deny_unknown_fields)]
216pub struct BizGroupReq {
217    #[schemars(description = "Trade env: real / simulate (default real)")]
218    #[serde(default = "default_env", alias = "trd_env")]
219    pub env: String,
220    #[schemars(description = "Trading account ID (u64)")]
221    pub acc_id: u64,
222    #[schemars(
223        description = "Optional legacy market hint; accepted for backward compatibility but ignored. Daemon derives backend market from acc_id/account cache."
224    )]
225    #[serde(
226        default,
227        deserialize_with = "tool_enums::deser_trd_market_as_option_string"
228    )]
229    pub market: Option<String>,
230}
231
232#[derive(Debug, Deserialize, schemars::JsonSchema)]
233#[serde(deny_unknown_fields)]
234pub struct BondSymbolReq {
235    #[schemars(description = "Trade env: real / simulate (default real)")]
236    #[serde(default = "default_env", alias = "trd_env")]
237    pub env: String,
238    #[schemars(description = "Trading account ID (u64) for per-broker routing")]
239    pub acc_id: u64,
240    #[schemars(description = "Market: HK / US / SG; aliases USA and SG_UNIVERSAL are accepted")]
241    pub market: String,
242    #[schemars(description = "Bond symbol (债券代码, 如 HK1234 或 11000018)")]
243    pub symbol: String,
244}
245
246/// v1.4.98 T2-3 (mobile-source-audit Phase 2): TickerStatistic params.
247#[derive(Debug, Deserialize, schemars::JsonSchema)]
248#[serde(deny_unknown_fields)]
249pub struct TickerStatisticReq {
250    #[schemars(description = "Security symbol in MARKET.CODE format, e.g. HK.00700, US.AAPL")]
251    #[serde(alias = "code", alias = "stock", alias = "security")]
252    pub symbol: String,
253    #[schemars(
254        description = "Ticker type filter: 0=ALL, 1=BUY, 2=SELL, 3=BUY_AND_SELL, 4=NEUTRAL (default ALL)"
255    )]
256    #[serde(default)]
257    pub ticker_type: Option<i32>,
258    #[schemars(description = "Market session: 0=ALL, 1=BEFORE, 2=TRADING, 3=AFTER (default ALL)")]
259    #[serde(default)]
260    pub stat_type: Option<u32>,
261}
262
263/// v1.4.106 codex 0500 ζ23-redo: TickerStatistic Detail params (cmd 6366).
264///
265/// 配套 `futu_get_ticker_statistic` (Info, cmd 6365): 先 call Info 拿
266/// `ticker_time`, 再 call Detail 同 `ticker_time` 拿该时点的价位分布 list.
267#[derive(Debug, Deserialize, schemars::JsonSchema)]
268#[serde(deny_unknown_fields)]
269pub struct TickerStatisticDetailReq {
270    #[schemars(description = "Security symbol in MARKET.CODE format, e.g. HK.00700, US.AAPL")]
271    #[serde(alias = "code", alias = "stock", alias = "security")]
272    pub symbol: String,
273    #[schemars(
274        description = "Ticker type filter: 0=ALL, 1=BUY, 2=SELL, 3=BUY_AND_SELL, 4=NEUTRAL (default ALL)"
275    )]
276    #[serde(default)]
277    pub ticker_type: Option<i32>,
278    #[schemars(
279        description = "Ticker timestamp (ms) — usually from prior futu_get_ticker_statistic call. \
280            0 / omit = use backend latest available."
281    )]
282    #[serde(default)]
283    pub ticker_time: Option<u64>,
284    #[schemars(
285        description = "Filter type: 0=all price levels, 1..N=top N levels (backend max ~100)"
286    )]
287    #[serde(default)]
288    pub select_num: Option<u32>,
289    #[schemars(description = "Pagination start offset (default 0)")]
290    #[serde(default)]
291    pub data_from: Option<u32>,
292    #[schemars(description = "Pagination size, max items returned; if provided, must be positive")]
293    #[serde(default)]
294    pub data_max_count: Option<u32>,
295    #[schemars(description = "Market session: 0=ALL, 1=BEFORE, 2=TRADING, 3=AFTER (default ALL)")]
296    #[serde(default)]
297    pub stat_type: Option<u32>,
298}
299
300impl TickerStatisticDetailReq {
301    pub fn validate(&self) -> Result<(), String> {
302        if self.data_max_count == Some(0) {
303            return Err("data_max_count must be positive when provided".to_string());
304        }
305        Ok(())
306    }
307}
308
309#[derive(Debug, Deserialize, schemars::JsonSchema)]
310#[serde(deny_unknown_fields)]
311pub struct QuoteRightsReq {
312    #[schemars(description = "If true, trigger request_highest_quote_right before querying")]
313    #[serde(default)]
314    pub refresh: Option<bool>,
315}