use crate::bar_indicators::indicator_value::IndicatorValue;
#[derive(Clone)]
pub struct Garch {
p: usize, q: usize,
returns: Vec<f64>, residuals: Vec<f64>, conditional_variance: Vec<f64>,
omega: f64, alpha_coefficients: Vec<f64>, beta_coefficients: Vec<f64>,
mu: f64, phi: f64,
current_variance: f64,
current_volatility: f64,
forecast_variance: f64,
log_likelihood: f64,
aic: f64,
bic: f64,
is_fitted: bool,
min_observations: usize,
}
impl Garch {
pub fn new(p: usize, q: usize) -> Self {
let min_obs = (p + q + 10).max(50);
Self {
p: p.min(8), q: q.min(8),
returns: Vec::with_capacity(512),
residuals: Vec::with_capacity(512),
conditional_variance: Vec::with_capacity(512),
omega: 0.01,
alpha_coefficients: Vec::with_capacity(16),
beta_coefficients: Vec::with_capacity(16),
mu: 0.0,
phi: 0.0,
current_variance: 0.01,
current_volatility: 0.1,
forecast_variance: 0.01,
log_likelihood: f64::NEG_INFINITY,
aic: f64::INFINITY,
bic: f64::INFINITY,
is_fitted: false,
min_observations: min_obs,
}
}
pub fn update(&mut self, price: f64) -> f64 {
if !self.returns.is_empty() {
let prev_price = if self.returns.is_empty() { price } else {
price / (1.0 + self.returns[self.returns.len() - 1])
};
let return_rate = (price / prev_price).ln();
if self.returns.len() >= 512 {
self.returns.remove(0);
}
self.returns.push(return_rate);
} else {
self.returns.push(0.0);
}
if self.returns.len() >= self.min_observations {
self.fit_model();
self.update_variance();
}
self.current_volatility
}
fn fit_model(&mut self) {
self.estimate_mean_model();
self.calculate_residuals();
self.estimate_garch_parameters();
self.calculate_conditional_variance();
self.calculate_likelihood_criteria();
self.is_fitted = true;
}
fn estimate_mean_model(&mut self) {
if self.returns.len() < 2 {
return;
}
let n = self.returns.len();
let mut sum_r = 0.0;
let mut sum_r_lag = 0.0;
let mut sum_r_r_lag = 0.0;
let mut sum_r_lag_sq = 0.0;
for i in 1..n {
let r_t = self.returns[i];
let r_t_lag = self.returns[i - 1];
sum_r += r_t;
sum_r_lag += r_t_lag;
sum_r_r_lag += r_t * r_t_lag;
sum_r_lag_sq += r_t_lag * r_t_lag;
}
let n_pairs = (n - 1) as f64;
let mean_r = sum_r / n_pairs;
let mean_r_lag = sum_r_lag / n_pairs;
let denominator = sum_r_lag_sq - n_pairs * mean_r_lag * mean_r_lag;
if denominator.abs() > 1e-10 {
self.phi = (sum_r_r_lag - n_pairs * mean_r * mean_r_lag) / denominator;
self.mu = mean_r - self.phi * mean_r_lag;
} else {
self.phi = 0.0;
self.mu = mean_r;
}
self.phi = self.phi.clamp(-0.99, 0.99);
}
fn calculate_residuals(&mut self) {
self.residuals.clear();
if self.returns.len() < 2 {
return;
}
self.residuals.push(self.returns[0] - self.mu);
for i in 1..self.returns.len() {
let expected_return = self.mu + self.phi * self.returns[i - 1];
let residual = self.returns[i] - expected_return;
self.residuals.push(residual);
}
}
fn estimate_garch_parameters(&mut self) {
if self.residuals.len() < self.p.max(self.q) + 5 {
return;
}
self.alpha_coefficients.clear();
self.beta_coefficients.clear();
let unconditional_var: f64 = self.residuals.iter()
.map(|&r| r * r)
.sum::<f64>() / self.residuals.len() as f64;
self.omega = unconditional_var * 0.1;
let total_arch_weight = 0.3;
for i in 0..self.p {
let weight = total_arch_weight * (0.8_f64).powi(i as i32);
self.alpha_coefficients.push(weight);
}
let total_garch_weight = 0.6;
for i in 0..self.q {
let weight = total_garch_weight * (0.9_f64).powi(i as i32);
self.beta_coefficients.push(weight);
}
let total_persistence: f64 = self.alpha_coefficients.iter().sum::<f64>() +
self.beta_coefficients.iter().sum::<f64>();
if total_persistence >= 0.99 {
let scale_factor = 0.95 / total_persistence;
for coeff in &mut self.alpha_coefficients {
*coeff *= scale_factor;
}
for coeff in &mut self.beta_coefficients {
*coeff *= scale_factor;
}
}
}
fn calculate_conditional_variance(&mut self) {
self.conditional_variance.clear();
if self.residuals.is_empty() {
return;
}
let unconditional_var: f64 = self.residuals.iter()
.map(|&r| r * r)
.sum::<f64>() / self.residuals.len() as f64;
let start_idx = self.p.max(self.q);
for _ in 0..start_idx {
self.conditional_variance.push(unconditional_var);
}
for t in start_idx..self.residuals.len() {
let mut variance = self.omega;
for (i, &alpha) in self.alpha_coefficients.iter().enumerate() {
if t > i {
let residual_sq = self.residuals[t - 1 - i].powi(2);
variance += alpha * residual_sq;
}
}
for (j, &beta) in self.beta_coefficients.iter().enumerate() {
if self.conditional_variance.len() > j {
let var_idx = self.conditional_variance.len() - 1 - j;
variance += beta * self.conditional_variance[var_idx];
}
}
variance = variance.max(1e-8); self.conditional_variance.push(variance);
}
if !self.conditional_variance.is_empty() {
self.current_variance = self.conditional_variance[self.conditional_variance.len() - 1];
self.current_volatility = self.current_variance.sqrt();
}
}
fn calculate_likelihood_criteria(&mut self) {
if self.conditional_variance.is_empty() || self.residuals.is_empty() {
return;
}
let mut log_likelihood = 0.0;
let start_idx = self.conditional_variance.len().saturating_sub(self.residuals.len());
for (i, &variance) in self.conditional_variance.iter().enumerate().skip(start_idx) {
if i < self.residuals.len() {
let residual = self.residuals[i];
let ll_term = -0.5 * (variance.ln() + (residual * residual) / variance + (2.0 * std::f64::consts::PI).ln());
log_likelihood += ll_term;
}
}
self.log_likelihood = log_likelihood;
let n = self.conditional_variance.len() as f64;
let k = (1 + self.p + self.q + 2) as f64;
self.aic = -2.0 * log_likelihood + 2.0 * k;
self.bic = -2.0 * log_likelihood + k * n.ln();
}
fn update_variance(&mut self) {
if !self.is_fitted || self.residuals.is_empty() {
return;
}
self.forecast_variance = self.omega;
for (i, &alpha) in self.alpha_coefficients.iter().enumerate() {
if self.residuals.len() > i {
let idx = self.residuals.len() - 1 - i;
self.forecast_variance += alpha * self.residuals[idx].powi(2);
}
}
for (j, &beta) in self.beta_coefficients.iter().enumerate() {
if self.conditional_variance.len() > j {
let idx = self.conditional_variance.len() - 1 - j;
self.forecast_variance += beta * self.conditional_variance[idx];
}
}
self.forecast_variance = self.forecast_variance.max(1e-8);
}
pub fn volatility(&self) -> f64 {
self.current_volatility
}
pub fn forecast_volatility(&self) -> f64 {
self.forecast_variance.sqrt()
}
pub fn variance(&self) -> f64 {
self.current_variance
}
pub fn get_parameters(&self) -> (f64, &[f64], &[f64]) {
(self.omega, &self.alpha_coefficients, &self.beta_coefficients)
}
pub fn get_metrics(&self) -> (f64, f64, f64) {
(self.log_likelihood, self.aic, self.bic)
}
pub fn is_fitted(&self) -> bool {
self.is_fitted
}
pub fn reset(&mut self) {
self.returns.clear();
self.residuals.clear();
self.conditional_variance.clear();
self.alpha_coefficients.clear();
self.beta_coefficients.clear();
self.omega = 0.01;
self.mu = 0.0;
self.phi = 0.0;
self.current_variance = 0.01;
self.current_volatility = 0.1;
self.forecast_variance = 0.01;
self.log_likelihood = f64::NEG_INFINITY;
self.aic = f64::INFINITY;
self.bic = f64::INFINITY;
self.is_fitted = false;
}
#[inline]
pub fn is_ready(&self) -> bool {
self.is_fitted && self.returns.len() >= self.min_observations
}
pub fn value(&self) -> IndicatorValue {
IndicatorValue::Single(self.current_volatility)
}
}
#[derive(Clone)]
pub struct EGarch {
p: usize, q: usize,
returns: Vec<f64>,
residuals: Vec<f64>,
log_conditional_variance: Vec<f64>, standardized_residuals: Vec<f64>,
omega: f64, alpha_coefficients: Vec<f64>, gamma_coefficients: Vec<f64>, beta_coefficients: Vec<f64>,
mu: f64,
phi: f64,
current_log_variance: f64,
current_variance: f64,
current_volatility: f64,
log_likelihood: f64,
aic: f64,
bic: f64,
is_fitted: bool,
min_observations: usize,
}
impl EGarch {
pub fn new(p: usize, q: usize) -> Self {
let min_obs = (p + q + 10).max(50);
Self {
p: p.min(8),
q: q.min(8),
returns: Vec::with_capacity(512),
residuals: Vec::with_capacity(512),
log_conditional_variance: Vec::with_capacity(512),
standardized_residuals: Vec::with_capacity(512),
omega: -1.0,
alpha_coefficients: Vec::with_capacity(16),
gamma_coefficients: Vec::with_capacity(16),
beta_coefficients: Vec::with_capacity(16),
mu: 0.0,
phi: 0.0,
current_log_variance: -2.3, current_variance: 0.1,
current_volatility: 0.316, log_likelihood: f64::NEG_INFINITY,
aic: f64::INFINITY,
bic: f64::INFINITY,
is_fitted: false,
min_observations: min_obs,
}
}
pub fn update(&mut self, price: f64) -> f64 {
if !self.returns.is_empty() {
let prev_price = if self.returns.is_empty() { price } else {
price / (1.0 + self.returns[self.returns.len() - 1])
};
let return_rate = (price / prev_price).ln();
if self.returns.len() >= 512 {
self.returns.remove(0);
}
self.returns.push(return_rate);
} else {
self.returns.push(0.0);
}
if self.returns.len() >= self.min_observations {
self.fit_model();
}
self.current_volatility
}
fn fit_model(&mut self) {
self.estimate_mean_model();
self.calculate_residuals();
self.estimate_egarch_parameters();
self.calculate_log_conditional_variance();
self.calculate_likelihood_criteria();
self.is_fitted = true;
}
fn estimate_mean_model(&mut self) {
if self.returns.len() < 2 {
return;
}
let n = self.returns.len();
let mut sum_r = 0.0;
let mut sum_r_lag = 0.0;
let mut sum_r_r_lag = 0.0;
let mut sum_r_lag_sq = 0.0;
for i in 1..n {
let r_t = self.returns[i];
let r_t_lag = self.returns[i - 1];
sum_r += r_t;
sum_r_lag += r_t_lag;
sum_r_r_lag += r_t * r_t_lag;
sum_r_lag_sq += r_t_lag * r_t_lag;
}
let n_pairs = (n - 1) as f64;
let mean_r = sum_r / n_pairs;
let mean_r_lag = sum_r_lag / n_pairs;
let denominator = sum_r_lag_sq - n_pairs * mean_r_lag * mean_r_lag;
if denominator.abs() > 1e-10 {
self.phi = (sum_r_r_lag - n_pairs * mean_r * mean_r_lag) / denominator;
self.mu = mean_r - self.phi * mean_r_lag;
} else {
self.phi = 0.0;
self.mu = mean_r;
}
self.phi = self.phi.clamp(-0.99, 0.99);
}
fn calculate_residuals(&mut self) {
self.residuals.clear();
if self.returns.len() < 2 {
return;
}
self.residuals.push(self.returns[0] - self.mu);
for i in 1..self.returns.len() {
let expected_return = self.mu + self.phi * self.returns[i - 1];
let residual = self.returns[i] - expected_return;
self.residuals.push(residual);
}
}
fn estimate_egarch_parameters(&mut self) {
self.alpha_coefficients.clear();
self.gamma_coefficients.clear();
self.beta_coefficients.clear();
self.omega = -0.5;
for i in 0..self.p {
let coeff = 0.2 * (0.8_f64).powi(i as i32);
self.alpha_coefficients.push(coeff);
}
for i in 0..self.p {
let coeff = -0.1 * (0.9_f64).powi(i as i32); self.gamma_coefficients.push(coeff);
}
for i in 0..self.q {
let coeff = 0.7 * (0.95_f64).powi(i as i32);
self.beta_coefficients.push(coeff);
}
}
fn calculate_log_conditional_variance(&mut self) {
self.log_conditional_variance.clear();
self.standardized_residuals.clear();
if self.residuals.is_empty() {
return;
}
let initial_log_var = -2.3; let start_idx = self.p.max(self.q);
for _ in 0..start_idx {
self.log_conditional_variance.push(initial_log_var);
}
for i in 0..start_idx.min(self.residuals.len()) {
let std_residual = self.residuals[i] / initial_log_var.exp().sqrt();
self.standardized_residuals.push(std_residual);
}
for t in start_idx..self.residuals.len() {
let mut log_variance = self.omega;
for (i, (&alpha, &gamma)) in self.alpha_coefficients.iter()
.zip(self.gamma_coefficients.iter()).enumerate() {
if self.standardized_residuals.len() > i {
let z_idx = self.standardized_residuals.len() - 1 - i;
let z = self.standardized_residuals[z_idx];
let g_z = alpha * z.abs() + gamma * z;
log_variance += g_z;
}
}
for (j, &beta) in self.beta_coefficients.iter().enumerate() {
if self.log_conditional_variance.len() > j {
let var_idx = self.log_conditional_variance.len() - 1 - j;
log_variance += beta * self.log_conditional_variance[var_idx];
}
}
self.log_conditional_variance.push(log_variance);
let variance = log_variance.exp();
let std_residual = self.residuals[t] / variance.sqrt();
self.standardized_residuals.push(std_residual);
}
if !self.log_conditional_variance.is_empty() {
self.current_log_variance = self.log_conditional_variance[self.log_conditional_variance.len() - 1];
self.current_variance = self.current_log_variance.exp();
self.current_volatility = self.current_variance.sqrt();
}
}
fn calculate_likelihood_criteria(&mut self) {
if self.log_conditional_variance.is_empty() || self.residuals.is_empty() {
return;
}
let mut log_likelihood = 0.0;
let start_idx = self.log_conditional_variance.len().saturating_sub(self.residuals.len());
for (i, &log_variance) in self.log_conditional_variance.iter().enumerate().skip(start_idx) {
if i < self.residuals.len() {
let residual = self.residuals[i];
let variance = log_variance.exp();
let ll_term = -0.5 * (log_variance + (residual * residual) / variance + (2.0 * std::f64::consts::PI).ln());
log_likelihood += ll_term;
}
}
self.log_likelihood = log_likelihood;
let n = self.log_conditional_variance.len() as f64;
let k = (1 + self.p * 2 + self.q + 2) as f64;
self.aic = -2.0 * log_likelihood + 2.0 * k;
self.bic = -2.0 * log_likelihood + k * n.ln();
}
pub fn volatility(&self) -> f64 {
self.current_volatility
}
pub fn variance(&self) -> f64 {
self.current_variance
}
pub fn get_parameters(&self) -> (f64, &[f64], &[f64], &[f64]) {
(self.omega, &self.alpha_coefficients, &self.gamma_coefficients, &self.beta_coefficients)
}
pub fn get_metrics(&self) -> (f64, f64, f64) {
(self.log_likelihood, self.aic, self.bic)
}
pub fn is_fitted(&self) -> bool {
self.is_fitted
}
pub fn reset(&mut self) {
self.returns.clear();
self.residuals.clear();
self.log_conditional_variance.clear();
self.standardized_residuals.clear();
self.alpha_coefficients.clear();
self.gamma_coefficients.clear();
self.beta_coefficients.clear();
self.omega = -1.0;
self.mu = 0.0;
self.phi = 0.0;
self.current_log_variance = -2.3;
self.current_variance = 0.1;
self.current_volatility = 0.316;
self.log_likelihood = f64::NEG_INFINITY;
self.aic = f64::INFINITY;
self.bic = f64::INFINITY;
self.is_fitted = false;
}
#[inline]
pub fn is_ready(&self) -> bool {
self.is_fitted && self.returns.len() >= self.min_observations
}
pub fn value(&self) -> IndicatorValue {
IndicatorValue::Single(self.current_volatility)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_garch_creation() {
let ind = Garch::new(1, 1);
assert!(!ind.is_ready());
assert!(ind.volatility() > 0.0);
}
#[test]
fn test_garch_warmup() {
let mut ind = Garch::new(1, 1);
for i in 0..100 {
let price = 100.0 + (i as f64 * 0.1).sin() * 5.0;
ind.update(price);
}
assert!(ind.is_ready());
}
#[test]
fn test_garch_volatility_finite() {
let mut ind = Garch::new(1, 1);
for i in 0..100 {
let price = 100.0 + (i as f64 * 0.2).sin() * 10.0;
ind.update(price);
}
assert!(ind.volatility().is_finite());
assert!(ind.volatility() >= 0.0);
assert!(ind.forecast_volatility().is_finite());
}
#[test]
fn test_garch_reset() {
let mut ind = Garch::new(1, 1);
for i in 0..100 {
let price = 100.0 + i as f64;
ind.update(price);
}
ind.reset();
assert!(!ind.is_ready());
assert!(!ind.is_fitted());
}
#[test]
fn test_egarch_creation() {
let ind = EGarch::new(1, 1);
assert!(!ind.is_ready());
assert!(ind.volatility() > 0.0);
}
#[test]
fn test_egarch_warmup() {
let mut ind = EGarch::new(1, 1);
for i in 0..100 {
let price = 100.0 + (i as f64 * 0.1).sin() * 5.0;
ind.update(price);
}
assert!(ind.is_ready());
}
#[test]
fn test_egarch_volatility_finite() {
let mut ind = EGarch::new(1, 1);
for i in 0..100 {
let price = 100.0 + (i as f64 * 0.2).sin() * 10.0;
ind.update(price);
}
assert!(ind.volatility().is_finite());
assert!(ind.volatility() >= 0.0);
}
#[test]
fn test_egarch_reset() {
let mut ind = EGarch::new(1, 1);
for i in 0..100 {
let price = 100.0 + i as f64;
ind.update(price);
}
ind.reset();
assert!(!ind.is_ready());
assert!(!ind.is_fitted());
}
}