1use futu_cache::trd_cache::{CachedOrder, OrderRelationSourceToken};
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 query_crypto_orders_with_source(
36 backend,
37 acc_id,
38 trd_cache,
39 OrderRelationSourceToken::unknown(backend.connection_generation()),
40 )
41 .await
42}
43
44pub async fn query_crypto_orders_with_source(
45 backend: &BackendConn,
46 acc_id: u64,
47 trd_cache: &TrdCache,
48 source: OrderRelationSourceToken,
49) -> Result<Vec<CachedOrder>> {
50 let ctx = lookup_crypto_account_context(trd_cache, acc_id)?;
51 query_crypto_orders_with_context(backend, acc_id, trd_cache, &ctx, source).await
52}
53
54pub async fn query_crypto_orders_for_startup(
58 backend: &BackendConn,
59 acc_id: u64,
60 trd_cache: &TrdCache,
61) -> Result<Vec<CachedOrder>> {
62 query_crypto_orders_for_startup_with_source(
63 backend,
64 acc_id,
65 trd_cache,
66 OrderRelationSourceToken::unknown(backend.connection_generation()),
67 )
68 .await
69}
70
71pub async fn query_crypto_orders_for_startup_with_source(
72 backend: &BackendConn,
73 acc_id: u64,
74 trd_cache: &TrdCache,
75 source: OrderRelationSourceToken,
76) -> Result<Vec<CachedOrder>> {
77 let ctx = lookup_crypto_read_account_context(trd_cache, acc_id)?;
78 query_crypto_orders_with_context(backend, acc_id, trd_cache, &ctx, source).await
79}
80
81async fn query_crypto_orders_with_context(
82 backend: &BackendConn,
83 acc_id: u64,
84 trd_cache: &TrdCache,
85 ctx: &CryptoAccountContext,
86 source: OrderRelationSourceToken,
87) -> Result<Vec<CachedOrder>> {
88 use prost::Message;
89
90 let spec = crypto_trade_command(CryptoTradeOperation::Orders);
91 let mut all_orders = Vec::new();
92 let mut dropped_order_count = 0usize;
93 let mut page_flag: Option<String> = None;
94
95 for _ in 0..MAX_PAGES {
96 let plan = plan_crypto_order_list_request_like_cpp(page_flag.as_deref());
97 let req = inbound_read::OrderListReq {
98 msg_header: Some(ctx.build_crypto_msg_header("order_list")),
99 page_size: Some(plan.page_size),
100 page_flag: plan.page_flag,
101 list_type: Some(plan.list_type),
102 };
103 let resp = crate::command_runtime::execute_crypto_trade_command(
104 backend,
105 CryptoTradeOperation::Orders,
106 None,
107 bytes::Bytes::from(req.encode_to_vec()),
108 )
109 .await
110 .inspect_err(|e| {
111 tracing::warn!(
112 cmd_id = spec.cmd,
113 error = ?futu_core::log_redact::RedactedFutuError::new(e),
114 "crypto order query failed"
115 );
116 })?;
117
118 let parsed: inbound_read::OrderListRsp =
119 Message::decode(resp.body.as_ref()).map_err(|e| {
120 tracing::warn!(
121 cmd_id = spec.cmd,
122 body_len = resp.body.len(),
123 error_class = "protobuf_decode",
124 "crypto order query decode failed"
125 );
126 FutuError::Proto(e)
127 })?;
128
129 ensure_crypto_order_read_status("CryptoOrderListRsp", parsed.msg_header.as_ref(), acc_id)?;
130 let raw_order_count = parsed.orders.len();
131 let projected = parsed
132 .orders
133 .iter()
134 .filter_map(project_crypto_order)
135 .collect::<Vec<_>>();
136 dropped_order_count =
137 dropped_order_count.saturating_add(raw_order_count.saturating_sub(projected.len()));
138 all_orders.extend(projected);
139 match decide_crypto_read_order_page_like_cpp(
140 "query_crypto_orders",
141 parsed.completed,
142 parsed.page_flag.as_deref(),
143 all_orders.len(),
144 ) {
145 CryptoReadCursorPageDecision::Complete => {
146 trd_cache.replace_order_snapshot_like_cpp_from_source(
147 acc_id,
148 &all_orders,
149 source,
150 dropped_order_count == 0,
151 );
152 tracing::debug!(count = all_orders.len(), "crypto orders queried");
153 return Ok(all_orders);
154 }
155 CryptoReadCursorPageDecision::Continue { next_page_flag } => {
156 page_flag = Some(next_page_flag);
157 }
158 CryptoReadCursorPageDecision::PartialMissingPageFlag { error_message, .. } => {
159 return Err(FutuError::Codec(error_message));
160 }
161 }
162 }
163
164 Err(FutuError::Codec(
165 crypto_read_order_pagination_exceeded_like_cpp("query_crypto_orders", MAX_PAGES),
166 ))
167}
168
169pub async fn query_crypto_order_info(
176 backend: &BackendConn,
177 acc_id: u64,
178 trd_cache: &TrdCache,
179 order_ids: &[String],
180) -> Result<Vec<CachedOrder>> {
181 query_crypto_order_info_with_source(
182 backend,
183 acc_id,
184 trd_cache,
185 order_ids,
186 OrderRelationSourceToken::unknown(backend.connection_generation()),
187 )
188 .await
189}
190
191pub async fn query_crypto_order_info_with_source(
192 backend: &BackendConn,
193 acc_id: u64,
194 trd_cache: &TrdCache,
195 order_ids: &[String],
196 source: OrderRelationSourceToken,
197) -> Result<Vec<CachedOrder>> {
198 use prost::Message;
199
200 let detail_plan =
201 plan_crypto_order_detail_request_like_cpp(order_ids).map_err(FutuError::Codec)?;
202 let requested = detail_plan.order_ids;
203
204 let ctx = lookup_crypto_account_context(trd_cache, acc_id)?;
205 let spec = crypto_trade_command(CryptoTradeOperation::OrderInfo);
206 let req = inbound_read::OrderDetailReq {
207 msg_header: Some(ctx.build_crypto_msg_header("order_detail")),
208 order_ids: requested.clone(),
209 };
210 let resp = crate::command_runtime::execute_crypto_trade_command(
211 backend,
212 CryptoTradeOperation::OrderInfo,
213 None,
214 bytes::Bytes::from(req.encode_to_vec()),
215 )
216 .await
217 .inspect_err(|e| {
218 tracing::warn!(
219 cmd_id = spec.cmd,
220 order_ids = ?requested,
221 error = ?futu_core::log_redact::RedactedFutuError::new(e),
222 "crypto order detail query failed"
223 );
224 })?;
225
226 let parsed: inbound_read::OrderDetailRsp =
227 Message::decode(resp.body.as_ref()).map_err(|e| {
228 tracing::warn!(
229 cmd_id = spec.cmd,
230 body_len = resp.body.len(),
231 error_class = "protobuf_decode",
232 "crypto order detail query decode failed"
233 );
234 FutuError::Proto(e)
235 })?;
236 ensure_crypto_order_detail_status(
237 "CryptoOrderDetailRsp",
238 parsed.msg_header.as_ref(),
239 acc_id,
240 requested.len(),
241 parsed.orders.len(),
242 )?;
243 let orders: Vec<CachedOrder> = parsed
244 .orders
245 .iter()
246 .filter_map(project_crypto_order)
247 .collect();
248 if orders.len() != parsed.orders.len() {
249 trd_cache.mark_order_relation_snapshot_incomplete_from_source(acc_id, source);
250 }
251 for order in &orders {
252 trd_cache.upsert_order_from_source(acc_id, order.clone(), source);
253 }
254 tracing::debug!(count = orders.len(), "crypto order details queried");
255 Ok(orders)
256}
257
258pub async fn query_crypto_history_orders(
265 backend: &BackendConn,
266 acc_id: u64,
267 trd_cache: &TrdCache,
268 start_micros: u64,
269 end_micros: u64,
270) -> Result<Vec<CachedOrder>> {
271 use prost::Message;
272
273 let ctx = lookup_crypto_account_context(trd_cache, acc_id)?;
274 let spec = crypto_trade_command(CryptoTradeOperation::HistoryOrders);
275 let mut all_orders = Vec::new();
276 let mut page_flag: Option<String> = None;
277
278 for _ in 0..MAX_PAGES {
279 let plan = plan_crypto_history_order_list_request_like_cpp(
280 start_micros,
281 end_micros,
282 page_flag.as_deref(),
283 );
284 let req = inbound_read::HistoryOrderListReq {
285 msg_header: Some(ctx.build_crypto_msg_header("history_order_list")),
286 page_size: Some(plan.page_size),
287 page_flag: plan.page_flag,
288 start_time: Some(plan.start_time_micros),
289 end_time: Some(plan.end_time_micros),
290 symbol: plan.symbol,
291 order_status: plan.order_status,
292 currency: plan.currency,
293 side: plan.side,
294 query_word: plan.query_word,
295 ord_type: plan.order_type,
296 };
297 let resp = crate::command_runtime::execute_crypto_trade_command(
298 backend,
299 CryptoTradeOperation::HistoryOrders,
300 None,
301 bytes::Bytes::from(req.encode_to_vec()),
302 )
303 .await
304 .inspect_err(|e| {
305 tracing::warn!(
306 cmd_id = spec.cmd,
307 error = ?futu_core::log_redact::RedactedFutuError::new(e),
308 "crypto history order query failed"
309 );
310 })?;
311
312 let parsed: inbound_read::HistoryOrderListRsp = Message::decode(resp.body.as_ref())
313 .map_err(|e| {
314 tracing::warn!(
315 cmd_id = spec.cmd,
316 body_len = resp.body.len(),
317 error_class = "protobuf_decode",
318 "crypto history order query decode failed"
319 );
320 FutuError::Proto(e)
321 })?;
322
323 ensure_crypto_order_read_status(
324 "CryptoHistoryOrderListRsp",
325 parsed.msg_header.as_ref(),
326 acc_id,
327 )?;
328 all_orders.extend(parsed.orders.iter().filter_map(project_crypto_order));
329 match decide_crypto_read_order_page_like_cpp(
330 "query_crypto_history_orders",
331 parsed.completed,
332 parsed.page_flag.as_deref(),
333 all_orders.len(),
334 ) {
335 CryptoReadCursorPageDecision::Complete => {
336 tracing::debug!(count = all_orders.len(), "crypto history orders queried");
337 return Ok(all_orders);
338 }
339 CryptoReadCursorPageDecision::Continue { next_page_flag } => {
340 page_flag = Some(next_page_flag);
341 }
342 CryptoReadCursorPageDecision::PartialMissingPageFlag { error_message, .. } => {
343 return Err(FutuError::Codec(error_message));
344 }
345 }
346 }
347
348 Err(FutuError::Codec(
349 crypto_read_order_pagination_exceeded_like_cpp("query_crypto_history_orders", MAX_PAGES),
350 ))
351}
352
353fn ensure_crypto_order_read_status(
354 message_name: &str,
355 msg_header: Option<&trade_cmn::CryptoMsgHeader>,
356 acc_id: u64,
357) -> Result<()> {
358 crypto_order_read_response_status_like_cpp(
359 message_name,
360 msg_header.map(crypto_msg_header_facts),
361 acc_id,
362 )
363 .map_err(crypto_order_status_error_to_futu_error)
364}
365
366fn ensure_crypto_order_detail_status(
367 message_name: &str,
368 msg_header: Option<&trade_cmn::CryptoMsgHeader>,
369 acc_id: u64,
370 expected_count: usize,
371 actual_count: usize,
372) -> Result<()> {
373 crypto_order_detail_response_status_like_cpp(
374 message_name,
375 msg_header.map(crypto_msg_header_facts),
376 acc_id,
377 expected_count,
378 actual_count,
379 )
380 .map_err(crypto_order_status_error_to_futu_error)
381}
382
383fn crypto_msg_header_facts(header: &trade_cmn::CryptoMsgHeader) -> BackendHistoryMsgHeaderFacts {
384 BackendHistoryMsgHeaderFacts {
385 account_id: header.account_id,
386 }
387}
388
389fn crypto_order_status_error_to_futu_error(err: BackendHistoryStatusError) -> FutuError {
390 if err.is_backend_error {
391 FutuError::ServerError {
392 ret_type: -1,
393 msg: err.message,
394 }
395 } else {
396 FutuError::Codec(err.message)
397 }
398}