Skip to main content

futu_trd/
types.rs

1// 交易域通用类型
2
3/// 交易环境
4#[derive(Debug, Clone, Copy, PartialEq, Eq)]
5#[repr(i32)]
6#[non_exhaustive]
7pub enum TrdEnv {
8    Simulate = 0,
9    Real = 1,
10}
11
12impl TryFrom<i32> for TrdEnv {
13    type Error = ();
14
15    fn try_from(value: i32) -> Result<Self, Self::Error> {
16        match value {
17            0 => Ok(Self::Simulate),
18            1 => Ok(Self::Real),
19            _ => Err(()),
20        }
21    }
22}
23
24/// 交易市场
25///
26/// 对齐 `Trd_Common.proto::TrdMarket`:
27/// HK=1 / US=2 / CN=3 / HKCC=4 / Futures=5 / SG=6 / Crypto=7 / AU=8 /
28/// FuturesSimulateHK=10 / FuturesSimulateUS=11 / FuturesSimulateSG=12 /
29/// FuturesSimulateJP=13 / JP=15 / Prediction=17 / MY=111 / CA=112 /
30/// fund markets 113/123/124/125/126.
31///
32/// v1.4.93 BUG-001 fix (S level ship-blocker): v1.4.86-90 五版只列 4 variants
33/// (HK/US/CN/HKCC), 而 MCP / CLI schema 都已暴露 9. SG/AU/JP/MY/CA 5 国 user 用
34/// 导致 daemon 返 `unknown trd market SG (HK|US|CN|HKCC)`. 端到端不可下单.
35///
36/// 注: `Futures=5` 是不分国家的期货市场 (历史 backend 标识), 与具体 SG/AU/JP/MY/CA
37/// 国家 trd_market 不同. Futures 通常用 sec_market 派生 (例如 US futures 用
38/// sec_market=11 加 trd_market=5). 本枚举包含 Futures 让 frontend 也能直接传,
39/// 但典型用法仍然走国家 trd_market.
40#[derive(Debug, Clone, Copy, PartialEq, Eq)]
41#[repr(i32)]
42#[non_exhaustive]
43pub enum TrdMarket {
44    Unknown = 0,
45    HK = 1,
46    US = 2,
47    CN = 3,
48    HKCC = 4,
49    Futures = 5,
50    SG = 6,
51    Crypto = 7,
52    AU = 8,
53    FuturesSimulateHK = 10,
54    FuturesSimulateUS = 11,
55    FuturesSimulateSG = 12,
56    FuturesSimulateJP = 13,
57    JP = 15,
58    /// Event-contract / prediction market.
59    /// Ref: C++ `Trd_Common.proto:42` and `_APIServer_Trd_Comm.cpp:2575-2577`.
60    Prediction = 17,
61    /// v1.8 local read/batch-close reservation aligned to Desktop
62    /// `FINEnableMarket::KRX=18`. Ordinary write parsing rejects this value;
63    /// only the guarded typed batch-close contract admits it.
64    KRX = 18,
65    MY = 111,
66    CA = 112,
67    /// HKFUND view-only 港币基金 (融资融券 / 基金账户) — v1.4.102 fund-market
68    /// handoff. C++ `NN_TrdMarket_HK_Fund=113` (NNBase_Define_Enum.h:113).
69    /// 注: cash-log backend `Market` enum 用 13 (MARKET_HKFUND), 翻译见
70    /// `cash_log_market_for_trd_market`.
71    HKFund = 113,
72    /// USFUND view-only 美元基金 — v1.4.102. C++ `NN_TrdMarket_US_Fund=123`.
73    /// cash-log Market enum 用 23 (MARKET_USFUND).
74    USFund = 123,
75    /// SGFUND view-only 新加坡基金 — C++ `NN_TrdMarket_SG_Fund=124`.
76    SGFund = 124,
77    /// MYFUND view-only 马来西亚基金 — C++ `NN_TrdMarket_MY_Fund=125`.
78    MYFund = 125,
79    /// JPFUND view-only 日本基金 — C++ `NN_TrdMarket_JP_Fund=126`.
80    JPFund = 126,
81}
82
83impl TryFrom<i32> for TrdMarket {
84    type Error = ();
85
86    fn try_from(value: i32) -> Result<Self, Self::Error> {
87        match value {
88            1 => Ok(Self::HK),
89            2 => Ok(Self::US),
90            3 => Ok(Self::CN),
91            4 => Ok(Self::HKCC),
92            5 => Ok(Self::Futures),
93            6 => Ok(Self::SG),
94            7 => Ok(Self::Crypto),
95            8 => Ok(Self::AU),
96            10 => Ok(Self::FuturesSimulateHK),
97            11 => Ok(Self::FuturesSimulateUS),
98            12 => Ok(Self::FuturesSimulateSG),
99            13 => Ok(Self::FuturesSimulateJP),
100            15 => Ok(Self::JP),
101            17 => Ok(Self::Prediction),
102            18 => Ok(Self::KRX),
103            111 => Ok(Self::MY),
104            112 => Ok(Self::CA),
105            113 => Ok(Self::HKFund),
106            123 => Ok(Self::USFund),
107            124 => Ok(Self::SGFund),
108            125 => Ok(Self::MYFund),
109            126 => Ok(Self::JPFund),
110            _ => Err(()),
111        }
112    }
113}
114
115/// 交易方向
116#[derive(Debug, Clone, Copy, PartialEq, Eq)]
117#[repr(i32)]
118#[non_exhaustive]
119pub enum TrdSide {
120    Unknown = 0,
121    Buy = 1,
122    Sell = 2,
123    SellShort = 3,
124    BuyBack = 4,
125}
126
127impl TryFrom<i32> for TrdSide {
128    type Error = ();
129
130    fn try_from(value: i32) -> Result<Self, Self::Error> {
131        match value {
132            1 => Ok(Self::Buy),
133            2 => Ok(Self::Sell),
134            3 => Ok(Self::SellShort),
135            4 => Ok(Self::BuyBack),
136            _ => Err(()),
137        }
138    }
139}
140
141/// 订单类型
142#[derive(Debug, Clone, Copy, PartialEq, Eq)]
143#[repr(i32)]
144#[non_exhaustive]
145pub enum OrderType {
146    Unknown = 0,
147    Normal = 1,
148    Market = 2,
149    AbsoluteLimit = 5,
150    Auction = 6,
151    AuctionLimit = 7,
152    SpecialLimit = 8,
153    SpecialLimitAll = 9,
154    // v1.4.53 F1 条件单
155    Stop = 10,              // 止损市价单
156    StopLimit = 11,         // 止损限价单
157    MarketifTouched = 12,   // 触及市价单(止盈)
158    LimitifTouched = 13,    // 触及限价单(止盈)
159    TrailingStop = 14,      // 跟踪止损市价单
160    TrailingStopLimit = 15, // 跟踪止损限价单
161    TwapMarket = 16,
162    TwapLimit = 17,
163    VwapMarket = 18,
164    VwapLimit = 19,
165    Moc = 20,
166}
167
168impl TryFrom<i32> for OrderType {
169    type Error = ();
170
171    fn try_from(value: i32) -> Result<Self, Self::Error> {
172        match value {
173            1 => Ok(Self::Normal),
174            2 => Ok(Self::Market),
175            5 => Ok(Self::AbsoluteLimit),
176            6 => Ok(Self::Auction),
177            7 => Ok(Self::AuctionLimit),
178            8 => Ok(Self::SpecialLimit),
179            9 => Ok(Self::SpecialLimitAll),
180            10 => Ok(Self::Stop),
181            11 => Ok(Self::StopLimit),
182            12 => Ok(Self::MarketifTouched),
183            13 => Ok(Self::LimitifTouched),
184            14 => Ok(Self::TrailingStop),
185            15 => Ok(Self::TrailingStopLimit),
186            16 => Ok(Self::TwapMarket),
187            17 => Ok(Self::TwapLimit),
188            18 => Ok(Self::VwapMarket),
189            19 => Ok(Self::VwapLimit),
190            20 => Ok(Self::Moc),
191            _ => Err(()),
192        }
193    }
194}
195
196/// 修改订单操作类型
197#[derive(Debug, Clone, Copy, PartialEq, Eq)]
198#[repr(i32)]
199#[non_exhaustive]
200pub enum ModifyOrderOp {
201    Unknown = 0,
202    Normal = 1,
203    Cancel = 2,
204    Disable = 3,
205    Enable = 4,
206    Delete = 5,
207}
208
209impl TryFrom<i32> for ModifyOrderOp {
210    type Error = ();
211
212    fn try_from(value: i32) -> Result<Self, Self::Error> {
213        match value {
214            1 => Ok(Self::Normal),
215            2 => Ok(Self::Cancel),
216            3 => Ok(Self::Disable),
217            4 => Ok(Self::Enable),
218            5 => Ok(Self::Delete),
219            _ => Err(()),
220        }
221    }
222}
223
224/// 交易请求头
225#[derive(Debug, Clone)]
226pub struct TrdHeader {
227    /// 交易环境(模拟 / 真实)
228    pub trd_env: TrdEnv,
229    /// 交易账户 ID
230    pub acc_id: u64,
231    /// 交易市场
232    pub trd_market: TrdMarket,
233    /// v1.4.106 codex F6 (P2): JP 子账户类型 (TrdSubAccType).
234    ///
235    /// 仅 JP broker (FutuJP) 在无 positionID 时**必填**, 否则 backend 拒
236    /// `MissNecessaryParameters`. 非 JP 场景为 `None`. C++ `Trd_Common.proto:320`
237    /// `TrdHeader.jpAccType` (field 4, optional).
238    pub jp_acc_type: Option<i32>,
239}
240
241impl TrdHeader {
242    pub fn to_proto(&self) -> futu_proto::trd_common::TrdHeader {
243        futu_proto::trd_common::TrdHeader {
244            trd_env: self.trd_env as i32,
245            acc_id: self.acc_id,
246            trd_market: self.trd_market as i32,
247            // v1.4.106 codex F6 (P2): SDK 现支持 jp_acc_type, 透传到 backend.
248            jp_acc_type: self.jp_acc_type,
249        }
250    }
251}
252
253/// 账户资金
254///
255/// v1.4.73 BUG-004 fix(external reviewer v1.4.71 AI tester P0 报告):之前只暴露 7 字段
256/// 给 MCP,而 C++ Python SDK `accinfo_query` 返 63+。MCP 客户端做多币种 /
257/// 多市场管理 / 风控监测都用不起来。本版补 18 个关键字段:
258///
259/// - **currency**:必备(之前 MCP 完全缺失,agent 无从判断币种)
260/// - **available_funds**:margin 账户可用资金(不同于 cash)
261/// - **unrealized_pl / realized_pl**:持仓盈亏
262/// - **risk_level / risk_status**:账户风控级别 / 状态
263/// - **initial_margin / maintenance_margin / margin_call_margin**:保证金
264/// - **max_power_short**:做空可用
265/// - **long_mv / short_mv**:多空持仓市值
266/// - **pending_asset**:挂单占用资产
267/// - **max_withdrawal**:可取现上限
268/// - **is_pdt / pdt_seq / remaining_dtbp / dt_call_amount / dt_status**:
269///   US 账户 Pattern Day Trader 相关(关键风控指标)
270/// - **securities_assets / fund_assets / bond_assets**:资产类别 breakdown
271///
272/// 保留 `cash_info_list` / `market_info_list` 为 raw proto,v1.4.74+ 按需解析。
273#[derive(Debug, Clone)]
274pub struct Funds {
275    // 旧 7 字段(保持二进制兼容,老 caller 不受影响)
276    /// 购买力
277    pub power: f64,
278    /// 资产净值(总资产)
279    pub total_assets: f64,
280    /// 现金 — top-level summary cash, in `currency` field's currency.
281    ///
282    /// **v1.4.106 codex 1612 Candidate A**: This is **NOT** a cross-currency sum
283    /// of `cash_info_list[].cash`. Different currencies cannot be summed without
284    /// FX conversion. Backend (`Ndt_Trd_AccFund.fTotalCash`) directly populates
285    /// this field, faithfully relayed via `pFunds->set_cash(nnFunds.fTotalCash)`
286    /// in C++ `APIServer_Trd_GetFunds.cpp::FillFunds`.
287    ///
288    /// **Semantics by account type**:
289    /// - **Futures / Universal**: cash in `union_currency` (request currency or
290    ///   account base if not requested). v1.4.106 codex 1556 F1 fix: daemon now
291    ///   passes user-requested currency to CMD3020 `union_currency`, ensuring
292    ///   `cash` is denominated in the requested currency.
293    /// - **Legacy single-currency accounts**: cash in account's primary market
294    ///   currency. Only one entry in `cash_info_list`; top-level `cash` equals
295    ///   that entry's `cash`.
296    ///
297    /// To match Futu mobile app's '现金总值 in HKD' display for universal
298    /// accounts, client must compute `sum(cash_info_list[i].cash * fx_rate(...))`
299    /// — daemon does not perform FX aggregation. Per-currency breakdown is in
300    /// `cash_info_list`.
301    pub cash: f64,
302    /// 证券市值
303    pub market_val: f64,
304    /// 冻结金额(未成交委托锁住的资金)
305    pub frozen_cash: f64,
306    /// 欠款金额(融资或透支)
307    pub debt_cash: f64,
308    /// 可提金额
309    pub avl_withdrawal_cash: f64,
310
311    // v1.4.73 BUG-004:新补 18 字段
312    /// 账户主币种(HKD / USD / CNH / ...,对齐 proto `TrdCommon.Currency`)
313    pub currency: Option<i32>,
314    /// 可用资金
315    pub available_funds: Option<f64>,
316    /// 未实现盈亏
317    pub unrealized_pl: Option<f64>,
318    /// 已实现盈亏
319    pub realized_pl: Option<f64>,
320    /// 账户风险等级
321    pub risk_level: Option<i32>,
322    /// 账户风险状态(预警 / 追保 / 平仓等)
323    pub risk_status: Option<i32>,
324    /// 起始保证金
325    pub initial_margin: Option<f64>,
326    /// 维持保证金
327    pub maintenance_margin: Option<f64>,
328    /// Margin Call 保证金
329    pub margin_call_margin: Option<f64>,
330    /// 做空最大购买力
331    pub max_power_short: Option<f64>,
332    /// 净现金购买力(无杠杆)
333    pub net_cash_power: Option<f64>,
334    /// 多头市值
335    pub long_mv: Option<f64>,
336    /// 空头市值
337    pub short_mv: Option<f64>,
338    /// 在途资产(T+N 未结算)
339    pub pending_asset: Option<f64>,
340    /// 最大可提资金
341    pub max_withdrawal: Option<f64>,
342    /// 是否为 Pattern Day Trader(美股规则)
343    pub is_pdt: Option<bool>,
344    /// PDT 违规序号 (mobile UI: 剩余日内交易次数)
345    pub pdt_seq: Option<String>,
346    /// v1.4.98 T1-4: 初始日内交易购买力 (DTBP, US PDT 账户)
347    pub beginning_dtbp: Option<f64>,
348    /// 剩余日内交易购买力 (DTBP)
349    pub remaining_dtbp: Option<f64>,
350    /// 日内追保金额 (DT Call)
351    pub dt_call_amount: Option<f64>,
352    /// 日内保证金状态
353    pub dt_status: Option<i32>,
354    /// 证券资产
355    pub securities_assets: Option<f64>,
356    /// 基金资产
357    pub fund_assets: Option<f64>,
358    /// 债券资产
359    pub bond_assets: Option<f64>,
360    /// 数字货币市值
361    pub crypto_mv: Option<f64>,
362    /// 数字货币风险等级
363    pub exposure_level: Option<i32>,
364    /// 数字货币持仓限额
365    pub exposure_limit: Option<f64>,
366    /// 数字货币已用限额
367    pub used_limit: Option<f64>,
368    /// 数字货币剩余额度
369    pub remaining_limit: Option<f64>,
370
371    // v1.4.74 C1 BUG-004 Phase 2:cash_info_list + market_info_list 展开
372    /// 按币种细分的现金信息列表
373    pub cash_info_list: Vec<FundsCashInfo>,
374    /// 按市场细分的资产信息列表
375    pub market_info_list: Vec<FundsMarketInfo>,
376}
377
378/// v1.4.74 C1 BUG-004 Phase 2: 分币种现金信息(对齐 proto `AccCashInfo`)。
379///
380/// 多币种账户(如美股账户持 USD + JPY 债券)每币种一条。
381#[derive(Debug, Clone)]
382pub struct FundsCashInfo {
383    /// 币种(对齐 proto `TrdCommon.Currency`:HKD=1 / USD=2 / CNH=3 / ...)
384    pub currency: Option<i32>,
385    /// 该币种现金
386    pub cash: Option<f64>,
387    /// 该币种可用余额
388    pub available_balance: Option<f64>,
389    /// 该币种净购买力
390    pub net_cash_power: Option<f64>,
391}
392
393/// v1.4.74 C1 BUG-004 Phase 2: 分市场资产信息(对齐 proto `AccMarketInfo`)。
394///
395/// 综合账户 / 跨市场账户每市场一条。
396#[derive(Debug, Clone)]
397pub struct FundsMarketInfo {
398    /// 所属交易市场(对齐 proto `TrdCommon.TrdMarket`)
399    pub trd_market: Option<i32>,
400    /// 该市场资产总值
401    pub assets: Option<f64>,
402}
403
404impl Funds {
405    pub fn from_proto(f: &futu_proto::trd_common::Funds) -> Self {
406        Self {
407            power: f.power,
408            total_assets: f.total_assets,
409            cash: f.cash,
410            market_val: f.market_val,
411            frozen_cash: f.frozen_cash,
412            debt_cash: f.debt_cash,
413            avl_withdrawal_cash: f.avl_withdrawal_cash,
414            // v1.4.73 BUG-004
415            currency: f.currency,
416            available_funds: f.available_funds,
417            unrealized_pl: f.unrealized_pl,
418            realized_pl: f.realized_pl,
419            risk_level: f.risk_level,
420            risk_status: f.risk_status,
421            initial_margin: f.initial_margin,
422            maintenance_margin: f.maintenance_margin,
423            margin_call_margin: f.margin_call_margin,
424            max_power_short: f.max_power_short,
425            net_cash_power: f.net_cash_power,
426            long_mv: f.long_mv,
427            short_mv: f.short_mv,
428            pending_asset: f.pending_asset,
429            max_withdrawal: f.max_withdrawal,
430            is_pdt: f.is_pdt,
431            pdt_seq: f.pdt_seq.clone(),
432            beginning_dtbp: f.beginning_dtbp, // v1.4.98 T1-4
433            remaining_dtbp: f.remaining_dtbp,
434            dt_call_amount: f.dt_call_amount,
435            dt_status: f.dt_status,
436            securities_assets: f.securities_assets,
437            fund_assets: f.fund_assets,
438            bond_assets: f.bond_assets,
439            crypto_mv: f.crypto_mv,
440            exposure_level: f.exposure_level,
441            exposure_limit: f.exposure_limit,
442            used_limit: f.used_limit,
443            remaining_limit: f.remaining_limit,
444            // v1.4.74 C1 BUG-004 Phase 2
445            cash_info_list: f
446                .cash_info_list
447                .iter()
448                .map(|c| FundsCashInfo {
449                    currency: c.currency,
450                    cash: c.cash,
451                    available_balance: c.available_balance,
452                    net_cash_power: c.net_cash_power,
453                })
454                .collect(),
455            market_info_list: f
456                .market_info_list
457                .iter()
458                .map(|m| FundsMarketInfo {
459                    trd_market: m.trd_market,
460                    assets: m.assets,
461                })
462                .collect(),
463        }
464    }
465}
466
467/// 持仓信息
468///
469/// v1.4.94 Tier M2 (mobile-driven extension): 加 `diluted_cost_price` /
470/// `average_cost_price` / `average_pl_ratio` / `currency` / `trd_market` 字段,
471/// 对齐 OpenD `Trd_Common.proto Position` 字段 32-34 + 30-31 + mobile NN
472/// `aas_cmn.proto CostProfitCalcMethod` 用 case (JP 加权平均 / 美 开仓价).
473///
474/// **`cost_price` (字段 8) 已 deprecated** (proto 注释: "已废弃,请使用
475/// dilutedCostPrice 或 averageCostPrice"), 但保留向后兼容. 客户端推荐用新字段:
476/// - `diluted_cost_price`: 摊薄成本价 (HK/US/CN 默认显示)
477/// - `average_cost_price`: 平均成本价 (JP 信用 / 模拟交易证券默认)
478/// - `average_pl_ratio`: 基于 average_cost_price 的盈亏百分数值
479#[derive(Debug, Clone)]
480pub struct Position {
481    /// 服务端分配的持仓 ID
482    pub position_id: u64,
483    /// 持仓方向(0=多 / 1=空,对齐 proto `PositionSide`)
484    pub position_side: i32,
485    /// 证券代码(市场内 code,不含 `MKT.` 前缀)
486    pub code: String,
487    /// 证券名称(中文或本地化)
488    pub name: String,
489    /// 持仓数量
490    pub qty: f64,
491    /// 可卖数量(已扣除冻结 / 当日买入不可卖等)
492    pub can_sell_qty: f64,
493    /// 当前价
494    pub price: f64,
495    /// 持仓均价(**已废弃**,用 diluted_cost_price 或 average_cost_price)
496    pub cost_price: f64,
497    /// 持仓市值(`qty * price`)
498    pub val: f64,
499    /// 盈亏金额
500    pub pl_val: f64,
501    /// C++ APIServer 原样返回的持仓盈亏比例数值(基于 cost_price 旧字段)。
502    /// Rust gateway/API/JSON 保持该数值不变;CLI 展示层再格式化为带符号的
503    /// 百分比字符串,例如 `0.6078` 显示为 `+60.78%`。
504    pub pl_ratio: f64,
505    /// v1.4.94 Tier M2: 摊薄成本价 (proto 字段 32, 仅证券账户)
506    /// 对齐 C++ `Trd_Common.proto:411` "仅支持证券账户使用".
507    pub diluted_cost_price: Option<f64>,
508    /// v1.4.94 Tier M2: 平均成本价 (proto 字段 33, 模拟交易证券账户不适用)
509    pub average_cost_price: Option<f64>,
510    /// v1.4.94 Tier M2: 平均成本价的盈亏百分数值 (proto 字段 34)
511    pub average_pl_ratio: Option<f64>,
512    /// v1.4.94 Tier M2: 货币类型 (proto 字段 30, 取值 Currency enum)
513    pub currency: Option<i32>,
514    /// v1.4.94 Tier M2: 交易市场 (proto 字段 31, 取值 TrdMarket enum)
515    pub trd_market: Option<i32>,
516    /// C++ OpenD 10.6 `Position.comboID` (proto field 35).
517    pub combo_id: Option<u64>,
518    /// C++ OpenD 10.6 `Position.strategyType` (proto field 36).
519    pub strategy_type: Option<i32>,
520    /// C++ OpenD 10.6 `Position.positionType` (proto field 37).
521    pub position_type: Option<i32>,
522    /// C++ OpenD 10.6 `Position.accID` (proto field 38).
523    pub acc_id: Option<u64>,
524    /// C++ OpenD 10.6 `Position.jpAccType` (proto field 39).
525    pub jp_acc_type: Option<i32>,
526    /// Legacy Rust-only option DTE field. Authoritative C++ uses public proto
527    /// tag 40 for `payoutIfWin`, so this value is no longer transported on the
528    /// FTAPI wire and remains `None` for source compatibility.
529    #[deprecated(note = "not part of the C++ FTAPI Position wire contract")]
530    pub expiry_date_distance: Option<i32>,
531    /// Event-contract payout exposed by C++ as `Position.payoutIfWin` tag 40.
532    pub payout_if_win: Option<f64>,
533}
534
535impl Position {
536    #[allow(deprecated)]
537    pub fn from_proto(p: &futu_proto::trd_common::Position) -> Self {
538        Self {
539            position_id: p.position_id,
540            position_side: p.position_side,
541            code: p.code.clone(),
542            name: p.name.clone(),
543            qty: p.qty,
544            can_sell_qty: p.can_sell_qty,
545            price: p.price,
546            cost_price: p.cost_price.unwrap_or(0.0),
547            val: p.val,
548            pl_val: p.pl_val,
549            pl_ratio: p.pl_ratio.unwrap_or(0.0),
550            // v1.4.94 Tier M2: 抽 mobile-aligned 字段
551            diluted_cost_price: p.diluted_cost_price,
552            average_cost_price: p.average_cost_price,
553            average_pl_ratio: p.average_pl_ratio,
554            currency: p.currency,
555            trd_market: p.trd_market,
556            combo_id: p.combo_id,
557            strategy_type: p.strategy_type,
558            position_type: p.position_type,
559            acc_id: p.acc_id,
560            jp_acc_type: p.jp_acc_type,
561            expiry_date_distance: None,
562            payout_if_win: p.payout_if_win,
563        }
564    }
565}
566
567/// 下单参数
568#[derive(Debug, Clone)]
569pub struct PlaceOrderParams {
570    /// 交易头(env + acc_id + market)
571    pub header: TrdHeader,
572    /// 买卖方向
573    pub trd_side: TrdSide,
574    /// 订单类型(限价 / 市价 / 竞价 / 止损 / ...)
575    pub order_type: OrderType,
576    /// 证券代码
577    pub code: String,
578    /// 下单数量
579    pub qty: f64,
580    /// 下单价(限价单必填;市价单可空)
581    pub price: Option<f64>,
582    /// 价格调整开关(超出涨跌幅时是否自动调整到 limit 内)
583    pub adjust_price: Option<bool>,
584    /// 调整侧与幅度(配合 `adjust_price`,百分比范围内向内调整)
585    pub adjust_side_and_limit: Option<f64>,
586    /// v1.4.39: 可选幂等键。设置后,`place_order` 会根据此键派生 `Common.PacketID`
587    /// 的 `conn_id`(serial_no=0),使同一键的重试命中 daemon 端 90s TTL cache,
588    /// 返回缓存结果而不真实下单。external reviewer v1.4.38 报告发现 CLI/MCP 没接此机制 → 修。
589    pub idempotency_key: Option<String>,
590    // v1.4.53 F1 条件单:对齐 FTAPI `Trd_PlaceOrder.C2S.auxPrice` / `trailType`
591    // / `trailValue` / `trailSpread`。仅对 Stop / StopLimit / MIT / LIT /
592    // TrailingStop / TrailingStopLimit 等 order_type 生效。
593    /// 止损/止盈触发价(FTAPI `auxPrice`)。
594    pub aux_price: Option<f64>,
595    /// 跟踪类型 1=Ratio(比例)/ 2=Amount(金额),对 Trailing 变种有效。
596    pub trail_type: Option<i32>,
597    /// 跟踪金额 / 百分比(`trail_type=1` 时为百分比,`trail_type=2` 时为金额)。
598    pub trail_value: Option<f64>,
599    /// 指定价差(跟踪限价单 TrailingStopLimit 用)。
600    pub trail_spread: Option<f64>,
601}
602
603/// 下单高级选项。
604///
605/// 这些字段直接对应 `Trd_PlaceOrder.C2S` 的 optional 字段。保留在独立
606/// options 结构里,避免给现有 `PlaceOrderParams { ... }` 调用方制造源码级
607/// breaking change。
608#[derive(Debug, Clone, Default)]
609pub struct PlaceOrderOptions {
610    /// 订单有效期限:0=DAY, 1=GTC, 2=IOC, 3=GTD。
611    pub time_in_force: Option<i32>,
612    /// 是否允许美股盘前/盘后成交。C++ 会把缺省当 false。
613    pub fill_outside_rth: Option<bool>,
614    /// 美股订单时段:0=NONE, 1=RTH, 2=ETH, 3=ALL, 4=OVERNIGHT。
615    pub session: Option<i32>,
616    /// GTD 到期日期,格式 `YYYY-MM-DD`,仅在 `time_in_force=3` 时有效。
617    pub expire_time: Option<String>,
618    /// Event Contract cash amount. The daemon derives the two-decimal qty.
619    pub amount: Option<f64>,
620    /// Event Contract side: 1=Yes, 2=No.
621    pub pred_side: Option<i32>,
622}
623
624/// 下单结果
625#[derive(Debug, Clone)]
626pub struct PlaceOrderResult {
627    pub order_id: u64,
628}
629
630/// 下单结果,包含参照版本同时返回的无损 backend identity。
631///
632/// 这是 [`PlaceOrderResult`] 的 additive companion,避免给既有 public struct
633/// 直接增加字段而破坏外部调用者的结构体构造与解构源码兼容性。
634#[derive(Debug, Clone)]
635pub struct PlaceOrderResultWithIdentity {
636    pub order_id: u64,
637    /// Backend/server order identity returned by C++ as `orderIDEx`.
638    ///
639    /// This string is the lossless identity for automation and is accepted by
640    /// modify/cancel helpers. Keep it alongside the C++ numeric projection so
641    /// JSON consumers never have to round-trip a `u64` through IEEE-754.
642    pub order_id_ex: String,
643}
644
645/// 改单参数
646#[derive(Debug, Clone)]
647pub struct ModifyOrderParams {
648    pub header: TrdHeader,
649    pub order_id: u64,
650    /// v1.4.110: backend/server order id string (`orderIDEx`).
651    /// C++ accepts this as an alternative to `orderID` and hashes it back to
652    /// `orderID` at APIServer entry.
653    pub order_id_ex: Option<String>,
654    pub modify_order_op: ModifyOrderOp,
655    pub qty: Option<f64>,
656    pub price: Option<f64>,
657    pub for_all: Option<bool>,
658    /// v1.4.39: 可选幂等键。同 `PlaceOrderParams.idempotency_key`。
659    pub idempotency_key: Option<String>,
660}