futu_trd/
order.rs

1use std::sync::atomic::{AtomicU32, Ordering};
2
3use futu_core::error::{FutuError, Result};
4use futu_core::proto_id;
5use futu_net::client::FutuClient;
6
7use crate::types::{ModifyOrderParams, PlaceOrderParams, PlaceOrderResult};
8
9/// 全局唯一的 packet ID 生成器(防重放攻击)
10static PACKET_SERIAL: AtomicU32 = AtomicU32::new(1);
11
12fn next_packet_id() -> futu_proto::common::PacketId {
13    let serial = PACKET_SERIAL.fetch_add(1, Ordering::Relaxed);
14    futu_proto::common::PacketId {
15        conn_id: 0, // 由服务端识别
16        serial_no: serial,
17    }
18}
19
20/// 下单
21///
22/// 向 FutuOpenD 发送下单请求。
23/// 注意:需要先解锁交易 (`unlock_trade`)。
24pub async fn place_order(
25    client: &FutuClient,
26    params: &PlaceOrderParams,
27) -> Result<PlaceOrderResult> {
28    let req = futu_proto::trd_place_order::Request {
29        c2s: futu_proto::trd_place_order::C2s {
30            packet_id: next_packet_id(),
31            header: params.header.to_proto(),
32            trd_side: params.trd_side as i32,
33            order_type: params.order_type as i32,
34            code: params.code.clone(),
35            qty: params.qty,
36            price: params.price,
37            adjust_price: params.adjust_price,
38            adjust_side_and_limit: params.adjust_side_and_limit,
39            sec_market: None,
40            remark: None,
41            time_in_force: None,
42            fill_outside_rth: None,
43            aux_price: None,
44            trail_type: None,
45            trail_value: None,
46            trail_spread: None,
47            session: None,
48            position_id: None,
49        },
50    };
51
52    let body = prost::Message::encode_to_vec(&req);
53    let resp_frame = client.request(proto_id::TRD_PLACE_ORDER, body).await?;
54
55    let resp: futu_proto::trd_place_order::Response =
56        prost::Message::decode(resp_frame.body.as_ref()).map_err(FutuError::Proto)?;
57
58    if resp.ret_type != 0 {
59        return Err(FutuError::ServerError {
60            ret_type: resp.ret_type,
61            msg: resp.ret_msg.unwrap_or_default(),
62        });
63    }
64
65    let s2c = resp
66        .s2c
67        .ok_or(FutuError::Codec("missing s2c in PlaceOrder".into()))?;
68
69    Ok(PlaceOrderResult {
70        order_id: s2c.order_id.unwrap_or(0),
71    })
72}
73
74/// 修改/撤销订单
75pub async fn modify_order(client: &FutuClient, params: &ModifyOrderParams) -> Result<u64> {
76    let req = futu_proto::trd_modify_order::Request {
77        c2s: futu_proto::trd_modify_order::C2s {
78            packet_id: next_packet_id(),
79            header: params.header.to_proto(),
80            order_id: params.order_id,
81            modify_order_op: params.modify_order_op as i32,
82            for_all: params.for_all,
83            trd_market: None,
84            qty: params.qty,
85            price: params.price,
86            adjust_price: None,
87            adjust_side_and_limit: None,
88            aux_price: None,
89            trail_type: None,
90            trail_value: None,
91            trail_spread: None,
92            order_id_ex: None,
93        },
94    };
95
96    let body = prost::Message::encode_to_vec(&req);
97    let resp_frame = client.request(proto_id::TRD_MODIFY_ORDER, body).await?;
98
99    let resp: futu_proto::trd_modify_order::Response =
100        prost::Message::decode(resp_frame.body.as_ref()).map_err(FutuError::Proto)?;
101
102    if resp.ret_type != 0 {
103        return Err(FutuError::ServerError {
104            ret_type: resp.ret_type,
105            msg: resp.ret_msg.unwrap_or_default(),
106        });
107    }
108
109    let s2c = resp
110        .s2c
111        .ok_or(FutuError::Codec("missing s2c in ModifyOrder".into()))?;
112
113    Ok(s2c.order_id)
114}
115
116/// 撤单(modify_order 的便捷封装)
117pub async fn cancel_order(
118    client: &FutuClient,
119    header: &crate::types::TrdHeader,
120    order_id: u64,
121) -> Result<u64> {
122    modify_order(
123        client,
124        &ModifyOrderParams {
125            header: header.clone(),
126            order_id,
127            modify_order_op: crate::types::ModifyOrderOp::Cancel,
128            qty: None,
129            price: None,
130            for_all: None,
131        },
132    )
133    .await
134}