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};
5
6use super::super::*;
7
8use crate::crypto_trade::lookup_crypto_account_context;
9use crate::trade_cmd::{CryptoTradeOperation, crypto_trade_command};
10
11use super::projections::*;
12use super::types::*;
13
14pub async fn query_crypto_order_fills(
15    backend: &BackendConn,
16    acc_id: u64,
17    trd_cache: &TrdCache,
18) -> Result<Vec<OrderFillInfo>> {
19    use prost::Message;
20
21    let _ctx = lookup_crypto_account_context(trd_cache, acc_id)?;
22    let spec = crypto_trade_command(CryptoTradeOperation::Deals);
23    let mut all_fills = Vec::new();
24    let mut page_token: Option<String> = None;
25
26    for _ in 0..MAX_PAGES {
27        let req = inbound_oe::FillListReq {
28            page_size: Some(CRYPTO_FILL_PAGE_SIZE),
29            page_token: page_token.clone(),
30            long_account_id: Some(acc_id),
31            symbol: None,
32            list_type: Some(inbound_oe::CryptoFillListType::CFillListTypeRecentFillEd as u32),
33        };
34        let resp = backend
35            .request(spec.cmd, req.encode_to_vec())
36            .await
37            .map_err(|e| {
38                tracing::warn!(
39                    acc_id,
40                    cmd_id = spec.cmd,
41                    error = %e,
42                    "crypto order fill query failed"
43                );
44                e
45            })?;
46
47        let parsed: inbound_oe::FillListRsp = Message::decode(resp.body.as_ref()).map_err(|e| {
48            tracing::warn!(
49                acc_id,
50                cmd_id = spec.cmd,
51                body_len = resp.body.len(),
52                error = %e,
53                "crypto order fill query decode failed"
54            );
55            FutuError::Proto(e)
56        })?;
57
58        all_fills.extend(
59            parsed
60                .base_fill_list
61                .iter()
62                .filter_map(project_crypto_base_fill),
63        );
64        match parsed.page_token {
65            Some(ref token) if !token.is_empty() => page_token = Some(token.clone()),
66            _ => {
67                tracing::debug!(
68                    acc_id,
69                    count = all_fills.len(),
70                    "crypto order fills queried"
71                );
72                return Ok(all_fills);
73            }
74        }
75    }
76
77    Err(FutuError::Codec(format!(
78        "query_crypto_order_fills: pagination exceeded {MAX_PAGES} pages"
79    )))
80}
81
82/// Query crypto history fills through CMD21234.
83///
84/// C++ 10.5.6508 `NNProto_Trd_DealCrypto.cpp:402-418` sends
85/// `inbound_oe::GetFillListByAccountAndTimeRangeRequest`, with begin/end in
86/// microseconds and page size 2000.
87pub async fn query_crypto_history_order_fills(
88    backend: &BackendConn,
89    acc_id: u64,
90    trd_cache: &TrdCache,
91    start_micros: u64,
92    end_micros: u64,
93) -> Result<Vec<OrderFillInfo>> {
94    use prost::Message;
95
96    let _ctx = lookup_crypto_account_context(trd_cache, acc_id)?;
97    let spec = crypto_trade_command(CryptoTradeOperation::HistoryDeals);
98    let mut all_fills = Vec::new();
99    let mut page_token: Option<String> = None;
100
101    for _ in 0..MAX_PAGES {
102        let req = inbound_oe::GetFillListByAccountAndTimeRangeRequest {
103            page_size: Some(CRYPTO_HISTORY_FILL_PAGE_SIZE),
104            page_token: page_token.clone(),
105            long_account_id: Some(acc_id),
106            start_time: Some(start_micros as i64),
107            end_time: Some(end_micros as i64),
108            symbol: None,
109        };
110        let resp = backend
111            .request(spec.cmd, req.encode_to_vec())
112            .await
113            .map_err(|e| {
114                tracing::warn!(
115                    acc_id,
116                    cmd_id = spec.cmd,
117                    error = %e,
118                    "crypto history fill query failed"
119                );
120                e
121            })?;
122
123        let parsed: inbound_oe::GetFillListByAccountAndTimeRangeResponse =
124            Message::decode(resp.body.as_ref()).map_err(|e| {
125                tracing::warn!(
126                    acc_id,
127                    cmd_id = spec.cmd,
128                    body_len = resp.body.len(),
129                    error = %e,
130                    "crypto history fill query decode failed"
131                );
132                FutuError::Proto(e)
133            })?;
134
135        all_fills.extend(
136            parsed
137                .base_fill_list
138                .iter()
139                .filter_map(project_crypto_base_fill),
140        );
141        match parsed.page_token {
142            Some(ref token) if !token.is_empty() => page_token = Some(token.clone()),
143            _ => {
144                tracing::debug!(
145                    acc_id,
146                    count = all_fills.len(),
147                    "crypto history fills queried"
148                );
149                return Ok(all_fills);
150            }
151        }
152    }
153
154    Err(FutuError::Codec(format!(
155        "query_crypto_history_order_fills: pagination exceeded {MAX_PAGES} pages"
156    )))
157}
158
159/// Query fills for one crypto order through CMD20624.
160///
161/// C++ 10.5.6508 `NNProto_Trd_DealCrypto.cpp:500-529` sends
162/// `inbound_read::OrderFillDetailReq` with crypto msg header, order id, page
163/// size 500, and follows `page_flag` until `completed=true`.
164pub async fn query_crypto_order_related_fills(
165    backend: &BackendConn,
166    acc_id: u64,
167    trd_cache: &TrdCache,
168    order_id_ex: &str,
169) -> Result<Vec<OrderFillInfo>> {
170    use prost::Message;
171
172    let order_id_ex = order_id_ex.trim();
173    if order_id_ex.is_empty() {
174        return Err(FutuError::Codec(
175            "query_crypto_order_related_fills: empty order id".to_string(),
176        ));
177    }
178
179    let ctx = lookup_crypto_account_context(trd_cache, acc_id)?;
180    let spec = crypto_trade_command(CryptoTradeOperation::OrderFillDetail);
181    let mut all_fills = Vec::new();
182    let mut page_flag: Option<String> = None;
183    let mut completed = false;
184
185    for _ in 0..MAX_PAGES {
186        let req = inbound_read::OrderFillDetailReq {
187            msg_header: Some(ctx.build_crypto_msg_header("order_fill_detail")),
188            page_size: Some(CRYPTO_FILL_PAGE_SIZE),
189            page_flag: page_flag.clone(),
190            order_id: Some(order_id_ex.to_string()),
191        };
192        let resp = backend
193            .request(spec.cmd, req.encode_to_vec())
194            .await
195            .map_err(|e| {
196                tracing::warn!(
197                    acc_id,
198                    cmd_id = spec.cmd,
199                    order_id = order_id_ex,
200                    error = %e,
201                    "crypto order related fill query failed"
202                );
203                e
204            })?;
205
206        let parsed: inbound_read::OrderFillDetailRsp = Message::decode(resp.body.as_ref())
207            .map_err(|e| {
208                tracing::warn!(
209                    acc_id,
210                    cmd_id = spec.cmd,
211                    order_id = order_id_ex,
212                    body_len = resp.body.len(),
213                    error = %e,
214                    "crypto order related fill query decode failed"
215                );
216                FutuError::Proto(e)
217            })?;
218
219        all_fills.extend(
220            parsed
221                .order_fills
222                .iter()
223                .filter_map(project_crypto_read_fill),
224        );
225        if parsed.completed.unwrap_or(false) {
226            completed = true;
227            break;
228        }
229        match parsed.page_flag {
230            Some(ref flag) if !flag.is_empty() => page_flag = Some(flag.clone()),
231            _ => {
232                return Err(FutuError::Codec(
233                    "query_crypto_order_related_fills: partial response without page_flag"
234                        .to_string(),
235                ));
236            }
237        }
238    }
239
240    if !completed {
241        return Err(FutuError::Codec(format!(
242            "query_crypto_order_related_fills: pagination exceeded {MAX_PAGES} pages"
243        )));
244    }
245
246    tracing::debug!(
247        acc_id,
248        order_id = order_id_ex,
249        count = all_fills.len(),
250        "crypto order related fills queried"
251    );
252    Ok(all_fills)
253}