Skip to main content

futu_backend/trade_query/crypto_orders/
queries_orders.rs

1//! trade_query/crypto_orders/queries_orders — query_crypto_orders / order_info / history_orders
2//! (v1.4.110 CC Batch K: 拆自 crypto_orders.rs L57-292)
3
4use futu_cache::trd_cache::CachedOrder;
5use futu_core::error::{FutuError, Result};
6
7use super::super::*;
8
9use crate::crypto_trade::lookup_crypto_account_context;
10use crate::trade_cmd::{CryptoTradeOperation, crypto_trade_command};
11
12use super::projections::*;
13use super::types::*;
14
15pub async fn query_crypto_orders(
16    backend: &BackendConn,
17    acc_id: u64,
18    trd_cache: &TrdCache,
19) -> Result<Vec<CachedOrder>> {
20    use prost::Message;
21
22    let ctx = lookup_crypto_account_context(trd_cache, acc_id)?;
23    let spec = crypto_trade_command(CryptoTradeOperation::Orders);
24    let mut all_orders = Vec::new();
25    let mut page_flag: Option<String> = None;
26    let mut completed = false;
27
28    for _ in 0..MAX_PAGES {
29        let req = inbound_read::OrderListReq {
30            msg_header: Some(ctx.build_crypto_msg_header("order_list")),
31            page_size: Some(CRYPTO_ORDER_PAGE_SIZE),
32            page_flag: page_flag.clone(),
33            list_type: Some(inbound_read::CryptoOrderListType::COrderListTypeRecentUpdate as u32),
34        };
35        let resp = backend
36            .request(spec.cmd, req.encode_to_vec())
37            .await
38            .map_err(|e| {
39                tracing::warn!(acc_id, cmd_id = spec.cmd, error = %e, "crypto order query failed");
40                e
41            })?;
42
43        let parsed: inbound_read::OrderListRsp =
44            Message::decode(resp.body.as_ref()).map_err(|e| {
45                tracing::warn!(
46                    acc_id,
47                    cmd_id = spec.cmd,
48                    body_len = resp.body.len(),
49                    error = %e,
50                    "crypto order query decode failed"
51                );
52                FutuError::Proto(e)
53            })?;
54
55        all_orders.extend(parsed.orders.iter().filter_map(project_crypto_order));
56        if parsed.completed.unwrap_or(false) {
57            completed = true;
58            break;
59        }
60        match parsed.page_flag {
61            Some(ref flag) if !flag.is_empty() => page_flag = Some(flag.clone()),
62            _ => {
63                return Err(FutuError::Codec(
64                    "query_crypto_orders: partial response without page_flag".to_string(),
65                ));
66            }
67        }
68    }
69
70    if !completed {
71        return Err(FutuError::Codec(format!(
72            "query_crypto_orders: pagination exceeded {MAX_PAGES} pages"
73        )));
74    }
75
76    trd_cache.merge_preserving_stubs(acc_id, all_orders.clone());
77    tracing::debug!(acc_id, count = all_orders.len(), "crypto orders queried");
78    Ok(all_orders)
79}
80
81/// Query crypto order details through CMD20625 and merge them into order cache.
82///
83/// C++ 10.5.6508 `NNProto_Trd_OrderCrypto.cpp:153-177` handles
84/// `NotifyCryptoOrder` by calling `QueryOrderInfo` with the pushed string order
85/// ids. Keep this detail path separate from the full recent-order list refresh
86/// so push updates only refresh the orders named by backend.
87pub async fn query_crypto_order_info(
88    backend: &BackendConn,
89    acc_id: u64,
90    trd_cache: &TrdCache,
91    order_ids: &[String],
92) -> Result<Vec<CachedOrder>> {
93    use prost::Message;
94
95    let requested: Vec<String> = order_ids
96        .iter()
97        .map(|id| id.trim())
98        .filter(|id| !id.is_empty())
99        .map(str::to_owned)
100        .collect();
101    if requested.is_empty() {
102        return Err(FutuError::Codec(
103            "query_crypto_order_info: empty order_ids".to_string(),
104        ));
105    }
106
107    let ctx = lookup_crypto_account_context(trd_cache, acc_id)?;
108    let spec = crypto_trade_command(CryptoTradeOperation::OrderInfo);
109    let req = inbound_read::OrderDetailReq {
110        msg_header: Some(ctx.build_crypto_msg_header("order_detail")),
111        order_ids: requested.clone(),
112    };
113    let resp = backend
114        .request(spec.cmd, req.encode_to_vec())
115        .await
116        .map_err(|e| {
117            tracing::warn!(
118                acc_id,
119                cmd_id = spec.cmd,
120                order_ids = ?requested,
121                error = %e,
122                "crypto order detail query failed"
123            );
124            e
125        })?;
126
127    let parsed: inbound_read::OrderDetailRsp =
128        Message::decode(resp.body.as_ref()).map_err(|e| {
129            tracing::warn!(
130                acc_id,
131                cmd_id = spec.cmd,
132                body_len = resp.body.len(),
133                error = %e,
134                "crypto order detail query decode failed"
135            );
136            FutuError::Proto(e)
137        })?;
138    let orders: Vec<CachedOrder> = parsed
139        .orders
140        .iter()
141        .filter_map(project_crypto_order)
142        .collect();
143    if orders.len() != requested.len() {
144        tracing::warn!(
145            acc_id,
146            requested = requested.len(),
147            returned = orders.len(),
148            order_ids = ?requested,
149            "crypto order detail response count differs from requested ids"
150        );
151    }
152
153    trd_cache.merge_preserving_stubs(acc_id, orders.clone());
154    tracing::debug!(acc_id, count = orders.len(), "crypto order details queried");
155    Ok(orders)
156}
157
158/// Query crypto history orders through CMD20623.
159///
160/// C++ 10.5.6508 `NNProto_Trd_OrderCrypto.cpp:373-398` sends
161/// `inbound_read::HistoryOrderListReq` with `start_time` / `end_time` already
162/// in microseconds. Unlike the active order query, this read path does not
163/// update the active order cache.
164pub async fn query_crypto_history_orders(
165    backend: &BackendConn,
166    acc_id: u64,
167    trd_cache: &TrdCache,
168    start_micros: u64,
169    end_micros: u64,
170) -> Result<Vec<CachedOrder>> {
171    use prost::Message;
172
173    let ctx = lookup_crypto_account_context(trd_cache, acc_id)?;
174    let spec = crypto_trade_command(CryptoTradeOperation::HistoryOrders);
175    let mut all_orders = Vec::new();
176    let mut page_flag: Option<String> = None;
177    let mut completed = false;
178
179    for _ in 0..MAX_PAGES {
180        let req = inbound_read::HistoryOrderListReq {
181            msg_header: Some(ctx.build_crypto_msg_header("history_order_list")),
182            page_size: Some(CRYPTO_HISTORY_ORDER_PAGE_SIZE),
183            page_flag: page_flag.clone(),
184            start_time: Some(start_micros as i64),
185            end_time: Some(end_micros as i64),
186            symbol: None,
187            order_status: Vec::new(),
188            currency: Vec::new(),
189            side: Vec::new(),
190            query_word: None,
191            ord_type: Vec::new(),
192        };
193        let resp = backend
194            .request(spec.cmd, req.encode_to_vec())
195            .await
196            .map_err(|e| {
197                tracing::warn!(
198                    acc_id,
199                    cmd_id = spec.cmd,
200                    error = %e,
201                    "crypto history order query failed"
202                );
203                e
204            })?;
205
206        let parsed: inbound_read::HistoryOrderListRsp = Message::decode(resp.body.as_ref())
207            .map_err(|e| {
208                tracing::warn!(
209                    acc_id,
210                    cmd_id = spec.cmd,
211                    body_len = resp.body.len(),
212                    error = %e,
213                    "crypto history order query decode failed"
214                );
215                FutuError::Proto(e)
216            })?;
217
218        all_orders.extend(parsed.orders.iter().filter_map(project_crypto_order));
219        if parsed.completed.unwrap_or(false) {
220            completed = true;
221            break;
222        }
223        match parsed.page_flag {
224            Some(ref flag) if !flag.is_empty() => page_flag = Some(flag.clone()),
225            _ => {
226                return Err(FutuError::Codec(
227                    "query_crypto_history_orders: partial response without page_flag".to_string(),
228                ));
229            }
230        }
231    }
232
233    if !completed {
234        return Err(FutuError::Codec(format!(
235            "query_crypto_history_orders: pagination exceeded {MAX_PAGES} pages"
236        )));
237    }
238
239    tracing::debug!(
240        acc_id,
241        count = all_orders.len(),
242        "crypto history orders queried"
243    );
244    Ok(all_orders)
245}