futu_backend/trade_query/crypto_orders/
queries_orders.rs1use futu_cache::trd_cache::CachedOrder;
5use futu_core::error::{FutuError, Result};
6use futu_domain_trade_history::pagination::{
7 CryptoReadCursorPageDecision, crypto_read_order_pagination_exceeded_like_cpp,
8 decide_crypto_read_order_page_like_cpp,
9};
10use futu_domain_trade_history::status::{
11 BackendHistoryMsgHeaderFacts, BackendHistoryStatusError,
12 crypto_order_detail_response_status_like_cpp, crypto_order_read_response_status_like_cpp,
13};
14use futu_domain_trade_history::{
15 plan_crypto_history_order_list_request_like_cpp, plan_crypto_order_detail_request_like_cpp,
16 plan_crypto_order_list_request_like_cpp,
17};
18
19use super::super::*;
20
21use crate::crypto_trade::{
22 CryptoAccountContext, lookup_crypto_account_context, lookup_crypto_read_account_context,
23};
24use crate::proto_internal::trade_cmn;
25use crate::trade_cmd::{CryptoTradeOperation, crypto_trade_command};
26
27use super::projections::*;
28use super::types::*;
29
30pub async fn query_crypto_orders(
31 backend: &BackendConn,
32 acc_id: u64,
33 trd_cache: &TrdCache,
34) -> Result<Vec<CachedOrder>> {
35 let ctx = lookup_crypto_account_context(trd_cache, acc_id)?;
36 query_crypto_orders_with_context(backend, acc_id, trd_cache, &ctx).await
37}
38
39pub async fn query_crypto_orders_for_startup(
43 backend: &BackendConn,
44 acc_id: u64,
45 trd_cache: &TrdCache,
46) -> Result<Vec<CachedOrder>> {
47 let ctx = lookup_crypto_read_account_context(trd_cache, acc_id)?;
48 query_crypto_orders_with_context(backend, acc_id, trd_cache, &ctx).await
49}
50
51async fn query_crypto_orders_with_context(
52 backend: &BackendConn,
53 acc_id: u64,
54 trd_cache: &TrdCache,
55 ctx: &CryptoAccountContext,
56) -> Result<Vec<CachedOrder>> {
57 use prost::Message;
58
59 let spec = crypto_trade_command(CryptoTradeOperation::Orders);
60 let mut all_orders = Vec::new();
61 let mut page_flag: Option<String> = None;
62
63 for _ in 0..MAX_PAGES {
64 let plan = plan_crypto_order_list_request_like_cpp(page_flag.as_deref());
65 let req = inbound_read::OrderListReq {
66 msg_header: Some(ctx.build_crypto_msg_header("order_list")),
67 page_size: Some(plan.page_size),
68 page_flag: plan.page_flag,
69 list_type: Some(plan.list_type),
70 };
71 let resp = crate::command_runtime::execute_crypto_trade_command(
72 backend,
73 CryptoTradeOperation::Orders,
74 None,
75 bytes::Bytes::from(req.encode_to_vec()),
76 )
77 .await
78 .map_err(|e| {
79 tracing::warn!(cmd_id = spec.cmd, error = %e, "crypto order query failed");
80 e
81 })?;
82
83 let parsed: inbound_read::OrderListRsp =
84 Message::decode(resp.body.as_ref()).map_err(|e| {
85 tracing::warn!(
86 cmd_id = spec.cmd,
87 body_len = resp.body.len(),
88 error = %e,
89 "crypto order query decode failed"
90 );
91 FutuError::Proto(e)
92 })?;
93
94 ensure_crypto_order_read_status("CryptoOrderListRsp", parsed.msg_header.as_ref(), acc_id)?;
95 all_orders.extend(parsed.orders.iter().filter_map(project_crypto_order));
96 match decide_crypto_read_order_page_like_cpp(
97 "query_crypto_orders",
98 parsed.completed,
99 parsed.page_flag.as_deref(),
100 all_orders.len(),
101 ) {
102 CryptoReadCursorPageDecision::Complete => {
103 trd_cache.merge_preserving_stubs(acc_id, all_orders.clone());
104 tracing::debug!(count = all_orders.len(), "crypto orders queried");
105 return Ok(all_orders);
106 }
107 CryptoReadCursorPageDecision::Continue { next_page_flag } => {
108 page_flag = Some(next_page_flag);
109 }
110 CryptoReadCursorPageDecision::PartialMissingPageFlag { error_message, .. } => {
111 return Err(FutuError::Codec(error_message));
112 }
113 }
114 }
115
116 Err(FutuError::Codec(
117 crypto_read_order_pagination_exceeded_like_cpp("query_crypto_orders", MAX_PAGES),
118 ))
119}
120
121pub async fn query_crypto_order_info(
128 backend: &BackendConn,
129 acc_id: u64,
130 trd_cache: &TrdCache,
131 order_ids: &[String],
132) -> Result<Vec<CachedOrder>> {
133 use prost::Message;
134
135 let detail_plan =
136 plan_crypto_order_detail_request_like_cpp(order_ids).map_err(FutuError::Codec)?;
137 let requested = detail_plan.order_ids;
138
139 let ctx = lookup_crypto_account_context(trd_cache, acc_id)?;
140 let spec = crypto_trade_command(CryptoTradeOperation::OrderInfo);
141 let req = inbound_read::OrderDetailReq {
142 msg_header: Some(ctx.build_crypto_msg_header("order_detail")),
143 order_ids: requested.clone(),
144 };
145 let resp = crate::command_runtime::execute_crypto_trade_command(
146 backend,
147 CryptoTradeOperation::OrderInfo,
148 None,
149 bytes::Bytes::from(req.encode_to_vec()),
150 )
151 .await
152 .map_err(|e| {
153 tracing::warn!(
154 cmd_id = spec.cmd,
155 order_ids = ?requested,
156 error = %e,
157 "crypto order detail query failed"
158 );
159 e
160 })?;
161
162 let parsed: inbound_read::OrderDetailRsp =
163 Message::decode(resp.body.as_ref()).map_err(|e| {
164 tracing::warn!(
165 cmd_id = spec.cmd,
166 body_len = resp.body.len(),
167 error = %e,
168 "crypto order detail query decode failed"
169 );
170 FutuError::Proto(e)
171 })?;
172 ensure_crypto_order_detail_status(
173 "CryptoOrderDetailRsp",
174 parsed.msg_header.as_ref(),
175 acc_id,
176 requested.len(),
177 parsed.orders.len(),
178 )?;
179 let orders: Vec<CachedOrder> = parsed
180 .orders
181 .iter()
182 .filter_map(project_crypto_order)
183 .collect();
184
185 trd_cache.merge_preserving_stubs(acc_id, orders.clone());
186 tracing::debug!(count = orders.len(), "crypto order details queried");
187 Ok(orders)
188}
189
190pub async fn query_crypto_history_orders(
197 backend: &BackendConn,
198 acc_id: u64,
199 trd_cache: &TrdCache,
200 start_micros: u64,
201 end_micros: u64,
202) -> Result<Vec<CachedOrder>> {
203 use prost::Message;
204
205 let ctx = lookup_crypto_account_context(trd_cache, acc_id)?;
206 let spec = crypto_trade_command(CryptoTradeOperation::HistoryOrders);
207 let mut all_orders = Vec::new();
208 let mut page_flag: Option<String> = None;
209
210 for _ in 0..MAX_PAGES {
211 let plan = plan_crypto_history_order_list_request_like_cpp(
212 start_micros,
213 end_micros,
214 page_flag.as_deref(),
215 );
216 let req = inbound_read::HistoryOrderListReq {
217 msg_header: Some(ctx.build_crypto_msg_header("history_order_list")),
218 page_size: Some(plan.page_size),
219 page_flag: plan.page_flag,
220 start_time: Some(plan.start_time_micros),
221 end_time: Some(plan.end_time_micros),
222 symbol: plan.symbol,
223 order_status: plan.order_status,
224 currency: plan.currency,
225 side: plan.side,
226 query_word: plan.query_word,
227 ord_type: plan.order_type,
228 };
229 let resp = crate::command_runtime::execute_crypto_trade_command(
230 backend,
231 CryptoTradeOperation::HistoryOrders,
232 None,
233 bytes::Bytes::from(req.encode_to_vec()),
234 )
235 .await
236 .map_err(|e| {
237 tracing::warn!(
238 cmd_id = spec.cmd,
239 error = %e,
240 "crypto history order query failed"
241 );
242 e
243 })?;
244
245 let parsed: inbound_read::HistoryOrderListRsp = Message::decode(resp.body.as_ref())
246 .map_err(|e| {
247 tracing::warn!(
248 cmd_id = spec.cmd,
249 body_len = resp.body.len(),
250 error = %e,
251 "crypto history order query decode failed"
252 );
253 FutuError::Proto(e)
254 })?;
255
256 ensure_crypto_order_read_status(
257 "CryptoHistoryOrderListRsp",
258 parsed.msg_header.as_ref(),
259 acc_id,
260 )?;
261 all_orders.extend(parsed.orders.iter().filter_map(project_crypto_order));
262 match decide_crypto_read_order_page_like_cpp(
263 "query_crypto_history_orders",
264 parsed.completed,
265 parsed.page_flag.as_deref(),
266 all_orders.len(),
267 ) {
268 CryptoReadCursorPageDecision::Complete => {
269 tracing::debug!(count = all_orders.len(), "crypto history orders queried");
270 return Ok(all_orders);
271 }
272 CryptoReadCursorPageDecision::Continue { next_page_flag } => {
273 page_flag = Some(next_page_flag);
274 }
275 CryptoReadCursorPageDecision::PartialMissingPageFlag { error_message, .. } => {
276 return Err(FutuError::Codec(error_message));
277 }
278 }
279 }
280
281 Err(FutuError::Codec(
282 crypto_read_order_pagination_exceeded_like_cpp("query_crypto_history_orders", MAX_PAGES),
283 ))
284}
285
286fn ensure_crypto_order_read_status(
287 message_name: &str,
288 msg_header: Option<&trade_cmn::CryptoMsgHeader>,
289 acc_id: u64,
290) -> Result<()> {
291 crypto_order_read_response_status_like_cpp(
292 message_name,
293 msg_header.map(crypto_msg_header_facts),
294 acc_id,
295 )
296 .map_err(crypto_order_status_error_to_futu_error)
297}
298
299fn ensure_crypto_order_detail_status(
300 message_name: &str,
301 msg_header: Option<&trade_cmn::CryptoMsgHeader>,
302 acc_id: u64,
303 expected_count: usize,
304 actual_count: usize,
305) -> Result<()> {
306 crypto_order_detail_response_status_like_cpp(
307 message_name,
308 msg_header.map(crypto_msg_header_facts),
309 acc_id,
310 expected_count,
311 actual_count,
312 )
313 .map_err(crypto_order_status_error_to_futu_error)
314}
315
316fn crypto_msg_header_facts(header: &trade_cmn::CryptoMsgHeader) -> BackendHistoryMsgHeaderFacts {
317 BackendHistoryMsgHeaderFacts {
318 account_id: header.account_id,
319 }
320}
321
322fn crypto_order_status_error_to_futu_error(err: BackendHistoryStatusError) -> FutuError {
323 if err.is_backend_error {
324 FutuError::ServerError {
325 ret_type: -1,
326 msg: err.message,
327 }
328 } else {
329 FutuError::Codec(err.message)
330 }
331}