Skip to main content

futu_backend/trade_query/orders/
order_info.rs

1use bytes::Bytes;
2use futu_cache::trd_cache::{CachedOrder, 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    let filtered_ids = match plan_current_trade_detail_request_like_cpp(order_ids) {
41        CurrentTradeDetailRequestPlan::SkipEmpty => return Ok(vec![]),
42        CurrentTradeDetailRequestPlan::Query { filtered_ids } => filtered_ids,
43    };
44
45    let (trd_env, _account_trd_market, _enabled_markets) =
46        derive_acc_query_context(trd_cache, acc_id);
47    let environment = if trd_env == 1 {
48        TradeQueryEnvironment::Real
49    } else {
50        TradeQueryEnvironment::Sim
51    };
52    let route =
53        trade_query_route(TradeQueryOperation::OrderInfo, environment).ok_or_else(|| {
54            FutuError::Codec(format!(
55                "QueryOrderInfo has no registered {environment:?} backend route"
56            ))
57        })?;
58    let cmd_id = route.cmd_id;
59    let real_cipher = if trd_env == 1 {
60        current_trade_query_real_cipher_or_error_like_cpp(
61            acc_id,
62            trd_cache,
63            QueryErrorMode::StrictPushRefresh,
64            "QueryOrderInfo",
65        )?
66    } else {
67        Vec::new()
68    };
69    let trade_cipher = (trd_env == 1).then_some(real_cipher.as_slice());
70    let req = build_order_info_req_base(
71        acc_id,
72        security_type,
73        exchange,
74        &filtered_ids,
75        real_cipher.clone(),
76    );
77    let resp = execute_trade_read(
78        backend,
79        TradeQueryOperation::OrderInfo,
80        environment,
81        trade_cipher,
82        Bytes::from(req.encode_to_vec()),
83    )
84    .await?;
85    let (orders, received): (Vec<CachedOrder>, usize) = if trd_env == 1 {
86        let parsed: order_sys_interface::OrderDetailRsp = Message::decode(resp.body.as_ref())?;
87        if let Err(status_err) = backend_order_info_status_like_cpp(
88            parsed.result,
89            parsed.msg_header.as_ref(),
90            parsed.err_msg.as_deref(),
91            acc_id,
92            filtered_ids.len(),
93            parsed.orders.len(),
94        ) {
95            return Err(FutuError::ServerError {
96                ret_type: status_err.result,
97                msg: format!("{} (acc_id={acc_id})", status_err.message),
98            });
99        }
100        let received = parsed.orders.len();
101        let orders = parsed
102            .orders
103            .iter()
104            .filter_map(|order| {
105                let jp_acc_type = order
106                    .sub_account_id
107                    .and_then(|id| trd_cache.jp_acc_type_for_sub_account(acc_id, id));
108                order_proto_to_cached_with_jp_acc_type_like_cpp(trd_env, order, jp_acc_type)
109            })
110            .collect();
111        (orders, received)
112    } else {
113        let parsed: sim_order_sys_interface::OrderDetailRsp = Message::decode(resp.body.as_ref())?;
114        if let Err(status_err) = backend_order_info_status_by_account_like_cpp(
115            parsed.result,
116            parsed
117                .msg_header
118                .as_ref()
119                .and_then(|header| header.account_id),
120            parsed.err_msg.as_deref(),
121            acc_id,
122            filtered_ids.len(),
123            parsed.orders.len(),
124        ) {
125            return Err(FutuError::ServerError {
126                ret_type: status_err.result,
127                msg: format!("{} (acc_id={acc_id})", status_err.message),
128            });
129        }
130        let received = parsed.orders.len();
131        let orders = parsed
132            .orders
133            .iter()
134            .filter_map(|order| sim_order_proto_to_cached_like_cpp(trd_env, order))
135            .collect();
136        (orders, received)
137    };
138    let mut applied_orders = Vec::with_capacity(orders.len());
139    for order in orders {
140        if trd_cache.upsert_order(acc_id, order.clone()) {
141            applied_orders.push(order);
142        }
143    }
144    tracing::debug!(
145        cmd_id,
146        requested = filtered_ids.len(),
147        received,
148        applied = applied_orders.len(),
149        "order details queried"
150    );
151    Ok(applied_orders)
152}