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