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        .inspect_err(|e| {
75            tracing::warn!(
76                cmd_id = spec.cmd,
77                error = ?futu_core::log_redact::RedactedFutuError::new(e),
78                "crypto order fill query failed"
79            );
80        })?;
81
82        let parsed: inbound_oe::FillListRsp = Message::decode(resp.body.as_ref()).map_err(|e| {
83            tracing::warn!(
84                cmd_id = spec.cmd,
85                body_len = resp.body.len(),
86                error_class = "protobuf_decode",
87                "crypto order fill query decode failed"
88            );
89            FutuError::Proto(e)
90        })?;
91
92        all_fills.extend(
93            parsed
94                .base_fill_list
95                .iter()
96                .filter_map(project_crypto_base_fill),
97        );
98        match decide_crypto_oe_fill_page_like_cpp(parsed.page_token.as_deref()) {
99            CryptoOeFillPageDecision::Complete => {
100                tracing::debug!(count = all_fills.len(), "crypto order fills queried");
101                return Ok(all_fills);
102            }
103            CryptoOeFillPageDecision::Continue { next_page_token } => {
104                page_token = Some(next_page_token);
105            }
106        }
107    }
108
109    Err(FutuError::Codec(
110        crypto_oe_fill_pagination_exceeded_like_cpp("query_crypto_order_fills", MAX_PAGES),
111    ))
112}
113
114/// Query crypto history fills through CMD21234.
115///
116/// C++ 10.5.6508 `NNProto_Trd_DealCrypto.cpp:402-418` sends
117/// `inbound_oe::GetFillListByAccountAndTimeRangeRequest`, with begin/end in
118/// microseconds and page size 2000.
119pub async fn query_crypto_history_order_fills(
120    backend: &BackendConn,
121    acc_id: u64,
122    trd_cache: &TrdCache,
123    start_micros: u64,
124    end_micros: u64,
125) -> Result<Vec<OrderFillInfo>> {
126    use prost::Message;
127
128    let _ctx = lookup_crypto_account_context(trd_cache, acc_id)?;
129    let spec = crypto_trade_command(CryptoTradeOperation::HistoryDeals);
130    let mut all_fills = Vec::new();
131    let mut page_token: Option<String> = None;
132
133    for _ in 0..MAX_PAGES {
134        let plan = plan_crypto_history_fill_list_request_like_cpp(
135            start_micros,
136            end_micros,
137            page_token.as_deref(),
138        );
139        let req = inbound_oe::GetFillListByAccountAndTimeRangeRequest {
140            page_size: Some(plan.page_size),
141            page_token: plan.page_token,
142            long_account_id: Some(acc_id),
143            start_time: Some(plan.start_time_micros),
144            end_time: Some(plan.end_time_micros),
145            symbol: plan.symbol,
146        };
147        let resp = crate::command_runtime::execute_crypto_trade_command(
148            backend,
149            CryptoTradeOperation::HistoryDeals,
150            None,
151            bytes::Bytes::from(req.encode_to_vec()),
152        )
153        .await
154        .inspect_err(|e| {
155            tracing::warn!(
156                cmd_id = spec.cmd,
157                error = ?futu_core::log_redact::RedactedFutuError::new(e),
158                "crypto history fill query failed"
159            );
160        })?;
161
162        let parsed: inbound_oe::GetFillListByAccountAndTimeRangeResponse =
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_class = "protobuf_decode",
168                    "crypto history fill query decode failed"
169                );
170                FutuError::Proto(e)
171            })?;
172
173        all_fills.extend(
174            parsed
175                .base_fill_list
176                .iter()
177                .filter_map(project_crypto_base_fill),
178        );
179        match decide_crypto_oe_fill_page_like_cpp(parsed.page_token.as_deref()) {
180            CryptoOeFillPageDecision::Complete => {
181                tracing::debug!(count = all_fills.len(), "crypto history fills queried");
182                return Ok(all_fills);
183            }
184            CryptoOeFillPageDecision::Continue { next_page_token } => {
185                page_token = Some(next_page_token);
186            }
187        }
188    }
189
190    Err(FutuError::Codec(
191        crypto_oe_fill_pagination_exceeded_like_cpp("query_crypto_history_order_fills", MAX_PAGES),
192    ))
193}
194
195/// Query fills for one crypto order through CMD20624.
196///
197/// C++ 10.5.6508 `NNProto_Trd_DealCrypto.cpp:500-529` sends
198/// `inbound_read::OrderFillDetailReq` with crypto msg header, order id, page
199/// size 500, and follows `page_flag` until `completed=true`.
200pub async fn query_crypto_order_related_fills(
201    backend: &BackendConn,
202    acc_id: u64,
203    trd_cache: &TrdCache,
204    order_id_ex: &str,
205) -> Result<Vec<OrderFillInfo>> {
206    use prost::Message;
207
208    let initial_plan = plan_crypto_order_related_fill_request_like_cpp(order_id_ex, None)
209        .map_err(FutuError::Codec)?;
210    let order_id_ex = initial_plan.order_id;
211
212    let ctx = lookup_crypto_account_context(trd_cache, acc_id)?;
213    let spec = crypto_trade_command(CryptoTradeOperation::OrderFillDetail);
214    let mut all_fills = Vec::new();
215    let mut page_flag: Option<String> = None;
216
217    for _ in 0..MAX_PAGES {
218        let plan =
219            plan_crypto_order_related_fill_request_like_cpp(&order_id_ex, page_flag.as_deref())
220                .map_err(FutuError::Codec)?;
221        let req = inbound_read::OrderFillDetailReq {
222            msg_header: Some(ctx.build_crypto_msg_header("order_fill_detail")),
223            page_size: Some(plan.page_size),
224            page_flag: plan.page_flag,
225            order_id: Some(plan.order_id),
226        };
227        let resp = crate::command_runtime::execute_crypto_trade_command(
228            backend,
229            CryptoTradeOperation::OrderFillDetail,
230            None,
231            bytes::Bytes::from(req.encode_to_vec()),
232        )
233        .await
234        .inspect_err(|e| {
235            tracing::warn!(
236                cmd_id = spec.cmd,
237                order_id = %order_id_ex,
238                error = ?futu_core::log_redact::RedactedFutuError::new(e),
239                "crypto order related fill query failed"
240            );
241        })?;
242
243        let parsed: inbound_read::OrderFillDetailRsp = Message::decode(resp.body.as_ref())
244            .map_err(|e| {
245                tracing::warn!(
246                    cmd_id = spec.cmd,
247                    order_id = %order_id_ex,
248                    body_len = resp.body.len(),
249                    error_class = "protobuf_decode",
250                    "crypto order related fill query decode failed"
251                );
252                FutuError::Proto(e)
253            })?;
254
255        all_fills.extend(
256            parsed
257                .order_fills
258                .iter()
259                .filter_map(project_crypto_read_fill),
260        );
261        match decide_crypto_read_cursor_page_like_cpp(
262            "query_crypto_order_related_fills",
263            parsed.completed,
264            parsed.page_flag.as_deref(),
265            all_fills.len(),
266        ) {
267            CryptoReadCursorPageDecision::Complete => {
268                tracing::debug!(
269                    order_id = %order_id_ex,
270                    count = all_fills.len(),
271                    "crypto order related fills queried"
272                );
273                return Ok(all_fills);
274            }
275            CryptoReadCursorPageDecision::Continue { next_page_flag } => {
276                page_flag = Some(next_page_flag);
277            }
278            CryptoReadCursorPageDecision::PartialMissingPageFlag { error_message, .. } => {
279                return Err(FutuError::Codec(error_message));
280            }
281        }
282    }
283
284    Err(FutuError::Codec(
285        crypto_read_cursor_pagination_exceeded_like_cpp(
286            "query_crypto_order_related_fills",
287            MAX_PAGES,
288        ),
289    ))
290}