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