Skip to main content

futu_backend/trade_query/crypto_orders/
queries_fills.rs

1//! trade_query/crypto_orders/queries_fills — query_crypto_order_fills / history_order_fills / related_fills
2//! (v1.4.110 CC Batch K: 拆自 crypto_orders.rs L293-538)
3
4use futu_core::error::{FutuError, Result};
5use futu_domain_trade_history::pagination::{
6    CryptoOeFillPageDecision, CryptoReadCursorPageDecision,
7    crypto_oe_fill_pagination_exceeded_like_cpp, crypto_read_cursor_pagination_exceeded_like_cpp,
8    decide_crypto_oe_fill_page_like_cpp, decide_crypto_read_cursor_page_like_cpp,
9};
10use futu_domain_trade_history::{
11    plan_crypto_current_fill_list_request_like_cpp, plan_crypto_history_fill_list_request_like_cpp,
12    plan_crypto_order_related_fill_request_like_cpp,
13};
14
15use super::super::*;
16
17use crate::crypto_trade::{
18    CryptoAccountContext, lookup_crypto_account_context, lookup_crypto_read_account_context,
19};
20use crate::trade_cmd::{CryptoTradeOperation, crypto_trade_command};
21
22use super::projections::*;
23use super::types::*;
24
25pub async fn query_crypto_order_fills(
26    backend: &BackendConn,
27    acc_id: u64,
28    trd_cache: &TrdCache,
29) -> Result<Vec<OrderFillInfo>> {
30    // C++ QueryDealList is a Login read and CMD21237 carries no cipher field.
31    // Keep strict context requirements on detail/write operations only.
32    let ctx = lookup_crypto_read_account_context(trd_cache, acc_id)?;
33    query_crypto_order_fills_with_context(backend, acc_id, &ctx).await
34}
35
36/// Startup-only current-fill read. CMD21237 carries the long account id but no
37/// cipher field; C++ permits this Login read before trade unlock.
38pub async fn query_crypto_order_fills_for_startup(
39    backend: &BackendConn,
40    acc_id: u64,
41    trd_cache: &TrdCache,
42) -> Result<Vec<OrderFillInfo>> {
43    let ctx = lookup_crypto_read_account_context(trd_cache, acc_id)?;
44    query_crypto_order_fills_with_context(backend, acc_id, &ctx).await
45}
46
47async fn query_crypto_order_fills_with_context(
48    backend: &BackendConn,
49    acc_id: u64,
50    _ctx: &CryptoAccountContext,
51) -> Result<Vec<OrderFillInfo>> {
52    use prost::Message;
53
54    let spec = crypto_trade_command(CryptoTradeOperation::Deals);
55    let mut all_fills = Vec::new();
56    let mut page_token: Option<String> = None;
57
58    for _ in 0..MAX_PAGES {
59        let plan = plan_crypto_current_fill_list_request_like_cpp(page_token.as_deref());
60        let req = inbound_oe::FillListReq {
61            page_size: Some(plan.page_size),
62            page_token: plan.page_token,
63            long_account_id: Some(acc_id),
64            symbol: plan.symbol,
65            list_type: Some(plan.list_type),
66        };
67        let resp = crate::command_runtime::execute_crypto_trade_command(
68            backend,
69            CryptoTradeOperation::Deals,
70            None,
71            bytes::Bytes::from(req.encode_to_vec()),
72        )
73        .await
74        .map_err(|e| {
75            tracing::warn!(
76                cmd_id = spec.cmd,
77                error = %e,
78                "crypto order fill query failed"
79            );
80            e
81        })?;
82
83        let parsed: inbound_oe::FillListRsp = Message::decode(resp.body.as_ref()).map_err(|e| {
84            tracing::warn!(
85                cmd_id = spec.cmd,
86                body_len = resp.body.len(),
87                error = %e,
88                "crypto order fill query decode failed"
89            );
90            FutuError::Proto(e)
91        })?;
92
93        all_fills.extend(
94            parsed
95                .base_fill_list
96                .iter()
97                .filter_map(project_crypto_base_fill),
98        );
99        match decide_crypto_oe_fill_page_like_cpp(parsed.page_token.as_deref()) {
100            CryptoOeFillPageDecision::Complete => {
101                tracing::debug!(count = all_fills.len(), "crypto order fills queried");
102                return Ok(all_fills);
103            }
104            CryptoOeFillPageDecision::Continue { next_page_token } => {
105                page_token = Some(next_page_token);
106            }
107        }
108    }
109
110    Err(FutuError::Codec(
111        crypto_oe_fill_pagination_exceeded_like_cpp("query_crypto_order_fills", MAX_PAGES),
112    ))
113}
114
115/// Query crypto history fills through CMD21234.
116///
117/// C++ 10.5.6508 `NNProto_Trd_DealCrypto.cpp:402-418` sends
118/// `inbound_oe::GetFillListByAccountAndTimeRangeRequest`, with begin/end in
119/// microseconds and page size 2000.
120pub async fn query_crypto_history_order_fills(
121    backend: &BackendConn,
122    acc_id: u64,
123    trd_cache: &TrdCache,
124    start_micros: u64,
125    end_micros: u64,
126) -> Result<Vec<OrderFillInfo>> {
127    use prost::Message;
128
129    let _ctx = lookup_crypto_account_context(trd_cache, acc_id)?;
130    let spec = crypto_trade_command(CryptoTradeOperation::HistoryDeals);
131    let mut all_fills = Vec::new();
132    let mut page_token: Option<String> = None;
133
134    for _ in 0..MAX_PAGES {
135        let plan = plan_crypto_history_fill_list_request_like_cpp(
136            start_micros,
137            end_micros,
138            page_token.as_deref(),
139        );
140        let req = inbound_oe::GetFillListByAccountAndTimeRangeRequest {
141            page_size: Some(plan.page_size),
142            page_token: plan.page_token,
143            long_account_id: Some(acc_id),
144            start_time: Some(plan.start_time_micros),
145            end_time: Some(plan.end_time_micros),
146            symbol: plan.symbol,
147        };
148        let resp = crate::command_runtime::execute_crypto_trade_command(
149            backend,
150            CryptoTradeOperation::HistoryDeals,
151            None,
152            bytes::Bytes::from(req.encode_to_vec()),
153        )
154        .await
155        .map_err(|e| {
156            tracing::warn!(
157                cmd_id = spec.cmd,
158                error = %e,
159                "crypto history fill query failed"
160            );
161            e
162        })?;
163
164        let parsed: inbound_oe::GetFillListByAccountAndTimeRangeResponse =
165            Message::decode(resp.body.as_ref()).map_err(|e| {
166                tracing::warn!(
167                    cmd_id = spec.cmd,
168                    body_len = resp.body.len(),
169                    error = %e,
170                    "crypto history fill query decode failed"
171                );
172                FutuError::Proto(e)
173            })?;
174
175        all_fills.extend(
176            parsed
177                .base_fill_list
178                .iter()
179                .filter_map(project_crypto_base_fill),
180        );
181        match decide_crypto_oe_fill_page_like_cpp(parsed.page_token.as_deref()) {
182            CryptoOeFillPageDecision::Complete => {
183                tracing::debug!(count = all_fills.len(), "crypto history fills queried");
184                return Ok(all_fills);
185            }
186            CryptoOeFillPageDecision::Continue { next_page_token } => {
187                page_token = Some(next_page_token);
188            }
189        }
190    }
191
192    Err(FutuError::Codec(
193        crypto_oe_fill_pagination_exceeded_like_cpp("query_crypto_history_order_fills", MAX_PAGES),
194    ))
195}
196
197/// Query fills for one crypto order through CMD20624.
198///
199/// C++ 10.5.6508 `NNProto_Trd_DealCrypto.cpp:500-529` sends
200/// `inbound_read::OrderFillDetailReq` with crypto msg header, order id, page
201/// size 500, and follows `page_flag` until `completed=true`.
202pub async fn query_crypto_order_related_fills(
203    backend: &BackendConn,
204    acc_id: u64,
205    trd_cache: &TrdCache,
206    order_id_ex: &str,
207) -> Result<Vec<OrderFillInfo>> {
208    use prost::Message;
209
210    let initial_plan = plan_crypto_order_related_fill_request_like_cpp(order_id_ex, None)
211        .map_err(FutuError::Codec)?;
212    let order_id_ex = initial_plan.order_id;
213
214    let ctx = lookup_crypto_account_context(trd_cache, acc_id)?;
215    let spec = crypto_trade_command(CryptoTradeOperation::OrderFillDetail);
216    let mut all_fills = Vec::new();
217    let mut page_flag: Option<String> = None;
218
219    for _ in 0..MAX_PAGES {
220        let plan =
221            plan_crypto_order_related_fill_request_like_cpp(&order_id_ex, page_flag.as_deref())
222                .map_err(FutuError::Codec)?;
223        let req = inbound_read::OrderFillDetailReq {
224            msg_header: Some(ctx.build_crypto_msg_header("order_fill_detail")),
225            page_size: Some(plan.page_size),
226            page_flag: plan.page_flag,
227            order_id: Some(plan.order_id),
228        };
229        let resp = crate::command_runtime::execute_crypto_trade_command(
230            backend,
231            CryptoTradeOperation::OrderFillDetail,
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                order_id = %order_id_ex,
240                error = %e,
241                "crypto order related fill query failed"
242            );
243            e
244        })?;
245
246        let parsed: inbound_read::OrderFillDetailRsp = Message::decode(resp.body.as_ref())
247            .map_err(|e| {
248                tracing::warn!(
249                    cmd_id = spec.cmd,
250                    order_id = %order_id_ex,
251                    body_len = resp.body.len(),
252                    error = %e,
253                    "crypto order related fill query decode failed"
254                );
255                FutuError::Proto(e)
256            })?;
257
258        all_fills.extend(
259            parsed
260                .order_fills
261                .iter()
262                .filter_map(project_crypto_read_fill),
263        );
264        match decide_crypto_read_cursor_page_like_cpp(
265            "query_crypto_order_related_fills",
266            parsed.completed,
267            parsed.page_flag.as_deref(),
268            all_fills.len(),
269        ) {
270            CryptoReadCursorPageDecision::Complete => {
271                tracing::debug!(
272                    order_id = %order_id_ex,
273                    count = all_fills.len(),
274                    "crypto order related fills queried"
275                );
276                return Ok(all_fills);
277            }
278            CryptoReadCursorPageDecision::Continue { next_page_flag } => {
279                page_flag = Some(next_page_flag);
280            }
281            CryptoReadCursorPageDecision::PartialMissingPageFlag { error_message, .. } => {
282                return Err(FutuError::Codec(error_message));
283            }
284        }
285    }
286
287    Err(FutuError::Codec(
288        crypto_read_cursor_pagination_exceeded_like_cpp(
289            "query_crypto_order_related_fills",
290            MAX_PAGES,
291        ),
292    ))
293}