use crate::indicators::av;
use crate::kernels;
fn rolling_sum(x: &[f64], n: usize) -> Vec<f64> {
(kernels::sma(av(x), n) * n as f64).to_vec()
}
pub fn vortex(high: &[f64], low: &[f64], close: &[f64], n: usize, plus: bool) -> Vec<f64> {
let len = high.len();
let tr = super::tr(high, low, close); let vm: Vec<f64> = (0..len)
.map(|i| {
if i == 0 {
f64::NAN
} else if plus {
(high[i] - low[i - 1]).abs()
} else {
(low[i] - high[i - 1]).abs()
}
})
.collect();
let sum_vm = rolling_sum(&vm, n);
let sum_tr = rolling_sum(&tr, n);
(0..len).map(|i| sum_vm[i] / sum_tr[i]).collect()
}
pub fn brar_ar(open: &[f64], high: &[f64], low: &[f64], n: usize) -> Vec<f64> {
let len = high.len();
let ho: Vec<f64> = (0..len).map(|i| high[i] - open[i]).collect();
let ol: Vec<f64> = (0..len).map(|i| open[i] - low[i]).collect();
let (s_ho, s_ol) = (rolling_sum(&ho, n), rolling_sum(&ol, n));
(0..len).map(|i| s_ho[i] / s_ol[i] * 100.0).collect()
}
pub fn brar_br(high: &[f64], low: &[f64], close: &[f64], n: usize) -> Vec<f64> {
let len = high.len();
let up: Vec<f64> = (0..len)
.map(|i| if i == 0 { f64::NAN } else { (high[i] - close[i - 1]).max(0.0) })
.collect();
let dn: Vec<f64> = (0..len)
.map(|i| if i == 0 { f64::NAN } else { (close[i - 1] - low[i]).max(0.0) })
.collect();
let (s_up, s_dn) = (rolling_sum(&up, n), rolling_sum(&dn, n));
(0..len).map(|i| s_up[i] / s_dn[i] * 100.0).collect()
}
pub fn vr(close: &[f64], volume: &[f64], n: usize) -> Vec<f64> {
let len = close.len();
let (mut uv, mut dv, mut pv) = (vec![f64::NAN; len], vec![f64::NAN; len], vec![f64::NAN; len]);
for i in 1..len {
let (mut u, mut d, mut p) = (0.0, 0.0, 0.0);
if close[i] > close[i - 1] {
u = volume[i];
} else if close[i] < close[i - 1] {
d = volume[i];
} else {
p = volume[i];
}
uv[i] = u;
dv[i] = d;
pv[i] = p;
}
let (suv, sdv, spv) = (rolling_sum(&uv, n), rolling_sum(&dv, n), rolling_sum(&pv, n));
(0..len)
.map(|i| (suv[i] + 0.5 * spv[i]) / (sdv[i] + 0.5 * spv[i]) * 100.0)
.collect()
}
pub fn coppock(close: &[f64], w: usize, roc_long: usize, roc_short: usize) -> Vec<f64> {
let len = close.len();
let roc = |p: usize| -> Vec<f64> {
(0..len)
.map(|i| if i >= p { (close[i] / close[i - p] - 1.0) * 100.0 } else { f64::NAN })
.collect::<Vec<_>>()
};
let (rl, rs) = (roc(roc_long), roc(roc_short));
let sum: Vec<f64> = (0..len).map(|i| rl[i] + rs[i]).collect();
super::wma(&sum, w)
}
fn swma4(x: &[f64]) -> Vec<f64> {
(0..x.len())
.map(|i| {
if i >= 3 {
(x[i] + 2.0 * x[i - 1] + 2.0 * x[i - 2] + x[i - 3]) / 6.0
} else {
f64::NAN
}
})
.collect()
}
pub fn relative_vigor(open: &[f64], high: &[f64], low: &[f64], close: &[f64], n: usize) -> Vec<f64> {
let len = close.len();
let co: Vec<f64> = (0..len).map(|i| close[i] - open[i]).collect();
let hl: Vec<f64> = (0..len).map(|i| high[i] - low[i]).collect();
let num = kernels::sma(av(&swma4(&co)), n);
let den = kernels::sma(av(&swma4(&hl)), n);
(0..len).map(|i| num[i] / den[i]).collect()
}
pub fn relative_vigor_signal(
open: &[f64],
high: &[f64],
low: &[f64],
close: &[f64],
n: usize,
) -> Vec<f64> {
swma4(&relative_vigor(open, high, low, close, n))
}
pub fn dkx(open: &[f64], high: &[f64], low: &[f64], close: &[f64]) -> Vec<f64> {
let mid: Vec<f64> = (0..close.len())
.map(|i| (3.0 * close[i] + low[i] + open[i] + high[i]) / 6.0)
.collect();
super::wma(&mid, 20)
}
pub fn dkx_ma(open: &[f64], high: &[f64], low: &[f64], close: &[f64], m: usize) -> Vec<f64> {
super::ma(&dkx(open, high, low, close), m)
}
pub fn wvad(
open: &[f64],
high: &[f64],
low: &[f64],
close: &[f64],
volume: &[f64],
n: usize,
) -> Vec<f64> {
let w: Vec<f64> = (0..close.len())
.map(|i| (close[i] - open[i]) / (high[i] - low[i]) * volume[i])
.collect();
rolling_sum(&w, n)
}
#[derive(Clone, Copy)]
pub enum CdpLine {
Cdp,
Ah,
Nh,
Nl,
Al,
}
pub fn cdp(high: &[f64], low: &[f64], close: &[f64], line: CdpLine) -> Vec<f64> {
(0..high.len())
.map(|i| {
if i == 0 {
return f64::NAN;
}
let (h, l, c) = (high[i - 1], low[i - 1], close[i - 1]);
let cdp = (h + l + 2.0 * c) / 4.0;
match line {
CdpLine::Cdp => cdp,
CdpLine::Ah => cdp + (h - l),
CdpLine::Nh => 2.0 * cdp - l,
CdpLine::Nl => 2.0 * cdp - h,
CdpLine::Al => cdp - (h - l),
}
})
.collect()
}
pub fn wad(high: &[f64], low: &[f64], close: &[f64]) -> Vec<f64> {
let mut out = vec![0.0; close.len()];
let mut acc = 0.0;
for i in 1..close.len() {
acc += wad_step(high[i], low[i], close[i], close[i - 1]);
out[i] = acc;
}
out
}
fn wad_step(h: f64, l: f64, c: f64, prev_c: f64) -> f64 {
if c > prev_c {
c - prev_c.min(l)
} else if c < prev_c {
c - prev_c.max(h)
} else {
0.0
}
}
pub fn wad_final_state(high: &[f64], low: &[f64], close: &[f64]) -> Option<Vec<f64>> {
let n = close.len();
if n == 0 {
return None;
}
let mut acc = 0.0;
for i in 1..n {
acc += wad_step(high[i], low[i], close[i], close[i - 1]);
}
Some(vec![acc, close[n - 1]])
}
pub fn wad_resume(
high: &[f64],
low: &[f64],
close: &[f64],
from: usize,
state: &[f64],
) -> (Vec<f64>, Vec<f64>) {
let mut acc = state[0];
let mut prev = state[1];
let mut out = Vec::with_capacity(close.len().saturating_sub(from));
for i in from..close.len() {
acc += wad_step(high[i], low[i], close[i], prev);
out.push(acc);
prev = close[i];
}
(out, vec![acc, prev])
}
fn asi_si(o: f64, h: f64, l: f64, c: f64, op: f64, cp: f64, t: f64) -> f64 {
let n = (c - cp) + 0.5 * (c - o) + 0.25 * (cp - op);
let (tr1, tr2, tr3) = ((h - cp).abs(), (l - cp).abs(), (h - l).abs());
let move_term = 0.25 * (cp - op).abs();
let r = if tr1 >= tr2 && tr1 >= tr3 {
tr1 - 0.5 * tr2 + move_term
} else if tr2 >= tr1 && tr2 >= tr3 {
tr2 - 0.5 * tr1 + move_term
} else {
tr3 + move_term
};
let k = tr1.max(tr2);
50.0 * (n / r) * (k / t)
}
pub fn asi(open: &[f64], high: &[f64], low: &[f64], close: &[f64], t: f64) -> Vec<f64> {
let mut out = vec![0.0; close.len()];
let mut acc = 0.0;
for i in 1..close.len() {
acc += asi_si(open[i], high[i], low[i], close[i], open[i - 1], close[i - 1], t);
out[i] = acc;
}
out
}
pub fn asi_final_state(
open: &[f64],
high: &[f64],
low: &[f64],
close: &[f64],
t: f64,
) -> Option<Vec<f64>> {
let n = close.len();
if n == 0 {
return None;
}
let mut acc = 0.0;
for i in 1..n {
acc += asi_si(open[i], high[i], low[i], close[i], open[i - 1], close[i - 1], t);
}
Some(vec![acc, close[n - 1], open[n - 1]])
}
pub fn asi_resume(
open: &[f64],
high: &[f64],
low: &[f64],
close: &[f64],
t: f64,
from: usize,
state: &[f64],
) -> (Vec<f64>, Vec<f64>) {
let (mut acc, mut pc, mut po) = (state[0], state[1], state[2]);
let mut out = Vec::with_capacity(close.len().saturating_sub(from));
for i in from..close.len() {
acc += asi_si(open[i], high[i], low[i], close[i], po, pc, t);
out.push(acc);
pc = close[i];
po = open[i];
}
(out, vec![acc, pc, po])
}
#[derive(Clone, Copy)]
pub enum PivotLine {
Pp,
R1,
S1,
R2,
S2,
R3,
S3,
}
pub fn pivot_points(high: &[f64], low: &[f64], close: &[f64], line: PivotLine) -> Vec<f64> {
(0..high.len())
.map(|i| {
if i == 0 {
return f64::NAN;
}
let (h, l, c) = (high[i - 1], low[i - 1], close[i - 1]);
let pp = (h + l + c) / 3.0;
match line {
PivotLine::Pp => pp,
PivotLine::R1 => 2.0 * pp - l,
PivotLine::S1 => 2.0 * pp - h,
PivotLine::R2 => pp + (h - l),
PivotLine::S2 => pp - (h - l),
PivotLine::R3 => h + 2.0 * (pp - l),
PivotLine::S3 => l - 2.0 * (h - pp),
}
})
.collect()
}
#[derive(Clone, Copy)]
pub enum MikeLine {
WeakR,
MidR,
StrongR,
WeakS,
MidS,
StrongS,
}
pub fn mike(high: &[f64], low: &[f64], close: &[f64], n: usize, line: MikeLine) -> Vec<f64> {
let len = high.len();
let hh = super::hhv(high, n);
let ll = super::llv(low, n);
(0..len)
.map(|i| {
let typ = (high[i] + low[i] + close[i]) / 3.0;
let (hi, lo) = (hh[i], ll[i]);
match line {
MikeLine::WeakR => typ + (typ - lo),
MikeLine::MidR => typ + (hi - lo),
MikeLine::StrongR => 2.0 * hi - lo,
MikeLine::WeakS => typ - (hi - typ),
MikeLine::MidS => typ - (hi - lo),
MikeLine::StrongS => 2.0 * lo - hi,
}
})
.collect()
}
pub fn keltner_band(
close: &[f64],
high: &[f64],
low: &[f64],
ema_period: usize,
atr_period: usize,
mult: f64,
upper: bool,
) -> Vec<f64> {
let ema = super::ema(close, ema_period);
let atr = super::atr(high, low, close, atr_period);
let sign = if upper { 1.0 } else { -1.0 };
(0..close.len()).map(|i| ema[i] + sign * mult * atr[i]).collect()
}
pub fn keltner_band_final_state(
close: &[f64],
high: &[f64],
low: &[f64],
ema_period: usize,
atr_period: usize,
) -> Option<Vec<f64>> {
let e = super::ema_final_state(close, ema_period)?;
let a = super::atr_final_state(high, low, close, atr_period)?;
Some(vec![e[0], a[0]])
}
pub fn keltner_band_resume(
close: &[f64],
high: &[f64],
low: &[f64],
ema_period: usize,
atr_period: usize,
mult: f64,
upper: bool,
from: usize,
state: &[f64],
) -> Option<(Vec<f64>, Vec<f64>)> {
let (e_vals, e_state) = super::ema_resume(close, ema_period, from, &state[0..1]);
let (a_vals, a_state) = super::atr_resume(high, low, close, atr_period, from, &state[1..2])?;
let sign = if upper { 1.0 } else { -1.0 };
let vals: Vec<f64> = (0..e_vals.len()).map(|i| e_vals[i] + sign * mult * a_vals[i]).collect();
Some((vals, vec![e_state[0], a_state[0]]))
}
pub fn stoch_momentum(high: &[f64], low: &[f64], close: &[f64], k: usize, d: usize) -> Vec<f64> {
let len = high.len();
let hh = super::hhv(high, k);
let ll = super::llv(low, k);
let rdiff: Vec<f64> = (0..len).map(|i| close[i] - (hh[i] + ll[i]) / 2.0).collect();
let diff: Vec<f64> = (0..len).map(|i| hh[i] - ll[i]).collect();
let ds = super::ema(&super::ema(&rdiff, d), d);
let dhl = super::ema(&super::ema(&diff, d), d);
(0..len).map(|i| ds[i] / (dhl[i] * 0.5) * 100.0).collect()
}
pub fn stoch_momentum_signal(
high: &[f64],
low: &[f64],
close: &[f64],
k: usize,
d: usize,
signal: usize,
) -> Vec<f64> {
super::ema(&stoch_momentum(high, low, close, k, d), signal)
}
fn shift_forward(x: &[f64], k: usize) -> Vec<f64> {
(0..x.len()).map(|i| if i >= k { x[i - k] } else { f64::NAN }).collect()
}
#[derive(Clone, Copy)]
pub enum IchimokuLine {
Tenkan,
Kijun,
SenkouA,
SenkouB,
Chikou,
}
pub fn ichimoku(
high: &[f64],
low: &[f64],
close: &[f64],
tenkan: usize,
kijun: usize,
senkou_b: usize,
line: IchimokuLine,
) -> Vec<f64> {
let midpoint = |period: usize| -> Vec<f64> {
let hh = super::hhv(high, period);
let ll = super::llv(low, period);
(0..high.len()).map(|i| (hh[i] + ll[i]) / 2.0).collect()
};
match line {
IchimokuLine::Tenkan => midpoint(tenkan),
IchimokuLine::Kijun => midpoint(kijun),
IchimokuLine::SenkouA => {
let (t, k) = (midpoint(tenkan), midpoint(kijun));
let raw: Vec<f64> = (0..high.len()).map(|i| (t[i] + k[i]) / 2.0).collect();
shift_forward(&raw, kijun)
}
IchimokuLine::SenkouB => shift_forward(&midpoint(senkou_b), kijun),
IchimokuLine::Chikou => close.to_vec(),
}
}
pub fn ttm_squeeze_momentum(high: &[f64], low: &[f64], close: &[f64], n: usize) -> Vec<f64> {
let len = high.len();
let hh = super::hhv(high, n);
let ll = super::llv(low, n);
let sma = super::ma(close, n);
let delta: Vec<f64> = (0..len)
.map(|i| close[i] - ((hh[i] + ll[i]) / 2.0 + sma[i]) / 2.0)
.collect();
let start = delta.iter().position(|x| !x.is_nan()).unwrap_or(len);
let mut out = vec![f64::NAN; len];
if start < len {
let sub = super::linearreg(&delta[start..], n);
out[start..].copy_from_slice(&sub);
}
out
}
pub fn ttm_squeeze_on(
high: &[f64],
low: &[f64],
close: &[f64],
n: usize,
bb_mult: f64,
kc_mult: f64,
) -> Vec<f64> {
let len = high.len();
let sma = super::ma(close, n);
let sd = super::stddev(close, n, 1.0);
let tr = super::tr(high, low, close);
let atr = kernels::sma(av(&tr), n).to_vec(); (0..len)
.map(|i| {
let bb_u = sma[i] + bb_mult * sd[i];
let bb_l = sma[i] - bb_mult * sd[i];
let kc_u = sma[i] + kc_mult * atr[i];
let kc_l = sma[i] - kc_mult * atr[i];
if bb_u.is_nan() || kc_u.is_nan() {
f64::NAN
} else if bb_l > kc_l && bb_u < kc_u {
1.0
} else {
0.0
}
})
.collect()
}
fn supertrend_step(
hl2: f64,
atr: f64,
close: f64,
mult: f64,
prev_trend: f64,
prev_fu: f64,
prev_fl: f64,
prev_close: f64,
) -> (f64, f64, f64) {
let (bu, bl) = (hl2 + mult * atr, hl2 - mult * atr);
let fu = if bu < prev_fu || prev_close > prev_fu { bu } else { prev_fu };
let fl = if bl > prev_fl || prev_close < prev_fl { bl } else { prev_fl };
let trend = if prev_trend < 0.0 {
if close > fu { 1.0 } else { -1.0 }
} else if close < fl {
-1.0
} else {
1.0
};
(trend, fu, fl)
}
fn supertrend_out(trend: f64, fu: f64, fl: f64, want_direction: bool) -> f64 {
if want_direction {
trend
} else if trend > 0.0 {
fl
} else {
fu
}
}
pub fn supertrend(
high: &[f64],
low: &[f64],
close: &[f64],
period: usize,
mult: f64,
want_direction: bool,
) -> Vec<f64> {
let len = close.len();
let mut out = vec![f64::NAN; len];
let atr = super::atr(high, low, close, period);
if period >= len {
return out; }
let hl2 = |i: usize| (high[i] + low[i]) / 2.0;
let (mut trend, mut fu, mut fl, mut pc) = (
-1.0_f64,
hl2(period) + mult * atr[period],
hl2(period) - mult * atr[period],
close[period],
);
out[period] = supertrend_out(trend, fu, fl, want_direction);
for i in (period + 1)..len {
let step = supertrend_step(hl2(i), atr[i], close[i], mult, trend, fu, fl, pc);
(trend, fu, fl) = step;
out[i] = supertrend_out(trend, fu, fl, want_direction);
pc = close[i];
}
out
}
pub fn supertrend_final_state(
high: &[f64],
low: &[f64],
close: &[f64],
period: usize,
mult: f64,
) -> Option<Vec<f64>> {
let len = close.len();
let atr_end = super::atr_final_state(high, low, close, period)?;
let atr = super::atr(high, low, close, period);
let hl2 = |i: usize| (high[i] + low[i]) / 2.0;
let (mut trend, mut fu, mut fl, mut pc) = (
-1.0_f64,
hl2(period) + mult * atr[period],
hl2(period) - mult * atr[period],
close[period],
);
for i in (period + 1)..len {
let step = supertrend_step(hl2(i), atr[i], close[i], mult, trend, fu, fl, pc);
(trend, fu, fl) = step;
pc = close[i];
}
Some(vec![trend, fu, fl, close[len - 1], atr_end[0]])
}
pub fn supertrend_resume(
high: &[f64],
low: &[f64],
close: &[f64],
period: usize,
mult: f64,
want_direction: bool,
from: usize,
state: &[f64],
) -> Option<(Vec<f64>, Vec<f64>)> {
let (mut trend, mut fu, mut fl, mut pc) = (state[0], state[1], state[2], state[3]);
let (atr_tail, atr_new) = super::atr_resume(high, low, close, period, from, &state[4..5])?;
let hl2 = |i: usize| (high[i] + low[i]) / 2.0;
let mut out = Vec::with_capacity(close.len().saturating_sub(from));
for (j, i) in (from..close.len()).enumerate() {
let step = supertrend_step(hl2(i), atr_tail[j], close[i], mult, trend, fu, fl, pc);
(trend, fu, fl) = step;
out.push(supertrend_out(trend, fu, fl, want_direction));
pc = close[i];
}
Some((out, vec![trend, fu, fl, pc, atr_new[0]]))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::indicators::test_support::*;
#[test]
fn cumulative_final_state_declines_on_empty() {
assert!(wad_final_state(&[], &[], &[]).is_none());
assert!(asi_final_state(&[], &[], &[], &[], 3.0).is_none());
}
#[test]
fn cumulative_resume_is_bit_identical_to_full() {
let (high, low, close) = ohlc(160);
let open: Vec<f64> = close.iter().map(|c| c - 0.3).collect();
let wad_full = wad(&high, &low, &close);
let asi_full = asi(&open, &high, &low, &close, 3.0);
for &from in &[1usize, 2, 40, 80, 159] {
let st = wad_final_state(&high[..from], &low[..from], &close[..from]).unwrap();
assert_bits(&wad_resume(&high, &low, &close, from, &st).0, &wad_full[from..], "wad");
let st =
asi_final_state(&open[..from], &high[..from], &low[..from], &close[..from], 3.0)
.unwrap();
assert_bits(
&asi_resume(&open, &high, &low, &close, 3.0, from, &st).0,
&asi_full[from..],
"asi",
);
}
}
#[test]
fn supertrend_resume_and_guards() {
let (high, low, close) = ohlc(160);
assert!(supertrend(&high[..5], &low[..5], &close[..5], 10, 3.0, false)
.iter()
.all(|x| x.is_nan()));
assert!(supertrend_final_state(&high[..5], &low[..5], &close[..5], 10, 3.0).is_none());
let line_full = supertrend(&high, &low, &close, 10, 3.0, false);
let dir_full = supertrend(&high, &low, &close, 10, 3.0, true);
for &from in &[11usize, 20, 80, 159] {
let st = supertrend_final_state(&high[..from], &low[..from], &close[..from], 10, 3.0)
.unwrap();
let (line, _) =
supertrend_resume(&high, &low, &close, 10, 3.0, false, from, &st).unwrap();
assert_bits(&line, &line_full[from..], "supertrend");
let (dir, _) =
supertrend_resume(&high, &low, &close, 10, 3.0, true, from, &st).unwrap();
assert_bits(&dir, &dir_full[from..], "supertrend.direction");
}
}
}