Skip to main content

quantwave_core/regimes/
tar.rs

1//! Threshold Autoregressive (TAR / SETAR) Models
2//!
3//! Source: Tong (1983) "Non-Linear Time Series: A Dynamical System Approach"
4//!
5//! TAR models allow regime shifts to be triggered by an observable signal
6//! (e.g., volatility or price relative to a threshold).
7
8use crate::regimes::MarketRegime;
9use crate::traits::Next;
10use serde::{Deserialize, Serialize};
11
12/// A Threshold Autoregressive model with multiple thresholds.
13#[derive(Debug, Clone, Serialize, Deserialize)]
14pub struct TAR {
15    /// Sorted thresholds [t1, t2, ..., tn]
16    pub thresholds: Vec<f64>,
17}
18
19impl TAR {
20    /// Creates a new TAR model with a single threshold.
21    pub fn new(threshold: f64) -> Self {
22        Self {
23            thresholds: vec![threshold],
24        }
25    }
26
27    /// Creates a multi-threshold TAR model (SETAR).
28    pub fn multi(thresholds: Vec<f64>) -> Self {
29        let mut t = thresholds;
30        t.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
31        Self { thresholds: t }
32    }
33}
34
35impl Next<f64> for TAR {
36    type Output = MarketRegime;
37
38    fn next(&mut self, signal: f64) -> Self::Output {
39        // Find which threshold bin the signal falls into
40        match self
41            .thresholds
42            .binary_search_by(|t| t.partial_cmp(&signal).unwrap_or(std::cmp::Ordering::Equal))
43        {
44            Ok(idx) => MarketRegime::Cluster(idx as u8 + 1), // Exact match
45            Err(idx) => {
46                if idx == 0 {
47                    MarketRegime::Steady // Below first threshold
48                } else if idx >= self.thresholds.len() {
49                    MarketRegime::Crisis // Above last threshold
50                } else {
51                    MarketRegime::Cluster(idx as u8)
52                }
53            }
54        }
55    }
56}