Skip to main content

futu_mcp/handlers/trade/
orders.rs

1//! mcp/handlers/trade/orders — OrderOut + DealOut + MaxQtys + OrderFee + HistoryOrder (orders/deals/max_qtys/order_fee/history_orders)
2//! (v1.4.110 CC Batch O: 拆自 trade.rs L538-786)
3
4use std::sync::Arc;
5
6use anyhow::Result;
7use futu_net::client::FutuClient;
8use serde::Serialize;
9
10use super::helpers::*;
11
12#[derive(Serialize)]
13struct OrderOut {
14    order_id: u64,
15    order_id_ex: String,
16    trd_side: i32,
17    order_type: i32,
18    order_status: i32,
19    code: String,
20    name: String,
21    qty: f64,
22    price: f64,
23    fill_qty: f64,
24    fill_avg_price: f64,
25    create_time: String,
26    update_time: String,
27    last_err_msg: String,
28}
29
30pub async fn get_orders(
31    client: &Arc<FutuClient>,
32    env: &str,
33    acc_id: u64,
34    market: &str,
35) -> Result<String> {
36    let header = build_header_strict_no_fund(env, acc_id, market)?;
37    let list = futu_trd::query::get_order_list(client, &header).await?;
38    let out: Vec<OrderOut> = list
39        .iter()
40        .map(|o| OrderOut {
41            order_id: o.order_id,
42            order_id_ex: o.order_id_ex.clone(),
43            trd_side: o.trd_side,
44            order_type: o.order_type,
45            order_status: o.order_status,
46            code: o.code.clone(),
47            name: o.name.clone(),
48            qty: o.qty,
49            price: o.price,
50            fill_qty: o.fill_qty,
51            fill_avg_price: o.fill_avg_price,
52            create_time: o.create_time.clone(),
53            update_time: o.update_time.clone(),
54            last_err_msg: o.last_err_msg.clone(),
55        })
56        .collect();
57    Ok(serde_json::to_string_pretty(&out)?)
58}
59
60#[derive(Serialize)]
61struct DealOut {
62    fill_id: u64,
63    fill_id_ex: String,
64    order_id: u64,
65    trd_side: i32,
66    code: String,
67    name: String,
68    qty: f64,
69    price: f64,
70    create_time: String,
71}
72
73pub async fn get_deals(
74    client: &Arc<FutuClient>,
75    env: &str,
76    acc_id: u64,
77    market: &str,
78) -> Result<String> {
79    let header = build_header_strict_no_fund(env, acc_id, market)?;
80    let list = futu_trd::query::get_order_fill_list(client, &header).await?;
81    let out: Vec<DealOut> = list
82        .iter()
83        .map(|f| DealOut {
84            fill_id: f.fill_id,
85            fill_id_ex: f.fill_id_ex.clone(),
86            order_id: f.order_id,
87            trd_side: f.trd_side,
88            code: f.code.clone(),
89            name: f.name.clone(),
90            qty: f.qty,
91            price: f.price,
92            create_time: f.create_time.clone(),
93        })
94        .collect();
95    Ok(serde_json::to_string_pretty(&out)?)
96}
97
98// ===== v1.4.25:补齐交易查询 5 件套 =====
99
100#[derive(Serialize)]
101struct MaxQtysOut {
102    max_cash_buy: f64,
103    max_cash_and_margin_buy: f64,
104    max_position_sell: f64,
105    max_sell_short: f64,
106    max_buy_back: f64,
107    /// 期货/期权开多仓每张合约初始保证金(股票无此字段)
108    long_required_im: f64,
109    /// 期货/期权开空仓每张合约初始保证金(股票无此字段)
110    short_required_im: f64,
111}
112
113/// 下单前查"最大可买 / 可卖 / 卖空 / 买回"。
114///
115/// `code` 必填(不含 market 前缀,如 `00700`)。`price` 对限价单必填,
116/// 市价单传 0 即可。`order_type` 对齐 Trd_Common.OrderType(1=普通/限价,
117/// 2=市价,5=竞价,6=绝对限价,等)。
118pub struct MaxTrdQtysInput<'a> {
119    pub env: &'a str,
120    pub acc_id: u64,
121    pub market: &'a str,
122    pub order_type: i32,
123    pub code: &'a str,
124    pub price: f64,
125    pub jp_acc_type: Option<i32>,
126    pub order_id: Option<u64>,
127}
128
129pub async fn get_max_trd_qtys(
130    client: &Arc<FutuClient>,
131    input: MaxTrdQtysInput<'_>,
132) -> Result<String> {
133    let header = build_header_strict_no_fund_with_jp_acc_type(
134        input.env,
135        input.acc_id,
136        input.market,
137        input.jp_acc_type,
138    )?;
139    let params = futu_trd::misc::MaxTrdQtysParams {
140        header,
141        order_type: input.order_type,
142        code: input.code.to_string(),
143        price: input.price,
144        order_id: input.order_id,
145    };
146    let s2c = futu_trd::misc::get_max_trd_qtys(client, &params).await?;
147    let q = s2c
148        .max_trd_qtys
149        .ok_or_else(|| anyhow::anyhow!("missing max_trd_qtys in response"))?;
150    let out = MaxQtysOut {
151        max_cash_buy: q.max_cash_buy,
152        max_cash_and_margin_buy: q.max_cash_and_margin_buy.unwrap_or(0.0),
153        max_position_sell: q.max_position_sell,
154        max_sell_short: q.max_sell_short.unwrap_or(0.0),
155        max_buy_back: q.max_buy_back.unwrap_or(0.0),
156        long_required_im: q.long_required_im.unwrap_or(0.0),
157        short_required_im: q.short_required_im.unwrap_or(0.0),
158    };
159    Ok(serde_json::to_string_pretty(&out)?)
160}
161
162#[derive(Serialize)]
163struct OrderFeeItemOut {
164    title: String,
165    value: f64,
166}
167
168#[derive(Serialize)]
169struct OrderFeeOut {
170    order_id_ex: String,
171    fee_amount: f64,
172    fee_list: Vec<OrderFeeItemOut>,
173}
174
175/// 按扩展订单号 (`order_id_ex`) 查费用明细。
176///
177/// 对齐 C++ `TRD_GET_ORDER_FEE`(proto_id 2225)。
178pub async fn get_order_fee(
179    client: &Arc<FutuClient>,
180    env: &str,
181    acc_id: u64,
182    market: &str,
183    order_id_ex_list: &[String],
184) -> Result<String> {
185    let header = build_header_strict_no_fund(env, acc_id, market)?;
186    let list = futu_trd::misc::get_order_fee(client, &header, order_id_ex_list).await?;
187    let out: Vec<OrderFeeOut> = list
188        .into_iter()
189        .map(|f| OrderFeeOut {
190            order_id_ex: f.order_id_ex,
191            fee_amount: f.fee_amount,
192            fee_list: f
193                .fee_list
194                .into_iter()
195                .map(|i| OrderFeeItemOut {
196                    title: i.title,
197                    value: i.value,
198                })
199                .collect(),
200        })
201        .collect();
202    Ok(serde_json::to_string_pretty(&out)?)
203}
204
205#[derive(Serialize)]
206struct HistoryOrderOut {
207    order_id: u64,
208    order_id_ex: String,
209    trd_side: i32,
210    order_type: i32,
211    order_status: i32,
212    code: String,
213    name: String,
214    qty: f64,
215    price: f64,
216    fill_qty: f64,
217    fill_avg_price: f64,
218    create_time: String,
219    update_time: String,
220}
221
222/// 查历史订单。`begin_time` / `end_time` 为 `"yyyy-MM-dd HH:mm:ss"` 格式,
223/// 为 None 时服务端默认返回近期。`code_list` 空 = 不过滤股票。
224pub struct HistoryQueryInput<'a> {
225    pub env: &'a str,
226    pub acc_id: u64,
227    pub market: &'a str,
228    pub code_list: Vec<String>,
229    pub begin_time: Option<String>,
230    pub end_time: Option<String>,
231}
232
233pub async fn get_history_orders(
234    client: &Arc<FutuClient>,
235    input: HistoryQueryInput<'_>,
236) -> Result<String> {
237    let header = build_header(input.env, input.acc_id, input.market)?;
238    let filter = futu_trd::misc::HistoryFilterConditions {
239        code_list: input.code_list,
240        id_list: vec![],
241        begin_time: input.begin_time,
242        end_time: input.end_time,
243        filter_market: Some(header.trd_market as i32),
244    };
245    let list = futu_trd::misc::get_history_order_list(client, &header, &filter).await?;
246    let out: Vec<HistoryOrderOut> = list
247        .iter()
248        .map(|o| HistoryOrderOut {
249            order_id: o.order_id,
250            order_id_ex: o.order_id_ex.clone(),
251            trd_side: o.trd_side,
252            order_type: o.order_type,
253            order_status: o.order_status,
254            code: o.code.clone(),
255            name: o.name.clone(),
256            qty: o.qty,
257            price: o.price,
258            fill_qty: o.fill_qty,
259            fill_avg_price: o.fill_avg_price,
260            create_time: o.create_time.clone(),
261            update_time: o.update_time.clone(),
262        })
263        .collect();
264    Ok(serde_json::to_string_pretty(&out)?)
265}