From 2b950cf8b96acccbd3eb803d83db5b2a33da080e Mon Sep 17 00:00:00 2001 From: m5l14i11 Date: Sat, 1 Jul 2023 16:39:09 +0400 Subject: [PATCH] refactor; atr --- ta_lib/Cargo.lock | 25 ++++++++++- ta_lib/Cargo.toml | 5 ++- ta_lib/momentum/src/aosc.rs | 22 +++------ ta_lib/momentum/src/macd.rs | 12 ++--- ta_lib/momentum/src/rsi.rs | 28 ++++++------ ta_lib/overlap/Cargo.toml | 2 - ta_lib/overlap/src/ema.rs | 22 ++++----- ta_lib/overlap/src/lib.rs | 3 +- ta_lib/overlap/src/sma.rs | 22 ++++----- ta_lib/overlap/src/smma.rs | 24 +++++----- ta_lib/overlap/src/vama.rs | 49 -------------------- ta_lib/overlap/src/wma.rs | 52 ++++++++++++++++++++++ ta_lib/price/src/average.rs | 8 ++-- ta_lib/price/src/median.rs | 11 ++--- ta_lib/price/src/typical.rs | 8 ++-- ta_lib/trend/Cargo.toml | 9 ++++ ta_lib/trend/src/lib.rs | 1 + ta_lib/{overlap => trend}/src/zlema.rs | 14 +++--- ta_lib/{statistics => utils}/Cargo.toml | 2 +- ta_lib/{statistics => utils}/src/lib.rs | 1 + ta_lib/{statistics => utils}/src/stddev.rs | 12 ++--- ta_lib/utils/src/tr.rs | 43 ++++++++++++++++++ ta_lib/volatility/Cargo.toml | 2 +- ta_lib/volatility/src/atr.rs | 38 ++++++++++++++++ ta_lib/volatility/src/bbands.rs | 16 +++---- ta_lib/volatility/src/lib.rs | 1 + ta_lib/volume/Cargo.toml | 9 ++++ ta_lib/volume/src/lib.rs | 1 + ta_lib/{overlap => volume}/src/vwap.rs | 28 +++++------- 29 files changed, 291 insertions(+), 179 deletions(-) delete mode 100644 ta_lib/overlap/src/vama.rs create mode 100644 ta_lib/overlap/src/wma.rs create mode 100644 ta_lib/trend/Cargo.toml create mode 100644 ta_lib/trend/src/lib.rs rename ta_lib/{overlap => trend}/src/zlema.rs (66%) rename ta_lib/{statistics => utils}/Cargo.toml (88%) rename ta_lib/{statistics => utils}/src/lib.rs (57%) rename ta_lib/{statistics => utils}/src/stddev.rs (76%) create mode 100644 ta_lib/utils/src/tr.rs create mode 100644 ta_lib/volatility/src/atr.rs create mode 100644 ta_lib/volume/Cargo.toml create mode 100644 ta_lib/volume/src/lib.rs rename ta_lib/{overlap => volume}/src/vwap.rs (55%) diff --git a/ta_lib/Cargo.lock b/ta_lib/Cargo.lock index ef82fb33..2d416138 100644 --- a/ta_lib/Cargo.lock +++ b/ta_lib/Cargo.lock @@ -7,6 +7,7 @@ name = "momentum" version = "0.1.0" dependencies = [ "overlap", + "price", ] [[package]] @@ -18,5 +19,27 @@ name = "price" version = "0.1.0" [[package]] -name = "statistics" +name = "trend" version = "0.1.0" +dependencies = [ + "overlap", +] + +[[package]] +name = "utils" +version = "0.1.0" + +[[package]] +name = "volatility" +version = "0.1.0" +dependencies = [ + "overlap", + "utils", +] + +[[package]] +name = "volume" +version = "0.1.0" +dependencies = [ + "price", +] diff --git a/ta_lib/Cargo.toml b/ta_lib/Cargo.toml index 9fa72d42..cdb89269 100644 --- a/ta_lib/Cargo.toml +++ b/ta_lib/Cargo.toml @@ -3,5 +3,8 @@ members = [ "overlap", "momentum", "price", -"statistics" +"trend", +"volatility", +"volume", +"utils" ] diff --git a/ta_lib/momentum/src/aosc.rs b/ta_lib/momentum/src/aosc.rs index aa753569..072806bd 100644 --- a/ta_lib/momentum/src/aosc.rs +++ b/ta_lib/momentum/src/aosc.rs @@ -1,20 +1,8 @@ use overlap::sma::sma; -use price::median::median_price; -pub fn aosc( - high: &[f64], - low: &[f64], - short_period: usize, - long_period: usize, -) -> Vec> { - let median_price = median_price(high, low); - let median_price_values = median_price - .iter() - .map(|&x| x.unwrap_or(0.0)) - .collect::>(); - - let ao_short = sma(&median_price_values, short_period); - let ao_long = sma(&median_price_values, long_period); +pub fn aosc(hl2: &[f64], short_period: usize, long_period: usize) -> Vec> { + let ao_short = sma(hl2, short_period); + let ao_long = sma(hl2, long_period); ao_short .iter() @@ -29,16 +17,18 @@ pub fn aosc( #[cfg(test)] mod tests { use super::*; + use price::median::median_price; #[test] fn test_aosc() { let high = &[3.0, 4.0, 5.0, 6.0, 7.0]; let low = &[1.0, 2.0, 3.0, 4.0, 5.0]; + let hl2 = median_price(high, low); let short_period = 2; let long_period = 4; let expected_result = vec![None, None, None, Some(1.0), Some(1.0)]; - let result = aosc(high, low, short_period, long_period); + let result = aosc(&hl2, short_period, long_period); assert_eq!(result, expected_result); } diff --git a/ta_lib/momentum/src/macd.rs b/ta_lib/momentum/src/macd.rs index 91b85224..6dd17aa0 100644 --- a/ta_lib/momentum/src/macd.rs +++ b/ta_lib/momentum/src/macd.rs @@ -1,13 +1,13 @@ use overlap::ema::ema; pub fn macd( - data: &[f64], + source: &[f64], fast_period: usize, slow_period: usize, signal_period: usize, ) -> (Vec>, Vec>, Vec>) { - let ema_fast = ema(data, fast_period); - let ema_slow = ema(data, slow_period); + let ema_fast = ema(source, fast_period); + let ema_slow = ema(source, slow_period); let macd_line = ema_fast .iter() @@ -44,7 +44,7 @@ mod tests { #[test] fn test_macd() { - let data = vec![2.0, 4.0, 6.0, 8.0, 10.0, 9.0, 8.0, 7.0, 6.0, 5.0]; + let source = vec![2.0, 4.0, 6.0, 8.0, 10.0, 9.0, 8.0, 7.0, 6.0, 5.0]; let fast_period = 3; let slow_period = 5; let signal_period = 4; @@ -87,9 +87,9 @@ mod tests { ]; let (macd_line, signal_line, histogram) = - macd(&data, fast_period, slow_period, signal_period); + macd(&source, fast_period, slow_period, signal_period); - for i in 0..data.len() { + for i in 0..source.len() { match (macd_line[i], expected_macd_line[i]) { (Some(a), Some(b)) => { assert!((a - b).abs() < epsilon, "at position {}: {} != {}", i, a, b) diff --git a/ta_lib/momentum/src/rsi.rs b/ta_lib/momentum/src/rsi.rs index 5c69cedc..f46fd232 100644 --- a/ta_lib/momentum/src/rsi.rs +++ b/ta_lib/momentum/src/rsi.rs @@ -1,7 +1,7 @@ use overlap::smma::smma; -pub fn rsi(data: &[f64], period: usize) -> Vec> { - let len = data.len(); +pub fn rsi(source: &[f64], period: usize) -> Vec> { + let len = source.len(); if len < period { return vec![None; len]; @@ -10,8 +10,8 @@ pub fn rsi(data: &[f64], period: usize) -> Vec> { let mut gains = vec![0.0; len]; let mut losses = vec![0.0; len]; - for i in 1..data.len() { - let change = data[i] - data[i - 1]; + for i in 1..len { + let change = source[i] - source[i - 1]; gains[i] = change.max(0.0); losses[i] = (-change).max(0.0); @@ -41,28 +41,28 @@ mod test { #[test] fn test_rsi_len() { - let data = vec![5.0]; - let result = rsi(&data, 1); - assert_eq!(data.len(), result.len()); + let source = vec![5.0]; + let result = rsi(&source, 1); + assert_eq!(source.len(), result.len()); } #[test] fn test_rsi_empty() { - let data = vec![]; - let result = rsi(&data, 14); + let source = vec![]; + let result = rsi(&source, 14); assert_eq!(result, vec![]); } #[test] fn test_rsi_single_value() { - let data = [10.0]; - let rsi_values = rsi(&data, 14); + let source = [10.0]; + let rsi_values = rsi(&source, 14); assert_eq!(rsi_values, vec![None]); } #[test] fn test_rsi_with_valid_data() { - let data = vec![ + let source = vec![ 44.34, 44.09, 44.15, 43.61, 44.33, 44.83, 45.10, 45.42, 45.84, ]; let epsilon = 0.001; @@ -78,9 +78,9 @@ mod test { Some(84.221979), ]; - let result = rsi(&data, 6); + let result = rsi(&source, 6); - for i in 0..result.len() { + for i in 0..source.len() { match (result[i], expected[i]) { (Some(a), Some(b)) => { assert!((a - b).abs() < epsilon, "at position {}: {} != {}", i, a, b) diff --git a/ta_lib/overlap/Cargo.toml b/ta_lib/overlap/Cargo.toml index 8e7511d2..e298255f 100644 --- a/ta_lib/overlap/Cargo.toml +++ b/ta_lib/overlap/Cargo.toml @@ -6,5 +6,3 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -price = { path = "../price" } -statistics = { path = "../statistics" } diff --git a/ta_lib/overlap/src/ema.rs b/ta_lib/overlap/src/ema.rs index 459ced6f..885f5d8a 100644 --- a/ta_lib/overlap/src/ema.rs +++ b/ta_lib/overlap/src/ema.rs @@ -1,5 +1,5 @@ -pub fn ema(data: &[f64], period: usize) -> Vec> { - let len = data.len(); +pub fn ema(source: &[f64], period: usize) -> Vec> { + let len = source.len(); if len < period { return vec![None; len]; @@ -8,10 +8,10 @@ pub fn ema(data: &[f64], period: usize) -> Vec> { let alpha = 2.0 / (period as f64 + 1.0); let mut ema = vec![None; len]; - let mut ema_current = data[0]; + let mut ema_current = source[0]; for i in 1..len { - ema_current = (data[i] - ema_current) * alpha + ema_current; + ema_current = (source[i] - ema_current) * alpha + ema_current; if i >= period - 1 { ema[i] = Some(ema_current); @@ -27,22 +27,22 @@ mod tests { #[test] fn test_ema_len() { - let data = vec![1.0, 2.0]; - let result = ema(&data, 3); - assert_eq!(data.len(), result.len()); + let source = vec![1.0, 2.0]; + let result = ema(&source, 3); + assert_eq!(source.len(), result.len()); } #[test] fn test_ema_edge_case() { - let data = vec![1.0, 2.0]; - let result = ema(&data, 3); + let source = vec![1.0, 2.0]; + let result = ema(&source, 3); assert_eq!(result, vec![None, None]); } #[test] fn test_ema() { - let data = vec![1.0, 2.0, 3.0, 4.0, 5.0]; - let result = ema(&data, 3); + let source = vec![1.0, 2.0, 3.0, 4.0, 5.0]; + let result = ema(&source, 3); assert_eq!( result, vec![None, None, Some(2.25), Some(3.125), Some(4.0625)] diff --git a/ta_lib/overlap/src/lib.rs b/ta_lib/overlap/src/lib.rs index 2933e247..7ad691f4 100644 --- a/ta_lib/overlap/src/lib.rs +++ b/ta_lib/overlap/src/lib.rs @@ -1,5 +1,4 @@ pub mod ema; pub mod sma; pub mod smma; -pub mod vwap; -pub mod zlema; +pub mod wma; diff --git a/ta_lib/overlap/src/sma.rs b/ta_lib/overlap/src/sma.rs index df39a640..23e755a9 100644 --- a/ta_lib/overlap/src/sma.rs +++ b/ta_lib/overlap/src/sma.rs @@ -1,5 +1,5 @@ -pub fn sma(data: &[f64], period: usize) -> Vec> { - let len = data.len(); +pub fn sma(source: &[f64], period: usize) -> Vec> { + let len = source.len(); if len < period { return vec![None; len]; @@ -9,10 +9,10 @@ pub fn sma(data: &[f64], period: usize) -> Vec> { let mut sum = 0.0; for i in 0..len { - sum += data[i]; + sum += source[i]; if i >= period { - sum -= data[i - period]; + sum -= source[i - period]; sma[i] = Some(sum / period as f64); } else if i + 1 == period { sma[i] = Some(sum / period as f64); @@ -28,22 +28,22 @@ mod tests { #[test] fn test_sma_len() { - let data = vec![1.0, 2.0]; - let result = sma(&data, 3); - assert_eq!(data.len(), result.len()); + let source = vec![1.0, 2.0]; + let result = sma(&source, 3); + assert_eq!(source.len(), result.len()); } #[test] fn test_sma_edge_case() { - let data = vec![1.0, 2.0]; - let result = sma(&data, 3); + let source = vec![1.0, 2.0]; + let result = sma(&source, 3); assert_eq!(result, vec![None, None]); } #[test] fn test_sma() { - let data = vec![1.0, 2.0, 3.0, 4.0, 5.0]; - let result = sma(&data, 3); + let source = vec![1.0, 2.0, 3.0, 4.0, 5.0]; + let result = sma(&source, 3); assert_eq!(result, vec![None, None, Some(2.0), Some(3.0), Some(4.0)]); } } diff --git a/ta_lib/overlap/src/smma.rs b/ta_lib/overlap/src/smma.rs index a0f3a524..2b370492 100644 --- a/ta_lib/overlap/src/smma.rs +++ b/ta_lib/overlap/src/smma.rs @@ -1,5 +1,5 @@ -pub fn smma(data: &[f64], period: usize) -> Vec> { - let len = data.len(); +pub fn smma(source: &[f64], period: usize) -> Vec> { + let len = source.len(); if len < period { return vec![None; len]; @@ -8,10 +8,10 @@ pub fn smma(data: &[f64], period: usize) -> Vec> { let alpha = 1.0 / period as f64; let mut smma = vec![None; len]; - let mut smma_current = data[0]; + let mut smma_current = source[0]; for i in 1..len { - smma_current = (data[i] - smma_current) * alpha + smma_current; + smma_current = (source[i] - smma_current) * alpha + smma_current; if i >= period - 1 { smma[i] = Some(smma_current); @@ -27,27 +27,27 @@ mod tests { #[test] fn test_smma_len() { - let data = vec![1.0, 2.0]; - let result = smma(&data, 3); - assert_eq!(data.len(), result.len()); + let source = vec![1.0, 2.0]; + let result = smma(&source, 3); + assert_eq!(source.len(), result.len()); } #[test] fn test_smmma_edge_case() { - let data = vec![1.0, 2.0]; - let result = smma(&data, 3); + let source = vec![1.0, 2.0]; + let result = smma(&source, 3); assert_eq!(result, vec![None, None]); } #[test] fn test_smma() { - let data = vec![1.0, 2.0, 3.0, 4.0, 5.0]; + let source = vec![1.0, 2.0, 3.0, 4.0, 5.0]; let expected = vec![None, None, Some(1.888), Some(2.592), Some(3.395)]; let epsilon = 0.001; - let result = smma(&data, 3); + let result = smma(&source, 3); - for i in 0..result.len() { + for i in 0..source.len() { match (result[i], expected[i]) { (Some(a), Some(b)) => { assert!((a - b).abs() < epsilon, "at position {}: {} != {}", i, a, b) diff --git a/ta_lib/overlap/src/vama.rs b/ta_lib/overlap/src/vama.rs deleted file mode 100644 index 156534a1..00000000 --- a/ta_lib/overlap/src/vama.rs +++ /dev/null @@ -1,49 +0,0 @@ -use statistics::stddev::std_dev; - -pub fn vama( - close: &[f64], - short_volatility: usize, - long_volatility: usize, - alpha_factor: f64, -) -> Vec> { - let short_std = std_dev(close, short_volatility); - let long_std = std_dev(close, long_volatility); - let len = close.len(); - - let mut alpha = vec![None; len]; - let mut vama = vec![None; len]; - - for i in 0..len { - if let (Some(ss), Some(ls)) = (short_std[i], long_std[i]) { - alpha[i] = Some((ss / ls) * alpha_factor); - } - } - - let alpha = alpha.iter().filter_map(|&x| x).collect::>(); - - vama = ema(&alpha, short_volatility); - - vama -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_vama() { - let close = vec![100.0, 105.0, 110.0, 115.0, 120.0]; - let short_volatility = 2; - let long_volatility = 3; - let alpha_factor = 0.5; - let expected = vec![ - None, - Some(102.5), - Some(106.25), - Some(110.625), - Some(115.3125), - ]; - let result = vama(&close, short_volatility, long_volatility, alpha_factor); - assert_eq!(result, expected); - } -} diff --git a/ta_lib/overlap/src/wma.rs b/ta_lib/overlap/src/wma.rs new file mode 100644 index 00000000..c06031e3 --- /dev/null +++ b/ta_lib/overlap/src/wma.rs @@ -0,0 +1,52 @@ +pub fn wma(source: &[f64], period: usize) -> Vec> { + let len = source.len(); + + if len < period { + return vec![None; len]; + } + + let mut wma = vec![None; len]; + let weight_sum = (period * (period + 1)) as f64 / 2.0; + + let mut sum = 0.0; + + for i in 0..period { + let weight = (i + 1) as f64; + let value = source[i]; + sum += value * weight; + } + + wma[period - 1] = Some(sum / weight_sum); + + for i in period..len { + sum = sum + (source[i] - source[i - period]) * period as f64 - (weight_sum - period as f64); + wma[i] = Some(sum / weight_sum); + } + + wma +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_wma() { + let source = vec![1.0, 2.0, 3.0, 4.0, 5.0]; + let period = 3; + let epsilon = 0.001; + let expected = vec![None, None, Some(2.333333), Some(3.333333), Some(4.333333)]; + + let result = wma(&source, period); + + for i in 0..source.len() { + match (result[i], expected[i]) { + (Some(a), Some(b)) => { + assert!((a - b).abs() < epsilon, "at position {}: {} != {}", i, a, b) + } + (None, None) => {} + _ => panic!("at position {}: {:?} != {:?}", i, result[i], expected[i]), + } + } + } +} diff --git a/ta_lib/price/src/average.rs b/ta_lib/price/src/average.rs index 7c436d7d..caca7fa7 100644 --- a/ta_lib/price/src/average.rs +++ b/ta_lib/price/src/average.rs @@ -1,15 +1,15 @@ -pub fn average_price(open: &[f64], high: &[f64], low: &[f64], close: &[f64]) -> Vec> { +pub fn average_price(open: &[f64], high: &[f64], low: &[f64], close: &[f64]) -> Vec { let len = high.len(); if len != low.len() || len != open.len() || len != close.len() { - return vec![None; len]; + return vec![0.0; len]; } high.iter() .zip(low) .zip(open) .zip(close) - .map(|(((&h, &l), &o), &c)| Some((h + l + o + c) / 4.0)) + .map(|(((&h, &l), &o), &c)| (h + l + o + c) / 4.0) .collect() } @@ -23,7 +23,7 @@ mod tests { let high = vec![2.0, 4.0, 6.0]; let low = vec![1.0, 2.0, 3.0]; let close = vec![1.75, 3.5, 5.25]; - let expected = vec![Some(1.5625), Some(3.125), Some(4.6875)]; + let expected = vec![1.5625, 3.125, 4.6875]; let result = average_price(&open, &high, &low, &close); diff --git a/ta_lib/price/src/median.rs b/ta_lib/price/src/median.rs index a6ee73c7..287b810b 100644 --- a/ta_lib/price/src/median.rs +++ b/ta_lib/price/src/median.rs @@ -1,14 +1,11 @@ -pub fn median_price(high: &[f64], low: &[f64]) -> Vec> { +pub fn median_price(high: &[f64], low: &[f64]) -> Vec { let len = high.len(); if len != low.len() { - return vec![None; len]; + return vec![0.0; len]; } - high.iter() - .zip(low) - .map(|(&h, &l)| Some((h + l) / 2.0)) - .collect() + high.iter().zip(low).map(|(&h, &l)| (h + l) / 2.0).collect() } #[cfg(test)] @@ -19,7 +16,7 @@ mod tests { fn test_median_price() { let high = vec![1.0, 2.0, 3.0]; let low = vec![0.5, 1.0, 2.0]; - let expected = vec![Some(0.75), Some(1.5), Some(2.5)]; + let expected = vec![0.75, 1.5, 2.5]; let result = median_price(&high, &low); diff --git a/ta_lib/price/src/typical.rs b/ta_lib/price/src/typical.rs index 0485eee7..a97c9dde 100644 --- a/ta_lib/price/src/typical.rs +++ b/ta_lib/price/src/typical.rs @@ -1,14 +1,14 @@ -pub fn typical_price(high: &[f64], low: &[f64], close: &[f64]) -> Vec> { +pub fn typical_price(high: &[f64], low: &[f64], close: &[f64]) -> Vec { let len = high.len(); if len != low.len() || len != close.len() { - return vec![None; len]; + return vec![0.0; len]; } high.iter() .zip(low) .zip(close) - .map(|((&h, &l), &c)| Some((h + l + c) / 3.0)) + .map(|((&h, &l), &c)| (h + l + c) / 3.0) .collect() } @@ -21,7 +21,7 @@ mod tests { let high = vec![1.0, 2.0, 3.0]; let low = vec![0.5, 1.0, 1.5]; let close = vec![0.75, 1.5, 2.25]; - let expected = vec![Some(0.75), Some(1.5), Some(2.25)]; + let expected = vec![0.75, 1.5, 2.25]; let result = typical_price(&high, &low, &close); diff --git a/ta_lib/trend/Cargo.toml b/ta_lib/trend/Cargo.toml new file mode 100644 index 00000000..5cb1e346 --- /dev/null +++ b/ta_lib/trend/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "trend" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +overlap = { path = "../overlap" } diff --git a/ta_lib/trend/src/lib.rs b/ta_lib/trend/src/lib.rs new file mode 100644 index 00000000..7bf12d1a --- /dev/null +++ b/ta_lib/trend/src/lib.rs @@ -0,0 +1 @@ +pub mod zlema; diff --git a/ta_lib/overlap/src/zlema.rs b/ta_lib/trend/src/zlema.rs similarity index 66% rename from ta_lib/overlap/src/zlema.rs rename to ta_lib/trend/src/zlema.rs index 46f2ab7d..e0133c0b 100644 --- a/ta_lib/overlap/src/zlema.rs +++ b/ta_lib/trend/src/zlema.rs @@ -1,7 +1,7 @@ -use crate::ema::ema; +use overlap::ema::ema; -pub fn zlema(close: &[f64], period: usize) -> Vec> { - let len = close.len(); +pub fn zlema(source: &[f64], period: usize) -> Vec> { + let len = source.len(); let lag = (period - 1) / 2; @@ -10,8 +10,8 @@ pub fn zlema(close: &[f64], period: usize) -> Vec> { for i in 0..len { if i >= lag { - let price = close[i]; - let prev_price = close[i - lag]; + let price = source[i]; + let prev_price = source[i - lag]; ema_data[i] = price + (price - prev_price); } } @@ -33,11 +33,11 @@ mod tests { #[test] fn test_zlema() { - let close = vec![100.0, 105.0, 110.0, 115.0, 120.0]; + let source = vec![100.0, 105.0, 110.0, 115.0, 120.0]; let period = 3; let expected = vec![None, None, Some(85.0), Some(102.5), Some(113.75)]; - let result = zlema(&close, period); + let result = zlema(&source, period); assert_eq!(result, expected); } diff --git a/ta_lib/statistics/Cargo.toml b/ta_lib/utils/Cargo.toml similarity index 88% rename from ta_lib/statistics/Cargo.toml rename to ta_lib/utils/Cargo.toml index cf1e5d73..a0cba9a2 100644 --- a/ta_lib/statistics/Cargo.toml +++ b/ta_lib/utils/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "statistics" +name = "utils" version = "0.1.0" edition = "2021" diff --git a/ta_lib/statistics/src/lib.rs b/ta_lib/utils/src/lib.rs similarity index 57% rename from ta_lib/statistics/src/lib.rs rename to ta_lib/utils/src/lib.rs index e7e7a8a5..9de04543 100644 --- a/ta_lib/statistics/src/lib.rs +++ b/ta_lib/utils/src/lib.rs @@ -1 +1,2 @@ pub mod stddev; +pub mod tr; diff --git a/ta_lib/statistics/src/stddev.rs b/ta_lib/utils/src/stddev.rs similarity index 76% rename from ta_lib/statistics/src/stddev.rs rename to ta_lib/utils/src/stddev.rs index e7af9538..662bec5b 100644 --- a/ta_lib/statistics/src/stddev.rs +++ b/ta_lib/utils/src/stddev.rs @@ -1,14 +1,14 @@ -pub fn std_dev(data: &[f64], period: usize) -> Vec> { - let mut std_dev = vec![None; data.len()]; +pub fn std_dev(source: &[f64], period: usize) -> Vec> { + let mut std_dev = vec![None; source.len()]; let mut sum = 0.0; let mut sum_sq = 0.0; - for (i, &value) in data.iter().enumerate() { + for (i, &value) in source.iter().enumerate() { sum += value; sum_sq += value * value; if i >= period { - let old_value = data[i - period]; + let old_value = source[i - period]; sum -= old_value; sum_sq -= old_value * old_value; } @@ -29,12 +29,12 @@ mod test { #[test] fn test_std_dev() { - let data = vec![1.0, 2.0, 3.0, 4.0, 5.0]; + let source = vec![1.0, 2.0, 3.0, 4.0, 5.0]; let period = 2; let epsilon = 0.001; let expected = vec![None, Some(0.5), Some(0.5), Some(0.5), Some(0.5)]; - let result = std_dev(&data, period); + let result = std_dev(&source, period); for i in 0..result.len() { match (result[i], expected[i]) { diff --git a/ta_lib/utils/src/tr.rs b/ta_lib/utils/src/tr.rs new file mode 100644 index 00000000..7c30c58b --- /dev/null +++ b/ta_lib/utils/src/tr.rs @@ -0,0 +1,43 @@ +pub fn true_range(high: &[f64], low: &[f64], close: &[f64]) -> Vec { + let len = high.len(); + + if len != low.len() || len != close.len() { + return vec![0.0; len]; + } + + let mut true_range = vec![0.0; len]; + + for i in 1..len { + let tr = if close[i - 1].is_nan() { + high[i] - low[i] + } else { + f64::max( + high[i] - low[i], + f64::max( + f64::abs(high[i] - close[i - 1]), + f64::abs(low[i] - close[i - 1]), + ), + ) + }; + + true_range[i] = tr; + } + + true_range +} +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_true_range() { + let high = vec![50.0, 60.0, 55.0, 70.0]; + let low = vec![40.0, 50.0, 45.0, 60.0]; + let close = vec![45.0, 55.0, 50.0, 65.0]; + let expected = vec![0.0, 15.0, 10.0, 20.0]; + + let result = true_range(&high, &low, &close); + + assert_eq!(result, expected); + } +} diff --git a/ta_lib/volatility/Cargo.toml b/ta_lib/volatility/Cargo.toml index cffb9c66..2067639a 100644 --- a/ta_lib/volatility/Cargo.toml +++ b/ta_lib/volatility/Cargo.toml @@ -6,5 +6,5 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -statistics = { path = "../statistics" } +utils = { path = "../utils" } overlap = { path = "../overlap" } \ No newline at end of file diff --git a/ta_lib/volatility/src/atr.rs b/ta_lib/volatility/src/atr.rs new file mode 100644 index 00000000..e5b489eb --- /dev/null +++ b/ta_lib/volatility/src/atr.rs @@ -0,0 +1,38 @@ +use overlap::{ema::ema, sma::sma, smma::smma, wma::wma}; +use utils::tr::true_range; + +pub fn atr( + high: &[f64], + low: &[f64], + close: &[f64], + period: usize, + smothing: Option<&str>, +) -> Vec> { + let tr = true_range(high, low, close); + + match smothing { + Some("SMMA") => smma(&tr, period), + Some("SMA") => sma(&tr, period), + Some("EMA") => ema(&tr, period), + _ => wma(&tr, period), + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_atr_smma() { + let high = vec![2.0, 4.0, 6.0]; + let low = vec![1.0, 2.0, 3.0]; + let close = vec![1.5, 3.0, 4.5]; + let period = 3; + let smothing = Some("SMMA"); + let expected = vec![None, None, Some(1.5555)]; + + let result = atr(&high, &low, &close, period, smothing); + + assert_eq!(result, expected); + } +} diff --git a/ta_lib/volatility/src/bbands.rs b/ta_lib/volatility/src/bbands.rs index d7e9d012..c43018cd 100644 --- a/ta_lib/volatility/src/bbands.rs +++ b/ta_lib/volatility/src/bbands.rs @@ -1,14 +1,14 @@ use overlap::sma::sma; -use statistics::stddev::std_dev; +use utils::stddev::std_dev; pub fn bbands( - data: &[f64], + source: &[f64], period: usize, factor: usize, ) -> (Vec>, Vec>, Vec>) { - let len = data.len(); - let stddev = std_dev(data, period); - let middle_band = sma(data, period); + let len = source.len(); + let stddev = std_dev(source, period); + let middle_band = sma(source, period); let mut upper_band = vec![None; len]; let mut lower_band = vec![None; len]; @@ -29,7 +29,7 @@ mod tests { #[test] fn test_bbands() { - let data = vec![2.0, 4.0, 6.0, 8.0, 10.0, 9.0, 8.0, 7.0, 6.0, 5.0]; + let source = vec![2.0, 4.0, 6.0, 8.0, 10.0, 9.0, 8.0, 7.0, 6.0, 5.0]; let period = 3; let factor = 2; let epsilon = 0.001; @@ -70,9 +70,9 @@ mod tests { Some(4.367007), ]; - let (upper_band, middle_band, lower_band) = bbands(&data, period, factor); + let (upper_band, middle_band, lower_band) = bbands(&source, period, factor); - for i in 0..data.len() { + for i in 0..source.len() { match (upper_band[i], expected_upper_band[i]) { (Some(a), Some(b)) => { assert!((a - b).abs() < epsilon, "at position {}: {} != {}", i, a, b) diff --git a/ta_lib/volatility/src/lib.rs b/ta_lib/volatility/src/lib.rs index 317ba665..060fc83e 100644 --- a/ta_lib/volatility/src/lib.rs +++ b/ta_lib/volatility/src/lib.rs @@ -1 +1,2 @@ +pub mod atr; pub mod bbands; diff --git a/ta_lib/volume/Cargo.toml b/ta_lib/volume/Cargo.toml new file mode 100644 index 00000000..e7eae806 --- /dev/null +++ b/ta_lib/volume/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "volume" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +price = { path = "../price" } diff --git a/ta_lib/volume/src/lib.rs b/ta_lib/volume/src/lib.rs new file mode 100644 index 00000000..cb70ceb2 --- /dev/null +++ b/ta_lib/volume/src/lib.rs @@ -0,0 +1 @@ +pub mod vwap; diff --git a/ta_lib/overlap/src/vwap.rs b/ta_lib/volume/src/vwap.rs similarity index 55% rename from ta_lib/overlap/src/vwap.rs rename to ta_lib/volume/src/vwap.rs index ee8f9b9d..d5e117a4 100644 --- a/ta_lib/overlap/src/vwap.rs +++ b/ta_lib/volume/src/vwap.rs @@ -1,9 +1,5 @@ -use price::typical::typical_price; - -pub fn vwap(high: &[f64], low: &[f64], close: &[f64], volume: &[f64]) -> Vec> { - let typical_price = typical_price(high, low, close); - - let len = typical_price.len(); +pub fn vwap(hlc3: &[f64], volume: &[f64]) -> Vec> { + let len = hlc3.len(); if len != volume.len() { return vec![None; len]; @@ -14,13 +10,13 @@ pub fn vwap(high: &[f64], low: &[f64], close: &[f64], volume: &[f64]) -> Vec { - sum_volume += volume[i]; - vwap_numerator += p * volume[i]; + let p = hlc3[i]; + if p.is_finite() { + sum_volume += volume[i]; + vwap_numerator += p * volume[i]; + if sum_volume != 0.0 { vwap[i] = Some(vwap_numerator / sum_volume); } - None => return vec![None; len], } } @@ -30,6 +26,7 @@ pub fn vwap(high: &[f64], low: &[f64], close: &[f64], volume: &[f64]) -> Vec