diff --git a/Cargo.toml b/Cargo.toml index 4a0fdda..19a3b29 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,9 @@ [package] name = "traquer" -version = "0.1.0" +version = "0.0.1" edition = "2021" +description = "statistical functions library" +license = "Apache-2.0" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -14,5 +16,5 @@ serde_json = "*" criterion = "0.5.1" [[bench]] -name = "my_benchmark" +name = "traquer" harness = false diff --git a/benches/my_benchmark.rs b/benches/traquer.rs similarity index 95% rename from benches/my_benchmark.rs rename to benches/traquer.rs index 52a4a54..d3c02cc 100644 --- a/benches/my_benchmark.rs +++ b/benches/traquer.rs @@ -33,7 +33,7 @@ fn criterion_benchmark(c: &mut Criterion) { }); c.bench_function("sig-twiggs", |b| { b.iter(|| { - black_box(indicator::twiggs( + black_box(volume::twiggs( &stats.high, &stats.low, &stats.close, @@ -47,7 +47,7 @@ fn criterion_benchmark(c: &mut Criterion) { }); c.bench_function("sig-kvo", |b| { b.iter(|| { - black_box(indicator::kvo( + black_box(volume::kvo( &stats.high, &stats.low, &stats.close, @@ -87,11 +87,11 @@ fn criterion_benchmark(c: &mut Criterion) { }) }); c.bench_function("sig-elder_force", |b| { - b.iter(|| black_box(indicator::elder_force(&stats.close, &stats.volume, 16))) + b.iter(|| black_box(volume::elder_force(&stats.close, &stats.volume, 16))) }); c.bench_function("sig-mfi", |b| { b.iter(|| { - black_box(indicator::mfi( + black_box(volume::mfi( &stats.high, &stats.low, &stats.close, @@ -102,7 +102,7 @@ fn criterion_benchmark(c: &mut Criterion) { }); c.bench_function("sig-ad", |b| { b.iter(|| { - black_box(indicator::ad( + black_box(volume::ad( &stats.high, &stats.low, &stats.close, @@ -112,7 +112,7 @@ fn criterion_benchmark(c: &mut Criterion) { }); c.bench_function("sig-ad_yahoo", |b| { b.iter(|| { - black_box(indicator::ad_yahoo( + black_box(volume::ad_yahoo( &stats.high, &stats.low, &stats.close, @@ -122,7 +122,7 @@ fn criterion_benchmark(c: &mut Criterion) { }); c.bench_function("sig-cmf", |b| { b.iter(|| { - black_box(indicator::cmf( + black_box(volume::cmf( &stats.high, &stats.low, &stats.close, @@ -205,7 +205,7 @@ fn criterion_benchmark(c: &mut Criterion) { b.iter(|| black_box(indicator::tii(&stats.close, 16))) }); c.bench_function("sig-tvi", |b| { - b.iter(|| black_box(indicator::tvi(&stats.close, &stats.volume, 0.5))) + b.iter(|| black_box(volume::tvi(&stats.close, &stats.volume, 0.5))) }); c.bench_function("sig-supertrend", |b| { b.iter(|| { diff --git a/src/indicator.rs b/src/indicator.rs index 2246037..04704f8 100644 --- a/src/indicator.rs +++ b/src/indicator.rs @@ -2,42 +2,6 @@ use itertools::{izip, multiunzip}; use crate::smooth; -fn vforce(high: &[f64], low: &[f64], close: &[f64], volume: &[f64]) -> Vec { - izip!(&high[1..], &low[1..], &close[1..], &volume[1..]) - .scan((high[0], low[0], close[0], 99), |state, (h, l, c, v)| { - let trend: i8 = { - if h + l + c > state.0 + state.1 + state.2 { - 1 - } else { - -1 - } - }; - *state = (*h, *l, *c, trend); - Some(v * trend as f64) - }) - .collect::>() -} - -/// klinger volume oscillator -/// different from formula defined by https://www.investopedia.com/terms/k/klingeroscillator.asp -pub fn kvo( - high: &[f64], - low: &[f64], - close: &[f64], - volume: &[f64], - short: usize, - long: usize, -) -> Vec { - let vf = vforce(high, low, close, volume); - let short_ma = smooth::ewma(&vf, short); - let long_ma = smooth::ewma(&vf, long); - short_ma - .skip(long - short) - .zip(long_ma) - .map(|(x, y)| x - y) - .collect::>() -} - /// quick stick /// https://www.investopedia.com/terms/q/qstick.asp pub fn qstick(open: &[f64], close: &[f64], window: usize) -> Vec { @@ -49,38 +13,6 @@ pub fn qstick(open: &[f64], close: &[f64], window: usize) -> Vec { smooth::ewma(&q, window).collect::>() } -fn wilder_sum(data: &[f64], window: usize) -> impl Iterator + '_ { - let initial = data[..(window - 1)].iter().sum::(); - data[(window - 1)..].iter().scan(initial, move |state, x| { - let ma = *state * (window - 1) as f64 / window as f64 + x; - *state = ma; - Some(ma) - }) -} - -/// twiggs money flow -/// https://www.marketvolume.com/technicalanalysis/twiggsmoneyflow.asp -/// https://www.incrediblecharts.com/indicators/twiggs_money_flow.php -pub fn twiggs(high: &[f64], low: &[f64], close: &[f64], volume: &[f64], window: usize) -> Vec { - let data = izip!(&high[1..], &low[1..], &close[1..], &volume[1..]); - // not using wilder moving average to minimise drift caused by floating point math - wilder_sum( - &data - .scan(close[0], |state, (h, l, c, vol)| { - let range_vol = vol - * ((2.0 * c - f64::min(*l, *state) - f64::max(*h, *state)) - / (f64::max(*h, *state) - f64::min(*l, *state))); - *state = *c; - Some(range_vol) - }) - .collect::>(), - window, - ) - .zip(wilder_sum(&volume[1..], window)) - .map(|(range, vol)| range / vol) - .collect() -} - /// shinohara intensity ratio /// https://www.sevendata.co.jp/shihyou/technical/shinohara.html pub fn shinohara(high: &[f64], low: &[f64], close: &[f64], period: usize) -> (Vec, Vec) { @@ -209,38 +141,6 @@ pub fn cog(data: &[f64], window: usize) -> Vec { .collect::>() } -/// accumulation/distribution -/// https://www.investopedia.com/terms/a/accumulationdistribution.asp -pub fn ad(high: &[f64], low: &[f64], close: &[f64], volume: &[f64]) -> Vec { - izip!(high, low, close, volume) - .scan(0.0, |state, (h, l, c, vol)| { - let mfm = ((c - l) - (h - c)) / (h - l); - let mfv = mfm * vol; - let adl = *state + mfv; - *state = adl; - Some(adl) - }) - .collect::>() -} - -/// accumulation/distribution -/// like yahoo -pub fn ad_yahoo(high: &[f64], low: &[f64], close: &[f64], volume: &[f64]) -> Vec { - izip!(&high[1..], &low[1..], &close[1..], &volume[1..]) - .scan((close[0], 0.0), |state, (h, l, c, vol)| { - let mfm = if *c > state.0 { - c - f64::min(*l, state.0) - } else { - c - f64::max(*h, state.0) - }; - let mfv = mfm * vol; - let adl = state.1 + mfv; - *state = (*c, adl); - Some(adl) - }) - .collect::>() -} - /// elder ray /// https://www.investopedia.com/articles/trading/03/022603.asp /// returns tuple of bull power vec and bear power vec @@ -255,59 +155,10 @@ pub fn elder_ray(high: &[f64], low: &[f64], close: &[f64], window: usize) -> (Ve .unzip() } -/// elder force index -/// https://www.investopedia.com/articles/trading/03/031203.asp -pub fn elder_force(close: &[f64], volume: &[f64], window: usize) -> Vec { - smooth::ewma( - &izip!(&close[..close.len() - 1], &close[1..], &volume[1..]) - .map(|(prev, curr, vol)| (curr - prev) * vol) - .collect::>(), - window, - ) - .collect::>() -} - /// williams alligator /// https://www.investopedia.com/articles/trading/072115/exploring-williams-alligator-indicator.asp pub fn alligator(_data: &[f64]) {} -/// money flow index -/// https://www.investopedia.com/terms/m/mfi.asp -pub fn mfi(high: &[f64], low: &[f64], close: &[f64], volume: &[f64], window: usize) -> Vec { - let (pos_mf, neg_mf): (Vec, Vec) = - izip!(&high[1..], &low[1..], &close[1..], &volume[1..]) - .scan( - (high[0] + low[0] + close[0]) / 3.0, - |state, (h, l, c, vol)| { - let hlc = (h + l + c) / 3.0; - let pos_mf = if hlc > *state { hlc * vol } else { 0.0 }; - let neg_mf = if hlc < *state { hlc * vol } else { 0.0 }; - *state = hlc; - Some((pos_mf, neg_mf)) - }, - ) - .unzip(); - pos_mf - .windows(window) - .zip(neg_mf.windows(window)) - .map(|(pos, neg)| { - 100.0 - (100.0 / (1.0 + pos.iter().sum::() / neg.iter().sum::())) - }) - .collect::>() -} - -/// chaikin money flow -/// https://corporatefinanceinstitute.com/resources/equities/chaikin-money-flow-cmf/ -pub fn cmf(high: &[f64], low: &[f64], close: &[f64], volume: &[f64], window: usize) -> Vec { - izip!(high, low, close, volume) - .map(|(h, l, c, vol)| vol * ((c - l) - (h - c)) / (h - l)) - .collect::>() - .windows(window) - .zip(volume.windows(window)) - .map(|(mfv_win, v_win)| mfv_win.iter().sum::() / v_win.iter().sum::()) - .collect::>() -} - /// chaikin volatility /// https://www.tradingview.com/chart/AUDUSD/gjfxqWqW-What-Is-a-Chaikin-Volatility-Indicator-in-Trading/ /// https://theforexgeek.com/chaikins-volatility-indicator/ @@ -623,25 +474,6 @@ pub fn tii(data: &[f64], window: usize) -> Vec { .collect::>() } -/// trade volume index -/// https://www.investopedia.com/terms/t/tradevolumeindex.asp -pub fn tvi(close: &[f64], volume: &[f64], min_tick: f64) -> Vec { - izip!(&close[..close.len() - 1], &close[1..], &volume[1..],) - .scan((1, 0.0), |state, (prev, curr, vol)| { - let direction = if curr - prev > min_tick { - 1 - } else if prev - curr > min_tick { - -1 - } else { - state.0 - }; - let tvi = state.1 + direction as f64 * vol; - *state = (direction, tvi); - Some(tvi) - }) - .collect::>() -} - /// supertrend /// https://www.tradingview.com/support/solutions/43000634738-supertrend/ /// https://www.investopedia.com/supertrend-indicator-7976167 @@ -727,7 +559,7 @@ fn _stc(series: &[f64], window: usize) -> Vec { .collect::>() } -/// Shaff Trend Cycle +/// Schaff Trend Cycle /// https://www.investopedia.com/articles/forex/10/schaff-trend-cycle-indicator.asp /// https://www.stockmaniacs.net/schaff-trend-cycle-indicator/ pub fn stc(close: &[f64], window: usize, short: usize, long: usize) -> Vec { diff --git a/src/lib.rs b/src/lib.rs index f733640..a11b0f6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,3 +4,4 @@ pub mod indicator; pub mod smooth; +pub mod volume; diff --git a/src/volume.rs b/src/volume.rs new file mode 100644 index 0000000..ccf8b34 --- /dev/null +++ b/src/volume.rs @@ -0,0 +1,171 @@ +use itertools::izip; + +use crate::smooth; + +fn vforce(high: &[f64], low: &[f64], close: &[f64], volume: &[f64]) -> Vec { + izip!(&high[1..], &low[1..], &close[1..], &volume[1..]) + .scan((high[0], low[0], close[0], 99), |state, (h, l, c, v)| { + let trend: i8 = { + if h + l + c > state.0 + state.1 + state.2 { + 1 + } else { + -1 + } + }; + *state = (*h, *l, *c, trend); + Some(v * trend as f64) + }) + .collect::>() +} + +/// klinger volume oscillator +/// different from formula defined by https://www.investopedia.com/terms/k/klingeroscillator.asp +pub fn kvo( + high: &[f64], + low: &[f64], + close: &[f64], + volume: &[f64], + short: usize, + long: usize, +) -> Vec { + let vf = vforce(high, low, close, volume); + let short_ma = smooth::ewma(&vf, short); + let long_ma = smooth::ewma(&vf, long); + short_ma + .skip(long - short) + .zip(long_ma) + .map(|(x, y)| x - y) + .collect::>() +} + +fn wilder_sum(data: &[f64], window: usize) -> impl Iterator + '_ { + let initial = data[..(window - 1)].iter().sum::(); + data[(window - 1)..].iter().scan(initial, move |state, x| { + let ma = *state * (window - 1) as f64 / window as f64 + x; + *state = ma; + Some(ma) + }) +} + +/// twiggs money flow +/// https://www.marketvolume.com/technicalanalysis/twiggsmoneyflow.asp +/// https://www.incrediblecharts.com/indicators/twiggs_money_flow.php +pub fn twiggs(high: &[f64], low: &[f64], close: &[f64], volume: &[f64], window: usize) -> Vec { + let data = izip!(&high[1..], &low[1..], &close[1..], &volume[1..]); + // not using wilder moving average to minimise drift caused by floating point math + wilder_sum( + &data + .scan(close[0], |state, (h, l, c, vol)| { + let range_vol = vol + * ((2.0 * c - f64::min(*l, *state) - f64::max(*h, *state)) + / (f64::max(*h, *state) - f64::min(*l, *state))); + *state = *c; + Some(range_vol) + }) + .collect::>(), + window, + ) + .zip(wilder_sum(&volume[1..], window)) + .map(|(range, vol)| range / vol) + .collect() +} + +/// accumulation/distribution +/// https://www.investopedia.com/terms/a/accumulationdistribution.asp +pub fn ad(high: &[f64], low: &[f64], close: &[f64], volume: &[f64]) -> Vec { + izip!(high, low, close, volume) + .scan(0.0, |state, (h, l, c, vol)| { + let mfm = ((c - l) - (h - c)) / (h - l); + let mfv = mfm * vol; + let adl = *state + mfv; + *state = adl; + Some(adl) + }) + .collect::>() +} + +/// accumulation/distribution +/// like yahoo +pub fn ad_yahoo(high: &[f64], low: &[f64], close: &[f64], volume: &[f64]) -> Vec { + izip!(&high[1..], &low[1..], &close[1..], &volume[1..]) + .scan((close[0], 0.0), |state, (h, l, c, vol)| { + let mfm = if *c > state.0 { + c - f64::min(*l, state.0) + } else { + c - f64::max(*h, state.0) + }; + let mfv = mfm * vol; + let adl = state.1 + mfv; + *state = (*c, adl); + Some(adl) + }) + .collect::>() +} + +/// elder force index +/// https://www.investopedia.com/articles/trading/03/031203.asp +pub fn elder_force(close: &[f64], volume: &[f64], window: usize) -> Vec { + smooth::ewma( + &izip!(&close[..close.len() - 1], &close[1..], &volume[1..]) + .map(|(prev, curr, vol)| (curr - prev) * vol) + .collect::>(), + window, + ) + .collect::>() +} + +/// money flow index +/// https://www.investopedia.com/terms/m/mfi.asp +pub fn mfi(high: &[f64], low: &[f64], close: &[f64], volume: &[f64], window: usize) -> Vec { + let (pos_mf, neg_mf): (Vec, Vec) = + izip!(&high[1..], &low[1..], &close[1..], &volume[1..]) + .scan( + (high[0] + low[0] + close[0]) / 3.0, + |state, (h, l, c, vol)| { + let hlc = (h + l + c) / 3.0; + let pos_mf = if hlc > *state { hlc * vol } else { 0.0 }; + let neg_mf = if hlc < *state { hlc * vol } else { 0.0 }; + *state = hlc; + Some((pos_mf, neg_mf)) + }, + ) + .unzip(); + pos_mf + .windows(window) + .zip(neg_mf.windows(window)) + .map(|(pos, neg)| { + 100.0 - (100.0 / (1.0 + pos.iter().sum::() / neg.iter().sum::())) + }) + .collect::>() +} + +/// chaikin money flow +/// https://corporatefinanceinstitute.com/resources/equities/chaikin-money-flow-cmf/ +pub fn cmf(high: &[f64], low: &[f64], close: &[f64], volume: &[f64], window: usize) -> Vec { + izip!(high, low, close, volume) + .map(|(h, l, c, vol)| vol * ((c - l) - (h - c)) / (h - l)) + .collect::>() + .windows(window) + .zip(volume.windows(window)) + .map(|(mfv_win, v_win)| mfv_win.iter().sum::() / v_win.iter().sum::()) + .collect::>() +} + +/// trade volume index +/// https://www.investopedia.com/terms/t/tradevolumeindex.asp +pub fn tvi(close: &[f64], volume: &[f64], min_tick: f64) -> Vec { + izip!(&close[..close.len() - 1], &close[1..], &volume[1..],) + .scan((1, 0.0), |state, (prev, curr, vol)| { + let direction = if curr - prev > min_tick { + 1 + } else if prev - curr > min_tick { + -1 + } else { + state.0 + }; + let tvi = state.1 + direction as f64 * vol; + *state = (direction, tvi); + Some(tvi) + }) + .collect::>() +} diff --git a/tests/indicator_test.rs b/tests/indicator_test.rs index 47f00eb..f65c032 100644 --- a/tests/indicator_test.rs +++ b/tests/indicator_test.rs @@ -108,35 +108,6 @@ fn test_qstick() { ); } -#[test] -fn test_twiggs() { - let stats = common::test_data(); - let result = indicator::twiggs(&stats.high, &stats.low, &stats.close, &stats.volume, 16); - assert_eq!( - vec![ - -0.18613223690613337, - -0.19317352339707441, - -0.17453508236798282, - -0.18375940487028466, - -0.17502586620511137, - -0.17382219844626962, - -0.17255131614049954, - -0.1640328604291291, - -0.1420920809902787, - -0.1527562191185741, - -0.16523607317121108, - -0.16430836437056545, - -0.15920358586187838, - -0.16715044705529314, - -0.1621303949690616, - -0.11881583358526457, - -0.14855035203055256, - -0.15469018900758857, - ], - result - ); -} - #[test] fn test_rsi() { let stats = common::test_data(); @@ -166,35 +137,6 @@ fn test_rsi() { ); } -#[test] -fn test_kvo() { - let stats = common::test_data(); - let result = indicator::kvo(&stats.high, &stats.low, &stats.close, &stats.volume, 10, 16); - assert_eq!( - vec![ - -656322.9297191856, - -713366.2205723817, - -454503.008716189, - -242243.40638757526, - -150673.24628108775, - -51176.64414472319, - -14990.826684893807, - -172705.0049532547, - -10141.336752269068, - 101922.08867095085, - -103711.73979187862, - -34908.10280425532, - 64722.50363716809, - -92755.34740538022, - 7627.067027119803, - 344638.3915205905, - 1045791.2002935112, - 491703.71571153356, - ], - result - ); -} - #[test] fn test_macd() { let stats = common::test_data(); @@ -361,182 +303,6 @@ fn test_elder_ray() { ); } -#[test] -fn test_elder_force() { - let stats = common::test_data(); - let result = indicator::elder_force(&stats.close, &stats.volume, 16); - assert_eq!( - vec![ - 15163924.326658249, - 12880758.096563116, - 12050605.61114628, - 10383051.49386349, - 9364365.879667005, - 8442213.142258454, - 7502743.65895507, - 6497707.801010304, - 6702367.440261993, - 6181683.142965115, - 5074974.977031417, - 4748656.497570231, - 4696379.1776512945, - 3995675.78829447, - 3931504.90271624, - 4298754.639673724, - 6967489.387947403, - 5483494.728818839, - ], - result - ); -} -#[test] -fn test_mfi() { - let stats = common::test_data(); - let result = indicator::mfi(&stats.high, &stats.low, &stats.close, &stats.volume, 16); - assert_eq!( - vec![ - 57.23427292919309, - 47.90601255639084, - 21.137944754987743, - 30.26117456879261, - 38.51678304832031, - 50.513828792387955, - 40.49727451951461, - 44.32715297759769, - 53.90985418627666, - 53.60555590702833, - 55.07839144768625, - 60.65330461162574, - 70.80167748372554, - 64.11003122868968, - 70.5203113367536, - 82.33336948811267, - 87.34401989374501, - 85.51147984689622, - ], - result, - ); -} - -#[test] -fn test_ad() { - let stats = common::test_data(); - let result = indicator::ad(&stats.high, &stats.low, &stats.close, &stats.volume); - assert_eq!( - vec![ - -12220014.53999535, - 5594494.456558675, - -18251656.973511968, - -27192954.017261956, - -38906280.775514156, - -43519171.793909825, - -36388813.33166392, - -40306772.321005546, - -43038979.11826538, - -41295751.02899514, - -42530068.87440958, - -44631518.28622454, - -47863110.10330983, - -47175528.73291944, - -48928369.757053, - -51464869.93675375, - -50970834.60616014, - -52061872.3366441, - -50169605.03257219, - -51716854.467872486, - -50995298.79832875, - -51206859.811250634, - -51316121.96369292, - -50145740.751600124, - -48579440.279196754, - -50497562.83272522, - -51860185.667756006, - -52138562.67733089, - -52113820.167047076, - -52915683.21006013, - -52969956.24185295, - -50690564.9792013, - -55780759.43377636, - -56496069.02735695, - ], - result - ); -} - -#[test] -fn test_ad_yahoo() { - let stats = common::test_data(); - let result = indicator::ad_yahoo(&stats.high, &stats.low, &stats.close, &stats.volume); - assert_eq!( - vec![ - 336703421.3851929, - 524311079.90493774, - 386249980.45578, - 279963693.60809326, - 236454248.4260559, - 297360960.63041687, - 276863635.63041687, - 264624868.58329773, - 273054631.709671, - 264990289.9585724, - 259319809.13619995, - 246661018.79997253, - 257403780.75141907, - 252853500.13084412, - 245025403.5522461, - 248117583.03375244, - 243874594.40460205, - 253856117.67807007, - 246398220.74928284, - 248672416.60499573, - 250625005.75447083, - 251593806.81037903, - 250553745.6768036, - 258791060.41145325, - 261067611.32469177, - 257009643.54515076, - 259310911.44676208, - 263993610.364151, - 261545710.7322693, - 264995933.9931488, - 277479333.9931488, - 304462333.9931488, - 298815868.7785034, - ], - result - ); -} - -#[test] -fn test_cmf() { - let stats = common::test_data(); - let result = indicator::cmf(&stats.high, &stats.low, &stats.close, &stats.volume, 16); - assert_eq!( - vec![ - -0.31996402726557827, - -0.26431568287267065, - -0.4636131359961498, - -0.3495321537587002, - -0.3266108255947919, - -0.18899633112243233, - -0.14523458977952866, - -0.3326493878867275, - -0.23687295947927223, - -0.13961905719447967, - -0.23334715737003828, - -0.2421809194283885, - -0.19835662209432817, - -0.11853261789898396, - -0.1750773026968712, - -0.12288304498976765, - 0.021699608427354102, - -0.1013140453290804, - -0.09237139625802167, - ], - result - ); -} - #[test] fn test_cvi() { let stats = common::test_data(); @@ -1082,22 +848,6 @@ fn test_tii_odd() { ); } -#[test] -fn test_tvi() { - let stats = common::test_data(); - let result = indicator::tvi(&stats.close, &stats.volume, 0.5); - assert_eq!( - vec![ - 24398800.0, 59729800.0, 40971500.0, 28363400.0, 15375500.0, 24818400.0, 19995500.0, - 15377100.0, 18304100.0, 15642600.0, 13365300.0, 9015200.0, 13278200.0, 11264800.0, - 7816300.0, 9515300.0, 7361500.0, 9645600.0, 7117500.0, 8603900.0, 10560400.0, - 11944400.0, 10458600.0, 13222800.0, 15901100.0, 14148200.0, 15746300.0, 18111300.0, - 16923000.0, 19039700.0, 25281400.0, 38772900.0, 36090498.0, - ], - result - ); -} - #[test] fn test_supertrend() { let stats = common::test_data(); diff --git a/tests/volume_test.rs b/tests/volume_test.rs new file mode 100644 index 0000000..b64dbca --- /dev/null +++ b/tests/volume_test.rs @@ -0,0 +1,253 @@ +use traquer::volume; + +mod common; + +#[test] +fn test_twiggs() { + let stats = common::test_data(); + let result = volume::twiggs(&stats.high, &stats.low, &stats.close, &stats.volume, 16); + assert_eq!( + vec![ + -0.18613223690613337, + -0.19317352339707441, + -0.17453508236798282, + -0.18375940487028466, + -0.17502586620511137, + -0.17382219844626962, + -0.17255131614049954, + -0.1640328604291291, + -0.1420920809902787, + -0.1527562191185741, + -0.16523607317121108, + -0.16430836437056545, + -0.15920358586187838, + -0.16715044705529314, + -0.1621303949690616, + -0.11881583358526457, + -0.14855035203055256, + -0.15469018900758857, + ], + result + ); +} + +#[test] +fn test_kvo() { + let stats = common::test_data(); + let result = volume::kvo(&stats.high, &stats.low, &stats.close, &stats.volume, 10, 16); + assert_eq!( + vec![ + -656322.9297191856, + -713366.2205723817, + -454503.008716189, + -242243.40638757526, + -150673.24628108775, + -51176.64414472319, + -14990.826684893807, + -172705.0049532547, + -10141.336752269068, + 101922.08867095085, + -103711.73979187862, + -34908.10280425532, + 64722.50363716809, + -92755.34740538022, + 7627.067027119803, + 344638.3915205905, + 1045791.2002935112, + 491703.71571153356, + ], + result + ); +} + +#[test] +fn test_elder_force() { + let stats = common::test_data(); + let result = volume::elder_force(&stats.close, &stats.volume, 16); + assert_eq!( + vec![ + 15163924.326658249, + 12880758.096563116, + 12050605.61114628, + 10383051.49386349, + 9364365.879667005, + 8442213.142258454, + 7502743.65895507, + 6497707.801010304, + 6702367.440261993, + 6181683.142965115, + 5074974.977031417, + 4748656.497570231, + 4696379.1776512945, + 3995675.78829447, + 3931504.90271624, + 4298754.639673724, + 6967489.387947403, + 5483494.728818839, + ], + result + ); +} +#[test] +fn test_mfi() { + let stats = common::test_data(); + let result = volume::mfi(&stats.high, &stats.low, &stats.close, &stats.volume, 16); + assert_eq!( + vec![ + 57.23427292919309, + 47.90601255639084, + 21.137944754987743, + 30.26117456879261, + 38.51678304832031, + 50.513828792387955, + 40.49727451951461, + 44.32715297759769, + 53.90985418627666, + 53.60555590702833, + 55.07839144768625, + 60.65330461162574, + 70.80167748372554, + 64.11003122868968, + 70.5203113367536, + 82.33336948811267, + 87.34401989374501, + 85.51147984689622, + ], + result, + ); +} + +#[test] +fn test_ad() { + let stats = common::test_data(); + let result = volume::ad(&stats.high, &stats.low, &stats.close, &stats.volume); + assert_eq!( + vec![ + -12220014.53999535, + 5594494.456558675, + -18251656.973511968, + -27192954.017261956, + -38906280.775514156, + -43519171.793909825, + -36388813.33166392, + -40306772.321005546, + -43038979.11826538, + -41295751.02899514, + -42530068.87440958, + -44631518.28622454, + -47863110.10330983, + -47175528.73291944, + -48928369.757053, + -51464869.93675375, + -50970834.60616014, + -52061872.3366441, + -50169605.03257219, + -51716854.467872486, + -50995298.79832875, + -51206859.811250634, + -51316121.96369292, + -50145740.751600124, + -48579440.279196754, + -50497562.83272522, + -51860185.667756006, + -52138562.67733089, + -52113820.167047076, + -52915683.21006013, + -52969956.24185295, + -50690564.9792013, + -55780759.43377636, + -56496069.02735695, + ], + result + ); +} + +#[test] +fn test_ad_yahoo() { + let stats = common::test_data(); + let result = volume::ad_yahoo(&stats.high, &stats.low, &stats.close, &stats.volume); + assert_eq!( + vec![ + 336703421.3851929, + 524311079.90493774, + 386249980.45578, + 279963693.60809326, + 236454248.4260559, + 297360960.63041687, + 276863635.63041687, + 264624868.58329773, + 273054631.709671, + 264990289.9585724, + 259319809.13619995, + 246661018.79997253, + 257403780.75141907, + 252853500.13084412, + 245025403.5522461, + 248117583.03375244, + 243874594.40460205, + 253856117.67807007, + 246398220.74928284, + 248672416.60499573, + 250625005.75447083, + 251593806.81037903, + 250553745.6768036, + 258791060.41145325, + 261067611.32469177, + 257009643.54515076, + 259310911.44676208, + 263993610.364151, + 261545710.7322693, + 264995933.9931488, + 277479333.9931488, + 304462333.9931488, + 298815868.7785034, + ], + result + ); +} + +#[test] +fn test_cmf() { + let stats = common::test_data(); + let result = volume::cmf(&stats.high, &stats.low, &stats.close, &stats.volume, 16); + assert_eq!( + vec![ + -0.31996402726557827, + -0.26431568287267065, + -0.4636131359961498, + -0.3495321537587002, + -0.3266108255947919, + -0.18899633112243233, + -0.14523458977952866, + -0.3326493878867275, + -0.23687295947927223, + -0.13961905719447967, + -0.23334715737003828, + -0.2421809194283885, + -0.19835662209432817, + -0.11853261789898396, + -0.1750773026968712, + -0.12288304498976765, + 0.021699608427354102, + -0.1013140453290804, + -0.09237139625802167, + ], + result + ); +} + +#[test] +fn test_tvi() { + let stats = common::test_data(); + let result = volume::tvi(&stats.close, &stats.volume, 0.5); + assert_eq!( + vec![ + 24398800.0, 59729800.0, 40971500.0, 28363400.0, 15375500.0, 24818400.0, 19995500.0, + 15377100.0, 18304100.0, 15642600.0, 13365300.0, 9015200.0, 13278200.0, 11264800.0, + 7816300.0, 9515300.0, 7361500.0, 9645600.0, 7117500.0, 8603900.0, 10560400.0, + 11944400.0, 10458600.0, 13222800.0, 15901100.0, 14148200.0, 15746300.0, 18111300.0, + 16923000.0, 19039700.0, 25281400.0, 38772900.0, 36090498.0, + ], + result + ); +}