Skip to main content

futu_mcp/tool_args/
trd.rs

1//! v1.4.110 P1-1: 拆自 `tool_args.rs` 按 handler 域分组.
2
3use rmcp::schemars;
4use serde::{Deserialize, Serialize};
5
6use crate::tool_enums;
7
8use super::*;
9
10mod write;
11
12pub use write::*;
13
14#[derive(Debug, Deserialize, schemars::JsonSchema)]
15#[serde(deny_unknown_fields)]
16pub struct TrdAccReq {
17    #[schemars(
18        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)."
19    )]
20    // v1.4.90 P0-E: int OR string 双接, normalize 到大写 canonical
21    #[serde(deserialize_with = "tool_enums::deser_trd_market_as_string")]
22    pub market: String,
23    #[schemars(
24        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."
25    )]
26    #[serde(default, skip_serializing_if = "Option::is_none")]
27    pub acc_id: Option<u64>,
28    #[schemars(
29        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."
30    )]
31    #[serde(default, skip_serializing_if = "Option::is_none")]
32    pub card_num: Option<String>,
33    #[schemars(description = "Trade environment: real|simulate (default real); alias: trd_env")]
34    // v1.4.83 §5 Phase 3: trd_env alias 对齐 py-futu-api TrdEnv
35    #[serde(default = "default_env", alias = "trd_env")]
36    pub env: String,
37
38    /// v1.4.103 (codex 51 F1 — B5): per-call API key override.
39    ///
40    /// 与 PlaceOrderReq.api_key 同模式 (优先级: tool args > HTTP Bearer > startup
41    /// key). 让 narrow-scope HTTP 客户端能在 read tool 上限定本次 call 的 caller
42    /// key. 留空 / 不传 → 走 HTTP Bearer 或 startup key.
43    #[schemars(
44        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."
45    )]
46    #[serde(default, skip_serializing_if = "Option::is_none")]
47    pub api_key: Option<String>,
48
49    /// 货币种类 (HKD|USD|CNH|JPY|SGD|AUD|CAD|MYR|USDT).
50    ///
51    /// 留空时 daemon 按账户所属券商派生默认资金视图币种;显式传入时会
52    /// 校验该账户是否支持对应币种。普通单市场账户可能由 backend 忽略
53    /// 显式币种, 返回其账户基准币种。
54    #[schemars(
55        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."
56    )]
57    #[serde(default, skip_serializing_if = "Option::is_none")]
58    pub currency: Option<String>,
59}
60
61#[derive(Debug, Deserialize, schemars::JsonSchema)]
62#[serde(deny_unknown_fields)]
63pub struct PositionReq {
64    #[schemars(
65        description = "Trade market — accepts STRING (HK|US|CN|HKCC|FUTURES|SG|AU|JP|MY|CA|CRYPTO) OR INT per Trd_Common.TrdMarket."
66    )]
67    #[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."
71    )]
72    #[serde(default, skip_serializing_if = "Option::is_none")]
73    pub acc_id: Option<u64>,
74    #[schemars(
75        description = "App-visible card number. Accepts 4-digit suffix or 16-digit full card number."
76    )]
77    #[serde(default, skip_serializing_if = "Option::is_none")]
78    pub card_num: Option<String>,
79    #[schemars(description = "Trade environment: real|simulate (default real); alias: trd_env")]
80    #[serde(default = "default_env", alias = "trd_env")]
81    pub env: String,
82    #[schemars(description = "Optional per-call API key plaintext.")]
83    #[serde(default, skip_serializing_if = "Option::is_none")]
84    pub api_key: Option<String>,
85    #[schemars(
86        description = "Optional position view currency (HKD|USD|CNH|JPY|SGD|AUD|CAD|MYR|USDT). Crypto accounts require an explicit view currency; non-crypto accounts may ignore it."
87    )]
88    #[serde(default, skip_serializing_if = "Option::is_none")]
89    pub currency: Option<String>,
90    #[schemars(description = "Request option strategy/combo position view. Defaults to false.")]
91    #[serde(default, alias = "optionStrategyView")]
92    pub option_strategy_view: bool,
93}
94
95impl PositionReq {
96    pub fn as_trd_acc_req(&self) -> TrdAccReq {
97        TrdAccReq {
98            market: self.market.clone(),
99            acc_id: self.acc_id,
100            card_num: self.card_num.clone(),
101            env: self.env.clone(),
102            api_key: self.api_key.clone(),
103            currency: self.currency.clone(),
104        }
105    }
106}
107
108#[derive(Debug, Deserialize, Serialize, schemars::JsonSchema)]
109#[serde(deny_unknown_fields)]
110pub struct UnlockTradeReq {
111    /// true = 解锁(默认),false = 重新锁住交易 cipher(防御用)
112    #[schemars(
113        description = "true to unlock trading (default); false to lock trading cipher back (defensive). Lock does not require a password."
114    )]
115    #[serde(default = "default_true")]
116    pub unlock: bool,
117    /// v1.4.31: OTP / 令牌动态密码。仅在首次调用返回 `need_otp=true` 或
118    /// `err_code=-8` 时需要传;普通账号(无 2FA)留空即可。
119    #[schemars(
120        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"
121    )]
122    // v1.4.84 §5 B1
123    #[serde(
124        default,
125        alias = "token",
126        alias = "one_time_password",
127        skip_serializing_if = "Option::is_none"
128    )]
129    pub otp: Option<String>,
130    /// v1.4.33: 只解锁该券商下的账户(对齐 C++ OpenD per-broker unlock 语义)。
131    /// SecurityFirm enum (i32):
132    ///   1=FutuSecurities (HK), 2=FutuInc (US/MooMoo), 3=FutuSG,
133    ///   4=FutuAU, 5=FutuCA, 6=FutuMY, 7=FutuJP.
134    /// 留空 = 解锁所有 broker(v1.4.31 行为,向后兼容)。
135    #[schemars(
136        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"
137    )]
138    // v1.4.84 §5 B1
139    #[serde(
140        default,
141        alias = "broker",
142        alias = "security_firm_id",
143        skip_serializing_if = "Option::is_none"
144    )]
145    pub security_firm: Option<i32>,
146    /// v1.4.34: 只解锁指定 acc_id 列表(正整数;空 / omitted = 不 per-account filter)。
147    /// 和 security_firm 同时传时取交集(账户必须同时满足)。解决同 broker 内
148    /// 影子账户拖垮主账户的场景——LLM 显式传主账户 acc_id 可以避免影子
149    /// 账户进 unlock 请求。
150    #[schemars(
151        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"
152    )]
153    // v1.4.84 §5 B1
154    #[serde(
155        default,
156        alias = "account_ids",
157        alias = "accounts",
158        skip_serializing_if = "Option::is_none"
159    )]
160    pub acc_ids: Option<Vec<u64>>,
161}
162
163#[derive(Debug, Deserialize, schemars::JsonSchema)]
164#[serde(deny_unknown_fields)]
165pub struct MaxTrdQtysReq {
166    #[schemars(
167        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)."
168    )]
169    // v1.4.90 P0-E: int OR string 双接, normalize 到大写 canonical
170    #[serde(deserialize_with = "tool_enums::deser_trd_market_as_string")]
171    pub market: String,
172    #[schemars(
173        description = "Trading account ID (u64). ⚠️ Call `futu_list_accounts` first to discover — gateway does NOT infer a default; omitting or inventing an id fails."
174    )]
175    pub acc_id: u64,
176    #[schemars(description = "Trade environment: real|simulate (default real); alias: trd_env")]
177    // v1.4.84 §5 B1
178    #[serde(default = "default_env", alias = "trd_env")]
179    pub env: String,
180    // v1.4.41 (P2.6): description 强调 INTEGER 避免 LLM 传 "NORMAL" 撞 -32602.
181    // v1.4.42 (P3.3 修): 加 deserialize_with 接 int OR string —— LLM agent 传
182    // "NORMAL" / "MARKET" 等字符串也能工作(跟 PlaceOrderReq / ModifyOrderReq
183    // 的 string enum 风格统一)。backward-compat:旧客户端传 int 继续能用。
184    #[schemars(
185        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\"."
186    )]
187    #[serde(deserialize_with = "deser_int_or_order_type_str")]
188    pub order_type: i32,
189    #[schemars(
190        description = "Security code WITHOUT market prefix (e.g. 00700 / AAPL); alias: symbol / stock"
191    )]
192    // v1.4.84 §5 B1
193    #[serde(alias = "symbol", alias = "stock")]
194    pub code: String,
195    #[schemars(description = "Limit price (pass 0.0 for market orders)")]
196    pub price: f64,
197    #[schemars(
198        description = "JP sub-account type (Trd_Common.TrdSubAccType / TrdHeader.jpAccType). Required for JP accounts unless order_id/position context supplies the sub-account. Alias: jpAccType."
199    )]
200    #[serde(default, alias = "jpAccType")]
201    pub jp_acc_type: Option<i32>,
202    #[schemars(description = "Existing order_id (for modify-order max-qty calc, optional)")]
203    #[serde(default)]
204    pub order_id: Option<u64>,
205}
206
207#[derive(Debug, Deserialize, schemars::JsonSchema)]
208#[serde(deny_unknown_fields)]
209pub struct OrderFeeReq {
210    #[schemars(
211        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)."
212    )]
213    // v1.4.90 P0-E: int OR string 双接, normalize 到大写 canonical
214    #[serde(deserialize_with = "tool_enums::deser_trd_market_as_string")]
215    pub market: String,
216    #[schemars(
217        description = "Trading account ID (u64). ⚠️ Call `futu_list_accounts` first to discover — gateway does NOT infer a default; omitting or inventing an id fails."
218    )]
219    pub acc_id: u64,
220    #[schemars(description = "Trade environment: real|simulate (default real); alias: trd_env")]
221    // v1.4.84 §5 B1
222    #[serde(default = "default_env", alias = "trd_env")]
223    pub env: String,
224    #[schemars(
225        description = "Order_id_ex list (strings) — returned by place_order response; alias: order_ids_ex / order_ids"
226    )]
227    // v1.4.84 §5 B1
228    #[serde(alias = "order_ids_ex", alias = "order_ids")]
229    pub order_id_ex_list: Vec<String>,
230}
231
232#[derive(Debug, Deserialize, schemars::JsonSchema)]
233#[serde(deny_unknown_fields)]
234pub struct MarginRatioReq {
235    #[schemars(
236        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)."
237    )]
238    // v1.4.90 P0-E: int OR string 双接, normalize 到大写 canonical
239    #[serde(deserialize_with = "tool_enums::deser_trd_market_as_string")]
240    pub market: String,
241    #[schemars(
242        description = "Trading account ID (u64). ⚠️ Call `futu_list_accounts` first to discover — gateway does NOT infer a default; omitting or inventing an id fails."
243    )]
244    pub acc_id: u64,
245    #[schemars(description = "Trade environment: real|simulate (default real); alias: trd_env")]
246    // v1.4.84 §5 B1
247    #[serde(default = "default_env", alias = "trd_env")]
248    pub env: String,
249    #[schemars(
250        description = "Symbols in MARKET.CODE format (e.g. HK.00700, US.AAPL); alias: symbols / code_list / symbol_list"
251    )]
252    // v1.4.84 §5 B1
253    #[serde(alias = "symbols", alias = "code_list", alias = "symbol_list")]
254    pub codes: Vec<String>,
255}
256
257#[derive(Debug, Deserialize, schemars::JsonSchema)]
258#[serde(deny_unknown_fields)]
259pub struct HistoryQueryReq {
260    #[schemars(
261        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)."
262    )]
263    // v1.4.90 P0-E: int OR string 双接, normalize 到大写 canonical
264    #[serde(deserialize_with = "tool_enums::deser_trd_market_as_string")]
265    pub market: String,
266    #[schemars(
267        description = "Trading account ID (u64). ⚠️ Call `futu_list_accounts` first to discover — gateway does NOT infer a default; omitting or inventing an id fails."
268    )]
269    pub acc_id: u64,
270    #[schemars(description = "Trade environment: real|simulate (default real); alias: trd_env")]
271    // v1.4.83 §5 Phase 3: trd_env alias 对齐 py-futu-api TrdEnv
272    #[serde(default = "default_env", alias = "trd_env")]
273    pub env: String,
274    #[schemars(
275        description = "Filter by codes (empty = all). Each item is bare code without market prefix. \
276                       Alias: symbols / symbol_list"
277    )]
278    // v1.4.83 §5 Phase 3: 接受 symbols / symbol_list alias
279    #[serde(default, alias = "symbols", alias = "symbol_list")]
280    pub code_list: Vec<String>,
281    #[schemars(
282        description = "Begin time 'yyyy-MM-dd HH:mm:ss' (optional); alias: begin / start_time / from"
283    )]
284    #[serde(default, alias = "begin", alias = "start_time", alias = "from")]
285    pub begin_time: Option<String>,
286    #[schemars(description = "End time 'yyyy-MM-dd HH:mm:ss' (optional); alias: end / to")]
287    #[serde(default, alias = "end", alias = "to")]
288    pub end_time: Option<String>,
289}
290
291#[derive(Debug, Deserialize, schemars::JsonSchema)]
292#[serde(deny_unknown_fields)]
293pub struct CapitalFlowReq {
294    #[schemars(
295        description = "Security symbol in MARKET.CODE format (e.g. HK.00700); alias: code / stock"
296    )]
297    // v1.4.83 §5 Phase 3
298    #[serde(alias = "code", alias = "stock")]
299    pub symbol: String,
300    #[schemars(description = "Period type: 1=INTRADAY 2=DAY 3=WEEK 4=MONTH (default 1)")]
301    #[serde(default)]
302    pub period_type: Option<i32>,
303    #[schemars(
304        description = "Begin time 'yyyy-MM-dd' (optional, DAY/WEEK/MONTH only); alias: begin / start_time / from"
305    )]
306    #[serde(default, alias = "begin", alias = "start_time", alias = "from")]
307    pub begin_time: Option<String>,
308    #[schemars(description = "End time 'yyyy-MM-dd' (optional); alias: end / to")]
309    #[serde(default, alias = "end", alias = "to")]
310    pub end_time: Option<String>,
311}
312
313#[derive(Debug, Deserialize, schemars::JsonSchema)]
314#[serde(deny_unknown_fields)]
315pub struct AccCashFlowReq {
316    #[schemars(description = "Trade env: real / simulate (default real); alias: trd_env")]
317    // v1.4.84 §5 B1
318    #[serde(default = "default_env", alias = "trd_env")]
319    pub env: String,
320    #[schemars(
321        description = "Trading account ID (u64). ⚠️ Call `futu_list_accounts` first to discover — gateway does NOT infer a default."
322    )]
323    pub acc_id: u64,
324    #[schemars(
325        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)."
326    )]
327    // v1.4.90 P0-E: int OR string 双接, normalize 到大写 canonical
328    #[serde(deserialize_with = "tool_enums::deser_trd_market_as_string")]
329    pub market: String,
330    #[schemars(
331        description = "Clearing date (yyyy-MM-dd); queries flow entries for that day; alias: date / query_date"
332    )]
333    // v1.4.84 §5 B1
334    #[serde(alias = "date", alias = "query_date")]
335    pub clearing_date: String,
336    #[schemars(
337        description = "Direction: 1=InFlow, 2=OutFlow, omit for both; alias: flow_direction"
338    )]
339    #[serde(default, alias = "flow_direction")]
340    pub direction: Option<i32>,
341}
342
343#[derive(Debug, Deserialize, Serialize, schemars::JsonSchema)]
344#[serde(deny_unknown_fields)]
345pub struct CancelAllOrderReq {
346    #[schemars(description = "Trading env: simulate (default) / real; alias: trd_env")]
347    // v1.4.84 §5 B1
348    #[serde(default = "default_env_simulate", alias = "trd_env")]
349    pub env: String,
350    #[schemars(
351        description = "Trading account ID (u64). ⚠️ Call `futu_list_accounts` first to discover — gateway does NOT infer a default."
352    )]
353    pub acc_id: u64,
354    #[schemars(
355        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."
356    )]
357    // v1.4.90 P0-E: int OR string 双接, normalize 到大写 canonical (空字符串经
358    // 自定义 deser default fn 保留,runtime validate 报错).
359    #[serde(default, deserialize_with = "deser_trd_market_string_allow_empty")]
360    pub market: String,
361    #[schemars(description = "Per-call API key override (optional)")]
362    #[serde(default)]
363    pub api_key: Option<String>,
364}
365
366impl CancelAllOrderReq {
367    /// Runtime validation: market must be non-empty.
368    ///
369    /// The schema description marks this field as required, but serde accepts
370    /// an omitted string as the empty default; this check returns a clear
371    /// validation error before the request reaches the backend.
372    pub fn validate(&self) -> Result<(), String> {
373        if self.market.trim().is_empty() {
374            return Err(
375                "CancelAllOrderReq: `market` is required and must be non-empty \
376                 (e.g. HK / US / HKCC / A_SH / A_SZ / SG / JP / AU / CA)"
377                    .to_string(),
378            );
379        }
380        Ok(())
381    }
382}
383
384#[derive(Debug, Deserialize, schemars::JsonSchema)]
385#[serde(deny_unknown_fields)]
386pub struct CashLogReq {
387    #[schemars(description = "Trade env: real / simulate (default real)")]
388    #[serde(default = "default_env", alias = "trd_env")]
389    pub env: String,
390    #[schemars(description = "Trading account ID (u64). Call futu_list_accounts first.")]
391    pub acc_id: u64,
392    #[schemars(
393        description = "Optional legacy market hint. Accepted for backward compatibility (HK/US/CN/HKCC/FUTURES/SG/CRYPTO/AU/JP/MY/CA/HKFUND/USFUND/SGFUND/MYFUND/JPFUND or 1/2/3/4/5/6/7/8/15/111/112/113/123/124/125/126), \
394                       but cash-log identity does not trust this field: daemon derives backend market from acc_id/account cache."
395    )]
396    #[serde(
397        default,
398        deserialize_with = "tool_enums::deser_trd_market_as_option_string"
399    )]
400    pub market: Option<String>,
401    #[schemars(description = "Begin time (epoch seconds, optional)")]
402    #[serde(default)]
403    pub begin_time: Option<u64>,
404    #[schemars(description = "End time (epoch seconds, optional)")]
405    #[serde(default)]
406    pub end_time: Option<u64>,
407    #[schemars(description = "Business group ID filter (default all)")]
408    #[serde(default)]
409    pub biz_group_id: Option<u32>,
410    #[schemars(
411        description = "Business sub-group ID filter (optional; value from futu_get_biz_group sub_groups)"
412    )]
413    #[serde(default)]
414    pub biz_sub_group_id: Option<u32>,
415    #[schemars(description = "In/out direction: 1=in, 2=out, 0/omit=all")]
416    #[serde(default)]
417    pub in_out: Option<u32>,
418    #[schemars(description = "Search keyword (optional)")]
419    #[serde(default)]
420    pub keyword: Option<String>,
421    #[schemars(description = "Stock symbol (e.g. AAPL.US, 00700.HK), exact match (optional)")]
422    #[serde(default)]
423    pub symbol: Option<String>,
424    #[schemars(
425        description = "Backend stock_id filter (optional; use when available from upstream/account UI data)"
426    )]
427    #[serde(default)]
428    pub stock_id: Option<u64>,
429    #[schemars(
430        description = "Cursor: log_id from previous response next_log_id (omit for first page)"
431    )]
432    #[serde(default)]
433    pub log_id: Option<String>,
434    #[schemars(description = "Max entries per response (daemon uses mobile default 50 if omit)")]
435    #[serde(default)]
436    pub max_cnt: Option<u32>,
437    #[schemars(description = "Currency filter: CNY/HKD/USD/JPY/SGD (optional)")]
438    #[serde(default)]
439    pub currency: Option<String>,
440}
441
442#[derive(Debug, Deserialize, schemars::JsonSchema)]
443#[serde(deny_unknown_fields)]
444pub struct CashDetailReq {
445    #[schemars(description = "Trade env: real / simulate (default real)")]
446    #[serde(default = "default_env", alias = "trd_env")]
447    pub env: String,
448    #[schemars(description = "Trading account ID (u64)")]
449    pub acc_id: u64,
450    #[schemars(
451        description = "Optional legacy market hint; accepted for backward compatibility but ignored. Daemon derives backend market from acc_id/account cache."
452    )]
453    #[serde(
454        default,
455        deserialize_with = "tool_enums::deser_trd_market_as_option_string"
456    )]
457    pub market: Option<String>,
458    #[schemars(description = "Cash log ID (from futu_get_cash_log response)")]
459    pub log_id: String,
460}
461
462// v1.4.95 U2-D Tier M (mobile-driven): per-account margin info request
463#[derive(Debug, Deserialize, schemars::JsonSchema)]
464#[serde(deny_unknown_fields)]
465pub struct MarginInfoReq {
466    #[schemars(description = "Trade env: real / simulate (default real)")]
467    #[serde(default = "default_env", alias = "trd_env")]
468    pub env: String,
469    #[schemars(description = "Trading account ID (u64). Call futu_list_accounts first.")]
470    pub acc_id: u64,
471    #[schemars(
472        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)."
473    )]
474    pub market: String,
475}
476
477// v1.4.95 U2-A Tier M (mobile-driven): account compliance flag query request
478#[derive(Debug, Deserialize, schemars::JsonSchema)]
479#[serde(deny_unknown_fields)]
480pub struct AccountFlagReq {
481    #[schemars(description = "Trade env: real / simulate (default real)")]
482    #[serde(default = "default_env", alias = "trd_env")]
483    pub env: String,
484    #[schemars(description = "Trading account ID (u64) for per-broker routing")]
485    pub acc_id: u64,
486    #[schemars(
487        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."
488    )]
489    pub flag_id: u32,
490}
491
492#[derive(Debug, Deserialize, schemars::JsonSchema)]
493#[serde(deny_unknown_fields)]
494pub struct BondAccountReq {
495    #[schemars(description = "Trade env: real / simulate (default real)")]
496    #[serde(default = "default_env", alias = "trd_env")]
497    pub env: String,
498    #[schemars(description = "Trading account ID (u64) for per-broker routing")]
499    pub acc_id: u64,
500    #[schemars(description = "Market: HK / US / SG; aliases USA and SG_UNIVERSAL are accepted")]
501    pub market: String,
502}