1use anyhow::{Result, bail};
2use serde::Serialize;
3use tabled::{Table, Tabled};
4
5use crate::cmd::account::{parse_trd_env, parse_trd_market};
6use crate::common::connect_gateway;
7use crate::output::OutputFormat;
8use futu_trd::misc::{
9 HistoryFilterConditions, get_history_order_fill_list, get_history_order_list,
10};
11use futu_trd::types::TrdHeader;
12
13#[derive(Tabled)]
14struct HistOrderRow {
15 #[tabled(rename = "ID")]
16 order_id: String,
17 #[tabled(rename = "Code")]
18 code: String,
19 #[tabled(rename = "Name")]
20 name: String,
21 #[tabled(rename = "Side")]
22 side: String,
23 #[tabled(rename = "Type")]
24 order_type: String,
25 #[tabled(rename = "Status")]
26 status: String,
27 #[tabled(rename = "Qty")]
28 qty: String,
29 #[tabled(rename = "Price")]
30 price: String,
31 #[tabled(rename = "Fill")]
32 fill: String,
33 #[tabled(rename = "Time")]
34 time: String,
35}
36
37#[derive(Serialize)]
38struct HistOrderJson {
39 order_id: u64,
40 order_id_ex: String,
41 code: String,
42 name: String,
43 trd_side: i32,
44 order_type: i32,
45 order_status: i32,
46 qty: f64,
47 price: f64,
48 fill_qty: f64,
49 fill_avg_price: f64,
50 create_time: String,
51 update_time: String,
52}
53
54pub struct HistoryOrdersCommand<'a> {
55 pub gateway: &'a str,
56 pub env: &'a str,
57 pub acc_id: u64,
58 pub market: &'a str,
59 pub codes: Vec<String>,
60 pub begin: Option<String>,
61 pub end: Option<String>,
62 pub output: OutputFormat,
63}
64
65pub(crate) fn validate_history_time_range(
68 command: &str,
69 market: &str,
70 begin: Option<&str>,
71 end: Option<&str>,
72) -> Result<()> {
73 if begin.is_none() || end.is_none() {
74 bail!(
75 "{command}: --begin 和 --end 必须同时提供,格式为 YYYY-MM-DD HH:MM:SS \
76 (market={market}). OpenD history query 要求 begin/end 同时存在。"
77 );
78 }
79 Ok(())
80}
81
82pub async fn run_history_orders(input: HistoryOrdersCommand<'_>) -> Result<()> {
83 let env_p = parse_trd_env(input.env)?;
84 let market_p = parse_trd_market(input.market)?;
85 validate_history_time_range(
86 "history-orders",
87 input.market,
88 input.begin.as_deref(),
89 input.end.as_deref(),
90 )?;
91 let header = TrdHeader {
92 trd_env: env_p,
93 acc_id: input.acc_id,
94 trd_market: market_p,
95 jp_acc_type: None,
96 };
97 let filter = HistoryFilterConditions {
98 code_list: input.codes,
99 id_list: vec![],
100 begin_time: input.begin,
101 end_time: input.end,
102 filter_market: Some(market_p as i32),
103 };
104 let (client, _push_rx) = connect_gateway(input.gateway, "futucli-trade-ext").await?;
105 let list = get_history_order_list(&client, &header, &filter).await?;
106
107 if matches!(input.output, OutputFormat::Json) {
108 let json: Vec<HistOrderJson> = list
109 .iter()
110 .map(|o| HistOrderJson {
111 order_id: o.order_id,
112 order_id_ex: o.order_id_ex.clone(),
113 code: o.code.clone(),
114 name: o.name.clone(),
115 trd_side: o.trd_side,
116 order_type: o.order_type,
117 order_status: o.order_status,
118 qty: o.qty,
119 price: o.price,
120 fill_qty: o.fill_qty,
121 fill_avg_price: o.fill_avg_price,
122 create_time: o.create_time.clone(),
123 update_time: o.update_time.clone(),
124 })
125 .collect();
126 println!("{}", serde_json::to_string_pretty(&json)?);
127 return Ok(());
128 }
129
130 let rows: Vec<HistOrderRow> = list
131 .iter()
132 .map(|o| HistOrderRow {
133 order_id: o.order_id.to_string(),
134 code: o.code.clone(),
135 name: o.name.clone(),
136 side: format!("{}", o.trd_side),
137 order_type: format!("{}", o.order_type),
138 status: format!("{}", o.order_status),
139 qty: format!("{:.0}", o.qty),
140 price: format!("{:.3}", o.price),
141 fill: format!("{:.0}", o.fill_qty),
142 time: o.create_time.clone(),
143 })
144 .collect();
145 println!("history orders: {} record(s) from gateway", rows.len());
146 println!("{}", Table::new(rows));
147 Ok(())
148}
149
150#[derive(Tabled)]
151struct HistDealRow {
152 #[tabled(rename = "Fill ID")]
153 fill_id: String,
154 #[tabled(rename = "Order ID")]
155 order_id: String,
156 #[tabled(rename = "Code")]
157 code: String,
158 #[tabled(rename = "Name")]
159 name: String,
160 #[tabled(rename = "Side")]
161 side: String,
162 #[tabled(rename = "Qty")]
163 qty: String,
164 #[tabled(rename = "Price")]
165 price: String,
166 #[tabled(rename = "Time")]
167 time: String,
168}
169
170pub struct HistoryDealsCommand<'a> {
171 pub gateway: &'a str,
172 pub env: &'a str,
173 pub acc_id: u64,
174 pub market: &'a str,
175 pub codes: Vec<String>,
176 pub begin: Option<String>,
177 pub end: Option<String>,
178 pub output: OutputFormat,
179}
180
181pub async fn run_history_deals(input: HistoryDealsCommand<'_>) -> Result<()> {
182 let env_p = parse_trd_env(input.env)?;
183 let market_p = parse_trd_market(input.market)?;
184 validate_history_time_range(
185 "history-deals",
186 input.market,
187 input.begin.as_deref(),
188 input.end.as_deref(),
189 )?;
190 let header = TrdHeader {
191 trd_env: env_p,
192 acc_id: input.acc_id,
193 trd_market: market_p,
194 jp_acc_type: None,
195 };
196 let filter = HistoryFilterConditions {
197 code_list: input.codes,
198 id_list: vec![],
199 begin_time: input.begin,
200 end_time: input.end,
201 filter_market: Some(market_p as i32),
202 };
203 let (client, _push_rx) = connect_gateway(input.gateway, "futucli-trade-ext").await?;
204 let list = get_history_order_fill_list(&client, &header, &filter).await?;
205
206 if matches!(input.output, OutputFormat::Json) {
207 let json: Vec<_> = list
208 .iter()
209 .map(|f| {
210 serde_json::json!({
211 "fill_id": f.fill_id,
212 "fill_id_ex": f.fill_id_ex,
213 "order_id": f.order_id,
214 "code": f.code,
215 "name": f.name,
216 "trd_side": f.trd_side,
217 "qty": f.qty,
218 "price": f.price,
219 "create_time": f.create_time,
220 })
221 })
222 .collect();
223 println!("{}", serde_json::to_string_pretty(&json)?);
224 return Ok(());
225 }
226 let rows: Vec<HistDealRow> = list
227 .iter()
228 .map(|f| HistDealRow {
229 fill_id: f.fill_id.to_string(),
230 order_id: f.order_id.to_string(),
231 code: f.code.clone(),
232 name: f.name.clone(),
233 side: format!("{}", f.trd_side),
234 qty: format!("{:.0}", f.qty),
235 price: format!("{:.3}", f.price),
236 time: f.create_time.clone(),
237 })
238 .collect();
239 println!("history deals: {} record(s)", rows.len());
240 println!("{}", Table::new(rows));
241 Ok(())
242}