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