1use 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 #[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 #[serde(default = "default_env", alias = "trd_env")]
36 pub env: String,
37
38 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[serde(default = "default_env", alias = "trd_env")]
179 pub env: String,
180 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 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#[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#[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}