diff --git a/.github/workflows/gate.yml b/.github/workflows/gate.yml new file mode 100644 index 0000000..023d59e --- /dev/null +++ b/.github/workflows/gate.yml @@ -0,0 +1,39 @@ +name: gate + +on: + push: + branches: + - main + pull_request: + +jobs: + check-style: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: setup rust + uses: actions-rust-lang/setup-rust-toolchain@v1 + with: + components: rustfmt + - name: check style + uses: actions-rust-lang/rustfmt@v1 + + analyze: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: setup rust + uses: actions-rust-lang/setup-rust-toolchain@v1 + with: + components: clippy + - name: lint + run: cargo clippy + + run-unit-tests: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: setup rust + uses: actions-rust-lang/setup-rust-toolchain@v1 + - name: run unit tests + run: cargo test diff --git a/src/indicator.rs b/src/indicator.rs index d4a03b2..dd01f81 100644 --- a/src/indicator.rs +++ b/src/indicator.rs @@ -2,7 +2,7 @@ use itertools::{izip, multiunzip}; use crate::smooth; -fn vforce(h: &Vec, l: &Vec, c: &Vec, v: &Vec) -> Vec { +fn vforce(h: &[f64], l: &[f64], c: &[f64], v: &[f64]) -> Vec { izip!(&h[1..], &l[1..], &c[1..], &v[1..]) .scan( (h[0], l[0], c[0], 99, 0.0, h[0] - l[0]), @@ -31,14 +31,7 @@ fn vforce(h: &Vec, l: &Vec, c: &Vec, v: &Vec) -> Vec { /// klinger oscillator /// https://www.investopedia.com/terms/k/klingeroscillator.asp -pub fn klinger( - h: &Vec, - l: &Vec, - c: &Vec, - v: &Vec, - short: u8, - long: u8, -) -> Vec { +pub fn klinger(h: &[f64], l: &[f64], c: &[f64], v: &[f64], short: u8, long: u8) -> Vec { let vf = vforce(h, l, c, v); let short_ma = smooth::ewma(&vf, short); let long_ma = smooth::ewma(&vf, long); @@ -49,7 +42,7 @@ pub fn klinger( .collect::>() } -fn vforce_simple(h: &Vec, l: &Vec, c: &Vec, v: &Vec) -> Vec { +fn vforce_simple(h: &[f64], l: &[f64], c: &[f64], v: &[f64]) -> Vec { izip!(&h[1..], &l[1..], &c[1..], &v[1..]) .scan((h[0], l[0], c[0], 99), |state, (h, l, c, v)| { let trend: i8 = { @@ -67,14 +60,7 @@ fn vforce_simple(h: &Vec, l: &Vec, c: &Vec, v: &Vec) -> Vec< /// klinger volume oscillator /// designed to match yahoo -pub fn klinger_vol( - h: &Vec, - l: &Vec, - c: &Vec, - v: &Vec, - short: u8, - long: u8, -) -> Vec { +pub fn klinger_vol(h: &[f64], l: &[f64], c: &[f64], v: &[f64], short: u8, long: u8) -> Vec { let vf = vforce_simple(h, l, c, v); let short_ma = smooth::ewma(&vf, short); let long_ma = smooth::ewma(&vf, long); @@ -87,7 +73,7 @@ pub fn klinger_vol( /// quick stick /// https://www.investopedia.com/terms/q/qstick.asp -pub fn qstick(o: &Vec, c: &Vec, window: u8) -> Vec { +pub fn qstick(o: &[f64], c: &[f64], window: u8) -> Vec { let q = c .iter() .zip(o.iter()) @@ -99,7 +85,7 @@ pub fn qstick(o: &Vec, c: &Vec, window: u8) -> Vec { /// twiggs money flow /// https://www.marketvolume.com/technicalanalysis/twiggsmoneyflow.asp /// https://www.incrediblecharts.com/indicators/twiggs_money_flow.php -pub fn twiggs(h: &Vec, l: &Vec, c: &Vec, v: &Vec, window: u8) -> Vec { +pub fn twiggs(h: &[f64], l: &[f64], c: &[f64], v: &[f64], window: u8) -> Vec { let data = izip!(h, l, c, v); let ma_range = smooth::wilder( &data @@ -124,7 +110,7 @@ pub fn twiggs(h: &Vec, l: &Vec, c: &Vec, v: &Vec, window: u8 /// shinohara intensity ratio /// https://www.sevendata.co.jp/shihyou/technical/shinohara.html -pub fn shinohara(h: &Vec, l: &Vec, c: &Vec, period: u8) -> (Vec, Vec) { +pub fn shinohara(h: &[f64], l: &[f64], c: &[f64], period: u8) -> (Vec, Vec) { // yahoo uses close rather than open for weak ratio described above let high = h .windows(period.into()) @@ -150,9 +136,9 @@ pub fn shinohara(h: &Vec, l: &Vec, c: &Vec, period: u8) -> (Vec, - l: &Vec, - c: &Vec, + h: &[f64], + l: &[f64], + c: &[f64], period: u8, smoothing: u8, ) -> (Vec, Vec, Vec) { @@ -194,7 +180,7 @@ pub fn adx( /// relative strength index /// https://www.investopedia.com/terms/r/rsi.asp -pub fn rsi(values: &Vec, window: u8) -> Vec { +pub fn rsi(values: &[f64], window: u8) -> Vec { let (gain, loss): (Vec, Vec) = values[1..] .iter() .zip(values[..values.len() - 1].iter()) @@ -209,7 +195,7 @@ pub fn rsi(values: &Vec, window: u8) -> Vec { /// moving average convergence/divergence /// https://www.investopedia.com/terms/m/macd.asp -pub fn macd(close: &Vec, fast: u8, slow: u8) -> Vec { +pub fn macd(close: &[f64], fast: u8, slow: u8) -> Vec { let fast_ma = smooth::ewma(close, fast); let slow_ma = smooth::ewma(close, slow); fast_ma[fast_ma.len() - slow_ma.len()..] @@ -221,7 +207,7 @@ pub fn macd(close: &Vec, fast: u8, slow: u8) -> Vec { /// chande momentum oscillator /// https://www.investopedia.com/terms/c/chandemomentumoscillator.asp -pub fn cmo(data: &Vec, window: u8) -> Vec { +pub fn cmo(data: &[f64], window: u8) -> Vec { smooth::_cmo(data, window) .iter() .map(|x| x * 100.0) @@ -230,7 +216,7 @@ pub fn cmo(data: &Vec, window: u8) -> Vec { /// centre of gravity /// https://www.stockmaniacs.net/center-of-gravity-indicator/ -pub fn cog(data: &Vec, window: u8) -> Vec { +pub fn cog(data: &[f64], window: u8) -> Vec { data.windows(window.into()) .map(|w| { -w.iter() @@ -245,7 +231,7 @@ pub fn cog(data: &Vec, window: u8) -> Vec { /// accumulation/distribution /// https://www.investopedia.com/terms/a/accumulationdistribution.asp -pub fn acc_dist(h: &Vec, l: &Vec, c: &Vec, v: &Vec) -> Vec { +pub fn acc_dist(h: &[f64], l: &[f64], c: &[f64], v: &[f64]) -> Vec { izip!(h, l, c, v) .scan(0.0, |state, (high, low, close, vol)| { let mfm = ((close - low) - (high - close)) / (high - low); @@ -259,7 +245,7 @@ pub fn acc_dist(h: &Vec, l: &Vec, c: &Vec, v: &Vec) -> Vec, l: &Vec, c: &Vec, v: &Vec) -> Vec { +pub fn acc_dist_yahoo(h: &[f64], l: &[f64], c: &[f64], v: &[f64]) -> Vec { izip!(&h[1..], &l[1..], &c[1..], &v[1..]) .scan((c[0], 0.0), |state, (high, low, close, vol)| { let mfm = if *close > state.0 { @@ -277,7 +263,7 @@ pub fn acc_dist_yahoo(h: &Vec, l: &Vec, c: &Vec, v: &Vec) -> /// elder ray /// https://www.investopedia.com/articles/trading/03/022603.asp -pub fn elder_ray(h: &Vec, l: &Vec, c: &Vec, window: u8) -> (Vec, Vec) { +pub fn elder_ray(h: &[f64], l: &[f64], c: &[f64], window: u8) -> (Vec, Vec) { let close_ma = smooth::ewma(c, window); izip!( &h[h.len() - close_ma.len()..], @@ -290,7 +276,7 @@ pub fn elder_ray(h: &Vec, l: &Vec, c: &Vec, window: u8) -> (Vec, v: &Vec, window: u8) -> Vec { +pub fn elder_force(c: &[f64], v: &[f64], window: u8) -> Vec { smooth::ewma( &izip!(&c[..c.len() - 1], &c[1..], &v[1..]) .map(|(prev, curr, vol)| (curr - prev) * vol) @@ -301,11 +287,11 @@ pub fn elder_force(c: &Vec, v: &Vec, window: u8) -> Vec { /// williams alligator /// https://www.investopedia.com/articles/trading/072115/exploring-williams-alligator-indicator.asp -pub fn alligator(data: &Vec) {} +pub fn alligator(_data: &[f64]) {} /// money flow index /// https://www.investopedia.com/terms/m/mfi.asp -pub fn mfi(h: &Vec, l: &Vec, c: &Vec, v: &Vec, window: u8) -> Vec { +pub fn mfi(h: &[f64], l: &[f64], c: &[f64], v: &[f64], window: u8) -> Vec { let (pos_mf, neg_mf): (Vec, Vec) = izip!(&h[1..], &l[1..], &c[1..], &v[1..]) .scan( (h[0] + l[0] + c[0]) / 3.0, diff --git a/src/smooth.rs b/src/smooth.rs index ebc88e6..a7b781f 100644 --- a/src/smooth.rs +++ b/src/smooth.rs @@ -47,7 +47,7 @@ pub fn wma(data: &[f64], window: u8) -> Vec { } /// welles wilder's moving average -pub fn wilder(data: &Vec, window: u8) -> Vec { +pub fn wilder(data: &[f64], window: u8) -> Vec { data[window as usize..] .iter() .scan( @@ -62,9 +62,9 @@ pub fn wilder(data: &Vec, window: u8) -> Vec { } /// hull's moving average -pub fn hull(data: &Vec, window: u8) -> Vec { - let ma = wma(&data, window); - let ma2 = wma(&data, u8::div_ceil(window, 2)); +pub fn hull(data: &[f64], window: u8) -> Vec { + let ma = wma(data, window); + let ma2 = wma(data, u8::div_ceil(window, 2)); wma( &ma2[(ma2.len() - ma.len())..] .iter() @@ -76,7 +76,7 @@ pub fn hull(data: &Vec, window: u8) -> Vec { } /// standard deviation -fn std_dev(data: &Vec, window: u8) -> Vec { +fn std_dev(data: &[f64], window: u8) -> Vec { data.windows(window.into()) .map(|w| { let mean = w.iter().sum::() / window as f64; @@ -86,7 +86,7 @@ fn std_dev(data: &Vec, window: u8) -> Vec { } /// volatility index dynamic average (vidya) -pub fn vidya(data: &Vec, window: u8) -> Vec { +pub fn vidya(data: &[f64], window: u8) -> Vec { let alpha = 2.0 / (window + 1) as f64; let std5 = std_dev(data, 5); let std20 = sma(&std5, 20); @@ -103,7 +103,7 @@ pub fn vidya(data: &Vec, window: u8) -> Vec { } /// chande momentum oscillator -pub(crate) fn _cmo(data: &Vec, window: u8) -> Vec { +pub(crate) fn _cmo(data: &[f64], window: u8) -> Vec { let (gain, loss): (Vec, Vec) = data[..data.len() - 1] .iter() .zip(data[1..].iter()) @@ -120,7 +120,7 @@ pub(crate) fn _cmo(data: &Vec, window: u8) -> Vec { } /// variable moving average (vma) -pub fn vma(data: &Vec, window: u8) -> Vec { +pub fn vma(data: &[f64], window: u8) -> Vec { let alpha = 2.0 / (window + 1) as f64; let vi = _cmo(data, 9); // maybe make this configurable? izip!(&vi, &data[data.len() - vi.len()..])