futu_backend/trade_query/
common.rs1use super::*;
2
3use std::time::{SystemTime, UNIX_EPOCH};
4
5use base64::Engine as _;
6pub(super) use futu_domain_trade_account::{
7 FundsCashInfoFact, FundsMarketAssetFact, FundsMarketCurrencyAssetFact,
8 FundsSecuritiesAssetsFacts, FundsTopLevelFact, ProjectedFundsMarketInfo,
9 api_currency_to_backend_like_cpp as api_currency_to_backend,
10 backend_currency_to_api_like_cpp as backend_currency_to_api,
11 fund_bond_currency_label_like_cpp as currency_to_fund_bond_ccy,
12 project_funds_cash_info_list_like_cpp, project_funds_market_info_list_like_cpp,
13 project_funds_securities_assets_like_cpp, project_funds_top_level_like_cpp,
14 stock_market_to_funds_nn_trd_market_like_cpp,
15};
16use futu_domain_trade_order::{hash_backend_id_to_u64_like_cpp, hash_str_to_u64_like_om};
17use rand::Rng;
18
19const OPEN_D_KEY_PREFIX: &str = "OD|";
20const REQ_ID_RAW_LEN: usize = 24;
21const REQ_ID_PREFIX: &str = "OD|";
22
23pub(super) fn parse_open_d_text(text: Option<&str>) -> Option<(u64, String)> {
39 let text = text?;
40 let body = text.strip_prefix(OPEN_D_KEY_PREFIX)?;
41 let (id_part, remark) = body.split_once('|')?;
42 let local_id: u64 = id_part.parse().ok()?;
43 Some((local_id, remark.to_string()))
44}
45
46pub(super) fn pf(s: &Option<String>) -> f64 {
48 s.as_ref()
49 .and_then(|v| v.parse::<f64>().ok())
50 .unwrap_or(0.0)
51}
52
53pub(super) fn pfo(s: &Option<String>) -> Option<f64> {
55 s.as_ref().and_then(|v| v.parse::<f64>().ok())
56}
57
58pub(super) fn build_market_info_list(
66 fund_info_list: &[crate::proto_internal::asset_query::AccFundInfo],
67) -> Vec<CachedMarketInfo> {
68 let currency_assets: Vec<FundsMarketCurrencyAssetFact> = fund_info_list
69 .iter()
70 .filter_map(|fi| {
71 let currency = backend_currency_to_api(fi.currency?);
72 Some(FundsMarketCurrencyAssetFact {
73 currency,
74 total_asset: pf(&fi.total_asset),
75 })
76 })
77 .collect();
78
79 project_funds_market_info_list_like_cpp(¤cy_assets)
80 .into_iter()
81 .map(|item| CachedMarketInfo {
82 trd_market: item.trd_market,
83 assets: item.assets,
84 })
85 .collect()
86}
87
88pub(super) fn project_securities_assets_in_response_currency(
89 diff_market_fund_info_list: &[crate::proto_internal::asset_query::AccFundInfo],
90 response_currency: Option<i32>,
91 market_info_list: &[CachedMarketInfo],
92) -> Option<f64> {
93 let market_assets: Vec<FundsMarketAssetFact> = diff_market_fund_info_list
94 .iter()
95 .filter_map(|fund_info| {
96 let trd_market = stock_market_to_funds_nn_trd_market_like_cpp(fund_info.stock_market?)?;
97 Some(FundsMarketAssetFact {
98 trd_market,
99 total_asset: pf(&fund_info.total_asset),
100 })
101 })
102 .collect();
103 let legacy_market_info_list: Vec<ProjectedFundsMarketInfo> = market_info_list
104 .iter()
105 .map(|item| ProjectedFundsMarketInfo {
106 trd_market: item.trd_market,
107 assets: item.assets,
108 })
109 .collect();
110
111 project_funds_securities_assets_like_cpp(FundsSecuritiesAssetsFacts {
112 response_currency,
113 market_assets_in_response_currency: &market_assets,
114 legacy_market_info_list: &legacy_market_info_list,
115 })
116}
117
118pub(super) fn project_cash_info_list(
119 cash_info_list: &[crate::proto_internal::asset_query::AccCashInfo],
120) -> Vec<CachedCashInfo> {
121 let facts: Vec<FundsCashInfoFact> = cash_info_list
122 .iter()
123 .map(|cash_info| FundsCashInfoFact {
124 backend_currency: cash_info.currency,
125 cash: pf(&cash_info.balance),
126 available_balance: pf(&cash_info.cash_drawable),
127 net_cash_power: pf(&cash_info.cash_buypower),
128 })
129 .collect();
130
131 project_funds_cash_info_list_like_cpp(&facts)
132 .into_iter()
133 .map(|cash_info| CachedCashInfo {
134 currency: cash_info.currency,
135 cash: cash_info.cash,
136 available_balance: cash_info.available_balance,
137 net_cash_power: cash_info.net_cash_power,
138 })
139 .collect()
140}
141
142pub(super) fn project_real_account_funds(
143 fund_info: &crate::proto_internal::asset_query::AccFundInfo,
144 union_cash_info: Option<&crate::proto_internal::asset_query::AccCashInfo>,
145 cash_info_list_pb: &[crate::proto_internal::asset_query::AccCashInfo],
146 securities_assets: Option<f64>,
147 market_info_list: Vec<CachedMarketInfo>,
148 exposure_level: Option<i32>,
149) -> CachedFunds {
150 let cash_info_list = project_cash_info_list(cash_info_list_pb);
151 let projected = project_funds_top_level_like_cpp(FundsTopLevelFact {
152 fund_currency: fund_info.currency,
153 cash_currency: union_cash_info.and_then(|cash| cash.currency),
154 max_power_long: pf(&fund_info.max_power_long),
155 total_asset: pf(&fund_info.total_asset),
156 market_value: pf(&fund_info.mv),
157 frozen_fund: pf(&fund_info.hold),
158 debit_recover: pf(&fund_info.debit_recover),
159 total_cash: union_cash_info.map(|cash| pf(&cash.balance)).unwrap_or(0.0),
160 total_available_cash: union_cash_info
161 .map(|cash| pf(&cash.cash_drawable))
162 .unwrap_or(0.0),
163 available_funds: pfo(&fund_info.available),
164 unrealized_profit: pfo(&fund_info.unrealized_profit),
165 realized_profit: pfo(&fund_info.realized_profit),
166 risk_level: fund_info.risk_level.map(|risk_level| risk_level as i32),
167 initial_margin: pfo(&fund_info.initial_margin),
168 maintenance_margin: pfo(&fund_info.maintenance_margin),
169 max_power_short: pfo(&fund_info.max_power_short),
170 net_cash_power: pfo(&fund_info.net_cash_power),
171 long_mv: pfo(&fund_info.long_mv),
172 short_mv: pfo(&fund_info.short_mv),
173 pending_asset: pfo(&fund_info.pending_asset),
174 max_withdrawal: pfo(&fund_info.drawable),
175 risk_status: fund_info.risk_status_client,
176 margin_call_margin: pfo(&fund_info.margin_call),
177 is_pdt: fund_info.is_pdt,
178 pdt_seq: &fund_info.pdt_seq,
179 beginning_dtbp: pfo(&fund_info.beginning_dtbp),
180 remaining_dtbp: pfo(&fund_info.remaining_dtbp),
181 dt_call_amount: pfo(&fund_info.dt_call_amount),
182 dt_status: fund_info.dt_status,
183 securities_assets,
184 });
185
186 CachedFunds {
187 power: projected.power,
188 total_assets: projected.total_assets,
189 cash: projected.cash,
190 market_val: projected.market_val,
191 frozen_cash: projected.frozen_cash,
192 debt_cash: projected.debt_cash,
193 avl_withdrawal_cash: projected.avl_withdrawal_cash,
194 currency: projected.currency,
195 available_funds: projected.available_funds,
196 unrealized_pl: projected.unrealized_pl,
197 realized_pl: projected.realized_pl,
198 risk_level: projected.risk_level,
199 initial_margin: projected.initial_margin,
200 maintenance_margin: projected.maintenance_margin,
201 max_power_short: projected.max_power_short,
202 net_cash_power: projected.net_cash_power,
203 long_mv: projected.long_mv,
204 short_mv: projected.short_mv,
205 pending_asset: projected.pending_asset,
206 max_withdrawal: projected.max_withdrawal,
207 risk_status: projected.risk_status,
208 margin_call_margin: projected.margin_call_margin,
209 securities_assets: projected.securities_assets,
210 fund_assets: None,
211 bond_assets: None,
212 crypto_mv: None,
213 exposure_level,
214 exposure_limit: None,
215 used_limit: None,
216 remaining_limit: None,
217 is_pdt: projected.is_pdt,
218 pdt_seq: (!projected.pdt_seq.is_empty()).then_some(projected.pdt_seq),
219 beginning_dtbp: projected.beginning_dtbp,
220 remaining_dtbp: projected.remaining_dtbp,
221 dt_call_amount: projected.dt_call_amount,
222 dt_status: projected.dt_status,
223 cash_info_list,
224 market_info_list,
225 }
226}
227
228pub fn create_backend_req_id(acc_id: u64) -> String {
241 let mut rng = rand::rng();
242 let timestamp = match SystemTime::now().duration_since(UNIX_EPOCH) {
243 Ok(duration) => duration.as_secs(),
244 Err(_) => 0,
245 };
246 let hash_seed = format!("{}{}{}", timestamp, acc_id, rng.next_u32());
247 let hash = hash_str_to_u64_like_om(&hash_seed);
248
249 let mut raw = [0u8; REQ_ID_RAW_LEN];
250 raw[..8].copy_from_slice(&hash.to_le_bytes());
251 rng.fill_bytes(&mut raw[8..]);
252
253 let mut req_id = base64::engine::general_purpose::STANDARD.encode(raw);
254 req_id.replace_range(0..REQ_ID_PREFIX.len(), REQ_ID_PREFIX);
255 req_id
256}
257
258pub fn hash_str_to_u64(s: &str) -> u64 {
267 hash_backend_id_to_u64_like_cpp(s)
268}
269
270#[cfg(test)]
271mod tests;