quantaxis_rs/
qaperformance.rs

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/// performace is a simple way for analaysis single pair of every trades
11#[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                //println!("{:#?}", u);
128
129                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                    // close> raw ==> 注销继续loop
135                    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                    //u.insert(0, f.clone());
194                } 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    ///
236    /// 15%交易盈亏比:每天交易10次,,平均亏损,最大亏损
237    /// 参考:交易实时盈亏比:引入行情,重点在评估每次操盘手平均冒多大风险,赚多大利润
238    /// 15%胜率:多少次盈利,多少次亏损
239    /// 40%绝对收益能力:通过操盘手收益(元)/日初总金额(万)。
240    /// 30%资源周转能力:实际交易金额(元)/日初总金额(万)
241    /// 手续费贡献:差额手续费(元)/日出总金额(万)
242    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        // acc.buy_open(code, 2000.0, "2020-04-03 13:54:00", 47.1);
372        // acc.sell_close(code, 1000.0, "2020-04-03 13:55:00", 47.11);
373        //
374        // acc.sell_close(code, 1000.0, "2020-04-03 14:52:00", 47.17);
375
376        for (_, i) in acc.dailytrades.iter_mut() {
377            println!("{:#?}", i);
378            let ux: Trade = i.to_owned();
379
380            // insert_date_time: Utc
381            //     .datetime_from_str(time, "%Y-%m-%d %H:%M:%S")
382            //     .unwrap()
383            //     .timestamp_nanos()
384            //     - 28800000000000,
385            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        // println!("{:#?}", p.get_maxprofit());
390        // println!("{:#?}", p.get_averageprofit());
391    }
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        // acc.buy_open(code, 2000.0, "2020-04-03 13:54:00", 47.1);
411        // acc.sell_close(code, 1000.0, "2020-04-03 13:55:00", 47.11);
412        //
413        // acc.sell_close(code, 1000.0, "2020-04-03 14:52:00", 47.17);
414
415        for (_, i) in acc.dailytrades.iter_mut() {
416            println!("{:#?}", i);
417            let ux: Trade = i.to_owned();
418
419            // insert_date_time: Utc
420            //     .datetime_from_str(time, "%Y-%m-%d %H:%M:%S")
421            //     .unwrap()
422            //     .timestamp_nanos()
423            //     - 28800000000000,
424            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        // println!("{:#?}", p.get_maxprofit());
429        // println!("{:#?}", p.get_averageprofit());
430    }
431}