Skip to main content

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}