Skip to main content

quantwave_core/indicators/
robustness.rs

1/// Robustness Score
2///
3/// Based on John Ehlers' "A Procedure to Evaluate Trading Strategy Robustness".
4/// Robustness is determined by the slope of the net profit as a function of the number of tests run.
5/// Score = (NetProfit at midpoint of ranked tests) / (Maximum NetProfit).
6/// A score >= 0.75 (75%) indicates a reasonable expectation that out-of-sample performance
7/// will be at least 75% of optimized in-sample net profit.
8#[derive(Debug, Clone, Default)]
9pub struct RobustnessEvaluator {
10    profits: Vec<f64>,
11}
12
13impl RobustnessEvaluator {
14    pub fn new() -> Self {
15        Self { profits: Vec::new() }
16    }
17
18    pub fn add_test_result(&mut self, net_profit: f64) {
19        self.profits.push(net_profit);
20    }
21
22    pub fn calculate_score(&self) -> f64 {
23        if self.profits.is_empty() {
24            return 0.0;
25        }
26
27        let mut sorted = self.profits.clone();
28        // Sort descending
29        sorted.sort_by(|a, b| b.partial_cmp(a).unwrap_or(std::cmp::Ordering::Equal));
30
31        let max_profit = sorted[0];
32        if max_profit <= 0.0 {
33            return 0.0;
34        }
35
36        let midpoint = sorted.len() / 2;
37        sorted[midpoint] / max_profit
38    }
39}
40
41pub fn calculate_robustness(net_profits: &[f64]) -> f64 {
42    let mut evaluator = RobustnessEvaluator::new();
43    for &p in net_profits {
44        evaluator.add_test_result(p);
45    }
46    evaluator.calculate_score()
47}
48
49#[cfg(test)]
50mod tests {
51    use super::*;
52
53    #[test]
54    fn test_robustness_basic() {
55        let profits = vec![100.0, 90.0, 80.0, 70.0, 60.0];
56        // Midpoint index is 2 (80.0)
57        // Score = 80 / 100 = 0.8
58        assert_eq!(calculate_robustness(&profits), 0.8);
59    }
60
61    #[test]
62    fn test_robustness_empty() {
63        assert_eq!(calculate_robustness(&[]), 0.0);
64    }
65
66    #[test]
67    fn test_robustness_negative() {
68        let profits = vec![-10.0, -20.0];
69        assert_eq!(calculate_robustness(&profits), 0.0);
70    }
71}