use crate::catalog::{
IndicatorSignature, IndicatorCategory, ParamConstraint, ParamType, ParamValue, IndicatorRoleKind,
};
use crate::bar_indicators::indicator_value::IndicatorValueKind;
use super::super::bar_indicator_id::BarIndicatorId;
use once_cell::sync::Lazy;
use std::collections::HashMap;
pub const CATEGORY: IndicatorCategory = IndicatorCategory::Regression;
pub fn signature_arima() -> IndicatorSignature {
IndicatorSignature::builder("ARIMA", CATEGORY)
.name("ARIMA")
.description("AutoRegressive Integrated Moving Average model")
.add_constraint(
ParamConstraint::new("p", ParamType::USize)
.with_min(ParamValue::USize(0))
.with_max(ParamValue::USize(16))
.with_default(ParamValue::USize(1))
.required()
)
.add_constraint(
ParamConstraint::new("d", ParamType::USize)
.with_min(ParamValue::USize(0))
.with_max(ParamValue::USize(3))
.with_default(ParamValue::USize(1))
.required()
)
.add_constraint(
ParamConstraint::new("q", ParamType::USize)
.with_min(ParamValue::USize(0))
.with_max(ParamValue::USize(16))
.with_default(ParamValue::USize(1))
.required()
)
.metadata("outputs", "forecast, aic, bic")
.metadata("min_observations", "30+")
.machine_id(BarIndicatorId::Arima)
.role_kind(IndicatorRoleKind::Smoother)
.output_kind(IndicatorValueKind::Single)
.alias("Arima")
.alias("arima")
.build()
}
pub fn signature_arimax() -> IndicatorSignature {
IndicatorSignature::builder("ARIMAX", CATEGORY)
.name("ARIMAX")
.description("ARIMA with exogenous variables")
.add_constraint(
ParamConstraint::new("p", ParamType::USize)
.with_min(ParamValue::USize(0))
.with_max(ParamValue::USize(16))
.with_default(ParamValue::USize(1))
.required()
)
.add_constraint(
ParamConstraint::new("d", ParamType::USize)
.with_min(ParamValue::USize(0))
.with_max(ParamValue::USize(3))
.with_default(ParamValue::USize(1))
.required()
)
.add_constraint(
ParamConstraint::new("q", ParamType::USize)
.with_min(ParamValue::USize(0))
.with_max(ParamValue::USize(16))
.with_default(ParamValue::USize(1))
.required()
)
.add_constraint(
ParamConstraint::new("num_exog_vars", ParamType::USize)
.with_min(ParamValue::USize(1))
.with_max(ParamValue::USize(16))
.with_default(ParamValue::USize(1))
.required()
)
.metadata("outputs", "forecast, aic, bic, exog_coefficients")
.metadata("min_observations", "30+")
.machine_id(BarIndicatorId::Arimax)
.role_kind(IndicatorRoleKind::Smoother)
.output_kind(IndicatorValueKind::Single)
.alias("Arimax")
.alias("arimax")
.build()
}
pub fn signature_egarch() -> IndicatorSignature {
IndicatorSignature::builder("EGARCH", CATEGORY)
.name("EGARCH")
.description("Exponential GARCH with asymmetric leverage effects")
.add_constraint(
ParamConstraint::new("p", ParamType::USize)
.with_min(ParamValue::USize(1))
.with_max(ParamValue::USize(8))
.with_default(ParamValue::USize(1))
.required()
)
.add_constraint(
ParamConstraint::new("q", ParamType::USize)
.with_min(ParamValue::USize(1))
.with_max(ParamValue::USize(8))
.with_default(ParamValue::USize(1))
.required()
)
.metadata("outputs", "volatility, variance, log_likelihood, aic, bic")
.metadata("features", "asymmetric, leverage_effect")
.metadata("min_observations", "50+")
.metadata("author", "Nelson (1991)")
.machine_id(BarIndicatorId::Egarch)
.role_kind(IndicatorRoleKind::Smoother)
.output_kind(IndicatorValueKind::Single)
.alias("Egarch")
.alias("egarch")
.build()
}
pub fn signature_garch() -> IndicatorSignature {
IndicatorSignature::builder("GARCH", CATEGORY)
.name("GARCH")
.description("Generalized AutoRegressive Conditional Heteroskedasticity model for volatility")
.add_constraint(
ParamConstraint::new("p", ParamType::USize)
.with_min(ParamValue::USize(1))
.with_max(ParamValue::USize(8))
.with_default(ParamValue::USize(1))
.required()
)
.add_constraint(
ParamConstraint::new("q", ParamType::USize)
.with_min(ParamValue::USize(1))
.with_max(ParamValue::USize(8))
.with_default(ParamValue::USize(1))
.required()
)
.metadata("outputs", "volatility, variance, forecast_volatility, log_likelihood, aic, bic")
.metadata("author", "Bollerslev (1986)")
.metadata("min_observations", "50+")
.machine_id(BarIndicatorId::Garch)
.role_kind(IndicatorRoleKind::Smoother)
.output_kind(IndicatorValueKind::Single)
.alias("Garch")
.alias("garch")
.build()
}
pub fn signature_polynomial_regression() -> IndicatorSignature {
IndicatorSignature::builder("POLY_REG", CATEGORY)
.name("Polynomial Regression")
.description("Polynomial regression for nonlinear trend modeling")
.add_constraint(
ParamConstraint::new("degree", ParamType::USize)
.with_min(ParamValue::USize(1))
.with_max(ParamValue::USize(8))
.with_default(ParamValue::USize(2))
.required()
)
.metadata("outputs", "forecast, r_squared, adjusted_r_squared, mse, rmse, first_derivative, second_derivative, trend_direction")
.metadata("trend_directions", "StrongUptrend, Uptrend, Sideways, Downtrend, StrongDowntrend")
.metadata("min_observations", "10+")
.machine_id(BarIndicatorId::PolyReg)
.role_kind(IndicatorRoleKind::Smoother)
.output_kind(IndicatorValueKind::Single)
.alias("PolyReg")
.alias("poly_reg")
.alias("POLYNOMIALREGRESSION")
.alias("PolynomialRegression")
.alias("polynomialregression")
.alias("polynomial_regression")
.alias("POLYNOMIAL_REGRESSION")
.alias("Polynomial_Regression")
.build()
}
pub fn signature_var() -> IndicatorSignature {
IndicatorSignature::builder("VAR", CATEGORY)
.name("VAR")
.description("Vector AutoRegression model for multivariate time series")
.add_constraint(
ParamConstraint::new("p", ParamType::USize)
.with_min(ParamValue::USize(1))
.with_max(ParamValue::USize(8))
.with_default(ParamValue::USize(1))
.required()
)
.add_constraint(
ParamConstraint::new("n_vars", ParamType::USize)
.with_min(ParamValue::USize(2))
.with_max(ParamValue::USize(16))
.with_default(ParamValue::USize(2))
.required()
)
.metadata("outputs", "forecasts, residual_covariance, impulse_responses, log_likelihood, aic, bic")
.metadata("features", "multivariate, impulse_response_analysis")
.metadata("min_observations", "30+")
.metadata("author", "Sims (1980)")
.machine_id(BarIndicatorId::Var)
.role_kind(IndicatorRoleKind::Smoother)
.output_kind(IndicatorValueKind::Single)
.alias("Var")
.alias("var")
.build()
}
const BASE_CATALOG: &[(&str, fn() -> IndicatorSignature)] = &[
("ARIMA", signature_arima as fn() -> IndicatorSignature),
("ARIMAX", signature_arimax as fn() -> IndicatorSignature),
("EGARCH", signature_egarch as fn() -> IndicatorSignature),
("GARCH", signature_garch as fn() -> IndicatorSignature),
("POLY_REG", signature_polynomial_regression as fn() -> IndicatorSignature),
("VAR", signature_var as fn() -> IndicatorSignature),
];
pub static REGRESSION_CATALOG: Lazy<HashMap<String, fn() -> IndicatorSignature>> = Lazy::new(|| {
let mut m = HashMap::new();
for &(main_id, func) in BASE_CATALOG {
let sig = func();
m.insert(main_id.to_string(), func);
for alias in &sig.aliases {
m.insert(alias.clone(), func);
}
}
m
});
pub fn get_signature(id: &str) -> Option<IndicatorSignature> {
REGRESSION_CATALOG.get(id).map(|f| f())
}
pub fn all_indicator_ids() -> Vec<&'static str> {
BASE_CATALOG.iter().map(|(id, _)| *id).collect()
}
pub fn count() -> usize {
BASE_CATALOG.len()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_get_arima_signature() {
let sig = get_signature("ARIMA").unwrap();
assert_eq!(sig.id, "ARIMA");
assert_eq!(sig.category, CATEGORY);
assert_eq!(sig.required_params().len(), 3); }
#[test]
fn test_get_garch_signature() {
let sig = get_signature("GARCH").unwrap();
assert_eq!(sig.id, "GARCH");
assert_eq!(sig.category, CATEGORY);
assert_eq!(sig.required_params().len(), 2); }
#[test]
fn test_get_polynomial_signature() {
let sig = get_signature("POLY_REG").unwrap();
assert_eq!(sig.id, "POLY_REG");
assert_eq!(sig.required_params().len(), 1); }
#[test]
fn test_get_var_signature() {
let sig = get_signature("VAR").unwrap();
assert_eq!(sig.id, "VAR");
assert_eq!(sig.required_params().len(), 2); }
#[test]
fn test_all_signatures_valid() {
for id in all_indicator_ids() {
let sig = get_signature(id).unwrap();
assert_eq!(sig.id, id);
assert_eq!(sig.category, CATEGORY);
}
}
#[test]
fn test_count() {
assert_eq!(count(), 6); }
#[test]
fn test_arima_validation() {
let sig = get_signature("ARIMA").unwrap();
let params = vec![
("p", ParamValue::USize(1)),
("d", ParamValue::USize(1)),
("q", ParamValue::USize(1)),
];
assert!(sig.validate_params(¶ms).is_ok());
let params = vec![
("p", ParamValue::USize(20)),
("d", ParamValue::USize(1)),
("q", ParamValue::USize(1)),
];
assert!(sig.validate_params(¶ms).is_err());
let params = vec![
("p", ParamValue::USize(1)),
("d", ParamValue::USize(5)),
("q", ParamValue::USize(1)),
];
assert!(sig.validate_params(¶ms).is_err());
}
#[test]
fn test_cache_key_generation() {
let sig = get_signature("GARCH").unwrap();
let params = vec![
("p", ParamValue::USize(1)),
("q", ParamValue::USize(1)),
];
let key = sig.cache_key(¶ms);
assert!(key.contains("GARCH"));
assert!(key.contains("1"));
}
#[test]
fn test_polynomial_degree_validation() {
let sig = get_signature("POLY_REG").unwrap();
let params = vec![("degree", ParamValue::USize(3))];
assert!(sig.validate_params(¶ms).is_ok());
let params = vec![("degree", ParamValue::USize(0))];
assert!(sig.validate_params(¶ms).is_err());
let params = vec![("degree", ParamValue::USize(10))];
assert!(sig.validate_params(¶ms).is_err());
}
#[test]
fn test_var_multivariate_validation() {
let sig = get_signature("VAR").unwrap();
let params = vec![
("p", ParamValue::USize(2)),
("n_vars", ParamValue::USize(3)),
];
assert!(sig.validate_params(¶ms).is_ok());
let params = vec![
("p", ParamValue::USize(1)),
("n_vars", ParamValue::USize(1)),
];
assert!(sig.validate_params(¶ms).is_err());
}
#[test]
fn test_egarch_vs_garch() {
let garch_sig = get_signature("GARCH").unwrap();
let egarch_sig = get_signature("EGARCH").unwrap();
assert_eq!(garch_sig.required_params().len(), 2);
assert_eq!(egarch_sig.required_params().len(), 2);
assert_ne!(garch_sig.id, egarch_sig.id);
}
}