From 275b59217d3b59bd6bc8bc9f46eac452c71d2ca3 Mon Sep 17 00:00:00 2001 From: gord chung <5091603+chungg@users.noreply.github.com> Date: Mon, 13 May 2024 14:22:04 -0400 Subject: [PATCH] add vhf and ultimate indicators (#12) --- benches/my_benchmark.rs | 55 ++++++++++++++++++++++++++++++----------- src/indicator.rs | 52 ++++++++++++++++++++++++++++++++++++++ src/main.rs | 9 ++++++- tests/indicator_test.rs | 50 +++++++++++++++++++++++++++++++++++++ 4 files changed, 150 insertions(+), 16 deletions(-) diff --git a/benches/my_benchmark.rs b/benches/my_benchmark.rs index 9467610..e410c64 100644 --- a/benches/my_benchmark.rs +++ b/benches/my_benchmark.rs @@ -1,19 +1,44 @@ -//use criterion::{black_box, criterion_group, criterion_main, Criterion}; -// -//use traquer::*; -// -//pub fn criterion_benchmark(c: &mut Criterion) { -// c.bench_function("fib 20", |b| b.iter(|| black_box(main()))); -//} -// -//criterion_group!(benches, criterion_benchmark); -//criterion_main!(benches); +use criterion::{black_box, criterion_group, criterion_main, Criterion}; + +use serde::{Deserialize, Serialize}; +use std::fs; + +use traquer::indicator; -#[divan::bench] -fn run() { - divan::black_box(traquer::main()); +#[derive(Deserialize, Serialize, Debug)] +struct SecStats { + high: Vec, + low: Vec, + open: Vec, + close: Vec, + volume: Vec, } -fn main() { - divan::main(); +pub fn criterion_benchmark(c: &mut Criterion) { + let data = fs::read_to_string("./aapl.input").expect("Unable to read file"); + let stats: SecStats = serde_json::from_str(&data).expect("JSON does not have correct format."); + c.bench_function("fib 20", |b| { + b.iter(|| { + black_box(indicator::ultimate( + &stats.high, + &stats.low, + &stats.close, + 6, + 12, + 24, + )) + }) + }); } + +criterion_group!(benches, criterion_benchmark); +criterion_main!(benches); + +//#[divan::bench] +//fn run() { +// divan::black_box(traquer::main()); +//} +// +//fn main() { +// divan::main(); +//} diff --git a/src/indicator.rs b/src/indicator.rs index 6c611f2..6eaa893 100644 --- a/src/indicator.rs +++ b/src/indicator.rs @@ -370,3 +370,55 @@ pub fn po(data: &[f64], short: u8, long: u8) -> Vec { .map(|(x, y)| 100.0 * (x / y - 1.0)) .collect::>() } + +/// vertical horizontal filter +/// https://www.upcomingtrader.com/blog/the-vertical-horizontal-filter-a-traders-guide-to-market-phases/ +pub fn vhf(h: &[f64], l: &[f64], c: &[f64], window: u8) -> Vec { + let diffs = &c[1..] + .iter() + .zip(&c[..c.len() - 1]) + .map(|(curr, prev)| (curr - prev).abs()) + .collect::>(); + izip!( + diffs.windows(window.into()), + h.windows(window.into()).skip(1), + l.windows(window.into()).skip(1) + ) + .map(|(diff, highs, lows)| { + (highs.iter().fold(f64::NAN, |state, &x| state.max(x)) + - lows.iter().fold(f64::NAN, |state, &x| state.min(x))) + / diff.iter().sum::() + }) + .collect::>() +} + +/// ultimate oscillator +/// https://www.investopedia.com/terms/u/ultimateoscillator.asp +pub fn ultimate(h: &[f64], l: &[f64], c: &[f64], win1: u8, win2: u8, win3: u8) -> Vec { + let bp_tr_vals = izip!(&h[1..], &l[1..], &c[..c.len() - 1], &c[1..],) + .map(|(h, l, prevc, c)| { + ( + c - f64::min(*l, *prevc), + f64::max(*h, *prevc) - f64::min(*l, *prevc), + ) + }) + .collect::>(); + bp_tr_vals + .windows(win3.into()) + .map(|w| { + let (bp_sum1, tr_sum1) = w + .iter() + .skip((win3 - win1).into()) + .fold((0.0, 0.0), |acc, (bp, tr)| (acc.0 + bp, acc.1 + tr)); + let (bp_sum2, tr_sum2) = w + .iter() + .skip((win3 - win2).into()) + .fold((0.0, 0.0), |acc, (bp, tr)| (acc.0 + bp, acc.1 + tr)); + let (bp_sum3, tr_sum3) = w + .iter() + .fold((0.0, 0.0), |acc, (bp, tr)| (acc.0 + bp, acc.1 + tr)); + 100.0 * (bp_sum1 / tr_sum1 * 4.0 + bp_sum2 / tr_sum2 * 2.0 + bp_sum3 / tr_sum3) + / (4 + 2 + 1) as f64 + }) + .collect::>() +} diff --git a/src/main.rs b/src/main.rs index ceb342f..b29014e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -16,5 +16,12 @@ fn main() { let data = fs::read_to_string("./tests/rddt.input").expect("Unable to read file"); let stats: SecStats = serde_json::from_str(&data).expect("JSON does not have correct format."); - dbg!(indicator::po(&stats.volume, 10, 16)); + dbg!(indicator::ultimate( + &stats.high, + &stats.low, + &stats.close, + 6, + 12, + 24 + )); } diff --git a/tests/indicator_test.rs b/tests/indicator_test.rs index 477e83b..25bd711 100644 --- a/tests/indicator_test.rs +++ b/tests/indicator_test.rs @@ -676,3 +676,53 @@ fn test_po() { result ); } + +#[test] +fn test_vhf() { + let stats = common::test_data(); + let result = indicator::vhf(&stats.high, &stats.low, &stats.close, 16); + assert_eq!( + vec![ + 0.5669216159971233, + 0.7107794587594408, + 0.5482664867838217, + 0.4309723520119755, + 0.40721343838432617, + 0.44011310017913846, + 0.5021691775026703, + 0.4751003004570894, + 0.4435695260250405, + 0.4595960007629394, + 0.4405808834558327, + 0.4357521288992697, + 0.4843910183060343, + 0.5122550500601386, + 0.5359587346575249, + 0.5841583342518132, + 0.7716635213608084, + 0.7671760704973449, + ], + result + ); +} + +#[test] +fn test_ultimate() { + let stats = common::test_data(); + let result = indicator::ultimate(&stats.high, &stats.low, &stats.close, 6, 12, 24); + assert_eq!( + vec![ + 52.64489292919164, + 51.59059656807282, + 46.03014177584667, + 46.83402417416914, + 47.63501864800235, + 43.80674742529631, + 38.16680505680669, + 44.10353752395525, + 44.154676988833835, + 42.65072465563253, + ], + result + ); +}