Skip to main content

futu_trd/
query.rs

1use futu_core::error::{FutuError, Result};
2use futu_core::proto_id;
3use futu_net::client::FutuClient;
4use std::time::Duration;
5
6use crate::types::TrdHeader;
7
8/// 订单信息
9#[derive(Debug, Clone)]
10pub struct Order {
11    pub order_id: u64,
12    pub order_id_ex: String,
13    pub trd_side: i32,
14    pub order_type: i32,
15    pub order_status: i32,
16    pub code: String,
17    pub name: String,
18    pub qty: f64,
19    pub price: f64,
20    pub create_time: String,
21    pub update_time: String,
22    pub fill_qty: f64,
23    pub fill_avg_price: f64,
24    pub last_err_msg: String,
25}
26
27impl Order {
28    pub fn from_proto(o: &futu_proto::trd_common::Order) -> Self {
29        Self {
30            order_id: o.order_id,
31            order_id_ex: o.order_id_ex.clone(),
32            trd_side: o.trd_side,
33            order_type: o.order_type,
34            order_status: o.order_status,
35            code: o.code.clone(),
36            name: o.name.clone(),
37            qty: o.qty,
38            price: o.price.unwrap_or(0.0),
39            create_time: o.create_time.clone(),
40            update_time: o.update_time.clone(),
41            fill_qty: o.fill_qty.unwrap_or(0.0),
42            fill_avg_price: o.fill_avg_price.unwrap_or(0.0),
43            last_err_msg: o.last_err_msg.clone().unwrap_or_default(),
44        }
45    }
46}
47
48/// 成交信息
49#[derive(Debug, Clone)]
50pub struct OrderFill {
51    pub fill_id: u64,
52    pub fill_id_ex: String,
53    pub order_id: u64,
54    pub trd_side: i32,
55    pub code: String,
56    pub name: String,
57    pub qty: f64,
58    pub price: f64,
59    pub create_time: String,
60}
61
62impl OrderFill {
63    pub fn from_proto(f: &futu_proto::trd_common::OrderFill) -> Result<Self> {
64        let order_id = f.order_id.ok_or_else(|| {
65            // Ref: _APIServer_Trd_Comm.cpp:2145-2151 sets orderID before FTAPI delivery.
66            FutuError::Codec("missing orderID in OrderFill".into())
67        })?;
68
69        Ok(Self {
70            fill_id: f.fill_id,
71            fill_id_ex: f.fill_id_ex.clone(),
72            order_id,
73            trd_side: f.trd_side,
74            code: f.code.clone(),
75            name: f.name.clone(),
76            qty: f.qty,
77            price: f.price,
78            create_time: f.create_time.clone(),
79        })
80    }
81}
82
83/// 查询当日订单列表
84pub async fn get_order_list(client: &FutuClient, header: &TrdHeader) -> Result<Vec<Order>> {
85    get_order_list_with_options(client, header, None, None).await
86}
87
88/// 强制刷新 backend 后查询当日订单列表。
89///
90/// 与 [`get_order_list`] 分开暴露,避免把既有 SDK 调用从 C++ 的 optional-absent
91/// 合同静默改成显式刷新。当前仅供需要确认写操作最终效果的 bounded reconciliation 使用。
92pub async fn get_order_list_with_refresh_cache(
93    client: &FutuClient,
94    header: &TrdHeader,
95    timeout: Duration,
96) -> Result<Vec<Order>> {
97    get_order_list_with_options(client, header, Some(true), Some(timeout)).await
98}
99
100fn build_get_order_list_request(
101    header: &TrdHeader,
102    refresh_cache: Option<bool>,
103) -> futu_proto::trd_get_order_list::Request {
104    futu_proto::trd_get_order_list::Request {
105        c2s: futu_proto::trd_get_order_list::C2s {
106            header: header.to_proto(),
107            filter_conditions: None,
108            filter_status_list: vec![],
109            refresh_cache,
110        },
111    }
112}
113
114async fn get_order_list_with_options(
115    client: &FutuClient,
116    header: &TrdHeader,
117    refresh_cache: Option<bool>,
118    timeout: Option<Duration>,
119) -> Result<Vec<Order>> {
120    let req = build_get_order_list_request(header, refresh_cache);
121
122    let body = prost::Message::encode_to_vec(&req);
123    let resp_frame = match timeout {
124        Some(timeout) => {
125            client
126                .request_with_timeout(proto_id::TRD_GET_ORDER_LIST, body, timeout)
127                .await?
128        }
129        None => client.request(proto_id::TRD_GET_ORDER_LIST, body).await?,
130    };
131
132    let resp: futu_proto::trd_get_order_list::Response =
133        prost::Message::decode(resp_frame.body.as_ref()).map_err(FutuError::Proto)?;
134
135    if resp.ret_type != 0 {
136        return Err(crate::server_err(
137            resp.ret_type,
138            resp.ret_msg,
139            resp.err_code,
140        ));
141    }
142
143    let s2c = resp
144        .s2c
145        .ok_or(FutuError::Codec("missing s2c in GetOrderList".into()))?;
146
147    Ok(s2c.order_list.iter().map(Order::from_proto).collect())
148}
149
150/// 查询当日成交列表
151pub async fn get_order_fill_list(
152    client: &FutuClient,
153    header: &TrdHeader,
154) -> Result<Vec<OrderFill>> {
155    let req = futu_proto::trd_get_order_fill_list::Request {
156        c2s: futu_proto::trd_get_order_fill_list::C2s {
157            header: header.to_proto(),
158            filter_conditions: None,
159            refresh_cache: None,
160        },
161    };
162
163    let body = prost::Message::encode_to_vec(&req);
164    let resp_frame = client
165        .request(proto_id::TRD_GET_ORDER_FILL_LIST, body)
166        .await?;
167
168    let resp: futu_proto::trd_get_order_fill_list::Response =
169        prost::Message::decode(resp_frame.body.as_ref()).map_err(FutuError::Proto)?;
170
171    if resp.ret_type != 0 {
172        return Err(crate::server_err(
173            resp.ret_type,
174            resp.ret_msg,
175            resp.err_code,
176        ));
177    }
178
179    let s2c = resp
180        .s2c
181        .ok_or(FutuError::Codec("missing s2c in GetOrderFillList".into()))?;
182
183    s2c.order_fill_list
184        .iter()
185        .map(OrderFill::from_proto)
186        .collect()
187}
188
189#[cfg(test)]
190mod tests;