futu_backend/trade_query/crypto_orders/
queries_misc.rs1use futu_cache::static_data::CryptoTradeConfig;
5use futu_core::error::{FutuError, Result};
6
7use super::super::common::pf;
8use super::super::*;
9
10use crate::crypto_trade::{CryptoAccountContext, lookup_crypto_account_context};
11use crate::proto_internal::cash_change_detail_cmn;
12use crate::trade_cmd::{CryptoTradeOperation, crypto_trade_command};
13
14use super::projections::*;
15use super::types::*;
16
17pub async fn query_crypto_order_fees(
18 backend: &BackendConn,
19 acc_id: u64,
20 trd_cache: &TrdCache,
21 order_ids: &[String],
22) -> Result<Vec<CryptoOrderFeeInfo>> {
23 use prost::Message;
24
25 let _ctx = lookup_crypto_account_context(trd_cache, acc_id)?;
26 let spec = crypto_trade_command(CryptoTradeOperation::BatchOrderFee);
27 let req = inbound_oe::BatchQueryOrderFeeReq {
28 order_id_list: order_ids.to_vec(),
29 long_account_id: Some(acc_id),
30 };
31 let resp = backend
32 .request(spec.cmd, req.encode_to_vec())
33 .await
34 .map_err(|e| {
35 tracing::warn!(
36 acc_id,
37 cmd_id = spec.cmd,
38 error = %e,
39 "crypto order fee query failed"
40 );
41 e
42 })?;
43
44 let parsed: inbound_oe::BatchQueryOrderFeeRsp =
45 Message::decode(resp.body.as_ref()).map_err(|e| {
46 tracing::warn!(
47 acc_id,
48 cmd_id = spec.cmd,
49 body_len = resp.body.len(),
50 error = %e,
51 "crypto order fee query decode failed"
52 );
53 FutuError::Proto(e)
54 })?;
55
56 let fees = parsed
57 .fee_group_list
58 .iter()
59 .filter_map(project_crypto_order_fee)
60 .collect();
61 Ok(fees)
62}
63
64pub async fn query_crypto_cash_logs(
70 backend: &BackendConn,
71 acc_id: u64,
72 trd_cache: &TrdCache,
73 begin_time: u64,
74 end_time: u64,
75) -> Result<Vec<CryptoCashLogInfo>> {
76 use prost::Message;
77
78 let ctx = lookup_crypto_account_context(trd_cache, acc_id)?;
79 let spec = crypto_trade_command(CryptoTradeOperation::CashLog);
80 let mut all_logs = Vec::new();
81 let mut log_id: Option<String> = None;
82
83 for _ in 0..MAX_PAGES {
84 let req = cash_change_detail_cmn::GetCashLogReq {
85 market: Some(cash_change_detail_cmn::Market::Crypto as u32),
86 account_id: Some(ctx.require_intra_acc_id("crypto_cash_log")?),
87 broker_id: Some(ctx.require_broker_id("crypto_cash_log")?),
88 long_account_id: Some(acc_id),
89 begin_time: Some(begin_time),
90 end_time: Some(end_time),
91 log_id: log_id.clone(),
92 ..Default::default()
93 };
94 let resp = backend
95 .request(spec.cmd, req.encode_to_vec())
96 .await
97 .map_err(|e| {
98 tracing::warn!(
99 acc_id,
100 cmd_id = spec.cmd,
101 error = %e,
102 "crypto cash log query failed"
103 );
104 e
105 })?;
106
107 let parsed: cash_change_detail_cmn::GetCashLogRsp = Message::decode(resp.body.as_ref())
108 .map_err(|e| {
109 tracing::warn!(
110 acc_id,
111 cmd_id = spec.cmd,
112 body_len = resp.body.len(),
113 error = %e,
114 "crypto cash log decode failed"
115 );
116 FutuError::Proto(e)
117 })?;
118
119 let result = parsed.result.unwrap_or(0);
123 if result != 0 {
124 return Err(FutuError::ServerError {
125 ret_type: result,
126 msg: parsed
127 .err_msg
128 .unwrap_or_else(|| "query_crypto_cash_logs: backend error".to_string()),
129 });
130 }
131
132 all_logs.extend(
133 parsed
134 .monthly_log_list
135 .iter()
136 .flat_map(|month| month.cash_log_list.iter())
137 .filter_map(project_crypto_cash_log),
138 );
139
140 if parsed.has_more.unwrap_or(false) {
141 match parsed.next_log_id {
142 Some(ref next) if !next.is_empty() => {
143 log_id = Some(next.clone());
144 continue;
145 }
146 _ => {
147 return Err(FutuError::Codec(
148 "query_crypto_cash_logs: has_more without next_log_id".to_string(),
149 ));
150 }
151 }
152 }
153
154 tracing::debug!(acc_id, count = all_logs.len(), "crypto cash logs queried");
155 return Ok(all_logs);
156 }
157
158 Err(FutuError::Codec(format!(
159 "query_crypto_cash_logs: pagination exceeded {MAX_PAGES} pages"
160 )))
161}
162
163pub async fn query_crypto_trade_configs(
169 backend: &BackendConn,
170 broker_id: u32,
171) -> Result<Vec<CryptoTradeConfig>> {
172 use prost::Message;
173
174 let spec = crypto_trade_command(CryptoTradeOperation::FetchTradeConfig);
175 let req = inbound_oe::FetchTradeConfigRequest {
176 currency_pair_list: Vec::new(),
177 data_from: None,
178 data_max_count: None,
179 symbol_list: Vec::new(),
180 status: None,
181 };
182 let resp = backend
183 .request(spec.cmd, req.encode_to_vec())
184 .await
185 .map_err(|e| {
186 tracing::warn!(broker_id, cmd_id = spec.cmd, error = %e, "crypto trade config query failed");
187 e
188 })?;
189
190 let parsed: inbound_oe::FetchTradeConfigResponse = Message::decode(resp.body.as_ref())
191 .map_err(|e| {
192 tracing::warn!(
193 broker_id,
194 cmd_id = spec.cmd,
195 body_len = resp.body.len(),
196 error = %e,
197 "crypto trade config decode failed"
198 );
199 FutuError::Proto(e)
200 })?;
201 crypto_trade_config_response_status(&parsed)?;
202
203 Ok(parsed
204 .full_trade_config_list
205 .iter()
206 .filter_map(project_crypto_trade_config)
207 .collect())
208}
209
210#[derive(Debug, Clone)]
216pub struct CryptoMaxBuySellQtyRequest {
217 pub account_market: u32,
218 pub order_type: u32,
219 pub coin: String,
220 pub currency: String,
221 pub price: Option<String>,
222 pub order_id: Option<String>,
223}
224
225pub async fn query_crypto_max_buy_sell_qty(
226 backend: &BackendConn,
227 ctx: &CryptoAccountContext,
228 input: CryptoMaxBuySellQtyRequest,
229) -> Result<CryptoMaxBuySellQtyInfo> {
230 use prost::Message;
231
232 let spec = crypto_trade_command(CryptoTradeOperation::MaxBuySellQty);
233 let req = crypto_risk::GetMaxBuySellReq {
234 account: Some(crypto_risk_comm::Account {
235 cid: Some(ctx.require_customer_id("max_buy_sell_qty")?),
236 acct_id: Some(ctx.require_intra_acc_id("max_buy_sell_qty")?),
237 market: Some(input.account_market),
238 broker_id: Some(ctx.require_broker_id("max_buy_sell_qty")?),
239 long_acct_id: Some(ctx.acc_id),
240 }),
241 order_type: Some(input.order_type),
242 currency: Some(input.currency),
243 coin: Some(input.coin),
244 price: input.price,
245 order_id: input.order_id,
246 };
247 let resp = backend
248 .request(spec.cmd, req.encode_to_vec())
249 .await
250 .map_err(|e| {
251 tracing::warn!(
252 acc_id = ctx.acc_id,
253 cmd_id = spec.cmd,
254 error = %e,
255 "crypto max buy/sell qty query failed"
256 );
257 e
258 })?;
259 let parsed: crypto_risk::GetMaxBuySellRsp =
260 Message::decode(resp.body.as_ref()).map_err(|e| {
261 tracing::warn!(
262 acc_id = ctx.acc_id,
263 cmd_id = spec.cmd,
264 body_len = resp.body.len(),
265 error = %e,
266 "crypto max buy/sell qty decode failed"
267 );
268 FutuError::Proto(e)
269 })?;
270
271 Ok(CryptoMaxBuySellQtyInfo {
272 max_cash_buy_qty: pf(&parsed.max_cash_buy_qty),
273 max_sell_qty: pf(&parsed.max_sell_qty),
274 })
275}