1use std::sync::Arc;
7
8use anyhow::{bail, Result};
9use futu_net::client::FutuClient;
10use futu_trd::types::{
11 ModifyOrderOp, ModifyOrderParams, OrderType, PlaceOrderParams, TrdEnv, TrdHeader, TrdMarket,
12 TrdSide,
13};
14use serde::Serialize;
15
16pub fn parse_trd_market(s: &str) -> Result<TrdMarket> {
19 let m = match s.trim().to_ascii_uppercase().as_str() {
20 "HK" => TrdMarket::HK,
21 "US" => TrdMarket::US,
22 "CN" => TrdMarket::CN,
23 "HKCC" => TrdMarket::HKCC,
24 other => bail!("unknown trd market {other:?} (HK|US|CN|HKCC)"),
25 };
26 Ok(m)
27}
28
29pub fn parse_trd_env(s: &str) -> Result<TrdEnv> {
30 let e = match s.trim().to_ascii_lowercase().as_str() {
31 "simulate" | "sim" => TrdEnv::Simulate,
32 "real" => TrdEnv::Real,
33 other => bail!("unknown trd env {other:?} (real|simulate)"),
34 };
35 Ok(e)
36}
37
38pub fn parse_trd_side(s: &str) -> Result<TrdSide> {
39 let v = match s.trim().to_ascii_uppercase().as_str() {
40 "BUY" => TrdSide::Buy,
41 "SELL" => TrdSide::Sell,
42 "SELL_SHORT" | "SHORT" => TrdSide::SellShort,
43 "BUY_BACK" | "COVER" => TrdSide::BuyBack,
44 other => bail!("unknown trd side {other:?} (BUY|SELL|SELL_SHORT|BUY_BACK)"),
45 };
46 Ok(v)
47}
48
49pub fn parse_order_type(s: &str) -> Result<OrderType> {
50 let v = match s.trim().to_ascii_uppercase().as_str() {
51 "NORMAL" | "LIMIT" => OrderType::Normal,
52 "MARKET" => OrderType::Market,
53 "ABSOLUTE_LIMIT" => OrderType::AbsoluteLimit,
54 "AUCTION" => OrderType::Auction,
55 "AUCTION_LIMIT" => OrderType::AuctionLimit,
56 "SPECIAL_LIMIT" => OrderType::SpecialLimit,
57 other => bail!(
58 "unknown order type {other:?} (NORMAL|MARKET|ABSOLUTE_LIMIT|AUCTION|AUCTION_LIMIT|SPECIAL_LIMIT)"
59 ),
60 };
61 Ok(v)
62}
63
64pub fn parse_modify_op(s: &str) -> Result<ModifyOrderOp> {
65 let v = match s.trim().to_ascii_uppercase().as_str() {
66 "NORMAL" | "MODIFY" => ModifyOrderOp::Normal,
67 "CANCEL" => ModifyOrderOp::Cancel,
68 "DISABLE" => ModifyOrderOp::Disable,
69 "ENABLE" => ModifyOrderOp::Enable,
70 "DELETE" => ModifyOrderOp::Delete,
71 other => bail!("unknown modify op {other:?} (NORMAL|CANCEL|DISABLE|ENABLE|DELETE)"),
72 };
73 Ok(v)
74}
75
76fn build_header(env: &str, acc_id: u64, market: &str) -> Result<TrdHeader> {
77 Ok(TrdHeader {
78 trd_env: parse_trd_env(env)?,
79 acc_id,
80 trd_market: parse_trd_market(market)?,
81 })
82}
83
84#[derive(Serialize)]
87struct PlaceOut {
88 order_id: u64,
89 env: &'static str,
90 market: String,
91 acc_id: u64,
92 side: String,
93 order_type: String,
94 code: String,
95 qty: f64,
96 price: Option<f64>,
97}
98
99#[allow(clippy::too_many_arguments)]
100pub async fn place_order(
101 client: &Arc<FutuClient>,
102 env: &str,
103 acc_id: u64,
104 market: &str,
105 side: &str,
106 order_type: &str,
107 code: &str,
108 qty: f64,
109 price: Option<f64>,
110) -> Result<String> {
111 let header = build_header(env, acc_id, market)?;
112 let trd_side = parse_trd_side(side)?;
113 let ord_type = parse_order_type(order_type)?;
114
115 let params = PlaceOrderParams {
116 header: header.clone(),
117 trd_side,
118 order_type: ord_type,
119 code: code.to_string(),
120 qty,
121 price,
122 adjust_price: None,
123 adjust_side_and_limit: None,
124 };
125 let res = futu_trd::order::place_order(client, ¶ms).await?;
126
127 let out = PlaceOut {
128 order_id: res.order_id,
129 env: match header.trd_env {
130 TrdEnv::Simulate => "simulate",
131 TrdEnv::Real => "real",
132 },
133 market: market.to_ascii_uppercase(),
134 acc_id,
135 side: side.to_ascii_uppercase(),
136 order_type: order_type.to_ascii_uppercase(),
137 code: code.to_string(),
138 qty,
139 price,
140 };
141 Ok(serde_json::to_string_pretty(&out)?)
142}
143
144#[derive(Serialize)]
147struct ModifyOut {
148 order_id: u64,
149 op: String,
150 env: &'static str,
151 qty: Option<f64>,
152 price: Option<f64>,
153}
154
155#[allow(clippy::too_many_arguments)]
156pub async fn modify_order(
157 client: &Arc<FutuClient>,
158 env: &str,
159 acc_id: u64,
160 market: &str,
161 order_id: u64,
162 op: &str,
163 qty: Option<f64>,
164 price: Option<f64>,
165) -> Result<String> {
166 let header = build_header(env, acc_id, market)?;
167 let mop = parse_modify_op(op)?;
168
169 let params = ModifyOrderParams {
170 header: header.clone(),
171 order_id,
172 modify_order_op: mop,
173 qty,
174 price,
175 for_all: None,
176 };
177 let returned_id = futu_trd::order::modify_order(client, ¶ms).await?;
178
179 let out = ModifyOut {
180 order_id: returned_id,
181 op: op.to_ascii_uppercase(),
182 env: match header.trd_env {
183 TrdEnv::Simulate => "simulate",
184 TrdEnv::Real => "real",
185 },
186 qty,
187 price,
188 };
189 Ok(serde_json::to_string_pretty(&out)?)
190}
191
192#[derive(Serialize)]
195struct CancelOut {
196 order_id: u64,
197 op: &'static str,
198 env: &'static str,
199}
200
201pub async fn cancel_order(
202 client: &Arc<FutuClient>,
203 env: &str,
204 acc_id: u64,
205 market: &str,
206 order_id: u64,
207) -> Result<String> {
208 let header = build_header(env, acc_id, market)?;
209 let returned_id = futu_trd::order::cancel_order(client, &header, order_id).await?;
210 let out = CancelOut {
211 order_id: returned_id,
212 op: "CANCEL",
213 env: match header.trd_env {
214 TrdEnv::Simulate => "simulate",
215 TrdEnv::Real => "real",
216 },
217 };
218 Ok(serde_json::to_string_pretty(&out)?)
219}
220
221pub fn is_real_env(env: &str) -> bool {
225 matches!(env.trim().to_ascii_lowercase().as_str(), "real")
226}