1use std::collections::HashMap;
2
3use chrono::{DateTime, FixedOffset, NaiveDate, NaiveDateTime, NaiveTime, TimeZone, Utc};
4use chrono::format::ParseError;
5use qifi_rs::account::Trade;
6use serde::{Deserialize, Serialize};
7
8use crate::market_preset::MarketPreset;
9
10#[derive(Debug, Clone, Deserialize, Serialize)]
12pub struct QATradePair {
13 pub open_datetime: i64,
14 pub close_datetime: i64,
15 pub opendate: String,
16 pub closedate: String,
17 pub if_buyopen: bool,
18 pub code: String,
19 pub amount: f64,
20 pub openprice: f64,
21 pub closeprice: f64,
22 pub open_trade_id: String,
23 pub close_trade_id: String,
24 pub pnl_ratio: f64,
25 pub pnl_money: f64,
26 pub hold_gap: f64,
27}
28
29
30
31#[derive(Debug, Clone, Deserialize, Serialize)]
32pub struct Temp {
33 pub amount: f64,
34 pub direction: String,
35 pub offset: String,
36 pub datetime: i64,
37 pub code: String,
38 pub price: f64,
39 pub trade_id: String,
40}
41
42#[derive(Debug, Clone, Deserialize, Serialize)]
43pub struct QAPerformance_Single {
44 pub market_set: MarketPreset,
45 pub pair: Vec<QATradePair>,
46 pub temp: HashMap<String, Vec<Temp>>,
47}
48
49#[derive(Debug, Clone, Deserialize, Serialize)]
50pub struct QAPerformance {
51 pub market: HashMap<String, QAPerformance_Single>
52}
53
54#[derive(Debug, Clone, Deserialize, Serialize)]
55pub struct QARiskMessage {}
56
57
58impl QAPerformance {
59 pub fn new() -> Self {
60 QAPerformance {
61 market: HashMap::new()
62 }
63 }
64
65 pub fn insert_trade(&mut self, trade: Trade) {
66 let code = trade.instrument_id.clone();
67 if self.market.contains_key(&code) {
68 self.market.get_mut(&code).unwrap().insert_trade(trade.clone());
69 } else {
70 let mut u = QAPerformance_Single::new();
71 u.insert_trade(trade.clone());
72 self.market.insert(code.clone(), u);
73 }
74 }
75 pub fn get_totalprofit(&mut self) -> f64 {
76 let mut tp: f64 = 0.0;
77 for (_, ps) in self.market.iter_mut() {
78 tp += ps.get_totalprofit();
79 }
80 tp
81 }
82
83 pub fn pair(&mut self) -> Vec<QATradePair> {
84 let mut px = vec![];
85 for (_, ps) in self.market.iter_mut() {
86 for item in &ps.pair {
87 px.push(item.to_owned())
88 }
89 }
90 px
91 }
92}
93
94impl QAPerformance_Single {
95 pub fn new() -> Self {
96 let mut temp = HashMap::new();
97 temp.insert("BUY".to_string(), vec![]);
98 temp.insert("SELL".to_string(), vec![]);
99 QAPerformance_Single {
100 market_set: MarketPreset::new(),
101 pair: vec![],
102 temp,
103 }
104 }
105 pub fn insert_trade(&mut self, trade: Trade) {
106 match trade.offset.as_str() {
107 "OPEN" => {
108 let direction = trade.direction.as_str();
109 let u = self.temp.get_mut(direction).unwrap();
110 u.push(Temp {
111 amount: trade.volume.clone(),
112 direction: trade.direction.clone(),
113 offset: "OPEN".to_string(),
114 datetime: trade.trade_date_time.clone(),
115 code: trade.instrument_id.clone(),
116 price: trade.price.clone(),
117 trade_id: trade.trade_id.clone(),
118 });
119 }
120 "CLOSE" | "CLOSETODAY" => {
121 let (raw_direction, is_buy) = match trade.direction.as_str() {
122 "BUY" => ("SELL", false),
123 "SELL" => ("BUY", true),
124 _ => ("", false),
125 };
126 let u = self.temp.get_mut(raw_direction).unwrap();
127 let mut codeset = self.market_set.get(trade.instrument_id.as_ref());
130
131 let f = u.get_mut(0).unwrap();
132
133 if trade.volume > f.amount {
134 let hold_gap = (trade.trade_date_time.clone() - f.datetime.clone()) as f64/1000000000.0;
136 let mut pnl_money = codeset.unit_table as f64
137 * (trade.price.clone() - f.price.clone())
138 * f.amount.clone();
139 if !is_buy{
140 pnl_money = pnl_money * -1.0;
141 }
142 let pnl_ratio =
143 pnl_money / (f.price.clone() * f.amount.clone() * codeset.calc_coeff());
144 self.pair.push(QATradePair {
145 open_datetime: f.datetime.clone(),
146 close_datetime: trade.trade_date_time.clone(),
147 opendate: Utc.timestamp_nanos(f.datetime.clone()+ 28800000000000).to_string()[0..19].to_string(),
148 closedate: Utc.timestamp_nanos(trade.trade_date_time.clone()+ 28800000000000).to_string()[0..19].to_string(),
149 if_buyopen: is_buy,
150 code: f.code.clone(),
151 amount: f.amount.clone(),
152 openprice: f.price.clone(),
153 closeprice: trade.price.clone(),
154 open_trade_id: f.trade_id.clone(),
155 close_trade_id: trade.trade_id.clone(),
156 pnl_ratio,
157 pnl_money,
158 hold_gap
159 });
160 let mut new_t = trade.clone();
161
162 new_t.volume -= f.amount;
163 u.remove(0);
164 self.insert_trade(new_t)
165 } else if trade.volume < f.amount {
166 let hold_gap: f64 = (trade.trade_date_time.clone() - f.datetime.clone()) as f64/1000000000.0;
167 let mut pnl_money = codeset.unit_table as f64
168 * (trade.price.clone() - f.price.clone())
169 * trade.volume.clone();
170 if !is_buy{
171 pnl_money = pnl_money * -1.0;
172 }
173 let pnl_ratio =
174 pnl_money / (f.price.clone() * trade.volume.clone() * codeset.calc_coeff());
175 self.pair.push(QATradePair {
176 open_datetime: f.datetime.clone(),
177 close_datetime: trade.trade_date_time.clone(),
178 opendate: Utc.timestamp_nanos(f.datetime.clone()+ 28800000000000).to_string()[0..19].to_string(),
179 closedate: Utc.timestamp_nanos(trade.trade_date_time.clone()+ 28800000000000).to_string()[0..19].to_string(),
180 if_buyopen: is_buy,
181 code: f.code.clone(),
182 amount: trade.volume.clone(),
183 openprice: f.price.clone(),
184 closeprice: trade.price.clone(),
185 open_trade_id: f.trade_id.clone(),
186 close_trade_id: trade.trade_id.clone(),
187 pnl_ratio,
188 pnl_money,
189 hold_gap
190 });
191 f.amount -= trade.volume.clone();
192
193 } else {
195 let mut pnl_money = codeset.unit_table as f64
196 * (trade.price.clone() - f.price.clone())
197 * f.amount.clone();
198 if !is_buy{
199 pnl_money = pnl_money * -1.0;
200 }
201 let pnl_ratio =
202 pnl_money / (f.price.clone() * f.amount.clone() * codeset.calc_coeff());
203 let hold_gap:f64 = (trade.trade_date_time.clone() - f.datetime.clone()) as f64/1000000000.0;
204 self.pair.push(QATradePair {
205 open_datetime: f.datetime.clone(),
206 close_datetime: trade.trade_date_time.clone(),
207 opendate: Utc.timestamp_nanos(f.datetime.clone()+ 28800000000000).to_string()[0..19].to_string(),
208 closedate: Utc.timestamp_nanos(trade.trade_date_time.clone()+ 28800000000000).to_string()[0..19].to_string(),
209 if_buyopen: is_buy,
210 code: f.code.clone(),
211 amount: f.amount.clone(),
212 openprice: f.price.clone(),
213 closeprice: trade.price.clone(),
214 open_trade_id: f.trade_id.clone(),
215 close_trade_id: trade.trade_id.clone(),
216 pnl_ratio,
217 pnl_money,
218 hold_gap
219 });
220 u.remove(0);
221 }
222 }
223 _ => {}
224 }
225 }
226 pub fn get_totalprofit(&mut self) -> f64 {
227 let mut profit = 0.0;
228 let _: Vec<_> = self
229 .pair
230 .iter_mut()
231 .map(|a| profit += a.pnl_money)
232 .collect();
233 profit
234 }
235 pub fn get_maxprofit(&mut self) -> f64 {
243 let mut profit: Vec<f64> = vec![];
244 let _: Vec<_> = self
245 .pair
246 .iter_mut()
247 .map(|a| profit.push(a.pnl_money))
248 .collect();
249 profit.iter().cloned().fold(0. / 0., f64::max)
250 }
251 pub fn get_averageprofit(&mut self) -> f64 {
252 if self.pair.len() > 0 {
253 self.get_totalprofit() / self.pair.len() as f64
254 } else {
255 0.0
256 }
257 }
258 pub fn get_profitcount(&mut self) -> i32 {
259 let mut count = 0;
260 let _: Vec<_> = self
261 .pair
262 .iter_mut()
263 .map(|a| {
264 if a.pnl_money > 0.0 {
265 count += 1
266 }
267 })
268 .collect();
269 count
270 }
271 pub fn get_losscount(&mut self) -> i32 {
272 let mut count = 0;
273 let _: Vec<_> = self
274 .pair
275 .iter_mut()
276 .map(|a| {
277 if a.pnl_money < 0.0 {
278 count += 1
279 }
280 })
281 .collect();
282 count
283 }
284}
285
286#[cfg(test)]
287mod tests {
288 use crate::qaaccount::QA_Account;
289
290 use super::*;
291
292 #[test]
293 fn test_to_qifi() {
294 let code = "rb2005";
295 let mut p = QAPerformance_Single::new();
296 let mut acc = QA_Account::new("RustT01B2_RBL8", "test", "admin", 10000000.0, false, "real");
297 acc.init_h(code);
298 acc.sell_open(code, 10.0, "2020-01-20 09:30:22", 3500.0);
299 acc.buy_open(code, 10.0, "2020-01-20 09:52:00", 3500.0);
300 assert_eq!(acc.get_volume_short(code), 10.0);
301 assert_eq!(acc.get_volume_long(code), 10.0);
302 acc.buy_close(code, 10.0, "2020-01-20 10:22:00", 3600.0);
303 acc.buy_open(code, 10.0, "2020-01-20 13:54:00", 3500.0);
304 acc.buy_open(code, 10.0, "2020-01-20 13:55:00", 3510.0);
305
306 acc.sell_close(code, 20.0, "2020-01-20 14:52:00", 3620.0);
307 acc.buy_open(code, 20.0, "2020-01-21 13:54:00", 3500.0);
308 acc.sell_close(code, 15.0, "2020-01-21 13:55:00", 3510.0);
309
310 acc.sell_close(code, 5.0, "2020-01-21 14:52:00", 3420.0);
311 println!("{:#?}", acc.dailytrades);
312 for (_, i) in acc.dailytrades.iter_mut() {
313 println!("{:#?}", i);
314 p.insert_trade(i.to_owned());
315 }
316 println!("{:#?}", p.pair);
317 println!("{}", p.get_totalprofit())
318 }
319
320 #[test]
321 fn test_backtest() {
322 let code = "rb2005";
323 let mut p = QAPerformance_Single::new();
324 let mut acc = QA_Account::new(
325 "RustT01B2_RBL8",
326 "test",
327 "admin",
328 10000000.0,
329 false,
330 "backtest",
331 );
332 acc.init_h(code);
333 acc.sell_open(code, 10.0, "2020-01-20 09:30:22", 3500.0);
334 acc.buy_open(code, 10.0, "2020-01-20 09:52:00", 3500.0);
335 assert_eq!(acc.get_volume_short(code), 10.0);
336 assert_eq!(acc.get_volume_long(code), 10.0);
337 acc.buy_close(code, 10.0, "2020-01-20 10:22:00", 3600.0);
338 acc.buy_open(code, 10.0, "2020-01-20 13:54:00", 3500.0);
339 acc.buy_open(code, 10.0, "2020-01-20 13:55:00", 3510.0);
340
341 acc.sell_close(code, 20.0, "2020-01-20 14:52:00", 3620.0);
342 acc.buy_open(code, 20.0, "2020-01-21 13:54:00", 3500.0);
343 acc.sell_close(code, 15.0, "2020-01-21 13:55:00", 3510.0);
344
345 acc.sell_close(code, 5.0, "2020-01-21 14:52:00", 3420.0);
346
347 for i in acc.history.iter_mut() {
348 p.insert_trade(i.to_qifitrade());
349 }
350 println!("{:#?}", p.pair);
351 println!("{}", p.get_totalprofit())
352 }
353
354 #[test]
355 fn test_pair() {
356 let mut acc = QA_Account::new("test", "test", "admin", 1000000.0, false, "real");
357 let code = "Z$002352";
358 let mut p = QAPerformance::new();
359 acc.sell_open(code, 1000.0, "2020-04-03 09:30:22", 46.33);
360 acc.sell_open("RB2005", 10.0, "2020-04-03 09:30:22", 3346.33);
361 acc.buy_open(code, 1000.0, "2020-04-03 09:52:00", 46.86);
362
363 acc.buy_close(code, 1000.0, "2020-04-03 10:22:00", 47.34);
364 acc.sell_close(code, 1000.0, "2020-04-03 10:22:00", 47.34);
365 acc.buy_close("RB2005", 10.0, "2020-04-03 10:30:22", 3246.33);
366 acc.buy_open(code, 1000.0, "2020-04-03 13:54:00", 47.1);
367 acc.buy_open(code, 1000.0, "2020-04-03 13:55:00", 47.11);
368
369 acc.sell_close(code, 2000.0, "2020-04-03 14:52:00", 47.17);
370
371 for (_, i) in acc.dailytrades.iter_mut() {
377 println!("{:#?}", i);
378 let ux: Trade = i.to_owned();
379
380 println!("{:#?}", Utc.timestamp_nanos(ux.trade_date_time.clone() + 28800000000000).to_string()[0..19].to_string());
386 p.insert_trade(i.to_owned());
387 }
388 println!("{:#?}", p.pair());
389 }
392
393 #[test]
394 fn test_pairtoday() {
395 let mut acc = QA_Account::new("test", "test", "admin", 1000000.0, false, "real");
396 let code = "Z$002352";
397 let mut p = QAPerformance::new();
398 acc.sell_open(code, 1000.0, "2020-04-03 09:30:22", 46.33);
399 acc.sell_open("RB2005", 10.0, "2020-04-03 09:30:22", 3346.33);
400 acc.buy_open(code, 1000.0, "2020-04-03 09:52:00", 46.86);
401
402 acc.buy_closetoday(code, 1000.0, "2020-04-03 10:22:00", 47.34);
403 acc.sell_closetoday(code, 1000.0, "2020-04-03 10:22:00", 47.34);
404 acc.buy_closetoday("RB2005", 10.0, "2020-04-03 10:30:22", 3246.33);
405 acc.buy_open(code, 1000.0, "2020-04-03 13:54:00", 47.1);
406 acc.buy_open(code, 1000.0, "2020-04-03 13:55:00", 47.11);
407
408 acc.sell_closetoday(code, 2000.0, "2020-04-03 14:52:00", 47.17);
409
410 for (_, i) in acc.dailytrades.iter_mut() {
416 println!("{:#?}", i);
417 let ux: Trade = i.to_owned();
418
419 println!("{:#?}", Utc.timestamp_nanos(ux.trade_date_time.clone() + 28800000000000).to_string()[0..19].to_string());
425 p.insert_trade(i.to_owned());
426 }
427 println!("{:#?}", p.pair());
428 }
431}