Skip to main content

futu_backend/trade_query/orders/
order_info.rs

1use bytes::Bytes;
2use futu_cache::trd_cache::{CachedOrder, OrderRelationSourceToken, TrdCache};
3use futu_command_spec::{TradeQueryEnvironment, TradeQueryOperation, trade::trade_query_route};
4use futu_core::error::{FutuError, Result};
5use futu_domain_trade_history::detail_request::{
6    CurrentTradeDetailRequestPlan, plan_current_trade_detail_request_like_cpp,
7};
8use prost::Message;
9
10use crate::command_runtime::execute_trade_read;
11use crate::conn::BackendConn;
12use crate::proto_internal::{order_sys_interface, sim_order_sys_interface};
13
14use super::builders::build_order_info_req_base;
15use super::cipher::current_trade_query_real_cipher_or_error_like_cpp;
16use super::helpers::derive_acc_query_context;
17use super::projection::{
18    order_proto_to_cached_with_jp_acc_type_like_cpp, sim_order_proto_to_cached_like_cpp,
19};
20use super::status_helpers::{
21    backend_order_info_status_by_account_like_cpp, backend_order_info_status_like_cpp,
22};
23use super::types::QueryErrorMode;
24
25/// Strict order detail query for C++ push-refresh parity.
26///
27/// C++ receives `NOTICE_TYPE_ORDER_UPDATE` / `NOTICE_TYPE_ORDER_OP_RESULT` and
28/// calls `QueryOrderInfo` (4707/14707) with the pushed order IDs, rather than
29/// running a full `OrderListReq` (4708/14708). This matters for terminal states
30/// such as Deleted(23), which can be visible through the detail path while the
31/// current-order list omits them.
32pub async fn query_order_info_strict_for_push_refresh(
33    backend: &BackendConn,
34    acc_id: u64,
35    trd_cache: &TrdCache,
36    order_ids: &[String],
37    security_type: Option<u32>,
38    exchange: Option<&str>,
39) -> Result<Vec<CachedOrder>> {
40    query_order_info_strict_for_push_refresh_with_source(
41        backend,
42        acc_id,
43        trd_cache,
44        order_ids,
45        security_type,
46        exchange,
47        OrderRelationSourceToken::unknown(backend.connection_generation()),
48    )
49    .await
50}
51
52pub async fn query_order_info_strict_for_push_refresh_with_source(
53    backend: &BackendConn,
54    acc_id: u64,
55    trd_cache: &TrdCache,
56    order_ids: &[String],
57    security_type: Option<u32>,
58    exchange: Option<&str>,
59    source: OrderRelationSourceToken,
60) -> Result<Vec<CachedOrder>> {
61    let filtered_ids = match plan_current_trade_detail_request_like_cpp(order_ids) {
62        CurrentTradeDetailRequestPlan::SkipEmpty => return Ok(vec![]),
63        CurrentTradeDetailRequestPlan::Query { filtered_ids } => filtered_ids,
64    };
65
66    let (trd_env, _account_trd_market, _enabled_markets) =
67        derive_acc_query_context(trd_cache, acc_id);
68    let environment = if trd_env == 1 {
69        TradeQueryEnvironment::Real
70    } else {
71        TradeQueryEnvironment::Sim
72    };
73    let route =
74        trade_query_route(TradeQueryOperation::OrderInfo, environment).ok_or_else(|| {
75            FutuError::Codec(format!(
76                "QueryOrderInfo has no registered {environment:?} backend route"
77            ))
78        })?;
79    let cmd_id = route.cmd_id;
80    let real_cipher = if trd_env == 1 {
81        current_trade_query_real_cipher_or_error_like_cpp(
82            acc_id,
83            trd_cache,
84            QueryErrorMode::StrictPushRefresh,
85            "QueryOrderInfo",
86        )?
87    } else {
88        Vec::new()
89    };
90    let trade_cipher = (trd_env == 1).then_some(real_cipher.as_slice());
91    let req = build_order_info_req_base(
92        acc_id,
93        security_type,
94        exchange,
95        &filtered_ids,
96        real_cipher.clone(),
97    );
98    let resp = execute_trade_read(
99        backend,
100        TradeQueryOperation::OrderInfo,
101        environment,
102        trade_cipher,
103        Bytes::from(req.encode_to_vec()),
104    )
105    .await?;
106    let (orders, received): (Vec<CachedOrder>, usize) = if trd_env == 1 {
107        let parsed: order_sys_interface::OrderDetailRsp = Message::decode(resp.body.as_ref())?;
108        if let Err(status_err) = backend_order_info_status_like_cpp(
109            parsed.result,
110            parsed.msg_header.as_ref(),
111            parsed.err_msg.as_deref(),
112            acc_id,
113            filtered_ids.len(),
114            parsed.orders.len(),
115        ) {
116            return Err(FutuError::ServerError {
117                ret_type: status_err.result,
118                msg: format!("{} (acc_id={acc_id})", status_err.message),
119            });
120        }
121        let received = parsed.orders.len();
122        let orders = parsed
123            .orders
124            .iter()
125            .filter_map(|order| {
126                let jp_acc_type = order
127                    .sub_account_id
128                    .and_then(|id| trd_cache.jp_acc_type_for_sub_account(acc_id, id));
129                order_proto_to_cached_with_jp_acc_type_like_cpp(trd_env, order, jp_acc_type)
130            })
131            .collect();
132        (orders, received)
133    } else {
134        let parsed: sim_order_sys_interface::OrderDetailRsp = Message::decode(resp.body.as_ref())?;
135        if let Err(status_err) = backend_order_info_status_by_account_like_cpp(
136            parsed.result,
137            parsed
138                .msg_header
139                .as_ref()
140                .and_then(|header| header.account_id),
141            parsed.err_msg.as_deref(),
142            acc_id,
143            filtered_ids.len(),
144            parsed.orders.len(),
145        ) {
146            return Err(FutuError::ServerError {
147                ret_type: status_err.result,
148                msg: format!("{} (acc_id={acc_id})", status_err.message),
149            });
150        }
151        let received = parsed.orders.len();
152        let orders = parsed
153            .orders
154            .iter()
155            .filter_map(|order| sim_order_proto_to_cached_like_cpp(trd_env, order))
156            .collect();
157        (orders, received)
158    };
159    if orders.len() != received {
160        trd_cache.mark_order_relation_snapshot_incomplete_from_source(acc_id, source);
161    }
162    let mut applied_orders = Vec::with_capacity(orders.len());
163    for order in orders {
164        if trd_cache.upsert_order_from_source(acc_id, order.clone(), source) {
165            applied_orders.push(order);
166        }
167    }
168    tracing::debug!(
169        cmd_id,
170        requested = filtered_ids.len(),
171        received,
172        applied = applied_orders.len(),
173        "order details queried"
174    );
175    Ok(applied_orders)
176}