quantwave_core/features/
hurst.rs1use crate::indicators::hurst::HurstExponent;
14use crate::traits::Next;
15
16#[derive(Debug, Clone, Copy, PartialEq)]
18pub struct HurstFeatures {
19 pub persistence: f64,
20 pub regime_label: Option<i8>,
23}
24
25impl HurstFeatures {
26 pub fn new(persistence: f64, regime_label: Option<i8>) -> Self {
27 Self {
28 persistence,
29 regime_label,
30 }
31 }
32}
33
34#[derive(Debug, Clone)]
36pub struct HurstFeatureExtractor {
37 inner: HurstExponent,
38 mean_reverting_threshold: f64,
40 trending_threshold: f64,
41}
42
43impl HurstFeatureExtractor {
44 pub fn new(period: usize) -> Self {
45 Self {
46 inner: HurstExponent::new(period),
47 mean_reverting_threshold: 0.45,
48 trending_threshold: 0.55,
49 }
50 }
51
52 pub fn with_thresholds(mut self, mean_rev: f64, trending: f64) -> Self {
53 self.mean_reverting_threshold = mean_rev;
54 self.trending_threshold = trending;
55 self
56 }
57}
58
59impl Next<f64> for HurstFeatureExtractor {
60 type Output = HurstFeatures;
61
62 fn next(&mut self, input: f64) -> Self::Output {
63 let persistence = self.inner.next(input);
64
65 let regime_label = if persistence.is_nan() {
66 None
67 } else if persistence < self.mean_reverting_threshold {
68 Some(-1)
69 } else if persistence > self.trending_threshold {
70 Some(1)
71 } else {
72 Some(0)
73 };
74
75 HurstFeatures::new(persistence, regime_label)
76 }
77}
78
79#[cfg(test)]
80mod tests {
81 use super::*;
82 use approx::assert_relative_eq;
83
84 #[test]
85 fn test_hurst_feature_basic() {
86 let mut extractor = HurstFeatureExtractor::new(20);
87
88 for i in 0..30 {
90 let val = 100.0 + (i as f64) * 0.5;
91 let f = extractor.next(val);
92 if !f.persistence.is_nan() {
93 assert!(
94 f.persistence > 0.5,
95 "Expected trending persistence, got {}",
96 f.persistence
97 );
98 }
99 }
100 }
101
102 #[test]
103 fn test_hurst_feature_regime_labels() {
104 let mut extractor = HurstFeatureExtractor::new(10).with_thresholds(0.4, 0.6);
105
106 let f = extractor.next(100.0);
109 if !f.persistence.is_nan() {
111 assert!(f.regime_label.is_some());
112 }
113 }
114}