diff --git a/src/indicator.rs b/src/indicator.rs index 861ef9d..5a1ce1c 100644 --- a/src/indicator.rs +++ b/src/indicator.rs @@ -311,3 +311,62 @@ pub fn cvi(h: &[f64], l: &[f64], window: u8, rate_of_change: u8) -> Vec { .map(|w| 100.0 * (w.last().unwrap() / w.first().unwrap() - 1.0)) .collect::>() } + +/// Williams Percent Range +/// https://www.investopedia.com/terms/w/williamsr.asp +pub fn wpr(h: &[f64], l: &[f64], c: &[f64], window: u8) -> Vec { + izip!( + h.windows(window.into()), + l.windows(window.into()), + &c[(window - 1).into()..] + ) + .map(|(high, low, close)| { + let hh = high.iter().fold(f64::NAN, |state, &x| state.max(x)); + let ll = low.iter().fold(f64::NAN, |state, &x| state.min(x)); + -100.0 * ((hh - close) / (hh - ll)) + }) + .collect::>() +} + +/// vortex +/// https://www.investopedia.com/terms/v/vortex-indicator-vi.asp +pub fn vortex(h: &[f64], l: &[f64], c: &[f64], window: u8) -> (Vec, Vec) { + izip!( + &h[..h.len() - 1], + &h[1..], + &l[..l.len() - 1], + &l[1..], + &c[..c.len() - 1], + ) + .map(|(prevh, h, prevl, l, prevc)| { + let vm_pos = (h - prevl).abs(); + let vm_neg = (l - prevh).abs(); + let tr = (h - l).max(f64::abs(h - prevc)).max(f64::abs(l - prevc)); + (vm_pos, vm_neg, tr) + }) + .collect::>() + .windows(window.into()) + .map(|w| { + let (vm_pos, vm_neg, tr) = w + .iter() + .copied() + .reduce(|(acc_pos, acc_neg, acc_tr), (pos, neg, tr)| { + (acc_pos + pos, acc_neg + neg, acc_tr + tr) + }) + .unwrap(); + (vm_pos / tr, vm_neg / tr) + }) + .unzip() +} + +/// percent oscillator +/// pass in any data (close, high, low, etc...), and two window ranges +pub fn po(data: &[f64], short: u8, long: u8) -> Vec { + let short_ma = smooth::ewma(&data, short); + let long_ma = smooth::ewma(&data, long); + short_ma[short_ma.len() - long_ma.len()..] + .iter() + .zip(long_ma) + .map(|(x, y)| 100.0 * (x / y - 1.0)) + .collect::>() +} diff --git a/src/main.rs b/src/main.rs index 1b4c276..ceb342f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -16,5 +16,5 @@ 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::cvi(&stats.high, &stats.low, 16, 2)); + dbg!(indicator::po(&stats.volume, 10, 16)); } diff --git a/tests/indicator_test.rs b/tests/indicator_test.rs index 99fed5d..477e83b 100644 --- a/tests/indicator_test.rs +++ b/tests/indicator_test.rs @@ -564,3 +564,115 @@ fn test_cvi() { result ); } + +#[test] +fn test_wpr() { + let stats = common::test_data(); + let result = indicator::wpr(&stats.high, &stats.low, &stats.close, 16); + assert_eq!( + vec![ + -99.09142622449394, + -94.88476784384058, + -98.7016646516565, + -83.45322691468988, + -80.33424822308433, + -66.49998256138393, + -60.92856270926339, + -58.24332819489061, + -56.88925190670342, + -31.699065254207774, + -24.51395669497002, + -38.50825188642196, + -26.019074805435594, + -15.778329765623885, + -24.403940042613893, + -12.779540060870623, + -7.164869527394986, + -21.111723928174033, + -32.930944560522704, + ], + result + ); +} + +#[test] +fn test_vortex() { + let stats = common::test_data(); + let (vi_pos, vi_neg) = indicator::vortex(&stats.high, &stats.low, &stats.close, 16); + assert_eq!( + vec![ + 0.8610723090930696, + 0.8159089697456218, + 0.5782198030623583, + 0.7200793857247078, + 0.8358118890986928, + 0.9355813847576413, + 0.9484126915125689, + 0.8489016637989781, + 0.9131108818882286, + 0.9790387062979787, + 0.9343265196480259, + 0.9443134085341751, + 1.0302488811514465, + 1.0364553935724832, + 1.0553301509762798, + 1.1219923299032897, + 1.161732264987062, + 1.1478332770638693, + ], + vi_pos + ); + assert_eq!( + vec![ + 1.029701151945177, + 1.1817046446451998, + 1.3803206728528217, + 1.238892593460365, + 1.1850444607043855, + 1.061167502645104, + 1.111042759028416, + 1.1313347365841422, + 0.9951327158267141, + 0.9280527122256887, + 0.9901265627745084, + 0.9313767804581332, + 0.8402113281909229, + 0.891932290028499, + 0.8280623772231498, + 0.7860652938018367, + 0.6974842970716879, + 0.7557323147066469, + ], + vi_neg + ); +} + +#[test] +fn test_po() { + let stats = common::test_data(); + let result = indicator::po(&stats.volume, 10, 16); + assert_eq!( + vec![ + -35.378723807894985, + -37.993168058882375, + -39.524346350340444, + -40.41967020222986, + -40.51097271301698, + -42.086823387150005, + -42.30015209438188, + -43.383528771209576, + -43.81409605428357, + -40.648471039972534, + -37.7876496415686, + -37.39351505516741, + -37.136103488993875, + -34.28067604316157, + -35.12026222042619, + -32.44673522414948, + -18.294669010949182, + 2.308566455542005, + -0.92080395315155, + ], + result + ); +}