use scirs2_core::ndarray::{s, Array1};
use scirs2_core::numeric::Float;
use crate::error::{Result, TimeSeriesError};
pub fn realized_volatility<F: Float>(returns: &Array1<F>) -> F {
returns.mapv(|x| x * x).sum()
}
pub fn garman_klass_volatility<F: Float + Clone>(
high: &Array1<F>,
low: &Array1<F>,
close: &Array1<F>,
open: &Array1<F>,
) -> Result<Array1<F>> {
if high.len() != low.len() || low.len() != close.len() || close.len() != open.len() {
return Err(TimeSeriesError::DimensionMismatch {
expected: high.len(),
actual: open.len(),
});
}
let mut gk_vol = Array1::zeros(high.len());
let half = F::from(0.5).expect("Failed to convert constant to float");
let ln_2_minus_1 = F::from(2.0 * (2.0_f64).ln() - 1.0).expect("Operation failed");
for i in 0..gk_vol.len() {
let log_hl = (high[i] / low[i]).ln();
let log_co = (close[i] / open[i]).ln();
gk_vol[i] = half * log_hl * log_hl - ln_2_minus_1 * log_co * log_co;
}
Ok(gk_vol)
}
pub fn parkinson_volatility<F: Float + Clone>(
high: &Array1<F>,
low: &Array1<F>,
) -> Result<Array1<F>> {
if high.len() != low.len() {
return Err(TimeSeriesError::DimensionMismatch {
expected: high.len(),
actual: low.len(),
});
}
let mut park_vol = Array1::zeros(high.len());
let four_ln_2 = F::from(4.0 * (2.0_f64).ln()).expect("Operation failed");
for i in 0..park_vol.len() {
let log_hl = (high[i] / low[i]).ln();
park_vol[i] = log_hl * log_hl / four_ln_2;
}
Ok(park_vol)
}
pub fn rogers_satchell_volatility<F: Float + Clone>(
high: &Array1<F>,
low: &Array1<F>,
close: &Array1<F>,
open: &Array1<F>,
) -> Result<Array1<F>> {
if high.len() != low.len() || low.len() != close.len() || close.len() != open.len() {
return Err(TimeSeriesError::DimensionMismatch {
expected: high.len(),
actual: open.len(),
});
}
let mut rs_vol = Array1::zeros(high.len());
for i in 0..rs_vol.len() {
let log_ho = (high[i] / open[i]).ln();
let log_co = (close[i] / open[i]).ln();
let log_lo = (low[i] / open[i]).ln();
rs_vol[i] = log_ho * log_co + log_lo * log_co;
}
Ok(rs_vol)
}
pub fn yang_zhang_volatility<F: Float + Clone>(
high: &Array1<F>,
low: &Array1<F>,
close: &Array1<F>,
open: &Array1<F>,
k: F,
) -> Result<Array1<F>> {
if high.len() != low.len() || low.len() != close.len() || close.len() != open.len() {
return Err(TimeSeriesError::DimensionMismatch {
expected: high.len(),
actual: open.len(),
});
}
if high.len() < 2 {
return Err(TimeSeriesError::InsufficientData {
message: "Need at least 2 data points for Yang-Zhang volatility".to_string(),
required: 2,
actual: high.len(),
});
}
let mut yz_vol = Array1::zeros(high.len() - 1);
for i in 1..high.len() {
let overnight = (open[i] / close[i - 1]).ln();
let open_close = (close[i] / open[i]).ln();
let log_ho = (high[i] / open[i]).ln();
let log_co = (close[i] / open[i]).ln();
let log_lo = (low[i] / open[i]).ln();
let rs = log_ho * log_co + log_lo * log_co;
yz_vol[i - 1] = overnight * overnight + k * open_close * open_close + rs;
}
Ok(yz_vol)
}
pub fn garch_volatility_estimate<F: Float + Clone>(
returns: &Array1<F>,
window: usize,
) -> Result<Array1<F>> {
if returns.len() < window + 1 {
return Err(TimeSeriesError::InsufficientData {
message: "Not enough data for GARCH volatility estimation".to_string(),
required: window + 1,
actual: returns.len(),
});
}
let mut volatilities = Array1::zeros(returns.len() - window + 1);
let omega = F::from(0.000001).expect("Failed to convert constant to float");
let alpha = F::from(0.1).expect("Failed to convert constant to float");
let beta = F::from(0.85).expect("Failed to convert constant to float");
for i in 0..volatilities.len() {
let window_returns = returns.slice(s![i..i + window]);
let mean = window_returns.sum() / F::from(window).expect("Failed to convert to float");
let mut variance = window_returns.mapv(|x| (x - mean).powi(2)).sum()
/ F::from(window - 1).expect("Failed to convert to float");
for j in 1..std::cmp::min(window, 10) {
let return_sq = window_returns[window - j].powi(2);
variance = omega + alpha * return_sq + beta * variance;
}
volatilities[i] = variance.sqrt();
}
Ok(volatilities)
}
pub fn ewma_volatility<F: Float + Clone>(returns: &Array1<F>, lambda: F) -> Result<Array1<F>> {
if returns.is_empty() {
return Err(TimeSeriesError::InvalidInput(
"Returns cannot be empty".to_string(),
));
}
if lambda <= F::zero() || lambda >= F::one() {
return Err(TimeSeriesError::InvalidParameter {
name: "lambda".to_string(),
message: "Lambda must be between 0 and 1 (exclusive)".to_string(),
});
}
let mut ewma_var = Array1::zeros(returns.len());
ewma_var[0] = returns[0].powi(2);
let one_minus_lambda = F::one() - lambda;
for i in 1..returns.len() {
ewma_var[i] = lambda * ewma_var[i - 1] + one_minus_lambda * returns[i].powi(2);
}
Ok(ewma_var.mapv(|x| x.sqrt()))
}
pub fn range_volatility<F: Float + Clone>(
high: &Array1<F>,
low: &Array1<F>,
period: usize,
) -> Result<Array1<F>> {
if high.len() != low.len() {
return Err(TimeSeriesError::DimensionMismatch {
expected: high.len(),
actual: low.len(),
});
}
if high.len() < period {
return Err(TimeSeriesError::InsufficientData {
message: "Not enough data for range volatility calculation".to_string(),
required: period,
actual: high.len(),
});
}
let mut range_vol = Array1::zeros(high.len() - period + 1);
let scaling_factor = F::from(1.0 / (4.0 * (2.0_f64).ln())).expect("Operation failed");
for i in 0..range_vol.len() {
let mut sum_log_range_sq = F::zero();
for j in 0..period {
let log_range = (high[i + j] / low[i + j]).ln();
sum_log_range_sq = sum_log_range_sq + log_range.powi(2);
}
range_vol[i] = (scaling_factor * sum_log_range_sq
/ F::from(period).expect("Failed to convert to float"))
.sqrt();
}
Ok(range_vol)
}
pub fn intraday_volatility<F: Float + Clone>(
prices: &Array1<F>,
sampling_frequency: usize,
) -> Result<F> {
if prices.len() < sampling_frequency + 1 {
return Err(TimeSeriesError::InsufficientData {
message: "Not enough data for intraday volatility calculation".to_string(),
required: sampling_frequency + 1,
actual: prices.len(),
});
}
let mut squared_returns = F::zero();
let mut count = 0;
for i in sampling_frequency..prices.len() {
let logreturn = (prices[i] / prices[i - sampling_frequency]).ln();
squared_returns = squared_returns + logreturn.powi(2);
count += 1;
}
if count == 0 {
return Err(TimeSeriesError::InvalidInput(
"No valid returns calculated".to_string(),
));
}
Ok((squared_returns / F::from(count).expect("Failed to convert to float")).sqrt())
}
#[cfg(test)]
mod tests {
use super::*;
use scirs2_core::ndarray::arr1;
#[test]
fn test_realized_volatility() {
let returns = arr1(&[0.01, -0.02, 0.015, -0.008, 0.012]);
let rv = realized_volatility(&returns);
let expected: f64 = 0.01_f64.powi(2)
+ 0.02_f64.powi(2)
+ 0.015_f64.powi(2)
+ 0.008_f64.powi(2)
+ 0.012_f64.powi(2);
assert!((rv - expected).abs() < 1e-10);
}
#[test]
fn test_garman_klass_volatility() {
let high = arr1(&[102.0, 105.0, 103.5]);
let low = arr1(&[98.0, 101.0, 99.5]);
let close = arr1(&[100.0, 103.0, 101.0]);
let open = arr1(&[99.0, 102.0, 102.5]);
let result = garman_klass_volatility(&high, &low, &close, &open);
assert!(result.is_ok());
let gk_vol = result.expect("Operation failed");
assert_eq!(gk_vol.len(), 3);
for &vol in gk_vol.iter() {
assert!(vol >= 0.0);
}
}
#[test]
fn test_parkinson_volatility() {
let high = arr1(&[102.0, 105.0, 103.5]);
let low = arr1(&[98.0, 101.0, 99.5]);
let result = parkinson_volatility(&high, &low);
assert!(result.is_ok());
let park_vol = result.expect("Operation failed");
assert_eq!(park_vol.len(), 3);
for &vol in park_vol.iter() {
assert!(vol >= 0.0);
}
}
#[test]
fn test_rogers_satchell_volatility() {
let high = arr1(&[102.0, 105.0, 103.5]);
let low = arr1(&[98.0, 101.0, 99.5]);
let close = arr1(&[100.0, 103.0, 101.0]);
let open = arr1(&[99.0, 102.0, 102.5]);
let result = rogers_satchell_volatility(&high, &low, &close, &open);
assert!(result.is_ok());
let rs_vol = result.expect("Operation failed");
assert_eq!(rs_vol.len(), 3);
}
#[test]
fn test_yang_zhang_volatility() {
let high = arr1(&[102.0, 105.0, 103.5, 106.0]);
let low = arr1(&[98.0, 101.0, 99.5, 102.0]);
let close = arr1(&[100.0, 103.0, 101.0, 104.0]);
let open = arr1(&[99.0, 102.0, 102.5, 100.5]);
let k = 0.34;
let result = yang_zhang_volatility(&high, &low, &close, &open, k);
assert!(result.is_ok());
let yz_vol = result.expect("Operation failed");
assert_eq!(yz_vol.len(), 3); }
#[test]
fn test_ewma_volatility() {
let returns = arr1(&[0.01, -0.02, 0.015, -0.008, 0.012]);
let lambda = 0.94;
let result = ewma_volatility(&returns, lambda);
assert!(result.is_ok());
let ewma_vol = result.expect("Operation failed");
assert_eq!(ewma_vol.len(), returns.len());
assert!((ewma_vol[0] - (returns[0] * returns[0]).sqrt()).abs() < 1e-10);
for &vol in ewma_vol.iter() {
assert!(vol >= 0.0);
}
}
#[test]
fn test_garch_volatility_estimate() {
let returns = arr1(&[0.01, -0.02, 0.015, -0.008, 0.012, 0.005, -0.003, 0.007]);
let window = 5;
let result = garch_volatility_estimate(&returns, window);
assert!(result.is_ok());
let garch_vol = result.expect("Operation failed");
assert_eq!(garch_vol.len(), returns.len() - window + 1);
for &vol in garch_vol.iter() {
assert!(vol > 0.0);
}
}
#[test]
fn test_range_volatility() {
let high = arr1(&[102.0, 105.0, 103.5, 106.0, 104.5]);
let low = arr1(&[98.0, 101.0, 99.5, 102.0, 101.0]);
let period = 3;
let result = range_volatility(&high, &low, period);
assert!(result.is_ok());
let range_vol = result.expect("Operation failed");
assert_eq!(range_vol.len(), high.len() - period + 1);
for &vol in range_vol.iter() {
assert!(vol >= 0.0);
}
}
#[test]
fn test_intraday_volatility() {
let prices = arr1(&[100.0, 100.1, 99.9, 100.05, 99.95, 100.2]);
let sampling_freq = 2;
let result = intraday_volatility(&prices, sampling_freq);
assert!(result.is_ok());
let vol = result.expect("Operation failed");
assert!(vol >= 0.0);
}
#[test]
fn test_dimension_mismatch() {
let high = arr1(&[102.0, 105.0]);
let low = arr1(&[98.0, 101.0, 99.5]);
let result = parkinson_volatility(&high, &low);
assert!(result.is_err());
}
#[test]
fn test_insufficient_data() {
let returns = arr1(&[0.01]);
let window = 5;
let result = garch_volatility_estimate(&returns, window);
assert!(result.is_err());
}
#[test]
fn test_invalid_parameters() {
let returns = arr1(&[0.01, -0.02, 0.015]);
let result = ewma_volatility(&returns, 1.1);
assert!(result.is_err());
let result = ewma_volatility(&returns, 0.0);
assert!(result.is_err());
}
}