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 order_id: Option<u64>,
126}
127
128pub async fn get_max_trd_qtys(
129    client: &Arc<FutuClient>,
130    input: MaxTrdQtysInput<'_>,
131) -> Result<String> {
132    let header = build_header_strict_no_fund(input.env, input.acc_id, input.market)?;
133    let params = futu_trd::misc::MaxTrdQtysParams {
134        header,
135        order_type: input.order_type,
136        code: input.code.to_string(),
137        price: input.price,
138        order_id: input.order_id,
139    };
140    let s2c = futu_trd::misc::get_max_trd_qtys(client, &params).await?;
141    let q = s2c
142        .max_trd_qtys
143        .ok_or_else(|| anyhow::anyhow!("missing max_trd_qtys in response"))?;
144    let out = MaxQtysOut {
145        max_cash_buy: q.max_cash_buy,
146        max_cash_and_margin_buy: q.max_cash_and_margin_buy.unwrap_or(0.0),
147        max_position_sell: q.max_position_sell,
148        max_sell_short: q.max_sell_short.unwrap_or(0.0),
149        max_buy_back: q.max_buy_back.unwrap_or(0.0),
150        long_required_im: q.long_required_im.unwrap_or(0.0),
151        short_required_im: q.short_required_im.unwrap_or(0.0),
152    };
153    Ok(serde_json::to_string_pretty(&out)?)
154}
155
156#[derive(Serialize)]
157struct OrderFeeItemOut {
158    title: String,
159    value: f64,
160}
161
162#[derive(Serialize)]
163struct OrderFeeOut {
164    order_id_ex: String,
165    fee_amount: f64,
166    fee_list: Vec<OrderFeeItemOut>,
167}
168
169/// 按扩展订单号 (`order_id_ex`) 查费用明细。
170///
171/// 对齐 C++ `TRD_GET_ORDER_FEE`(proto_id 2225)。
172pub async fn get_order_fee(
173    client: &Arc<FutuClient>,
174    env: &str,
175    acc_id: u64,
176    market: &str,
177    order_id_ex_list: &[String],
178) -> Result<String> {
179    let header = build_header_strict_no_fund(env, acc_id, market)?;
180    let list = futu_trd::misc::get_order_fee(client, &header, order_id_ex_list).await?;
181    let out: Vec<OrderFeeOut> = list
182        .into_iter()
183        .map(|f| OrderFeeOut {
184            order_id_ex: f.order_id_ex,
185            fee_amount: f.fee_amount,
186            fee_list: f
187                .fee_list
188                .into_iter()
189                .map(|i| OrderFeeItemOut {
190                    title: i.title,
191                    value: i.value,
192                })
193                .collect(),
194        })
195        .collect();
196    Ok(serde_json::to_string_pretty(&out)?)
197}
198
199#[derive(Serialize)]
200struct HistoryOrderOut {
201    order_id: u64,
202    order_id_ex: String,
203    trd_side: i32,
204    order_type: i32,
205    order_status: i32,
206    code: String,
207    name: String,
208    qty: f64,
209    price: f64,
210    fill_qty: f64,
211    fill_avg_price: f64,
212    create_time: String,
213    update_time: String,
214}
215
216/// 查历史订单。`begin_time` / `end_time` 为 `"yyyy-MM-dd HH:mm:ss"` 格式,
217/// 为 None 时服务端默认返回近期。`code_list` 空 = 不过滤股票。
218pub struct HistoryQueryInput<'a> {
219    pub env: &'a str,
220    pub acc_id: u64,
221    pub market: &'a str,
222    pub code_list: Vec<String>,
223    pub begin_time: Option<String>,
224    pub end_time: Option<String>,
225}
226
227pub async fn get_history_orders(
228    client: &Arc<FutuClient>,
229    input: HistoryQueryInput<'_>,
230) -> Result<String> {
231    let header = build_header(input.env, input.acc_id, input.market)?;
232    let filter = futu_trd::misc::HistoryFilterConditions {
233        code_list: input.code_list,
234        id_list: vec![],
235        begin_time: input.begin_time,
236        end_time: input.end_time,
237        filter_market: Some(header.trd_market as i32),
238    };
239    let list = futu_trd::misc::get_history_order_list(client, &header, &filter).await?;
240    let out: Vec<HistoryOrderOut> = list
241        .iter()
242        .map(|o| HistoryOrderOut {
243            order_id: o.order_id,
244            order_id_ex: o.order_id_ex.clone(),
245            trd_side: o.trd_side,
246            order_type: o.order_type,
247            order_status: o.order_status,
248            code: o.code.clone(),
249            name: o.name.clone(),
250            qty: o.qty,
251            price: o.price,
252            fill_qty: o.fill_qty,
253            fill_avg_price: o.fill_avg_price,
254            create_time: o.create_time.clone(),
255            update_time: o.update_time.clone(),
256        })
257        .collect();
258    Ok(serde_json::to_string_pretty(&out)?)
259}