use ndarray::Array1;
use super::av;
use super::stochastic::stoch_fastk;
use crate::kernels;
pub fn llv(data: &[f64], period: usize) -> Vec<f64> {
kernels::rolling_min(av(data), period)
.into_raw_vec_and_offset()
.0
}
pub fn hhv(data: &[f64], period: usize) -> Vec<f64> {
if period == 10 && period <= data.len() && !data.iter().any(|x| x.is_nan()) {
return hhv10_no_nan(data);
}
kernels::rolling_max(av(data), period)
.into_raw_vec_and_offset()
.0
}
fn hhv10_no_nan(data: &[f64]) -> Vec<f64> {
let n = data.len();
let mut out = vec![f64::NAN; n];
let src = data.as_ptr();
let dst = out.as_mut_ptr();
let mut highest_idx = 0usize;
let mut highest = unsafe { *src };
for i in 1..10 {
let value = unsafe { *src.add(i) };
if value > highest {
highest_idx = i;
highest = value;
}
}
unsafe {
*dst.add(9) = highest;
}
for today in 10..n {
let trailing = today - 9;
let x = unsafe { *src.add(today) };
if highest_idx < trailing {
highest_idx = trailing;
highest = unsafe { *src.add(trailing) };
let mut idx = trailing + 1;
while idx <= today {
let value = unsafe { *src.add(idx) };
if value > highest {
highest_idx = idx;
highest = value;
}
idx += 1;
}
} else if x >= highest {
highest_idx = today;
highest = x;
}
unsafe {
*dst.add(today) = highest;
}
}
out
}
pub fn rsv(high: &[f64], low: &[f64], close: &[f64], period: usize) -> Vec<f64> {
let llv = kernels::rolling_min(av(low), period);
let hhv = kernels::rolling_max(av(high), period);
let n = close.len();
let mut result = vec![f64::NAN; n];
for i in 0..n {
let denom = hhv[i] - llv[i];
if denom.abs() > 1e-10 {
result[i] = (close[i] - llv[i]) / denom * 100.0;
} else {
result[i] = 0.0;
}
}
result
}
fn stochrsi_ctx_depth(fastk_period: usize, is_d: bool, fastd_period: usize) -> usize {
let k = fastk_period.saturating_sub(1);
if is_d {
k + fastd_period.saturating_sub(1)
} else {
k
}
}
pub fn stochrsi_final_state(
close: &[f64],
rsi_period: usize,
fastk_period: usize,
is_d: bool,
fastd_period: usize,
) -> Option<Vec<f64>> {
let n = close.len();
let wilder = rsi_final_state(close, rsi_period)?; let c = stochrsi_ctx_depth(fastk_period, is_d, fastd_period);
if n < c || n - c < rsi_period {
return None;
}
let rsi = rsi(close, rsi_period);
let mut state = wilder;
state.extend_from_slice(&rsi[n - c..n]);
Some(state)
}
pub fn stochrsi_resume(
close: &[f64],
rsi_period: usize,
fastk_period: usize,
is_d: bool,
fastd_period: usize,
from: usize,
state: &[f64],
) -> Option<(Vec<f64>, Vec<f64>)> {
let n = close.len();
let c = stochrsi_ctx_depth(fastk_period, is_d, fastd_period);
if state.len() != c + 2 || from == 0 || from > n || from < c {
return None;
}
let (new_rsi, new_wilder) = rsi_resume(close, rsi_period, from, &state[..2])?;
let mut buf = Vec::with_capacity(c + new_rsi.len());
buf.extend_from_slice(&state[2..]); buf.extend_from_slice(&new_rsi); if buf.iter().any(|x| x.is_nan()) {
return None;
}
let fk = stoch_fastk(&buf, &buf, &buf, fastk_period);
let line = if is_d {
super::ma(&fk, fastd_period)
} else {
fk
};
let out: Vec<f64> = line[c..].to_vec();
debug_assert_eq!(out.len(), n - from);
let mut new_state = new_wilder;
let full_rsi_len = c + new_rsi.len(); new_state.extend_from_slice(&buf[full_rsi_len - c..]);
Some((out, new_state))
}
fn kdj_rsv(high: &[f64], low: &[f64], close: &[f64], period_rsv: usize) -> Array1<f64> {
let llv = kernels::rolling_min(av(low), period_rsv);
let hhv = kernels::rolling_max(av(high), period_rsv);
let n = close.len();
let mut rsv = Array1::from_elem(n, 0.0);
for i in 0..n {
let denom = hhv[i] - llv[i];
if denom.abs() > 1e-10 {
rsv[i] = (close[i] - llv[i]) / denom * 100.0;
}
}
rsv
}
pub fn kdj_k(
high: &[f64],
low: &[f64],
close: &[f64],
period_rsv: usize,
period_k: usize,
init: f64,
) -> Vec<f64> {
let rsv = kdj_rsv(high, low, close, period_rsv);
kernels::ewma_with_init(rsv.view(), period_k, init).to_vec()
}
pub fn kdj_d(
high: &[f64],
low: &[f64],
close: &[f64],
period_rsv: usize,
period_k: usize,
period_d: usize,
init: f64,
) -> Vec<f64> {
let rsv = kdj_rsv(high, low, close, period_rsv);
let k = kernels::ewma_with_init(rsv.view(), period_k, init);
kernels::ewma_with_init(k.view(), period_d, init).to_vec()
}
pub fn kdj_j(
high: &[f64],
low: &[f64],
close: &[f64],
period_rsv: usize,
period_k: usize,
period_d: usize,
init: f64,
) -> Vec<f64> {
let rsv = kdj_rsv(high, low, close, period_rsv);
let k = kernels::ewma_with_init(rsv.view(), period_k, init);
let d = kernels::ewma_with_init(k.view(), period_d, init);
(3.0 * &k - 2.0 * &d).to_vec()
}
#[derive(Clone, Copy)]
pub enum KdjLine {
K,
D,
J,
}
#[allow(clippy::too_many_arguments)]
pub fn kdj_final_state(
high: &[f64],
low: &[f64],
close: &[f64],
period_rsv: usize,
period_k: usize,
period_d: usize,
init: f64,
want_d: bool,
) -> Option<Vec<f64>> {
let n = close.len();
if n == 0 {
return None;
}
let rsv = kdj_rsv(high, low, close, period_rsv);
let k = kernels::ewma_with_init(rsv.view(), period_k, init);
if !want_d {
return Some(vec![k[n - 1]]);
}
let d = kernels::ewma_with_init(k.view(), period_d, init);
Some(vec![k[n - 1], d[n - 1]])
}
#[allow(clippy::too_many_arguments)]
pub fn kdj_resume(
high: &[f64],
low: &[f64],
close: &[f64],
period_rsv: usize,
period_k: usize,
period_d: usize,
line: KdjLine,
from: usize,
state: &[f64],
) -> Option<(Vec<f64>, Vec<f64>)> {
let n = close.len();
if period_rsv == 0 || from == 0 || from > n || from + 1 < period_rsv || state.is_empty() {
return None;
}
let lo = from + 1 - period_rsv;
let rsv = kdj_rsv(&high[lo..n], &low[lo..n], &close[lo..n], period_rsv);
let skip = from - lo; let (ak, bk) = (1.0 / period_k as f64, 1.0 - 1.0 / period_k as f64);
let mut k_prev = state[0];
match line {
KdjLine::K => {
let mut out = Vec::with_capacity(n - from);
for &r in rsv.iter().skip(skip) {
k_prev = bk * k_prev + ak * r;
out.push(k_prev);
}
Some((out, vec![k_prev]))
}
KdjLine::D | KdjLine::J => {
if state.len() < 2 {
return None;
}
let (ad, bd) = (1.0 / period_d as f64, 1.0 - 1.0 / period_d as f64);
let mut d_prev = state[1];
let is_j = matches!(line, KdjLine::J);
let mut out = Vec::with_capacity(n - from);
for &r in rsv.iter().skip(skip) {
k_prev = bk * k_prev + ak * r;
d_prev = bd * d_prev + ad * k_prev;
out.push(if is_j {
3.0 * k_prev - 2.0 * d_prev
} else {
d_prev
});
}
Some((out, vec![k_prev, d_prev]))
}
}
}
fn wilder_gain_loss(data: &[f64], period: usize) -> (Array1<f64>, Array1<f64>) {
let n = data.len();
let delta = kernels::diff(av(data));
let mut gains = Array1::from_elem(n, f64::NAN);
let mut losses = Array1::from_elem(n, f64::NAN);
for i in 1..n {
let d = delta[i];
if d.is_nan() {
continue;
}
gains[i] = d.max(0.0);
losses[i] = (-d).max(0.0);
}
(
kernels::wilder(gains.view(), period),
kernels::wilder(losses.view(), period),
)
}
pub fn rsi(close: &[f64], period: usize) -> Vec<f64> {
let n = close.len();
let mut out = vec![f64::NAN; n];
if period == 0 || n <= period {
return out;
}
let pf = period as f64;
let p1 = pf - 1.0;
let emit = |g: f64, l: f64| {
if l.abs() < 1e-10 {
100.0
} else {
100.0 - 100.0 / (1.0 + g / l)
}
};
let mut avg_gain = 0.0;
let mut avg_loss = 0.0;
for i in 1..=period {
let d = close[i] - close[i - 1];
if d > 0.0 {
avg_gain += d;
} else {
avg_loss -= d;
}
}
avg_gain /= pf;
avg_loss /= pf;
out[period] = emit(avg_gain, avg_loss);
for i in (period + 1)..n {
let d = close[i] - close[i - 1];
let (gain, loss) = if d > 0.0 { (d, 0.0) } else { (0.0, -d) };
avg_gain = (avg_gain * p1 + gain) / pf;
avg_loss = (avg_loss * p1 + loss) / pf;
out[i] = emit(avg_gain, avg_loss);
}
out
}
pub fn cmo(close: &[f64], period: usize) -> Vec<f64> {
let (sg, sl) = wilder_gain_loss(close, period);
let n = close.len();
let mut result = vec![f64::NAN; n];
for i in 0..n {
if sg[i].is_nan() || sl[i].is_nan() {
continue;
}
let denom = sg[i] + sl[i];
result[i] = if denom < 1e-14 {
0.0
} else {
100.0 * (sg[i] - sl[i]) / denom
};
}
result
}
pub fn rsi_final_state(close: &[f64], period: usize) -> Option<Vec<f64>> {
let n = close.len();
if period == 0 || n <= period {
return None;
}
let pf = period as f64;
let p1 = pf - 1.0;
let (mut avg_gain, mut avg_loss) = (0.0, 0.0);
for i in 1..=period {
let d = close[i] - close[i - 1];
if d > 0.0 {
avg_gain += d;
} else {
avg_loss -= d;
}
}
avg_gain /= pf;
avg_loss /= pf;
for i in (period + 1)..n {
let d = close[i] - close[i - 1];
let (gain, loss) = if d > 0.0 { (d, 0.0) } else { (0.0, -d) };
avg_gain = (avg_gain * p1 + gain) / pf;
avg_loss = (avg_loss * p1 + loss) / pf;
}
Some(vec![avg_gain, avg_loss])
}
pub fn rsi_resume(
close: &[f64],
period: usize,
from: usize,
state: &[f64],
) -> Option<(Vec<f64>, Vec<f64>)> {
if from == 0 {
return None;
}
let n = close.len();
let pf = period as f64;
let p1 = pf - 1.0;
let emit = |g: f64, l: f64| {
if l.abs() < 1e-10 {
100.0
} else {
100.0 - 100.0 / (1.0 + g / l)
}
};
let (mut avg_gain, mut avg_loss) = (state[0], state[1]);
let mut out = Vec::with_capacity(n.saturating_sub(from));
for i in from..n {
let d = close[i] - close[i - 1];
let (gain, loss) = if d > 0.0 { (d, 0.0) } else { (0.0, -d) };
avg_gain = (avg_gain * p1 + gain) / pf;
avg_loss = (avg_loss * p1 + loss) / pf;
out.push(emit(avg_gain, avg_loss));
}
Some((out, vec![avg_gain, avg_loss]))
}
pub fn cmo_final_state(close: &[f64], period: usize) -> Option<Vec<f64>> {
let n = close.len();
if period == 0 || n <= period {
return None;
}
let pf = period as f64;
let (a, b) = ((pf - 1.0) / pf, 1.0 / pf);
let (mut avg_gain, mut avg_loss) = (0.0, 0.0);
for i in 1..=period {
let d = close[i] - close[i - 1];
avg_gain += d.max(0.0);
avg_loss += (-d).max(0.0);
}
avg_gain /= pf;
avg_loss /= pf;
for i in (period + 1)..n {
let d = close[i] - close[i - 1];
avg_gain = avg_gain.mul_add(a, d.max(0.0) * b);
avg_loss = avg_loss.mul_add(a, (-d).max(0.0) * b);
}
Some(vec![avg_gain, avg_loss])
}
pub fn cmo_resume(
close: &[f64],
period: usize,
from: usize,
state: &[f64],
) -> Option<(Vec<f64>, Vec<f64>)> {
if from == 0 {
return None;
}
let n = close.len();
let pf = period as f64;
let (a, b) = ((pf - 1.0) / pf, 1.0 / pf);
let (mut avg_gain, mut avg_loss) = (state[0], state[1]);
let mut out = Vec::with_capacity(n.saturating_sub(from));
for i in from..n {
let d = close[i] - close[i - 1];
avg_gain = avg_gain.mul_add(a, d.max(0.0) * b);
avg_loss = avg_loss.mul_add(a, (-d).max(0.0) * b);
let denom = avg_gain + avg_loss;
out.push(if denom < 1e-14 {
0.0
} else {
100.0 * (avg_gain - avg_loss) / denom
});
}
Some((out, vec![avg_gain, avg_loss]))
}
pub fn donchian(high: &[f64], low: &[f64], period: usize) -> Vec<f64> {
let hhv = kernels::rolling_max(av(high), period);
let llv = kernels::rolling_min(av(low), period);
((&hhv + &llv) / 2.0).to_vec()
}
pub fn midpoint(data: &[f64], period: usize) -> Vec<f64> {
let hh = kernels::rolling_max(av(data), period);
let ll = kernels::rolling_min(av(data), period);
((&hh + &ll) / 2.0).to_vec()
}
pub fn midprice(high: &[f64], low: &[f64], period: usize) -> Vec<f64> {
let hh = kernels::rolling_max(av(high), period);
let ll = kernels::rolling_min(av(low), period);
((&hh + &ll) / 2.0).to_vec()
}
#[cfg(test)]
mod tests {
use super::*;
use crate::indicators::stochrsi_fastk;
use crate::indicators::test_support::*;
#[test]
fn stochrsi_resume_is_bit_identical_to_full() {
let close = series(200);
let (rp, fk, fd) = (14usize, 14usize, 3usize);
let k_full = stochrsi_fastk(&close, rp, fk);
let d_full = crate::indicators::ma(&k_full, fd);
for &from in &[60usize, 70, 120, 199] {
let head = &close[..from];
let st = stochrsi_final_state(head, rp, fk, false, fd).unwrap();
let (tail, _) = stochrsi_resume(&close, rp, fk, false, fd, from, &st).unwrap();
assert_bits(&tail, &k_full[from..], "stochrsi.k");
let st = stochrsi_final_state(head, rp, fk, true, fd).unwrap();
let (tail, _) = stochrsi_resume(&close, rp, fk, true, fd, from, &st).unwrap();
let want = &d_full[from..];
assert_eq!(tail.len(), want.len(), "stochrsi.d length");
for (i, (x, y)) in tail.iter().zip(want).enumerate() {
assert!(
(x - y).abs() <= 1e-9 || (x.is_nan() && y.is_nan()),
"stochrsi.d bar {i}: resume {x} != full {y}",
);
}
}
}
#[test]
fn stochrsi_guards_decline() {
let (rp, fk, fd) = (14usize, 14usize, 3usize);
let short = series(20);
assert!(stochrsi_final_state(&short, rp, fk, false, fd).is_none());
let close = series(200);
let st = stochrsi_final_state(&close[..120], rp, fk, false, fd).unwrap();
let bad = vec![0.0; 1]; assert!(stochrsi_resume(&close, rp, fk, false, fd, 120, &bad).is_none());
assert!(stochrsi_resume(&close, rp, fk, false, fd, 0, &st).is_none());
let mut nanclose = series(200);
nanclose[150] = f64::NAN; let st = stochrsi_final_state(&nanclose[..140], rp, fk, false, fd).unwrap();
assert!(stochrsi_resume(&nanclose, rp, fk, false, fd, 140, &st).is_none());
}
#[test]
fn kdj_resume_is_bit_identical_to_full() {
let (high, low, close) = ohlc(150);
let (p, pk, pd, init) = (9usize, 3usize, 3usize, 50.0);
let k_full = kdj_k(&high, &low, &close, p, pk, init);
let d_full = kdj_d(&high, &low, &close, p, pk, pd, init);
let j_full = kdj_j(&high, &low, &close, p, pk, pd, init);
for &from in &[p - 1, p, 30, 80, 149] {
let st_k = kdj_final_state(
&high[..from],
&low[..from],
&close[..from],
p,
pk,
pd,
init,
false,
)
.unwrap();
assert_eq!(st_k.len(), 1, "kdj.k carries [%K]");
let (tail, ret) =
kdj_resume(&high, &low, &close, p, pk, pd, KdjLine::K, from, &st_k).unwrap();
assert_bits(&tail, &k_full[from..], "kdj.k");
assert_eq!(ret.len(), 1);
let st_d = kdj_final_state(
&high[..from],
&low[..from],
&close[..from],
p,
pk,
pd,
init,
true,
)
.unwrap();
assert_eq!(st_d.len(), 2, "kdj.d/.j carry [%K, %D]");
let (tail, ret) =
kdj_resume(&high, &low, &close, p, pk, pd, KdjLine::D, from, &st_d).unwrap();
assert_bits(&tail, &d_full[from..], "kdj.d");
assert_eq!(ret.len(), 2);
let (tail, _) =
kdj_resume(&high, &low, &close, p, pk, pd, KdjLine::J, from, &st_d).unwrap();
assert_bits(&tail, &j_full[from..], "kdj.j");
}
}
#[test]
fn kdj_guards_decline() {
let (high, low, close) = ohlc(60);
let (p, pk, pd, init) = (9usize, 3usize, 3usize, 50.0);
let n = close.len();
assert!(kdj_final_state(&[], &[], &[], p, pk, pd, init, false).is_none());
let st =
kdj_final_state(&high[..40], &low[..40], &close[..40], p, pk, pd, init, true).unwrap();
assert!(kdj_resume(&high, &low, &close, 0, pk, pd, KdjLine::K, 40, &st).is_none()); assert!(kdj_resume(&high, &low, &close, p, pk, pd, KdjLine::K, 0, &st).is_none()); assert!(kdj_resume(&high, &low, &close, p, pk, pd, KdjLine::K, n + 1, &st).is_none()); assert!(kdj_resume(&high, &low, &close, p, pk, pd, KdjLine::K, p - 2, &st).is_none()); assert!(kdj_resume(&high, &low, &close, p, pk, pd, KdjLine::K, 40, &[]).is_none()); assert!(kdj_resume(&high, &low, &close, p, pk, pd, KdjLine::D, 40, &[1.0]).is_none());
}
#[test]
fn rsi_cmo_resume_and_flat_window_arms() {
let close = series(120);
let p = 14usize;
let rsi_full = rsi(&close, p);
let cmo_full = cmo(&close, p);
for &from in &[p + 1, 30, 60, 119] {
let st = rsi_final_state(&close[..from], p).unwrap();
let (tail, _) = rsi_resume(&close, p, from, &st).unwrap();
assert_bits(&tail, &rsi_full[from..], "rsi");
let st = cmo_final_state(&close[..from], p).unwrap();
let (tail, _) = cmo_resume(&close, p, from, &st).unwrap();
assert_bits(&tail, &cmo_full[from..], "cmo");
}
let st = rsi_final_state(&close, p).unwrap();
assert!(rsi_resume(&close, p, 0, &st).is_none());
let st = cmo_final_state(&close, p).unwrap();
assert!(cmo_resume(&close, p, 0, &st).is_none());
assert!(rsi_final_state(&[1.0, 2.0], 5).is_none());
assert!(cmo_final_state(&[1.0, 2.0], 5).is_none());
let up: Vec<f64> = (0..40).map(|i| i as f64).collect();
let st = rsi_final_state(&up[..20], p).unwrap();
let (tail, _) = rsi_resume(&up, p, 20, &st).unwrap();
assert!(tail.iter().all(|&x| x == 100.0), "rsi flat-loss -> 100");
let flat = vec![7.0; 40];
let st = cmo_final_state(&flat[..20], p).unwrap();
let (tail, _) = cmo_resume(&flat, p, 20, &st).unwrap();
assert!(tail.iter().all(|&x| x == 0.0), "cmo flat-window -> 0");
}
}