diff --git a/.github/workflows/quant.yml b/.github/workflows/quant.yml index 19796175..e8cc4ed9 100644 --- a/.github/workflows/quant.yml +++ b/.github/workflows/quant.yml @@ -13,5 +13,8 @@ jobs: - name: Check out code uses: actions/checkout@v2 - - name: Test ta_lib - run: cargo test --manifest-path=ta_lib/Cargo.toml + - name: Test + run: make test + + - name: Check + run: make check diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..ac7ce334 --- /dev/null +++ b/Makefile @@ -0,0 +1,10 @@ +.PHONY: test check + +TA_LIB_PATH ?= ta_lib/Cargo.toml + +test: + cargo test --manifest-path=$(TA_LIB_PATH) + +check: + cargo fmt --all --check --manifest-path=$(TA_LIB_PATH) + cargo clippy --all-features --all-targets --workspace --manifest-path=$(TA_LIB_PATH) \ No newline at end of file diff --git a/ta_lib/Cargo.lock b/ta_lib/Cargo.lock index daa55d28..ef82fb33 100644 --- a/ta_lib/Cargo.lock +++ b/ta_lib/Cargo.lock @@ -12,3 +12,11 @@ dependencies = [ [[package]] name = "overlap" version = "0.1.0" + +[[package]] +name = "price" +version = "0.1.0" + +[[package]] +name = "statistics" +version = "0.1.0" diff --git a/ta_lib/Cargo.toml b/ta_lib/Cargo.toml index f07f1793..9fa72d42 100644 --- a/ta_lib/Cargo.toml +++ b/ta_lib/Cargo.toml @@ -1,5 +1,7 @@ [workspace] members = [ "overlap", -"momentum" +"momentum", +"price", +"statistics" ] diff --git a/ta_lib/price/Cargo.toml b/ta_lib/price/Cargo.toml new file mode 100644 index 00000000..a00ab8fd --- /dev/null +++ b/ta_lib/price/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "price" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/ta_lib/price/src/lib.rs b/ta_lib/price/src/lib.rs new file mode 100644 index 00000000..5704d791 --- /dev/null +++ b/ta_lib/price/src/lib.rs @@ -0,0 +1 @@ +pub mod typical; diff --git a/ta_lib/price/src/typical.rs b/ta_lib/price/src/typical.rs new file mode 100644 index 00000000..4561c709 --- /dev/null +++ b/ta_lib/price/src/typical.rs @@ -0,0 +1,30 @@ +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]; + } + + high.iter() + .zip(low) + .zip(close) + .map(|((&h, &l), &c)| Some((h + l + c) / 3.0)) + .collect() +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_typical_price() { + 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 result = typical_price(&high, &low, &close); + + assert_eq!(result, expected); + } +} diff --git a/ta_lib/statistics/Cargo.toml b/ta_lib/statistics/Cargo.toml new file mode 100644 index 00000000..cf1e5d73 --- /dev/null +++ b/ta_lib/statistics/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "statistics" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/ta_lib/statistics/src/lib.rs b/ta_lib/statistics/src/lib.rs new file mode 100644 index 00000000..e7e7a8a5 --- /dev/null +++ b/ta_lib/statistics/src/lib.rs @@ -0,0 +1 @@ +pub mod stddev; diff --git a/ta_lib/statistics/src/stddev.rs b/ta_lib/statistics/src/stddev.rs new file mode 100644 index 00000000..e7af9538 --- /dev/null +++ b/ta_lib/statistics/src/stddev.rs @@ -0,0 +1,49 @@ +pub fn std_dev(data: &[f64], period: usize) -> Vec> { + let mut std_dev = vec![None; data.len()]; + let mut sum = 0.0; + let mut sum_sq = 0.0; + + for (i, &value) in data.iter().enumerate() { + sum += value; + sum_sq += value * value; + + if i >= period { + let old_value = data[i - period]; + sum -= old_value; + sum_sq -= old_value * old_value; + } + + if i + 1 >= period { + let mean = sum / period as f64; + let mean_sq = sum_sq / period as f64; + std_dev[i] = Some((mean_sq - mean * mean).sqrt()); + } + } + + std_dev +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_std_dev() { + let data = 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); + + for i in 0..result.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]), + } + } + } +}