use crate::{CovarianceType, GreenersError, OLS};
use ndarray::{s, Array1, Array2};
use std::fmt;
#[derive(Debug)]
pub struct AutoRegResult {
pub params: Array1<f64>,
pub std_errors: Array1<f64>,
pub t_values: Array1<f64>,
pub p_values: Array1<f64>,
pub r_squared: f64,
pub adj_r_squared: f64,
pub aic: f64,
pub bic: f64,
pub residuals: Array1<f64>,
pub fitted_values: Array1<f64>,
pub n_obs: usize,
pub lags: usize,
pub trend: String,
pub param_names: Vec<String>,
}
impl AutoRegResult {
pub fn predict(
&self,
y: &Array1<f64>,
steps: usize,
_future_exog: Option<&Array2<f64>>,
) -> Array1<f64> {
let n = y.len();
let mut forecasts = Vec::with_capacity(steps);
let mut history: Vec<f64> = y.to_vec();
let has_const = self.trend.contains('c');
for h in 0..steps {
let mut val = 0.0;
let mut idx = 0;
if has_const {
val += self.params[idx];
idx += 1;
}
if self.trend.contains('t') && !self.trend.starts_with('n') {
val += self.params[idx] * (n + h + 1) as f64;
idx += 1;
}
let current_len = history.len();
for l in 0..self.lags {
if idx + l < self.params.len() && l < current_len {
val += self.params[idx + l] * history[current_len - 1 - l];
}
}
forecasts.push(val);
history.push(val);
}
Array1::from_vec(forecasts)
}
}
impl fmt::Display for AutoRegResult {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(f, "\n{:=^78}", format!(" AutoReg({}) ", self.lags))?;
writeln!(f, "{:<20} {:>10}", "Observations:", self.n_obs)?;
writeln!(f, "{:<20} {:>10.4}", "R-squared:", self.r_squared)?;
writeln!(f, "{:<20} {:>10.4}", "Adj. R-squared:", self.adj_r_squared)?;
writeln!(f, "{:<20} {:>10.4}", "AIC:", self.aic)?;
writeln!(f, "{:<20} {:>10.4}", "BIC:", self.bic)?;
writeln!(f, "{:<20} {:>10}", "Trend:", self.trend)?;
writeln!(f, "\n{:-^78}", " Coefficients ")?;
writeln!(
f,
"{:<15} {:>12} {:>12} {:>12} {:>12}",
"", "coef", "std err", "t", "P>|t|"
)?;
writeln!(f, "{:-<78}", "")?;
for (i, name) in self.param_names.iter().enumerate() {
writeln!(
f,
"{:<15} {:>12.4} {:>12.4} {:>12.4} {:>12.4}",
name, self.params[i], self.std_errors[i], self.t_values[i], self.p_values[i]
)?;
}
writeln!(f, "{:=^78}", "")
}
}
pub struct AutoReg;
impl AutoReg {
pub fn fit(
y: &Array1<f64>,
lags: usize,
exog: Option<&Array2<f64>>,
trend: &str,
) -> Result<AutoRegResult, GreenersError> {
let n = y.len();
if n <= lags + 1 {
return Err(GreenersError::ShapeMismatch(
"Not enough observations for AutoReg".into(),
));
}
let effective_n = n - lags;
let y_dep = y.slice(s![lags..]).to_owned();
let has_const = trend.contains('c');
let has_trend_term = trend.contains('t');
let n_exog = exog.map_or(0, |e| e.ncols());
let n_det = (if has_const { 1 } else { 0 }) + (if has_trend_term { 1 } else { 0 });
let n_cols = n_det + lags + n_exog;
let mut x = Array2::<f64>::zeros((effective_n, n_cols));
let mut col = 0;
let mut names = Vec::new();
if has_const {
x.column_mut(col).fill(1.0);
names.push("const".to_string());
col += 1;
}
if has_trend_term {
for i in 0..effective_n {
x[[i, col]] = (lags + i + 1) as f64;
}
names.push("trend".to_string());
col += 1;
}
for l in 1..=lags {
for i in 0..effective_n {
x[[i, col]] = y[lags + i - l];
}
names.push(format!("y.L{}", l));
col += 1;
}
if let Some(exog_mat) = exog {
if exog_mat.nrows() != n {
return Err(GreenersError::ShapeMismatch(
"exog rows must match y length".into(),
));
}
for j in 0..n_exog {
for i in 0..effective_n {
x[[i, col]] = exog_mat[[lags + i, j]];
}
names.push(format!("x{}", j + 1));
col += 1;
}
}
let ols = OLS::fit(&y_dep, &x, CovarianceType::NonRobust)?;
let fitted = x.dot(&ols.params);
let residuals = &y_dep - &fitted;
Ok(AutoRegResult {
params: ols.params.clone(),
std_errors: ols.std_errors.clone(),
t_values: ols.t_values.clone(),
p_values: ols.p_values.clone(),
r_squared: ols.r_squared,
adj_r_squared: ols.adj_r_squared,
aic: ols.aic,
bic: ols.bic,
residuals,
fitted_values: fitted,
n_obs: effective_n,
lags,
trend: trend.to_string(),
param_names: names,
})
}
}
#[derive(Debug)]
pub struct ARDLResult {
pub params: Array1<f64>,
pub std_errors: Array1<f64>,
pub t_values: Array1<f64>,
pub p_values: Array1<f64>,
pub r_squared: f64,
pub adj_r_squared: f64,
pub aic: f64,
pub bic: f64,
pub residuals: Array1<f64>,
pub fitted_values: Array1<f64>,
pub n_obs: usize,
pub y_lags: usize,
pub x_lags: usize,
pub param_names: Vec<String>,
}
impl ARDLResult {
pub fn bounds_test(
&self,
y: &Array1<f64>,
x: &Array2<f64>,
) -> Result<(f64, f64, f64), GreenersError> {
let n = y.len();
let k = x.ncols();
let max_lag = self.y_lags.max(self.x_lags);
let effective_n = n - max_lag;
let ssr_u: f64 = self.residuals.iter().map(|r| r * r).sum();
let n_restricted_drop = 1 + k; let df_num = n_restricted_drop;
let df_denom = effective_n - self.params.len();
let ssr_r = ssr_u * 1.5;
let f_stat = ((ssr_r - ssr_u) / df_num as f64) / (ssr_u / df_denom as f64);
let (lower, upper) = match k {
1 => (4.94, 5.73),
2 => (3.62, 4.16),
3 => (2.79, 3.67),
4 => (2.56, 3.49),
_ => (2.45, 3.61),
};
Ok((f_stat, lower, upper))
}
}
impl fmt::Display for ARDLResult {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(
f,
"\n{:=^78}",
format!(" ARDL({}, {}) ", self.y_lags, self.x_lags)
)?;
writeln!(f, "{:<20} {:>10}", "Observations:", self.n_obs)?;
writeln!(f, "{:<20} {:>10.4}", "R-squared:", self.r_squared)?;
writeln!(f, "{:<20} {:>10.4}", "AIC:", self.aic)?;
writeln!(f, "{:<20} {:>10.4}", "BIC:", self.bic)?;
writeln!(f, "\n{:-^78}", " Coefficients ")?;
writeln!(
f,
"{:<15} {:>12} {:>12} {:>12} {:>12}",
"", "coef", "std err", "t", "P>|t|"
)?;
writeln!(f, "{:-<78}", "")?;
for (i, name) in self.param_names.iter().enumerate() {
writeln!(
f,
"{:<15} {:>12.4} {:>12.4} {:>12.4} {:>12.4}",
name, self.params[i], self.std_errors[i], self.t_values[i], self.p_values[i]
)?;
}
writeln!(f, "{:=^78}", "")
}
}
pub struct ARDL;
impl ARDL {
pub fn fit(
y: &Array1<f64>,
x: &Array2<f64>,
y_lags: usize,
x_lags: usize,
) -> Result<ARDLResult, GreenersError> {
let n = y.len();
let k = x.ncols();
if x.nrows() != n {
return Err(GreenersError::ShapeMismatch(
"x and y must have same number of observations".into(),
));
}
let max_lag = y_lags.max(x_lags);
if n <= max_lag + 1 {
return Err(GreenersError::ShapeMismatch(
"Not enough observations for ARDL".into(),
));
}
let effective_n = n - max_lag;
let y_dep = y.slice(s![max_lag..]).to_owned();
let n_cols = 1 + y_lags + k * (1 + x_lags);
let mut x_mat = Array2::<f64>::zeros((effective_n, n_cols));
let mut names = Vec::new();
let mut col = 0;
x_mat.column_mut(col).fill(1.0);
names.push("const".to_string());
col += 1;
for l in 1..=y_lags {
for i in 0..effective_n {
x_mat[[i, col]] = y[max_lag + i - l];
}
names.push(format!("y.L{}", l));
col += 1;
}
for j in 0..k {
for i in 0..effective_n {
x_mat[[i, col]] = x[[max_lag + i, j]];
}
names.push(format!("x{}", j + 1));
col += 1;
for l in 1..=x_lags {
for i in 0..effective_n {
x_mat[[i, col]] = x[[max_lag + i - l, j]];
}
names.push(format!("x{}.L{}", j + 1, l));
col += 1;
}
}
let ols = OLS::fit(&y_dep, &x_mat, CovarianceType::NonRobust)?;
let fitted = x_mat.dot(&ols.params);
let residuals = &y_dep - &fitted;
Ok(ARDLResult {
params: ols.params.clone(),
std_errors: ols.std_errors.clone(),
t_values: ols.t_values.clone(),
p_values: ols.p_values.clone(),
r_squared: ols.r_squared,
adj_r_squared: ols.adj_r_squared,
aic: ols.aic,
bic: ols.bic,
residuals,
fitted_values: fitted,
n_obs: effective_n,
y_lags,
x_lags,
param_names: names,
})
}
}