Skip to content

Commit

Permalink
separate volume indicators (#27)
Browse files Browse the repository at this point in the history
... and other cleanup
  • Loading branch information
chungg authored May 22, 2024
1 parent f9345de commit 1e1d2b0
Show file tree
Hide file tree
Showing 7 changed files with 438 additions and 429 deletions.
6 changes: 4 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -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

Expand All @@ -14,5 +16,5 @@ serde_json = "*"
criterion = "0.5.1"

[[bench]]
name = "my_benchmark"
name = "traquer"
harness = false
16 changes: 8 additions & 8 deletions benches/my_benchmark.rs → benches/traquer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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,
Expand Down Expand Up @@ -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,
Expand All @@ -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,
Expand All @@ -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,
Expand All @@ -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,
Expand Down Expand Up @@ -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(|| {
Expand Down
170 changes: 1 addition & 169 deletions src/indicator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,42 +2,6 @@ use itertools::{izip, multiunzip};

use crate::smooth;

fn vforce(high: &[f64], low: &[f64], close: &[f64], volume: &[f64]) -> Vec<f64> {
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::<Vec<f64>>()
}

/// 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<f64> {
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::<Vec<f64>>()
}

/// quick stick
/// https://www.investopedia.com/terms/q/qstick.asp
pub fn qstick(open: &[f64], close: &[f64], window: usize) -> Vec<f64> {
Expand All @@ -49,38 +13,6 @@ pub fn qstick(open: &[f64], close: &[f64], window: usize) -> Vec<f64> {
smooth::ewma(&q, window).collect::<Vec<f64>>()
}

fn wilder_sum(data: &[f64], window: usize) -> impl Iterator<Item = f64> + '_ {
let initial = data[..(window - 1)].iter().sum::<f64>();
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<f64> {
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::<Vec<f64>>(),
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<f64>, Vec<f64>) {
Expand Down Expand Up @@ -209,38 +141,6 @@ pub fn cog(data: &[f64], window: usize) -> Vec<f64> {
.collect::<Vec<f64>>()
}

/// accumulation/distribution
/// https://www.investopedia.com/terms/a/accumulationdistribution.asp
pub fn ad(high: &[f64], low: &[f64], close: &[f64], volume: &[f64]) -> Vec<f64> {
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::<Vec<f64>>()
}

/// accumulation/distribution
/// like yahoo
pub fn ad_yahoo(high: &[f64], low: &[f64], close: &[f64], volume: &[f64]) -> Vec<f64> {
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::<Vec<f64>>()
}

/// elder ray
/// https://www.investopedia.com/articles/trading/03/022603.asp
/// returns tuple of bull power vec and bear power vec
Expand All @@ -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<f64> {
smooth::ewma(
&izip!(&close[..close.len() - 1], &close[1..], &volume[1..])
.map(|(prev, curr, vol)| (curr - prev) * vol)
.collect::<Vec<f64>>(),
window,
)
.collect::<Vec<f64>>()
}

/// 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<f64> {
let (pos_mf, neg_mf): (Vec<f64>, Vec<f64>) =
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::<f64>() / neg.iter().sum::<f64>()))
})
.collect::<Vec<f64>>()
}

/// 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<f64> {
izip!(high, low, close, volume)
.map(|(h, l, c, vol)| vol * ((c - l) - (h - c)) / (h - l))
.collect::<Vec<f64>>()
.windows(window)
.zip(volume.windows(window))
.map(|(mfv_win, v_win)| mfv_win.iter().sum::<f64>() / v_win.iter().sum::<f64>())
.collect::<Vec<f64>>()
}

/// chaikin volatility
/// https://www.tradingview.com/chart/AUDUSD/gjfxqWqW-What-Is-a-Chaikin-Volatility-Indicator-in-Trading/
/// https://theforexgeek.com/chaikins-volatility-indicator/
Expand Down Expand Up @@ -623,25 +474,6 @@ pub fn tii(data: &[f64], window: usize) -> Vec<f64> {
.collect::<Vec<f64>>()
}

/// trade volume index
/// https://www.investopedia.com/terms/t/tradevolumeindex.asp
pub fn tvi(close: &[f64], volume: &[f64], min_tick: f64) -> Vec<f64> {
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::<Vec<f64>>()
}

/// supertrend
/// https://www.tradingview.com/support/solutions/43000634738-supertrend/
/// https://www.investopedia.com/supertrend-indicator-7976167
Expand Down Expand Up @@ -727,7 +559,7 @@ fn _stc(series: &[f64], window: usize) -> Vec<f64> {
.collect::<Vec<f64>>()
}

/// 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<f64> {
Expand Down
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@
pub mod indicator;
pub mod smooth;
pub mod volume;
Loading

0 comments on commit 1e1d2b0

Please sign in to comment.