futu_cache/trd_cache/types/account.rs
1use futu_core::account_locator::AccountCardRecord;
2
3/// 账户 key: acc_id
4pub type AccKey = u64;
5
6/// 缓存的账户信息
7#[derive(Debug, Clone, Default)]
8pub struct CachedTrdAcc {
9 /// 账户 ID
10 pub acc_id: u64,
11 /// 后端/mobile native intra account id (C++ `Ndt_Trd_AccItem.nIntraAccID`).
12 ///
13 /// 公开 FTAPI `acc_id` 与 backend 查询 body 里的 `account_id` 不是同一层
14 /// 语义。需要发 backend native account_id 的 handler 应优先用这个字段,
15 /// 不要从公开 `acc_id` 低 32 位反推。
16 pub intra_acc_id: Option<u64>,
17 /// 交易环境(0=Simulate / 1=Real)
18 pub trd_env: i32,
19 /// 该账户有权限访问的交易市场列表
20 pub trd_market_auth_list: Vec<i32>,
21 /// 账户类型(Cash / Margin / Derivative / ...)
22 pub acc_type: Option<i32>,
23 /// 账户卡号(后段数字,仅用于显示识别)
24 pub card_num: Option<String>,
25 /// 账户所属 broker(FutuHK=1 / FutuUS=2 / ...)
26 pub security_firm: Option<i32>,
27 /// 模拟账户子类型
28 pub sim_acc_type: Option<i32>,
29 /// C++ CMD14800 competition account title, exposed only when
30 /// `sim_acc_type == SimAccType_Competition`.
31 pub competition_acc_name: Option<String>,
32 /// 统一卡号(跨市场账户聚合标识)
33 pub uni_card_num: Option<String>,
34 /// 账户状态码(正常 / 冻结 / ...)
35 pub acc_status: Option<i32>,
36 /// Backend raw `FTUsrTrdAcc::Account.state`.
37 ///
38 /// C++ API layer keeps this distinct from public `TrdAccStatus`:
39 /// `OPENED(1)` is returned as Active, `CLOSED(2)` is returned in the
40 /// disabled-real tail, while `OPENING(0)` is skipped by
41 /// `APIServer_Trd_GetAccList.cpp:109-115`. Do not derive this back from
42 /// `acc_status`, because both CLOSED and OPENING are non-active.
43 pub acc_open_state: Option<i32>,
44 /// 账户角色(主账户 / 子账户 / 顾问)
45 pub acc_role: Option<i32>,
46 /// Daemon-derived user-visible account label.
47 ///
48 /// Some opened business accounts are not representable by
49 /// `Trd_Common.TrdMarket` (for example crypto) or overload protocol role
50 /// values (for example equity-incentive / IPO route). The bridge derives a
51 /// label from backend account metadata and stores it here so public account
52 /// discovery does not rely on numeric market allowlists.
53 pub acc_label: Option<String>,
54 /// 日本账户附加类型标签
55 pub jp_acc_type: Vec<i32>,
56 // --- 以下为审计补全的字段 ---
57 /// 账户所有者 UID
58 pub owner_uid: Option<u64>,
59 /// 账户操作者 UID
60 pub opr_uid: Option<u64>,
61 /// 混合状态 (C++ enAccState / MixedState)
62 pub mixed_state: Option<i32>,
63 /// IRA 类型 (CA: TFSA=1, RRSP=2, SRRSP=3)
64 pub ira_type: Option<i32>,
65 /// 授权状态 (GrantState)
66 pub grant_state: Option<i32>,
67 /// 口座类型 (JP: Cash=1, Margin=2, Derivative=3)
68 pub kouza_type: Option<i32>,
69 /// 交易市场 (Account.market, 单个值)
70 pub trd_market: Option<i32>,
71 /// 关联账户 ID (基金账户绑定)
72 pub association_acc_id: Option<u64>,
73 /// 综合账户子账户标志 (0=非子账户)
74 pub acc_flag: Option<i32>,
75 /// 原始顺序索引 (用于保持后端返回的自然顺序)
76 pub order_index: usize,
77 /// C++ 排序 key: (BrokerID << 48) | (TrdMkt << 32) | IntraAccID
78 pub sort_key: u64,
79}
80
81impl AccountCardRecord for CachedTrdAcc {
82 fn acc_id(&self) -> u64 {
83 self.acc_id
84 }
85
86 fn card_num(&self) -> Option<&str> {
87 self.card_num.as_deref()
88 }
89
90 fn uni_card_num(&self) -> Option<&str> {
91 self.uni_card_num.as_deref()
92 }
93}
94
95impl CachedTrdAcc {
96 /// v1.4.108: identify crypto from bridge-derived backend metadata label.
97 ///
98 /// Account discovery must not hide opened crypto accounts, but `Trd_Common`
99 /// has no public `TrdMarket_Crypto` variant. The bridge therefore derives
100 /// `acc_label=crypto` from `FTUsrTrdAcc.AccountMarket::Crypto` /
101 /// `TradingCapability::NaCrypto`; cache consumers should read that label
102 /// rather than re-hardcoding market numbers.
103 pub fn is_crypto_account(&self) -> bool {
104 self.acc_label.as_deref() == Some("crypto")
105 }
106
107 pub fn is_encrypted(&self) -> bool {
108 self.is_crypto_account()
109 }
110
111 /// v1.4.97 J-Acc-Q3 + v1.4.98 T2-6: derived `acc_label` for
112 /// `/api/accounts` REST response.
113 ///
114 /// **Priority order**:
115 /// 1. bridge-derived backend label (`crypto`, `equity_incentive`,
116 /// `ipo_route`, ...);
117 /// 2. `"paper_trade"` — `trd_env==0 (Simulate)` (v1.4.98).
118 ///
119 /// Returns `None` for "no special label" (default Margin / regular Cash).
120 ///
121 /// **Spec**: REST-only enrichment (no proto change for gRPC clients —
122 /// gRPC 不看 proto extension field, 只看 /api/accounts REST output).
123 /// Clients should `treat unknown labels as opaque strings` for forward
124 /// compatibility; new labels may appear when backend account categories are
125 /// surfaced through the bridge.
126 ///
127 /// Labels are opaque strings for clients. Unknown labels should be rendered
128 /// as-is rather than treated as an error.
129 pub fn derive_acc_label(&self) -> Option<&str> {
130 if let Some(label) = self.acc_label.as_deref() {
131 return Some(label);
132 }
133 if self.trd_env == 0 {
134 return Some("paper_trade");
135 }
136 None
137 }
138}