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