nautilus_analysis/statistics/
expectancy.rs1use super::{loser_avg::AvgLoser, winner_avg::AvgWinner};
17use crate::statistic::PortfolioStatistic;
18
19#[repr(C)]
20#[derive(Debug)]
21#[cfg_attr(
22 feature = "python",
23 pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.analysis")
24)]
25pub struct Expectancy {}
26
27impl PortfolioStatistic for Expectancy {
28 type Item = f64;
29
30 fn name(&self) -> String {
31 stringify!(Expectancy).to_string()
32 }
33
34 fn calculate_from_realized_pnls(&self, realized_pnls: &[f64]) -> Option<Self::Item> {
35 if realized_pnls.is_empty() {
36 return Some(0.0);
37 }
38
39 let avg_winner = AvgWinner {}
40 .calculate_from_realized_pnls(realized_pnls)
41 .unwrap_or(0.0);
42 let avg_loser = AvgLoser {}
43 .calculate_from_realized_pnls(realized_pnls)
44 .unwrap_or(0.0);
45
46 let (winners, losers): (Vec<f64>, Vec<f64>) =
47 realized_pnls.iter().partition(|&&pnl| pnl > 0.0);
48
49 let total_trades = winners.len() + losers.len();
50 let win_rate = winners.len() as f64 / total_trades.max(1) as f64;
51 let loss_rate = 1.0 - win_rate;
52
53 Some(avg_winner.mul_add(win_rate, avg_loser * loss_rate))
54 }
55}
56
57#[cfg(test)]
58mod tests {
59 use super::*;
60
61 #[test]
62 fn test_empty_pnl_list() {
63 let expectancy = Expectancy {};
64 let result = expectancy.calculate_from_realized_pnls(&[]);
65 assert!(result.is_some());
66 assert_eq!(result.unwrap(), 0.0);
67 }
68
69 #[test]
70 fn test_all_winners() {
71 let expectancy = Expectancy {};
72 let pnls = vec![10.0, 20.0, 30.0];
73 let result = expectancy.calculate_from_realized_pnls(&pnls);
74
75 assert!(result.is_some());
76 assert_eq!(result.unwrap(), 20.0);
79 }
80
81 #[test]
82 fn test_all_losers() {
83 let expectancy = Expectancy {};
84 let pnls = vec![-10.0, -20.0, -30.0];
85 let result = expectancy.calculate_from_realized_pnls(&pnls);
86
87 assert!(result.is_some());
88 assert_eq!(result.unwrap(), -20.0);
91 }
92
93 #[test]
94 fn test_mixed_pnls() {
95 let expectancy = Expectancy {};
96 let pnls = vec![10.0, -5.0, 15.0, -10.0];
97 let result = expectancy.calculate_from_realized_pnls(&pnls);
98
99 assert!(result.is_some());
100 assert_eq!(result.unwrap(), 2.5);
107 }
108
109 #[test]
110 fn test_single_trade() {
111 let expectancy = Expectancy {};
112 let pnls = vec![10.0];
113 let result = expectancy.calculate_from_realized_pnls(&pnls);
114
115 assert!(result.is_some());
116 assert_eq!(result.unwrap(), 10.0);
119 }
120
121 #[test]
122 fn test_name() {
123 let expectancy = Expectancy {};
124 assert_eq!(expectancy.name(), "Expectancy");
125 }
126}