1#![allow(unused_imports)]
4
5use rmcp::schemars;
6use serde::{Deserialize, Serialize};
7
8use crate::tool_enums;
9use crate::tool_enums::ToolEnum;
10
11use super::*;
12
13#[derive(Debug, Deserialize, schemars::JsonSchema)]
14#[serde(deny_unknown_fields)]
15pub struct TrdAccReq {
16 #[schemars(
17 description = "Trade market — accepts STRING (HK|US|CN|HKCC|FUTURES|SG|AU|JP|MY|CA) OR INT (1=HK, 2=US, 3=CN, 4=HKCC, 5=Futures, 6=SG, 8=AU, 15=JP, 111=MY, 112=CA per Trd_Common.TrdMarket). v1.4.90 P0-E + P1-G 双接 + 5 markets 扩, v1.4.93 C2 加 Futures=5 (期货下单入口)."
18 )]
19 #[serde(deserialize_with = "tool_enums::deser_trd_market_as_string")]
21 pub market: String,
22 #[schemars(
23 description = "Trading account ID (u64). Either `acc_id` OR `card_num` is required. `card_num` accepts the 4-digit suffix shown in the App or the full 16-digit card number."
24 )]
25 #[serde(default, skip_serializing_if = "Option::is_none")]
26 pub acc_id: Option<u64>,
27 #[schemars(
28 description = "App-visible card number. Accepts 4-digit suffix or 16-digit full card number. Either `acc_id` OR `card_num` is required; if both are passed, daemon validates they refer to the same account."
29 )]
30 #[serde(default, skip_serializing_if = "Option::is_none")]
31 pub card_num: Option<String>,
32 #[schemars(description = "Trade environment: real|simulate (default real); alias: trd_env")]
33 #[serde(default = "default_env", alias = "trd_env")]
35 pub env: String,
36
37 #[schemars(
43 description = "Optional per-call API key plaintext. Same priority as PlaceOrderReq.api_key: tool args > HTTP Bearer > startup. Use to scope this call to a specific narrow key."
44 )]
45 #[serde(default, skip_serializing_if = "Option::is_none")]
46 pub api_key: Option<String>,
47
48 #[schemars(
54 description = "Optional currency for fund response unit (HKD|USD|CNH|JPY|SGD|AUD|CAD|MYR|USDT). If omitted, daemon uses the broker/account default view currency. Explicit values are validated against the account; single-market accounts may ignore explicit currency and return their base currency."
55 )]
56 #[serde(default, skip_serializing_if = "Option::is_none")]
57 pub currency: Option<String>,
58}
59
60#[derive(Debug, Deserialize, Serialize, schemars::JsonSchema)]
61#[serde(deny_unknown_fields)]
62pub struct PlaceOrderReq {
63 #[schemars(
64 description = "Trade market — accepts STRING (HK|US|CN|HKCC|FUTURES|SG|AU|JP|MY|CA) OR INT (1=HK, 2=US, 3=CN, 4=HKCC, 5=Futures, 6=SG, 8=AU, 15=JP, 111=MY, 112=CA per Trd_Common.TrdMarket). v1.4.90 P0-E + P1-G 双接 + 5 markets 扩, v1.4.93 C2 加 Futures=5 (期货下单入口)."
65 )]
66 #[serde(deserialize_with = "tool_enums::deser_trd_market_as_string")]
68 pub market: String,
69 #[schemars(
70 description = "Trading account ID (u64). Either `acc_id` OR `card_num` is required. ⚠️ Call `futu_list_accounts` first to discover acc_id — gateway does NOT infer a default. v1.4.105 D12: alternatively pass `card_num` (App 显示的 4 位末尾或 16 位完整) and daemon resolves it via GetAccList."
71 )]
72 #[serde(default, skip_serializing_if = "Option::is_none")]
75 pub acc_id: Option<u64>,
76 #[schemars(
77 description = "v1.4.105 D12: card number (App 显示卡号). Accepts 4-digit suffix (e.g. `<card-suffix>`, App 内显示如 \"Margin Composite Account (`<card-suffix>`)\") OR 16-digit full (e.g. `<full-card-num>`). 示例为 synthetic placeholder, 不是真实账户信息. Daemon resolves via GetAccList → matched acc_id. **Either `acc_id` OR `card_num` required**; if both passed, daemon validates resolution matches acc_id (mismatch = 400 reject)."
78 )]
79 #[serde(default, skip_serializing_if = "Option::is_none")]
80 pub card_num: Option<String>,
81 #[schemars(
82 description = "Trade environment: real|simulate. Defaults to simulate for safety. Alias: trd_env"
83 )]
84 #[serde(default = "default_env_simulate", alias = "trd_env")]
86 pub env: String,
87 #[schemars(description = "Order side: BUY|SELL|SELL_SHORT|BUY_BACK. Alias: trd_side")]
88 #[serde(alias = "trd_side")]
90 pub side: String,
91 #[schemars(
92 description = "Order type — accepts STRING enum OR INT (Trd_Common.OrderType). v1.4.90 P0-E 双接覆盖 v1.4.53 全 17 variant: \
93 NORMAL=1 (limit) | MARKET=2 | ABSOLUTE_LIMIT=5 | AUCTION=6 | AUCTION_LIMIT=7 | SPECIAL_LIMIT=8 | SPECIAL_LIMIT_ALL=9 | \
94 v1.4.53 条件单: STOP=10 (止损市价) | STOP_LIMIT=11 (止损限价) | MIT=12 (止盈触及市价) | LIT=13 (止盈触及限价) | TRAILING_STOP=14 (跟踪止损市价) | \
95 TRAILING_STOP_LIMIT=15 (跟踪止损限价) | TWAP_MARKET=16 | TWAP_LIMIT=17 | VWAP_MARKET=18 | VWAP_LIMIT=19. 条件单须搭配 `stop_price` / `trail_type` / `trail_value` / `trail_spread` 字段。alias: LIMIT → NORMAL."
96 )]
97 #[serde(
100 default = "default_order_type",
101 deserialize_with = "tool_enums::deser_order_type_as_string"
102 )]
103 pub order_type: String,
104 #[schemars(description = "Security code WITHOUT market prefix, e.g. 00700 / AAPL / 600519")]
105 pub code: String,
106 #[schemars(description = "Order quantity (shares / contracts)")]
107 pub qty: f64,
108 #[schemars(description = "Limit price (required for NORMAL; optional for MARKET)")]
109 pub price: Option<f64>,
110 #[schemars(
111 description = "Optional per-call API key override (plaintext). When set, this key is used for scope/limits/audit instead of the process-wide FUTU_MCP_API_KEY. Useful for multi-tenant scenarios where different calls should be billed / scoped to different keys."
112 )]
113 #[serde(default, skip_serializing_if = "Option::is_none")]
114 pub api_key: Option<String>,
115 #[schemars(
116 description = "v1.4.39: Optional idempotency key. When set, retries with the same key within 90-second TTL return the cached response WITHOUT placing a duplicate order. Example: generate a UUID per logical order intent; if agent retry fires, pass the same key. Without this field, each call places a separate order (backward-compatible with v1.4.38)."
117 )]
118 #[serde(default, skip_serializing_if = "Option::is_none")]
119 pub idempotency_key: Option<String>,
120 #[schemars(
122 description = "v1.4.53 条件单: stop / take-profit trigger price (aka aux_price). Required for STOP / STOP_LIMIT / MIT (market-if-touched) / LIT (limit-if-touched). For MIT/LIT it's the take-profit trigger."
123 )]
124 #[serde(default, skip_serializing_if = "Option::is_none")]
125 pub stop_price: Option<f64>,
126 #[schemars(
127 description = "v1.4.53 trailing stop: 1=Ratio (percentage) / 2=Amount (absolute value). Only for TRAILING_STOP / TRAILING_STOP_LIMIT order types."
128 )]
129 #[serde(default, skip_serializing_if = "Option::is_none")]
130 pub trail_type: Option<i32>,
131 #[schemars(
132 description = "v1.4.53 trailing stop: trail percentage (if trail_type=1) or amount (if trail_type=2)."
133 )]
134 #[serde(default, skip_serializing_if = "Option::is_none")]
135 pub trail_value: Option<f64>,
136 #[schemars(
137 description = "v1.4.53 trailing stop limit: price spread for TRAILING_STOP_LIMIT (limit offset from trigger)."
138 )]
139 #[serde(default, skip_serializing_if = "Option::is_none")]
140 pub trail_spread: Option<f64>,
141}
142
143#[derive(Debug, Deserialize, Serialize, schemars::JsonSchema)]
144#[serde(deny_unknown_fields)]
145pub struct ModifyOrderReq {
146 #[schemars(
147 description = "Trade market — accepts STRING (HK|US|CN|HKCC|FUTURES|SG|AU|JP|MY|CA) OR INT (1=HK, 2=US, 3=CN, 4=HKCC, 5=Futures, 6=SG, 8=AU, 15=JP, 111=MY, 112=CA per Trd_Common.TrdMarket). v1.4.90 P0-E + P1-G 双接 + 5 markets 扩, v1.4.93 C2 加 Futures=5 (期货下单入口)."
148 )]
149 #[serde(deserialize_with = "tool_enums::deser_trd_market_as_string")]
151 pub market: String,
152 #[schemars(
153 description = "Trading account ID (u64). Either `acc_id` OR `card_num` is required. v1.4.105 D12: alternative is `card_num`."
154 )]
155 #[serde(default, skip_serializing_if = "Option::is_none")]
156 pub acc_id: Option<u64>,
157 #[schemars(
158 description = "v1.4.105 D12: card number (4-digit suffix or 16-digit full). See PlaceOrderReq.card_num for semantics."
159 )]
160 #[serde(default, skip_serializing_if = "Option::is_none")]
161 pub card_num: Option<String>,
162 #[schemars(
163 description = "Trade environment: real|simulate (default simulate); alias: trd_env"
164 )]
165 #[serde(default = "default_env_simulate", alias = "trd_env")]
167 pub env: String,
168 #[schemars(
169 description = "Order ID to modify. Accepts numeric orderID (integer or integer string) OR backend orderIDEx string such as FU.../FH...; string recommended for JS clients since u64 > 2^53 loses precision as JSON number."
170 )]
171 #[serde(deserialize_with = "deser_order_id_raw_from_int_or_str")]
173 pub order_id: String,
174 #[schemars(
175 description = "Modify op: NORMAL (change qty/price) | CANCEL | DISABLE | ENABLE | DELETE"
176 )]
177 #[serde(default = "default_modify_op")]
178 pub op: String,
179 #[schemars(description = "New quantity (for NORMAL op)")]
180 pub qty: Option<f64>,
181 #[schemars(description = "New price (for NORMAL op)")]
182 pub price: Option<f64>,
183 #[schemars(description = "Optional per-call API key override. See PlaceOrderReq.api_key.")]
184 #[serde(default, skip_serializing_if = "Option::is_none")]
185 pub api_key: Option<String>,
186 #[schemars(
187 description = "v1.4.39: Optional idempotency key (90s TTL). See PlaceOrderReq.idempotency_key."
188 )]
189 #[serde(default, skip_serializing_if = "Option::is_none")]
190 pub idempotency_key: Option<String>,
191}
192
193#[derive(Debug, Deserialize, Serialize, schemars::JsonSchema)]
194#[serde(deny_unknown_fields)]
195pub struct CancelOrderReq {
196 #[schemars(
197 description = "Trade market — accepts STRING (HK|US|CN|HKCC|FUTURES|SG|AU|JP|MY|CA) OR INT (1=HK, 2=US, 3=CN, 4=HKCC, 5=Futures, 6=SG, 8=AU, 15=JP, 111=MY, 112=CA per Trd_Common.TrdMarket). v1.4.90 P0-E + P1-G 双接 + 5 markets 扩, v1.4.93 C2 加 Futures=5 (期货下单入口)."
198 )]
199 #[serde(deserialize_with = "tool_enums::deser_trd_market_as_string")]
201 pub market: String,
202 #[schemars(
203 description = "Trading account ID (u64). Either `acc_id` OR `card_num` is required. v1.4.105 D12: alternative is `card_num`."
204 )]
205 #[serde(default, skip_serializing_if = "Option::is_none")]
206 pub acc_id: Option<u64>,
207 #[schemars(
208 description = "v1.4.105 D12: card number (4-digit suffix or 16-digit full). See PlaceOrderReq.card_num for semantics."
209 )]
210 #[serde(default, skip_serializing_if = "Option::is_none")]
211 pub card_num: Option<String>,
212 #[schemars(
213 description = "Trade environment: real|simulate (default simulate); alias: trd_env"
214 )]
215 #[serde(default = "default_env_simulate", alias = "trd_env")]
217 pub env: String,
218 #[schemars(
219 description = "Order ID to cancel. Accepts numeric orderID (integer or integer string) OR backend orderIDEx string such as FU.../FH...; string recommended for JS clients since u64 > 2^53 loses precision as JSON number."
220 )]
221 #[serde(deserialize_with = "deser_order_id_raw_from_int_or_str")]
223 pub order_id: String,
224 #[schemars(description = "Optional per-call API key override. See PlaceOrderReq.api_key.")]
225 #[serde(default, skip_serializing_if = "Option::is_none")]
226 pub api_key: Option<String>,
227 #[schemars(
228 description = "v1.4.39: Optional idempotency key (90s TTL). See PlaceOrderReq.idempotency_key."
229 )]
230 #[serde(default, skip_serializing_if = "Option::is_none")]
231 pub idempotency_key: Option<String>,
232}
233
234#[derive(Debug, Deserialize, Serialize, schemars::JsonSchema)]
235#[serde(deny_unknown_fields)]
236pub struct ReconfirmOrderReq {
237 #[schemars(
238 description = "Trade market — accepts STRING (HK|US|CN|HKCC|FUTURES|SG|AU|JP|MY|CA) OR INT (1=HK, 2=US, 3=CN, 4=HKCC, 5=Futures, 6=SG, 8=AU, 15=JP, 111=MY, 112=CA per Trd_Common.TrdMarket)."
239 )]
240 #[serde(deserialize_with = "tool_enums::deser_trd_market_as_string")]
241 pub market: String,
242 #[schemars(
243 description = "Trading account ID (u64). Either `acc_id` OR `card_num` is required."
244 )]
245 #[serde(default, skip_serializing_if = "Option::is_none")]
246 pub acc_id: Option<u64>,
247 #[schemars(
248 description = "Card number (4-digit suffix or 16-digit full). Either `acc_id` OR `card_num` is required."
249 )]
250 #[serde(default, skip_serializing_if = "Option::is_none")]
251 pub card_num: Option<String>,
252 #[schemars(
253 description = "Trade environment: real|simulate (default simulate); alias: trd_env"
254 )]
255 #[serde(default = "default_env_simulate", alias = "trd_env")]
256 pub env: String,
257 #[schemars(
258 description = "FTAPI numeric order_id to reconfirm. Accepts JSON number or integer string; orderIDEx strings are not supported by Trd_ReconfirmOrder."
259 )]
260 #[serde(deserialize_with = "deser_order_id_raw_from_int_or_str")]
261 pub order_id: String,
262 #[schemars(description = "Reconfirm reason int per Trd_Common.ReconfirmOrderReason.")]
263 pub reason: i32,
264 #[schemars(description = "Optional per-call API key override. See PlaceOrderReq.api_key.")]
265 #[serde(default, skip_serializing_if = "Option::is_none")]
266 pub api_key: Option<String>,
267}
268
269#[derive(Debug, Deserialize, Serialize, schemars::JsonSchema)]
270#[serde(deny_unknown_fields)]
271pub struct UnlockTradeReq {
272 #[schemars(
274 description = "true to unlock trading (default); false to lock trading cipher back (defensive). Lock does not require a password."
275 )]
276 #[serde(default = "default_true")]
277 pub unlock: bool,
278 #[schemars(
281 description = "OTP / 2FA token (plaintext). Only required when a previous unlock call returned need_otp=true or err_code=-8 (TRADE_AUTH_NEED_AUTH_TOKEN). Leave empty for accounts without 2FA. Alias: token / one_time_password"
282 )]
283 #[serde(
285 default,
286 alias = "token",
287 alias = "one_time_password",
288 skip_serializing_if = "Option::is_none"
289 )]
290 pub otp: Option<String>,
291 #[schemars(
297 description = "Optional. Restrict unlock to a single security firm (broker). SecurityFirm enum (i32): 1=FutuHK, 2=FutuUS/MooMoo, 3=FutuSG, 4=FutuAU, 5=FutuCA, 6=FutuMY, 7=FutuJP. If omitted, unlocks all brokers in parallel (backward-compatible default). Alias: broker / security_firm_id"
298 )]
299 #[serde(
301 default,
302 alias = "broker",
303 alias = "security_firm_id",
304 skip_serializing_if = "Option::is_none"
305 )]
306 pub security_firm: Option<i32>,
307 #[schemars(
312 description = "Optional. Array of positive non-zero u64 acc_ids to unlock (empty / omitted = no per-account filter, use security_firm rule or unlock all). Intersects with security_firm: account must satisfy BOTH. Use when you need to exclude a shadow sub-account that shares a broker with the main account — pass only the main acc_id here. Alias: account_ids / accounts"
313 )]
314 #[serde(
316 default,
317 alias = "account_ids",
318 alias = "accounts",
319 skip_serializing_if = "Option::is_none"
320 )]
321 pub acc_ids: Option<Vec<u64>>,
322}
323
324#[derive(Debug, Deserialize, schemars::JsonSchema)]
325#[serde(deny_unknown_fields)]
326pub struct MaxTrdQtysReq {
327 #[schemars(
328 description = "Trade market — accepts STRING (HK|US|CN|HKCC|FUTURES|SG|AU|JP|MY|CA) OR INT (1=HK, 2=US, 3=CN, 4=HKCC, 5=Futures, 6=SG, 8=AU, 15=JP, 111=MY, 112=CA per Trd_Common.TrdMarket). v1.4.90 P0-E + P1-G 双接 + 5 markets 扩, v1.4.93 C2 加 Futures=5 (期货下单入口)."
329 )]
330 #[serde(deserialize_with = "tool_enums::deser_trd_market_as_string")]
332 pub market: String,
333 #[schemars(
334 description = "Trading account ID (u64). ⚠️ Call `futu_list_accounts` first to discover — gateway does NOT infer a default; omitting or inventing an id fails."
335 )]
336 pub acc_id: u64,
337 #[schemars(description = "Trade environment: real|simulate (default real); alias: trd_env")]
338 #[serde(default = "default_env", alias = "trd_env")]
340 pub env: String,
341 #[schemars(
346 description = "Order type. Accepts both INTEGER (1=NORMAL/limit, 2=MARKET, 5=AUCTION, 6=ABSOLUTE_LIMIT, 7=SPECIAL_LIMIT) and STRING enum (NORMAL|MARKET|AUCTION|ABSOLUTE_LIMIT|SPECIAL_LIMIT). Example: 1 or \"NORMAL\"."
347 )]
348 #[serde(deserialize_with = "deser_int_or_order_type_str")]
349 pub order_type: i32,
350 #[schemars(
351 description = "Security code WITHOUT market prefix (e.g. 00700 / AAPL); alias: symbol / stock"
352 )]
353 #[serde(alias = "symbol", alias = "stock")]
355 pub code: String,
356 #[schemars(description = "Limit price (pass 0.0 for market orders)")]
357 pub price: f64,
358 #[schemars(description = "Existing order_id (for modify-order max-qty calc, optional)")]
359 #[serde(default)]
360 pub order_id: Option<u64>,
361}
362
363#[derive(Debug, Deserialize, schemars::JsonSchema)]
364#[serde(deny_unknown_fields)]
365pub struct OrderFeeReq {
366 #[schemars(
367 description = "Trade market — accepts STRING (HK|US|CN|HKCC|FUTURES|SG|AU|JP|MY|CA) OR INT (1=HK, 2=US, 3=CN, 4=HKCC, 5=Futures, 6=SG, 8=AU, 15=JP, 111=MY, 112=CA per Trd_Common.TrdMarket). v1.4.90 P0-E + P1-G 双接 + 5 markets 扩, v1.4.93 C2 加 Futures=5 (期货下单入口)."
368 )]
369 #[serde(deserialize_with = "tool_enums::deser_trd_market_as_string")]
371 pub market: String,
372 #[schemars(
373 description = "Trading account ID (u64). ⚠️ Call `futu_list_accounts` first to discover — gateway does NOT infer a default; omitting or inventing an id fails."
374 )]
375 pub acc_id: u64,
376 #[schemars(description = "Trade environment: real|simulate (default real); alias: trd_env")]
377 #[serde(default = "default_env", alias = "trd_env")]
379 pub env: String,
380 #[schemars(
381 description = "Order_id_ex list (strings) — returned by place_order response; alias: order_ids_ex / order_ids"
382 )]
383 #[serde(alias = "order_ids_ex", alias = "order_ids")]
385 pub order_id_ex_list: Vec<String>,
386}
387
388#[derive(Debug, Deserialize, schemars::JsonSchema)]
389#[serde(deny_unknown_fields)]
390pub struct MarginRatioReq {
391 #[schemars(
392 description = "Trade market — accepts STRING (HK|US|CN|HKCC|FUTURES|SG|AU|JP|MY|CA) OR INT (1=HK, 2=US, 3=CN, 4=HKCC, 5=Futures, 6=SG, 8=AU, 15=JP, 111=MY, 112=CA per Trd_Common.TrdMarket). v1.4.90 P0-E + P1-G 双接 + 5 markets 扩, v1.4.93 C2 加 Futures=5 (期货下单入口)."
393 )]
394 #[serde(deserialize_with = "tool_enums::deser_trd_market_as_string")]
396 pub market: String,
397 #[schemars(
398 description = "Trading account ID (u64). ⚠️ Call `futu_list_accounts` first to discover — gateway does NOT infer a default; omitting or inventing an id fails."
399 )]
400 pub acc_id: u64,
401 #[schemars(description = "Trade environment: real|simulate (default real); alias: trd_env")]
402 #[serde(default = "default_env", alias = "trd_env")]
404 pub env: String,
405 #[schemars(
406 description = "Symbols in MARKET.CODE format (e.g. HK.00700, US.AAPL); alias: symbols / code_list / symbol_list"
407 )]
408 #[serde(alias = "symbols", alias = "code_list", alias = "symbol_list")]
410 pub codes: Vec<String>,
411}
412
413#[derive(Debug, Deserialize, schemars::JsonSchema)]
414#[serde(deny_unknown_fields)]
415pub struct HistoryQueryReq {
416 #[schemars(
417 description = "Trade market — accepts STRING (HK|US|CN|HKCC|FUTURES|SG|AU|JP|MY|CA) OR INT (1=HK, 2=US, 3=CN, 4=HKCC, 5=Futures, 6=SG, 8=AU, 15=JP, 111=MY, 112=CA per Trd_Common.TrdMarket). v1.4.90 P0-E + P1-G 双接 + 5 markets 扩, v1.4.93 C2 加 Futures=5 (期货下单入口)."
418 )]
419 #[serde(deserialize_with = "tool_enums::deser_trd_market_as_string")]
421 pub market: String,
422 #[schemars(
423 description = "Trading account ID (u64). ⚠️ Call `futu_list_accounts` first to discover — gateway does NOT infer a default; omitting or inventing an id fails."
424 )]
425 pub acc_id: u64,
426 #[schemars(description = "Trade environment: real|simulate (default real); alias: trd_env")]
427 #[serde(default = "default_env", alias = "trd_env")]
429 pub env: String,
430 #[schemars(
431 description = "Filter by codes (empty = all). Each item is bare code without market prefix. \
432 Alias: symbols / symbol_list"
433 )]
434 #[serde(default, alias = "symbols", alias = "symbol_list")]
436 pub code_list: Vec<String>,
437 #[schemars(
438 description = "Begin time 'yyyy-MM-dd HH:mm:ss' (optional); alias: begin / start_time / from"
439 )]
440 #[serde(default, alias = "begin", alias = "start_time", alias = "from")]
441 pub begin_time: Option<String>,
442 #[schemars(description = "End time 'yyyy-MM-dd HH:mm:ss' (optional); alias: end / to")]
443 #[serde(default, alias = "end", alias = "to")]
444 pub end_time: Option<String>,
445}
446
447#[derive(Debug, Deserialize, schemars::JsonSchema)]
448#[serde(deny_unknown_fields)]
449pub struct CapitalFlowReq {
450 #[schemars(
451 description = "Security symbol in MARKET.CODE format (e.g. HK.00700); alias: code / stock"
452 )]
453 #[serde(alias = "code", alias = "stock")]
455 pub symbol: String,
456 #[schemars(description = "Period type: 1=INTRADAY 2=DAY 3=WEEK 4=MONTH (default 1)")]
457 #[serde(default)]
458 pub period_type: Option<i32>,
459 #[schemars(
460 description = "Begin time 'yyyy-MM-dd' (optional, DAY/WEEK/MONTH only); alias: begin / start_time / from"
461 )]
462 #[serde(default, alias = "begin", alias = "start_time", alias = "from")]
463 pub begin_time: Option<String>,
464 #[schemars(description = "End time 'yyyy-MM-dd' (optional); alias: end / to")]
465 #[serde(default, alias = "end", alias = "to")]
466 pub end_time: Option<String>,
467}
468
469#[derive(Debug, Deserialize, schemars::JsonSchema)]
470#[serde(deny_unknown_fields)]
471pub struct AccCashFlowReq {
472 #[schemars(description = "Trade env: real / simulate (default real); alias: trd_env")]
473 #[serde(default = "default_env", alias = "trd_env")]
475 pub env: String,
476 #[schemars(
477 description = "Trading account ID (u64). ⚠️ Call `futu_list_accounts` first to discover — gateway does NOT infer a default."
478 )]
479 pub acc_id: u64,
480 #[schemars(
481 description = "Trade market — accepts STRING (HK / US / CN / HKCC / SG / AU / JP / MY / CA) OR INT (1=HK, 2=US, 3=CN, 4=HKCC, 6=SG, 8=AU, 15=JP, 111=MY, 112=CA per Trd_Common.TrdMarket). v1.4.90 P0-E + P1-G 双接 + 5 markets 扩."
482 )]
483 #[serde(deserialize_with = "tool_enums::deser_trd_market_as_string")]
485 pub market: String,
486 #[schemars(
487 description = "Clearing date (yyyy-MM-dd); queries flow entries for that day; alias: date / query_date"
488 )]
489 #[serde(alias = "date", alias = "query_date")]
491 pub clearing_date: String,
492 #[schemars(
493 description = "Direction: 1=InFlow, 2=OutFlow, omit for both; alias: flow_direction"
494 )]
495 #[serde(default, alias = "flow_direction")]
496 pub direction: Option<i32>,
497}
498
499#[derive(Debug, Deserialize, Serialize, schemars::JsonSchema)]
500#[serde(deny_unknown_fields)]
501pub struct CancelAllOrderReq {
502 #[schemars(description = "Trading env: simulate (default) / real; alias: trd_env")]
503 #[serde(default = "default_env_simulate", alias = "trd_env")]
505 pub env: String,
506 #[schemars(
507 description = "Trading account ID (u64). ⚠️ Call `futu_list_accounts` first to discover — gateway does NOT infer a default."
508 )]
509 pub acc_id: u64,
510 #[schemars(
511 description = "Market (REQUIRED, NOT optional) — accepts STRING (HK|US|CN|HKCC|FUTURES|SG|AU|JP|MY|CA) OR INT (1=HK, 2=US, 3=CN, 4=HKCC, 5=Futures, 6=SG, 8=AU, 15=JP, 111=MY, 112=CA). Leaving empty returns a validation error — the backend needs a specific market to cancel orders in. v1.4.90 P0-E + P1-G 双接 + 5 markets 扩, v1.4.93 C2 加 Futures=5."
512 )]
513 #[serde(default, deserialize_with = "deser_trd_market_string_allow_empty")]
516 pub market: String,
517 #[schemars(description = "Per-call API key override (optional)")]
518 #[serde(default)]
519 pub api_key: Option<String>,
520}
521
522impl CancelAllOrderReq {
523 pub fn validate(&self) -> Result<(), String> {
529 if self.market.trim().is_empty() {
530 return Err(
531 "CancelAllOrderReq: `market` is required and must be non-empty \
532 (e.g. HK / US / HKCC / A_SH / A_SZ / SG / JP / AU / CA)"
533 .to_string(),
534 );
535 }
536 Ok(())
537 }
538}
539
540#[derive(Debug, Deserialize, schemars::JsonSchema)]
541#[serde(deny_unknown_fields)]
542pub struct CashLogReq {
543 #[schemars(description = "Trade env: real / simulate (default real)")]
544 #[serde(default = "default_env", alias = "trd_env")]
545 pub env: String,
546 #[schemars(description = "Trading account ID (u64). Call futu_list_accounts first.")]
547 pub acc_id: u64,
548 #[schemars(
549 description = "Optional legacy market hint. Accepted for backward compatibility (HK/US/CN/HKCC/SG/AU/JP/MY/CA/HKFUND/USFUND or 1/2/3/4/6/8/15/111/112/113/123), \
550 but cash-log identity does not trust this field: daemon derives backend market from acc_id/account cache."
551 )]
552 #[serde(
553 default,
554 deserialize_with = "tool_enums::deser_trd_market_as_option_string"
555 )]
556 pub market: Option<String>,
557 #[schemars(description = "Begin time (epoch seconds, optional)")]
558 #[serde(default)]
559 pub begin_time: Option<u64>,
560 #[schemars(description = "End time (epoch seconds, optional)")]
561 #[serde(default)]
562 pub end_time: Option<u64>,
563 #[schemars(description = "Business group ID filter (default all)")]
564 #[serde(default)]
565 pub biz_group_id: Option<u32>,
566 #[schemars(
567 description = "Business sub-group ID filter (optional; value from futu_get_biz_group sub_groups)"
568 )]
569 #[serde(default)]
570 pub biz_sub_group_id: Option<u32>,
571 #[schemars(description = "In/out direction: 1=in, 2=out, 0/omit=all")]
572 #[serde(default)]
573 pub in_out: Option<u32>,
574 #[schemars(description = "Search keyword (optional)")]
575 #[serde(default)]
576 pub keyword: Option<String>,
577 #[schemars(description = "Stock symbol (e.g. AAPL.US, 00700.HK), exact match (optional)")]
578 #[serde(default)]
579 pub symbol: Option<String>,
580 #[schemars(
581 description = "Backend stock_id filter (optional; use when available from upstream/account UI data)"
582 )]
583 #[serde(default)]
584 pub stock_id: Option<u64>,
585 #[schemars(
586 description = "Cursor: log_id from previous response next_log_id (omit for first page)"
587 )]
588 #[serde(default)]
589 pub log_id: Option<String>,
590 #[schemars(description = "Max entries per response (daemon uses mobile default 50 if omit)")]
591 #[serde(default)]
592 pub max_cnt: Option<u32>,
593 #[schemars(description = "Currency filter: CNY/HKD/USD/JPY/SGD (optional)")]
594 #[serde(default)]
595 pub currency: Option<String>,
596}
597
598#[derive(Debug, Deserialize, schemars::JsonSchema)]
599#[serde(deny_unknown_fields)]
600pub struct CashDetailReq {
601 #[schemars(description = "Trade env: real / simulate (default real)")]
602 #[serde(default = "default_env", alias = "trd_env")]
603 pub env: String,
604 #[schemars(description = "Trading account ID (u64)")]
605 pub acc_id: u64,
606 #[schemars(
607 description = "Optional legacy market hint; accepted for backward compatibility but ignored. Daemon derives backend market from acc_id/account cache."
608 )]
609 #[serde(
610 default,
611 deserialize_with = "tool_enums::deser_trd_market_as_option_string"
612 )]
613 pub market: Option<String>,
614 #[schemars(description = "Cash log ID (from futu_get_cash_log response)")]
615 pub log_id: String,
616}
617
618#[derive(Debug, Deserialize, schemars::JsonSchema)]
620#[serde(deny_unknown_fields)]
621pub struct MarginInfoReq {
622 #[schemars(description = "Trade env: real / simulate (default real)")]
623 #[serde(default = "default_env", alias = "trd_env")]
624 pub env: String,
625 #[schemars(description = "Trading account ID (u64). Call futu_list_accounts first.")]
626 pub acc_id: u64,
627 #[schemars(
628 description = "Market: HK / US / CN_AH (only these 3 supported; mobile cmd 3101/3102/3107). Other markets: use futu_get_margin_ratio (per-security ratio)."
629 )]
630 pub market: String,
631}
632
633#[derive(Debug, Deserialize, schemars::JsonSchema)]
635#[serde(deny_unknown_fields)]
636pub struct AccountFlagReq {
637 #[schemars(description = "Trade env: real / simulate (default real)")]
638 #[serde(default = "default_env", alias = "trd_env")]
639 pub env: String,
640 #[schemars(description = "Trading account ID (u64) for per-broker routing")]
641 pub acc_id: u64,
642 #[schemars(
643 description = "Flag ID to query. Common: 5=US 期权确认, 8=期权测评, 10=基金 KYC (R1~R5), 11=HK 期权确认, 16=PDT 风披, 22=衍生品风批 (合并新), 23=美股 OTC, 24=港股期权测评, 25=算法交易风披, 34=人脸识别风批, 46=OpenAPI 免责. Full 36+ list in proto header."
644 )]
645 pub flag_id: u32,
646}
647
648#[derive(Debug, Deserialize, schemars::JsonSchema)]
649#[serde(deny_unknown_fields)]
650pub struct BondAccountReq {
651 #[schemars(description = "Trade env: real / simulate (default real)")]
652 #[serde(default = "default_env", alias = "trd_env")]
653 pub env: String,
654 #[schemars(description = "Trading account ID (u64) for per-broker routing")]
655 pub acc_id: u64,
656 #[schemars(description = "Market: HK / US / SG (仅 3 市场有债券业务)")]
657 pub market: String,
658}