futu_backend/trade_query/crypto_orders/
queries_misc.rs1use futu_cache::static_data::CryptoTradeConfig;
5use futu_core::error::{FutuError, Result};
6use futu_domain_trade_history::projection::crypto::{
7 CryptoCashLogPageDecision, CryptoCashLogResponseStatusDecision,
8 CryptoCashLogResponseStatusFacts, crypto_cash_log_pagination_exceeded_like_cpp,
9 decide_crypto_cash_log_page_like_cpp, decide_crypto_cash_log_response_status_like_cpp,
10 plan_crypto_cash_log_request_like_cpp, plan_crypto_order_fee_batch_ranges_like_cpp,
11};
12use futu_domain_trade_write::{
13 CryptoMaxQtyBackendRequestPlan, CryptoTradeConfigRequestPlan,
14 plan_crypto_trade_config_full_request_like_cpp, project_crypto_max_qty_backend_values_like_cpp,
15};
16
17use super::super::*;
18
19use crate::crypto_trade::{CryptoAccountContext, lookup_crypto_account_context};
20use crate::proto_internal::cash_change_detail_cmn;
21use crate::trade_cmd::{CryptoTradeOperation, crypto_trade_command};
22
23use super::projections::*;
24use super::types::*;
25
26pub async fn query_crypto_order_fees(
27 backend: &BackendConn,
28 acc_id: u64,
29 trd_cache: &TrdCache,
30 order_ids: &[String],
31) -> Result<Vec<CryptoOrderFeeInfo>> {
32 use prost::Message;
33
34 let _ctx = lookup_crypto_account_context(trd_cache, acc_id)?;
35 let spec = crypto_trade_command(CryptoTradeOperation::BatchOrderFee);
36 let mut fees = Vec::new();
37
38 for batch in plan_crypto_order_fee_batch_ranges_like_cpp(order_ids.len()) {
39 let req = inbound_oe::BatchQueryOrderFeeReq {
40 order_id_list: order_ids[batch].to_vec(),
41 long_account_id: Some(acc_id),
42 };
43 let resp = crate::command_runtime::execute_crypto_trade_command(
44 backend,
45 CryptoTradeOperation::BatchOrderFee,
46 None,
47 bytes::Bytes::from(req.encode_to_vec()),
48 )
49 .await
50 .map_err(|e| {
51 tracing::warn!(
52 cmd_id = spec.cmd,
53 error = %e,
54 "crypto order fee query failed"
55 );
56 e
57 })?;
58
59 let parsed: inbound_oe::BatchQueryOrderFeeRsp = Message::decode(resp.body.as_ref())
60 .map_err(|e| {
61 tracing::warn!(
62 cmd_id = spec.cmd,
63 body_len = resp.body.len(),
64 error = %e,
65 "crypto order fee query decode failed"
66 );
67 FutuError::Proto(e)
68 })?;
69
70 fees.extend(
71 parsed
72 .fee_group_list
73 .iter()
74 .filter_map(project_crypto_order_fee),
75 );
76 }
77
78 Ok(fees)
79}
80
81pub async fn query_crypto_cash_logs(
87 backend: &BackendConn,
88 acc_id: u64,
89 trd_cache: &TrdCache,
90 begin_time: u64,
91 end_time: u64,
92) -> Result<Vec<CryptoCashLogInfo>> {
93 use prost::Message;
94
95 let ctx = lookup_crypto_account_context(trd_cache, acc_id)?;
96 let spec = crypto_trade_command(CryptoTradeOperation::CashLog);
97 let mut all_logs = Vec::new();
98 let mut log_id: Option<String> = None;
99
100 for _ in 0..MAX_PAGES {
101 let plan = plan_crypto_cash_log_request_like_cpp(
102 ctx.require_intra_acc_id("crypto_cash_log")?,
103 ctx.require_broker_id("crypto_cash_log")?,
104 acc_id,
105 begin_time,
106 end_time,
107 log_id.as_deref(),
108 );
109 let req = cash_change_detail_cmn::GetCashLogReq {
110 market: Some(plan.market),
111 account_id: Some(plan.account_id),
112 broker_id: Some(plan.broker_id),
113 long_account_id: Some(plan.long_account_id),
114 begin_time: Some(plan.begin_time),
115 end_time: Some(plan.end_time),
116 log_id: plan.log_id,
117 ..Default::default()
123 };
124 let resp = crate::command_runtime::execute_crypto_trade_command(
125 backend,
126 CryptoTradeOperation::CashLog,
127 None,
128 bytes::Bytes::from(req.encode_to_vec()),
129 )
130 .await
131 .map_err(|e| {
132 tracing::warn!(
133 cmd_id = spec.cmd,
134 error = %e,
135 "crypto cash log query failed"
136 );
137 e
138 })?;
139
140 let parsed: cash_change_detail_cmn::GetCashLogRsp = Message::decode(resp.body.as_ref())
141 .map_err(|e| {
142 tracing::warn!(
143 cmd_id = spec.cmd,
144 body_len = resp.body.len(),
145 error = %e,
146 "crypto cash log decode failed"
147 );
148 FutuError::Proto(e)
149 })?;
150
151 match decide_crypto_cash_log_response_status_like_cpp(CryptoCashLogResponseStatusFacts {
152 result: parsed.result,
153 err_msg: parsed.err_msg.as_deref(),
154 }) {
155 CryptoCashLogResponseStatusDecision::Accepted => {}
156 CryptoCashLogResponseStatusDecision::BusinessError { ret_type, message } => {
157 return Err(FutuError::ServerError {
158 ret_type,
159 msg: message,
160 });
161 }
162 }
163
164 all_logs.extend(
165 parsed
166 .monthly_log_list
167 .iter()
168 .flat_map(|month| month.cash_log_list.iter())
169 .filter_map(project_crypto_cash_log),
170 );
171
172 match decide_crypto_cash_log_page_like_cpp(parsed.has_more, parsed.next_log_id.as_deref()) {
173 CryptoCashLogPageDecision::Continue { next_log_id } => {
174 log_id = Some(next_log_id);
175 continue;
176 }
177 CryptoCashLogPageDecision::Complete => {}
178 }
179
180 tracing::debug!(count = all_logs.len(), "crypto cash logs queried");
181 return Ok(all_logs);
182 }
183
184 Err(FutuError::Codec(
185 crypto_cash_log_pagination_exceeded_like_cpp("query_crypto_cash_logs", MAX_PAGES),
186 ))
187}
188
189pub async fn query_crypto_trade_configs(
195 backend: &BackendConn,
196 broker_id: u32,
197) -> Result<Vec<CryptoTradeConfig>> {
198 use prost::Message;
199
200 let spec = crypto_trade_command(CryptoTradeOperation::FetchTradeConfig);
201 let req =
202 fetch_trade_config_request_from_plan(plan_crypto_trade_config_full_request_like_cpp());
203 let resp = crate::command_runtime::execute_crypto_trade_command(
204 backend,
205 CryptoTradeOperation::FetchTradeConfig,
206 None,
207 bytes::Bytes::from(req.encode_to_vec()),
208 )
209 .await
210 .map_err(|e| {
211 tracing::warn!(broker_id, cmd_id = spec.cmd, error = %e, "crypto trade config query failed");
212 e
213 })?;
214
215 let parsed: inbound_oe::FetchTradeConfigResponse = Message::decode(resp.body.as_ref())
216 .map_err(|e| {
217 tracing::warn!(
218 broker_id,
219 cmd_id = spec.cmd,
220 body_len = resp.body.len(),
221 error = %e,
222 "crypto trade config decode failed"
223 );
224 FutuError::Proto(e)
225 })?;
226 crypto_trade_config_response_status(&parsed)?;
227
228 Ok(parsed
229 .full_trade_config_list
230 .iter()
231 .filter_map(project_crypto_trade_config)
232 .collect())
233}
234
235fn fetch_trade_config_request_from_plan(
236 plan: CryptoTradeConfigRequestPlan,
237) -> inbound_oe::FetchTradeConfigRequest {
238 inbound_oe::FetchTradeConfigRequest {
239 currency_pair_list: plan
240 .currency_pairs
241 .into_iter()
242 .map(|pair| config_base::CurrencyPair {
243 base_currency: Some(pair.base_currency),
244 quote_currency: Some(pair.quote_currency),
245 })
246 .collect(),
247 data_from: plan.data_from,
248 data_max_count: plan.data_max_count,
249 symbol_list: plan.symbols,
250 status: plan.status,
251 }
252}
253
254pub async fn query_crypto_max_buy_sell_qty(
260 backend: &BackendConn,
261 ctx: &CryptoAccountContext,
262 input: CryptoMaxQtyBackendRequestPlan,
263) -> Result<CryptoMaxBuySellQtyInfo> {
264 use prost::Message;
265
266 let spec = crypto_trade_command(CryptoTradeOperation::MaxBuySellQty);
267 let req = crypto_risk::GetMaxBuySellReq {
268 account: Some(crypto_risk_comm::Account {
269 cid: Some(ctx.require_customer_id("max_buy_sell_qty")?),
270 acct_id: Some(ctx.require_intra_acc_id("max_buy_sell_qty")?),
271 market: Some(input.account_market),
272 broker_id: Some(ctx.require_broker_id("max_buy_sell_qty")?),
273 long_acct_id: Some(ctx.acc_id),
274 }),
275 order_type: Some(input.order_type),
276 currency: Some(input.currency),
277 coin: Some(input.coin),
278 price: input.price,
279 order_id: input.order_id,
280 };
281 let resp = crate::command_runtime::execute_crypto_trade_command(
282 backend,
283 CryptoTradeOperation::MaxBuySellQty,
284 None,
285 bytes::Bytes::from(req.encode_to_vec()),
286 )
287 .await
288 .map_err(|e| {
289 tracing::warn!(
290 cmd_id = spec.cmd,
291 error = %e,
292 "crypto max buy/sell qty query failed"
293 );
294 e
295 })?;
296 let parsed: crypto_risk::GetMaxBuySellRsp =
297 Message::decode(resp.body.as_ref()).map_err(|e| {
298 tracing::warn!(
299 cmd_id = spec.cmd,
300 body_len = resp.body.len(),
301 error = %e,
302 "crypto max buy/sell qty decode failed"
303 );
304 FutuError::Proto(e)
305 })?;
306
307 let values = project_crypto_max_qty_backend_values_like_cpp(
308 parsed.max_cash_buy_qty.as_deref(),
309 parsed.max_sell_qty.as_deref(),
310 );
311
312 Ok(CryptoMaxBuySellQtyInfo {
313 max_cash_buy_qty: values.max_cash_buy_qty,
314 max_sell_qty: values.max_sell_qty,
315 })
316}