From 0a3caeed86fa37c610ca609ed2ba2b35d62a4ba9 Mon Sep 17 00:00:00 2001 From: chungfam Date: Mon, 8 Jul 2024 15:11:16 -0400 Subject: [PATCH] add historical vol estimators --- benches/traquer.rs | 16 +++++++- src/volatility.rs | 79 ++++++++++++++++++++++++++++++++++++++++ tests/volatility_test.rs | 61 +++++++++++++++++++++++++++++++ 3 files changed, 154 insertions(+), 2 deletions(-) diff --git a/benches/traquer.rs b/benches/traquer.rs index 896ae94..991dc48 100644 --- a/benches/traquer.rs +++ b/benches/traquer.rs @@ -202,7 +202,7 @@ fn criterion_benchmark(c: &mut Criterion) { b.iter(|| black_box(volume::tvi(&stats.close, &stats.volume, 0.5).collect::>())) }); c.bench_function("sig-volume-vpt", |b| { - b.iter(|| black_box(volume::vpt(&stats.close, &stats.volume, 0.5).collect::>())) + b.iter(|| black_box(volume::vpt(&stats.close, &stats.volume).collect::>())) }); c.bench_function("sig-trend-supertrend", |b| { b.iter(|| { @@ -299,7 +299,8 @@ fn criterion_benchmark(c: &mut Criterion) { c.bench_function("sig-momentum-kdj", |b| { b.iter(|| { black_box( - momentum::kdj(&stats.high, &stats.low, &stats.close, 16, 3).collect::>(), + momentum::kdj(&stats.high, &stats.low, &stats.close, 16, 3, None, None) + .collect::>(), ) }) }); @@ -478,6 +479,17 @@ fn criterion_benchmark(c: &mut Criterion) { black_box(volatility::wcp(&stats.high, &stats.low, &stats.close).collect::>()) }) }); + c.bench_function("sig-volatility-gkyz_hv", |b| { + b.iter(|| { + black_box( + volatility::gkyz_hv(&stats.open, &stats.high, &stats.low, &stats.close, 16) + .collect::>(), + ) + }) + }); + c.bench_function("sig-volatility-cc_hv", |b| { + b.iter(|| black_box(volatility::cc_hv(&stats.close, 16).collect::>())) + }); c.bench_function("ma-ewma", |b| { b.iter(|| black_box(smooth::ewma(&stats.close, 16).collect::>())) }); diff --git a/src/volatility.rs b/src/volatility.rs index 8b57925..d1470d5 100644 --- a/src/volatility.rs +++ b/src/volatility.rs @@ -752,3 +752,82 @@ pub fn wcp<'a>( ) -> impl Iterator + 'a { izip!(high, low, close).map(|(h, l, c)| (h + l + 2.0 * c) / 4.0) } + +/// Close-to-Close Historical Volatility +/// +/// The simplest volatility estimator. Calculated using only stock's closing prices. Value is annualised. +/// +/// ## Usage +/// +/// Higher value implies increase volatility. +/// +/// ## Sources +/// +/// [[1]](https://harbourfronts.com/close-close-historical-volatility-calculation-volatility-analysis-python/) +/// +/// # Examples +/// +/// ``` +/// use traquer::volatility; +/// +/// volatility::cc_hv( +/// &vec![1.0,2.0,3.0,4.0,5.0,6.0,4.0,5.0], +/// 3).collect::>(); +/// +/// ``` +pub fn cc_hv(data: &[f64], window: usize) -> impl Iterator + '_ { + iter::repeat(f64::NAN).take(window).chain( + data.windows(2) + .map(|w| (w[1] / w[0]).ln().powi(2)) + .collect::>() + .windows(window) + .map(|w| (w.iter().sum::() * 252.0 / window as f64).sqrt()) + .collect::>(), + ) +} + +/// Garman-Klass-Yang-Zhang Historical Volatility +/// +/// Extends Garman-Klass volatility estimator by taking into consideration overnight jumps. Value +/// is annualised. +/// +/// ## Usage +/// +/// Higher value implies increase volatility. +/// +/// ## Sources +/// +/// [[1]](https://harbourfronts.com/garman-klass-yang-zhang-historical-volatility-calculation-volatility-analysis-python/) +/// +/// # Examples +/// +/// ``` +/// use traquer::volatility; +/// +/// volatility::gkyz_hv( +/// &vec![1.0,2.0,3.0,4.0,5.0,6.0,4.0,5.0], +/// &vec![1.0,2.0,3.0,4.0,5.0,6.0,4.0,5.0], +/// &vec![1.0,2.0,3.0,4.0,5.0,6.0,4.0,5.0], +/// &vec![1.0,2.0,3.0,4.0,5.0,6.0,4.0,5.0], +/// 3).collect::>(); +/// +/// ``` +pub fn gkyz_hv<'a>( + open: &'a [f64], + high: &'a [f64], + low: &'a [f64], + close: &'a [f64], + window: usize, +) -> impl Iterator + 'a { + iter::repeat(f64::NAN).take(window).chain( + izip!(&open[1..], &high[1..], &low[1..], &close[1..], close) + .map(|(o, h, l, c, c0)| { + (o / c0).ln().powi(2) + 0.5 * (h / l).ln().powi(2) + - (2.0 * 2_f64.ln() - 1.0) * (c / o).ln().powi(2) + }) + .collect::>() + .windows(window) + .map(|w| (w.iter().sum::() * 252.0 / window as f64).sqrt()) + .collect::>(), + ) +} diff --git a/tests/volatility_test.rs b/tests/volatility_test.rs index 55f7b1a..853abf3 100644 --- a/tests/volatility_test.rs +++ b/tests/volatility_test.rs @@ -803,3 +803,64 @@ fn test_wcp() { result ); } + +#[test] +fn test_cc_hv() { + let stats = common::test_data(); + let result = volatility::cc_hv(&stats.close, 16).collect::>(); + assert_eq!(stats.close.len(), result.len()); + assert_eq!( + vec![ + 1.5466252225674613, + 1.1600956142944576, + 1.1377538069208708, + 1.0365183751845641, + 0.8333933231891163, + 0.7886313354821333, + 0.6976132908975086, + 0.6413848562022525, + 0.6878616751364196, + 0.6750178844345274, + 0.6835988364394175, + 0.6879642217531283, + 0.6532534112956555, + 0.6202239625971376, + 0.6004459227341106, + 0.5665520605752907, + 0.5773661957866805, + 0.5683025268885697 + ], + result[16..] + ); +} + +#[test] +fn test_gkyz_hv() { + let stats = common::test_data(); + let result = volatility::gkyz_hv(&stats.open, &stats.high, &stats.low, &stats.close, 16) + .collect::>(); + assert_eq!(stats.close.len(), result.len()); + assert_eq!( + vec![ + 1.2449176146400096, + 1.1032341200989624, + 0.9166361517142042, + 0.9043585332042633, + 0.8544447166060107, + 0.8287797413114523, + 0.7613077936213194, + 0.744867758696185, + 0.7420973336106507, + 0.7402566589618695, + 0.7225740295183557, + 0.7325311315992543, + 0.7480130797826274, + 0.7207898315110103, + 0.7295291396594658, + 0.7351330923035614, + 0.8608114872074828, + 0.8604757592723771 + ], + result[16..] + ); +}