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 {
16            profits: Vec::new(),
17        }
18    }
19
20    pub fn add_test_result(&mut self, net_profit: f64) {
21        self.profits.push(net_profit);
22    }
23
24    pub fn calculate_score(&self) -> f64 {
25        if self.profits.is_empty() {
26            return 0.0;
27        }
28
29        let mut sorted = self.profits.clone();
30        // Sort descending
31        sorted.sort_by(|a, b| b.partial_cmp(a).unwrap_or(std::cmp::Ordering::Equal));
32
33        let max_profit = sorted[0];
34        if max_profit <= 0.0 {
35            return 0.0;
36        }
37
38        let midpoint = sorted.len() / 2;
39        sorted[midpoint] / max_profit
40    }
41}
42
43pub fn calculate_robustness(net_profits: &[f64]) -> f64 {
44    let mut evaluator = RobustnessEvaluator::new();
45    for &p in net_profits {
46        evaluator.add_test_result(p);
47    }
48    evaluator.calculate_score()
49}
50
51#[cfg(test)]
52mod tests {
53    use super::*;
54
55    #[test]
56    fn test_robustness_basic() {
57        let profits = vec![100.0, 90.0, 80.0, 70.0, 60.0];
58        // Midpoint index is 2 (80.0)
59        // Score = 80 / 100 = 0.8
60        assert_eq!(calculate_robustness(&profits), 0.8);
61    }
62
63    #[test]
64    fn test_robustness_empty() {
65        assert_eq!(calculate_robustness(&[]), 0.0);
66    }
67
68    #[test]
69    fn test_robustness_negative() {
70        let profits = vec![-10.0, -20.0];
71        assert_eq!(calculate_robustness(&profits), 0.0);
72    }
73}