#![allow(unused)]
use rust_decimal::Decimal;
use std::collections::VecDeque;
use crate::types::OHLC;
use super::{MarketRegime, RegimeState, RegimeConfig};
use super::detector::{RegimeDetector, helpers};
use super::statistics::RollingStatistics;
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum VolatilityRegime {
Low,
Normal,
High,
Extreme,
}
impl VolatilityRegime {
pub fn to_market_regime(&self, trend_direction: MarketRegime) -> MarketRegime {
match self {
VolatilityRegime::Low | VolatilityRegime::Normal => trend_direction,
VolatilityRegime::High => {
if trend_direction == MarketRegime::Bear {
MarketRegime::Bear
} else {
MarketRegime::Sideways
}
}
VolatilityRegime::Extreme => MarketRegime::Bear, }
}
}
pub struct VolatilityRegimeDetector {
stats: RollingStatistics,
volatility_buffer: VecDeque<Decimal>,
current_state: Option<RegimeState>,
max_buffer_size: usize,
vol_percentiles: VolatilityPercentiles,
}
#[derive(Debug, Clone)]
struct VolatilityPercentiles {
low: Decimal, normal: Decimal, high: Decimal, extreme: Decimal, }
impl Default for VolatilityPercentiles {
fn default() -> Self {
Self {
low: Decimal::new(5, 3), normal: Decimal::new(1, 2), high: Decimal::new(2, 2), extreme: Decimal::new(4, 2), }
}
}
impl VolatilityRegimeDetector {
pub fn new(window_size: usize) -> Self {
Self {
stats: RollingStatistics::new(window_size),
volatility_buffer: VecDeque::with_capacity(window_size),
current_state: None,
max_buffer_size: window_size,
vol_percentiles: VolatilityPercentiles::default(),
}
}
fn classify_volatility(&self, volatility: Decimal) -> VolatilityRegime {
if volatility < self.vol_percentiles.low {
VolatilityRegime::Low
} else if volatility < self.vol_percentiles.normal {
VolatilityRegime::Normal
} else if volatility < self.vol_percentiles.high {
VolatilityRegime::High
} else {
VolatilityRegime::Extreme
}
}
fn update_percentiles(&mut self) {
if self.volatility_buffer.len() < 20 {
return;
}
let mut sorted: Vec<Decimal> = self.volatility_buffer.iter().copied().collect();
sorted.sort();
let len = sorted.len();
self.vol_percentiles.low = sorted[len * 25 / 100];
self.vol_percentiles.normal = sorted[len * 50 / 100];
self.vol_percentiles.high = sorted[len * 75 / 100];
self.vol_percentiles.extreme = sorted[len * 90 / 100];
}
fn detect_regime(&self, data: &[OHLC]) -> (MarketRegime, Decimal) {
if data.len() < 2 {
return (MarketRegime::Sideways, Decimal::new(5, 1));
}
let current_vol = self.stats.std_dev();
let vol_regime = self.classify_volatility(current_vol);
let trend = helpers::identify_trend(data, 5, 20);
let momentum = self.stats.momentum();
let sharpe = self.stats.sharpe_ratio(252);
let mut confidence = Decimal::new(5, 1);
match vol_regime {
VolatilityRegime::Low => confidence += Decimal::new(2, 1), VolatilityRegime::Normal => confidence += Decimal::new(1, 1), VolatilityRegime::High => confidence -= Decimal::new(1, 1), VolatilityRegime::Extreme => confidence -= Decimal::new(2, 1), }
if sharpe > Decimal::ONE {
confidence += Decimal::new(15, 2); } else if sharpe < -Decimal::ONE {
confidence += Decimal::new(15, 2); }
let regime = if vol_regime == VolatilityRegime::Extreme {
MarketRegime::Bear
} else if momentum > Decimal::new(5, 2) && vol_regime != VolatilityRegime::High {
MarketRegime::Bull
} else if momentum < Decimal::new(-5, 2) && vol_regime != VolatilityRegime::High {
MarketRegime::Bear
} else {
vol_regime.to_market_regime(trend)
};
confidence = confidence.max(Decimal::ZERO).min(Decimal::ONE);
(regime, confidence)
}
fn analyze_clustering(&self) -> Decimal {
if self.volatility_buffer.len() < 10 {
return Decimal::ZERO;
}
let mut persistence_count = 0;
let current_vol_regime = self.classify_volatility(*self.volatility_buffer.back().unwrap());
for vol in self.volatility_buffer.iter().rev().take(10) {
if self.classify_volatility(*vol) == current_vol_regime {
persistence_count += 1;
} else {
break;
}
}
Decimal::from(persistence_count) / Decimal::from(10)
}
}
impl RegimeDetector for VolatilityRegimeDetector {
fn detect(&mut self, data: &[OHLC], config: &RegimeConfig) -> Option<RegimeState> {
if !self.has_sufficient_data(data.len(), config) {
return None;
}
self.stats = RollingStatistics::new(self.max_buffer_size);
self.volatility_buffer.clear();
for candle in data {
self.stats.update_with_candle(candle);
if self.stats.is_ready() {
let vol = self.stats.std_dev();
self.volatility_buffer.push_back(vol);
if self.volatility_buffer.len() > self.max_buffer_size {
self.volatility_buffer.pop_front();
}
}
}
if self.volatility_buffer.len() < 2 {
return None;
}
if self.volatility_buffer.len() >= 20 {
self.update_percentiles();
}
let (regime, mut confidence) = self.detect_regime(data);
let clustering = self.analyze_clustering();
confidence = (confidence + clustering * Decimal::new(2, 1)) / Decimal::new(12, 1);
confidence = confidence.min(Decimal::ONE);
if confidence >= config.min_confidence {
let last_candle = data.last()?;
let state = RegimeState::new(regime, confidence, last_candle.timestamp, last_candle.close);
self.current_state = Some(state.clone());
Some(state)
} else {
self.current_state.clone()
}
}
fn update(&mut self, candle: &OHLC, config: &RegimeConfig) -> Option<RegimeState> {
self.stats.update_with_candle(candle);
let vol = self.stats.std_dev();
self.volatility_buffer.push_back(vol);
if self.volatility_buffer.len() > self.max_buffer_size {
self.volatility_buffer.pop_front();
}
if !self.stats.is_ready() {
return None;
}
if self.volatility_buffer.len() >= 20 && self.volatility_buffer.len() % 10 == 0 {
self.update_percentiles();
}
let recent_data = vec![candle.clone()];
let (regime, mut confidence) = self.detect_regime(&recent_data);
let clustering = self.analyze_clustering();
confidence = (confidence + clustering * Decimal::new(1, 1)) / Decimal::new(11, 1);
if let Some(ref mut state) = self.current_state {
if state.should_transition(regime, confidence) {
state.transition(regime, confidence, candle.timestamp, candle.close);
} else if state.current_regime == regime {
state.increment_duration();
state.confidence = (state.confidence + confidence) / Decimal::TWO;
}
} else if confidence >= config.min_confidence {
self.current_state = Some(RegimeState::new(regime, confidence, candle.timestamp, candle.close));
}
self.current_state.clone()
}
fn calculate_confidence(&self, regime: MarketRegime, data: &[OHLC]) -> Decimal {
if data.is_empty() || !self.stats.is_ready() {
return Decimal::ZERO;
}
let (detected_regime, confidence) = self.detect_regime(data);
if detected_regime == regime {
confidence
} else {
Decimal::ONE - confidence
}
}
fn reset(&mut self) {
self.stats.reset();
self.volatility_buffer.clear();
self.current_state = None;
self.vol_percentiles = VolatilityPercentiles::default();
}
fn name(&self) -> &str {
"VolatilityRegimeDetector"
}
}
#[cfg(test)]
mod tests {
use super::*;
use rust_decimal::prelude::*;
fn create_volatile_data(periods: usize, volatility_factor: i64) -> Vec<OHLC> {
let mut data = Vec::new();
let mut price = Decimal::from(100);
let mut rng = 42u64;
for i in 0..periods {
rng = (rng * 1664525 + 1013904223) % (1 << 32);
let random = ((rng % 200) as i64 - 100) as i64;
let change = Decimal::from(random * volatility_factor) / Decimal::from(1000);
price = price + change;
let high = price + Decimal::from(volatility_factor.abs()) / Decimal::from(100);
let low = price - Decimal::from(volatility_factor.abs()) / Decimal::from(100);
data.push(OHLC::new(price, high, low, price, 1000, 1000000 + i as i64));
}
data
}
#[test]
fn test_volatility_detector_low_vol() {
let mut detector = VolatilityRegimeDetector::new(20);
let config = RegimeConfig::default();
let data = create_volatile_data(30, 1);
let _ = detector.detect(&data, &config);
}
#[test]
fn test_volatility_detector_high_vol() {
let mut detector = VolatilityRegimeDetector::new(20);
let config = RegimeConfig::default();
let data = create_volatile_data(30, 10);
let _ = detector.detect(&data, &config);
}
#[test]
fn test_volatility_regime_classification() {
let detector = VolatilityRegimeDetector::new(20);
let low_vol = Decimal::new(3, 3); let _ = detector.classify_volatility(low_vol);
let normal_vol = Decimal::new(8, 3); let _ = detector.classify_volatility(normal_vol);
let high_vol = Decimal::new(25, 3); let _ = detector.classify_volatility(high_vol);
let extreme_vol = Decimal::new(5, 2); let _ = detector.classify_volatility(extreme_vol);
}
#[test]
fn test_volatility_detector_update() {
let mut detector = VolatilityRegimeDetector::new(20);
let config = RegimeConfig::default();
let data = create_volatile_data(30, 5);
for candle in data.iter() {
let _ = detector.update(candle, &config);
}
assert!(!detector.volatility_buffer.is_empty());
}
#[test]
fn test_volatility_detector_reset() {
let mut detector = VolatilityRegimeDetector::new(20);
let config = RegimeConfig::default();
let data = create_volatile_data(30, 5);
let _ = detector.detect(&data, &config);
assert!(!detector.volatility_buffer.is_empty());
detector.reset();
assert!(detector.current_state.is_none());
assert!(detector.volatility_buffer.is_empty());
}
#[test]
fn test_volatility_clustering_analysis() {
let mut detector = VolatilityRegimeDetector::new(20);
for _ in 0..15 {
detector.volatility_buffer.push_back(Decimal::new(1, 2)); }
let clustering = detector.analyze_clustering();
assert!(clustering > Decimal::new(8, 1)); }
#[test]
fn test_volatility_to_market_regime() {
assert_eq!(
VolatilityRegime::Low.to_market_regime(MarketRegime::Bull),
MarketRegime::Bull
);
assert_eq!(
VolatilityRegime::Extreme.to_market_regime(MarketRegime::Bull),
MarketRegime::Bear
);
assert_eq!(
VolatilityRegime::High.to_market_regime(MarketRegime::Bull),
MarketRegime::Sideways
);
}
}