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 = "posei_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 rstest::rstest;
60
61 use super::*;
62
63 #[rstest]
64 fn test_empty_pnl_list() {
65 let expectancy = Expectancy {};
66 let result = expectancy.calculate_from_realized_pnls(&[]);
67 assert!(result.is_some());
68 assert_eq!(result.unwrap(), 0.0);
69 }
70
71 #[rstest]
72 fn test_all_winners() {
73 let expectancy = Expectancy {};
74 let pnls = vec![10.0, 20.0, 30.0];
75 let result = expectancy.calculate_from_realized_pnls(&pnls);
76
77 assert!(result.is_some());
78 assert_eq!(result.unwrap(), 20.0);
81 }
82
83 #[rstest]
84 fn test_all_losers() {
85 let expectancy = Expectancy {};
86 let pnls = vec![-10.0, -20.0, -30.0];
87 let result = expectancy.calculate_from_realized_pnls(&pnls);
88
89 assert!(result.is_some());
90 assert_eq!(result.unwrap(), -20.0);
93 }
94
95 #[rstest]
96 fn test_mixed_pnls() {
97 let expectancy = Expectancy {};
98 let pnls = vec![10.0, -5.0, 15.0, -10.0];
99 let result = expectancy.calculate_from_realized_pnls(&pnls);
100
101 assert!(result.is_some());
102 assert_eq!(result.unwrap(), 2.5);
109 }
110
111 #[rstest]
112 fn test_single_trade() {
113 let expectancy = Expectancy {};
114 let pnls = vec![10.0];
115 let result = expectancy.calculate_from_realized_pnls(&pnls);
116
117 assert!(result.is_some());
118 assert_eq!(result.unwrap(), 10.0);
121 }
122
123 #[rstest]
124 fn test_name() {
125 let expectancy = Expectancy {};
126 assert_eq!(expectancy.name(), "Expectancy");
127 }
128}