1use std::sync::Arc;
4
5use anyhow::{bail, Result};
6use futu_net::client::FutuClient;
7use futu_trd::types::{TrdEnv, TrdHeader, TrdMarket};
8use serde::Serialize;
9
10fn parse_trd_market(s: &str) -> Result<TrdMarket> {
11 let m = match s.trim().to_ascii_uppercase().as_str() {
12 "HK" => TrdMarket::HK,
13 "US" => TrdMarket::US,
14 "CN" => TrdMarket::CN,
15 "HKCC" => TrdMarket::HKCC,
16 other => bail!("unknown trd market {other:?} (HK|US|CN|HKCC)"),
17 };
18 Ok(m)
19}
20
21fn parse_trd_env(s: &str) -> Result<TrdEnv> {
22 let e = match s.trim().to_ascii_lowercase().as_str() {
23 "simulate" | "sim" => TrdEnv::Simulate,
24 "real" => TrdEnv::Real,
25 other => bail!("unknown trd env {other:?} (real|simulate)"),
26 };
27 Ok(e)
28}
29
30fn build_header(env: &str, acc_id: u64, market: &str) -> Result<TrdHeader> {
31 Ok(TrdHeader {
32 trd_env: parse_trd_env(env)?,
33 acc_id,
34 trd_market: parse_trd_market(market)?,
35 })
36}
37
38#[derive(Serialize)]
39struct AccountOut {
40 acc_id: u64,
41 trd_env: i32,
42 env_label: &'static str,
43 trd_market_auth_list: Vec<i32>,
44}
45
46pub async fn list_accounts(client: &Arc<FutuClient>) -> Result<String> {
47 let accs = futu_trd::account::get_acc_list(client).await?;
48 let out: Vec<AccountOut> = accs
49 .iter()
50 .map(|a| AccountOut {
51 acc_id: a.acc_id,
52 trd_env: a.trd_env,
53 env_label: match a.trd_env {
54 0 => "simulate",
55 1 => "real",
56 _ => "unknown",
57 },
58 trd_market_auth_list: a.trd_market_auth_list.clone(),
59 })
60 .collect();
61 Ok(serde_json::to_string_pretty(&out)?)
62}
63
64#[derive(Serialize)]
65struct FundsOut {
66 power: f64,
67 total_assets: f64,
68 cash: f64,
69 market_val: f64,
70 frozen_cash: f64,
71 debt_cash: f64,
72 avl_withdrawal_cash: f64,
73}
74
75pub async fn get_funds(
76 client: &Arc<FutuClient>,
77 env: &str,
78 acc_id: u64,
79 market: &str,
80) -> Result<String> {
81 let header = build_header(env, acc_id, market)?;
82 let f = futu_trd::account::get_funds(client, &header).await?;
83 let out = FundsOut {
84 power: f.power,
85 total_assets: f.total_assets,
86 cash: f.cash,
87 market_val: f.market_val,
88 frozen_cash: f.frozen_cash,
89 debt_cash: f.debt_cash,
90 avl_withdrawal_cash: f.avl_withdrawal_cash,
91 };
92 Ok(serde_json::to_string_pretty(&out)?)
93}
94
95#[derive(Serialize)]
96struct PositionOut {
97 position_id: u64,
98 code: String,
99 name: String,
100 qty: f64,
101 can_sell_qty: f64,
102 price: f64,
103 cost_price: f64,
104 val: f64,
105 pl_val: f64,
106 pl_ratio: f64,
107}
108
109pub async fn get_positions(
110 client: &Arc<FutuClient>,
111 env: &str,
112 acc_id: u64,
113 market: &str,
114) -> Result<String> {
115 let header = build_header(env, acc_id, market)?;
116 let list = futu_trd::account::get_position_list(client, &header).await?;
117 let out: Vec<PositionOut> = list
118 .iter()
119 .map(|p| PositionOut {
120 position_id: p.position_id,
121 code: p.code.clone(),
122 name: p.name.clone(),
123 qty: p.qty,
124 can_sell_qty: p.can_sell_qty,
125 price: p.price,
126 cost_price: p.cost_price,
127 val: p.val,
128 pl_val: p.pl_val,
129 pl_ratio: p.pl_ratio,
130 })
131 .collect();
132 Ok(serde_json::to_string_pretty(&out)?)
133}
134
135#[derive(Serialize)]
136struct OrderOut {
137 order_id: u64,
138 order_id_ex: String,
139 trd_side: i32,
140 order_type: i32,
141 order_status: i32,
142 code: String,
143 name: String,
144 qty: f64,
145 price: f64,
146 fill_qty: f64,
147 fill_avg_price: f64,
148 create_time: String,
149 update_time: String,
150 last_err_msg: String,
151}
152
153pub async fn get_orders(
154 client: &Arc<FutuClient>,
155 env: &str,
156 acc_id: u64,
157 market: &str,
158) -> Result<String> {
159 let header = build_header(env, acc_id, market)?;
160 let list = futu_trd::query::get_order_list(client, &header).await?;
161 let out: Vec<OrderOut> = list
162 .iter()
163 .map(|o| OrderOut {
164 order_id: o.order_id,
165 order_id_ex: o.order_id_ex.clone(),
166 trd_side: o.trd_side,
167 order_type: o.order_type,
168 order_status: o.order_status,
169 code: o.code.clone(),
170 name: o.name.clone(),
171 qty: o.qty,
172 price: o.price,
173 fill_qty: o.fill_qty,
174 fill_avg_price: o.fill_avg_price,
175 create_time: o.create_time.clone(),
176 update_time: o.update_time.clone(),
177 last_err_msg: o.last_err_msg.clone(),
178 })
179 .collect();
180 Ok(serde_json::to_string_pretty(&out)?)
181}
182
183#[derive(Serialize)]
184struct DealOut {
185 fill_id: u64,
186 fill_id_ex: String,
187 order_id: u64,
188 trd_side: i32,
189 code: String,
190 name: String,
191 qty: f64,
192 price: f64,
193 create_time: String,
194}
195
196pub async fn get_deals(
197 client: &Arc<FutuClient>,
198 env: &str,
199 acc_id: u64,
200 market: &str,
201) -> Result<String> {
202 let header = build_header(env, acc_id, market)?;
203 let list = futu_trd::query::get_order_fill_list(client, &header).await?;
204 let out: Vec<DealOut> = list
205 .iter()
206 .map(|f| DealOut {
207 fill_id: f.fill_id,
208 fill_id_ex: f.fill_id_ex.clone(),
209 order_id: f.order_id,
210 trd_side: f.trd_side,
211 code: f.code.clone(),
212 name: f.name.clone(),
213 qty: f.qty,
214 price: f.price,
215 create_time: f.create_time.clone(),
216 })
217 .collect();
218 Ok(serde_json::to_string_pretty(&out)?)
219}