futu_backend/
crypto_trade.rs1#[cfg(test)]
9mod tests;
10
11use futu_cache::trd_cache::{CachedTrdAcc, TrdCache};
12use futu_core::error::{FutuError, Result};
13
14use crate::{
15 msg_header,
16 proto_internal::{odr_sys_cmn, trade_cmn},
17};
18
19#[derive(Debug, Clone, PartialEq, Eq)]
20pub struct CryptoAccountContext {
21 pub acc_id: u64,
22 pub intra_acc_id: Option<u64>,
23 pub broker_id: Option<u32>,
24 pub customer_id: Option<u64>,
25 pub cipher: Vec<u8>,
26}
27
28impl CryptoAccountContext {
29 pub fn build_asset_msg_header(&self, _op: &str) -> odr_sys_cmn::MsgHeader {
40 msg_header::build_real(self.acc_id, Some(self.cipher.clone()), None, None)
41 }
42
43 pub fn build_crypto_msg_header(&self, _op: &str) -> trade_cmn::CryptoMsgHeader {
52 msg_header::build_crypto(self.acc_id, self.cipher.clone())
53 }
54
55 pub fn require_intra_acc_id(&self, op: &str) -> Result<u64> {
56 self.intra_acc_id.ok_or_else(|| {
57 crypto_context_error(format!(
58 "Crypto {op}: account {} missing intra_acc_id",
59 self.acc_id
60 ))
61 })
62 }
63
64 pub fn require_broker_id(&self, op: &str) -> Result<u32> {
65 self.broker_id.ok_or_else(|| {
66 crypto_context_error(format!(
67 "Crypto {op}: account {} missing broker_id",
68 self.acc_id
69 ))
70 })
71 }
72
73 pub fn require_customer_id(&self, op: &str) -> Result<u64> {
74 self.customer_id.ok_or_else(|| {
75 crypto_context_error(format!(
76 "Crypto {op}: account {} missing customer_id",
77 self.acc_id
78 ))
79 })
80 }
81}
82
83pub fn lookup_crypto_account_context(
84 cache: &TrdCache,
85 acc_id: u64,
86) -> Result<CryptoAccountContext> {
87 let acc = cache.lookup_account(acc_id).ok_or_else(|| {
88 crypto_context_error(format!("Crypto account {acc_id} not found in trade cache"))
89 })?;
90 crypto_account_context_from_acc(cache, &acc)
91}
92
93pub fn crypto_account_context_from_acc(
94 cache: &TrdCache,
95 acc: &CachedTrdAcc,
96) -> Result<CryptoAccountContext> {
97 if !acc.is_crypto_account() {
98 return Err(crypto_context_error(format!(
99 "Account {} is not a crypto account",
100 acc.acc_id
101 )));
102 }
103 Ok(CryptoAccountContext {
104 acc_id: acc.acc_id,
105 intra_acc_id: acc.intra_acc_id,
106 broker_id: broker_id_from_account(acc),
107 customer_id: acc
111 .owner_uid
112 .filter(|uid| *uid != 0)
113 .or_else(|| acc.opr_uid.filter(|uid| *uid != 0)),
114 cipher: cache.get_cipher(acc.acc_id).unwrap_or_default(),
115 })
116}
117
118pub fn broker_id_from_account(acc: &CachedTrdAcc) -> Option<u32> {
125 let from_sort_key = (acc.sort_key >> 48) as u32;
126 if from_sort_key != 0 {
127 return Some(from_sort_key);
128 }
129 acc.security_firm.and_then(security_firm_to_broker_id)
130}
131
132pub fn security_firm_to_broker_id(sf: i32) -> Option<u32> {
133 match sf {
134 1 => Some(1001),
135 2 => Some(1007),
136 3 => Some(1008),
137 4 => Some(1009),
138 5 => Some(1019),
139 6 => Some(1017),
140 7 => Some(1012),
141 _ => None,
142 }
143}
144
145pub fn single_crypto_account_broker(trd_cache: &futu_cache::trd_cache::TrdCache) -> Option<u32> {
157 let crypto_brokers: std::collections::HashSet<u32> = trd_cache
158 .accounts
159 .iter()
160 .filter(|r| r.value().is_crypto_account())
161 .filter_map(|r| broker_id_from_account(r.value()))
162 .collect();
163 if crypto_brokers.len() == 1 {
164 crypto_brokers.into_iter().next()
165 } else {
166 None
167 }
168}
169
170fn crypto_context_error(msg: String) -> FutuError {
171 FutuError::ServerError { ret_type: -1, msg }
172}