use crate::core::defaults::{
APO_FAST, APO_SLOW, AROON_PERIOD, ATR_PERIOD, CMO_PERIOD, DX_PERIOD, IMI_PERIOD, MACD_FAST,
MACD_SIGNAL, MACD_SLOW, MFI_PERIOD, MOM_PERIOD, RSI_PERIOD, STOCH_FAST_K, STOCH_SLOW_D,
STOCH_SLOW_K, STOCHRSI_PERIOD, STOCHRSI_RSI_PERIOD, TRIX_PERIOD, ULTOSC_PERIOD1,
ULTOSC_PERIOD2, ULTOSC_PERIOD3,
};
use crate::core::{
check_eq_len, ema, rolling_mean, rolling_mean_skip, wilder_step, wilder_step_sum,
};
use crate::error::{check_period, TaError};
use crate::indicator::indicator;
#[derive(Debug, Clone)]
pub struct Macd {
pub macd: Vec<f64>,
pub signal: Vec<f64>,
pub hist: Vec<f64>,
}
#[derive(Debug, Clone)]
pub struct Aroon {
pub up: Vec<f64>,
pub down: Vec<f64>,
}
#[derive(Debug, Clone)]
pub struct Stoch {
pub slow_k: Vec<f64>,
pub slow_d: Vec<f64>,
}
#[derive(Debug, Clone)]
pub struct StochF {
pub fast_k: Vec<f64>,
pub fast_d: Vec<f64>,
}
indicator! {
fn mom(values: &[f64], time_period: usize) -> Vec<f64> with mom_with_output
default mom_default(values: &[f64]) => (MOM_PERIOD)
;
}
pub fn mom_with_output(
values: &[f64],
time_period: usize,
out: &mut [f64],
) -> Result<(), TaError> {
check_period(time_period)?;
if out.len() != values.len() {
return Err(TaError::BadParam(
"mom_with_output: out length must equal values length".into(),
));
}
let n = values.len();
for i in time_period..n {
out[i] = values[i] - values[i - time_period];
}
Ok(())
}
fn rate_of_change(values: &[f64], time_period: usize, mode: u8, out: &mut [f64]) -> Result<(), TaError> {
check_period(time_period)?;
let n = values.len();
if out.len() != n {
return Err(TaError::BadParam(
"rate_of_change: out length must equal values length".into(),
));
}
for v in out.iter_mut().take(time_period) {
*v = f64::NAN;
}
let p = time_period;
match mode {
0 => {
for i in p..n {
let prev = values[i - p];
out[i] = if prev == 0.0 { 0.0 } else { 100.0 * (values[i] - prev) / prev };
}
}
1 => {
for i in p..n {
let prev = values[i - p];
out[i] = if prev == 0.0 { 0.0 } else { (values[i] - prev) / prev };
}
}
2 => {
for i in p..n {
let prev = values[i - p];
out[i] = if prev == 0.0 { 0.0 } else { values[i] / prev };
}
}
_ => {
for i in p..n {
let prev = values[i - p];
out[i] = if prev == 0.0 { 0.0 } else { 100.0 * values[i] / prev };
}
}
}
Ok(())
}
indicator! {
fn roc(values: &[f64], time_period: usize) -> Vec<f64> with roc_with_output
default roc_default(values: &[f64]) => (MOM_PERIOD)
;
}
pub fn roc_with_output(
values: &[f64],
time_period: usize,
out: &mut [f64],
) -> Result<(), TaError> {
check_period(time_period)?;
if out.len() != values.len() {
return Err(TaError::BadParam(
"roc_with_output: out length must equal values length".into(),
));
}
rate_of_change(values, time_period, 0, out)
}
indicator! {
fn rocp(values: &[f64], time_period: usize) -> Vec<f64> with rocp_with_output
default rocp_default(values: &[f64]) => (MOM_PERIOD)
;
}
pub fn rocp_with_output(
values: &[f64],
time_period: usize,
out: &mut [f64],
) -> Result<(), TaError> {
check_period(time_period)?;
if out.len() != values.len() {
return Err(TaError::BadParam(
"rocp_with_output: out length must equal values length".into(),
));
}
rate_of_change(values, time_period, 1, out)
}
indicator! {
fn rocr(values: &[f64], time_period: usize) -> Vec<f64> with rocr_with_output
default rocr_default(values: &[f64]) => (MOM_PERIOD)
;
}
pub fn rocr_with_output(
values: &[f64],
time_period: usize,
out: &mut [f64],
) -> Result<(), TaError> {
check_period(time_period)?;
if out.len() != values.len() {
return Err(TaError::BadParam(
"rocr_with_output: out length must equal values length".into(),
));
}
rate_of_change(values, time_period, 2, out)
}
indicator! {
fn rocr100(values: &[f64], time_period: usize) -> Vec<f64> with rocr100_with_output
default rocr100_default(values: &[f64]) => (MOM_PERIOD)
;
}
pub fn rocr100_with_output(
values: &[f64],
time_period: usize,
out: &mut [f64],
) -> Result<(), TaError> {
check_period(time_period)?;
if out.len() != values.len() {
return Err(TaError::BadParam(
"rocr100_with_output: out length must equal values length".into(),
));
}
rate_of_change(values, time_period, 3, out)
}
indicator! {
#[allow(clippy::needless_return)]
fn rsi(values: &[f64], time_period: usize) -> Vec<f64> with rsi_with_output
default rsi_default(values: &[f64]) => (RSI_PERIOD)
;
}
pub fn rsi_with_output(
values: &[f64],
time_period: usize,
out: &mut [f64],
) -> Result<(), TaError> {
check_period(time_period)?;
let n = values.len();
if out.len() != n {
return Err(TaError::BadParam(
"rsi_with_output: out length must equal values length".into(),
));
}
if n < time_period + 1 {
return Ok(());
}
let p = time_period as f64;
let k = 1.0 / p; let mut gain = 0.0;
let mut loss = 0.0;
for i in 1..=time_period {
let d = values[i] - values[i - 1];
if d >= 0.0 {
gain += d;
} else {
loss -= d;
}
}
gain /= p;
loss /= p;
let rs = gain / loss;
out[time_period] = 100.0 - 100.0 / (1.0 + rs);
for i in (time_period + 1)..n {
let d = values[i] - values[i - 1];
let g = if d >= 0.0 { d } else { 0.0 };
let l = if d >= 0.0 { 0.0 } else { -d };
gain = wilder_step(gain, g, k);
loss = wilder_step(loss, l, k);
out[i] = if loss == 0.0 {
100.0
} else {
100.0 - 100.0 / (1.0 + gain / loss)
};
}
Ok(())
}
pub fn macd(
values: &[f64],
fast_period: usize,
slow_period: usize,
signal_period: usize,
) -> Result<Macd, TaError> {
let n = values.len();
let mut out = Macd {
macd: vec![f64::NAN; n],
signal: vec![f64::NAN; n],
hist: vec![f64::NAN; n],
};
macd_with_output(values, fast_period, slow_period, signal_period, &mut out)?;
Ok(out)
}
pub fn macd_with_output(
values: &[f64],
fast_period: usize,
slow_period: usize,
signal_period: usize,
out: &mut Macd,
) -> Result<(), TaError> {
check_period(fast_period)?;
check_period(slow_period)?;
check_period(signal_period)?;
if fast_period >= slow_period {
return Err(TaError::BadParam(
"fast_period must be < slow_period".into(),
));
}
let n = values.len();
if out.macd.len() != n || out.signal.len() != n || out.hist.len() != n {
return Err(TaError::BadParam(
"macd_with_output: out vectors must have length == values length".into(),
));
}
let fast_k = 2.0 / (fast_period as f64 + 1.0);
let slow_k = 2.0 / (slow_period as f64 + 1.0);
let signal_k = 2.0 / (signal_period as f64 + 1.0);
let lookback_signal = signal_period - 1;
let lookback_total = lookback_signal + (slow_period - 1); if n <= lookback_total {
return Ok(());
}
let mut today = 0usize;
let mut temp_real = 0.0_f64;
let mut i = slow_period - fast_period;
while i > 0 {
temp_real += values[today];
today += 1;
i -= 1;
}
let mut prev_fast = 0.0_f64;
i = fast_period;
while i > 0 {
prev_fast += values[today];
temp_real += values[today];
today += 1;
i -= 1;
}
let mut prev_slow = temp_real / slow_period as f64;
prev_fast = prev_fast / fast_period as f64;
while today <= lookback_total - lookback_signal {
temp_real = values[today];
today += 1;
prev_fast = (temp_real - prev_fast) * fast_k + prev_fast;
prev_slow = (temp_real - prev_slow) * slow_k + prev_slow;
}
let mut macd_value = prev_fast - prev_slow;
let mut prev_signal = 0.0_f64;
prev_signal += macd_value;
i = signal_period - 1;
while i > 0 {
temp_real = values[today];
today += 1;
prev_fast = (temp_real - prev_fast) * fast_k + prev_fast;
prev_slow = (temp_real - prev_slow) * slow_k + prev_slow;
macd_value = prev_fast - prev_slow;
prev_signal += macd_value;
i -= 1;
}
prev_signal = prev_signal / signal_period as f64;
while today <= lookback_total {
temp_real = values[today];
today += 1;
prev_fast = (temp_real - prev_fast) * fast_k + prev_fast;
prev_slow = (temp_real - prev_slow) * slow_k + prev_slow;
macd_value = prev_fast - prev_slow;
prev_signal = (macd_value - prev_signal) * signal_k + prev_signal;
}
let mut out_idx = lookback_total;
out.macd[out_idx] = macd_value;
out.signal[out_idx] = prev_signal;
out.hist[out_idx] = macd_value - prev_signal;
while today < n {
temp_real = values[today];
today += 1;
prev_fast = (temp_real - prev_fast) * fast_k + prev_fast;
prev_slow = (temp_real - prev_slow) * slow_k + prev_slow;
macd_value = prev_fast - prev_slow;
prev_signal = (macd_value - prev_signal) * signal_k + prev_signal;
out_idx += 1;
out.macd[out_idx] = macd_value;
out.signal[out_idx] = prev_signal;
out.hist[out_idx] = macd_value - prev_signal;
}
Ok(())
}
pub fn macd_default(values: &[f64]) -> Result<Macd, TaError> {
macd(values, MACD_FAST, MACD_SLOW, MACD_SIGNAL)
}
pub fn macd_fix(
values: &[f64],
fast_period: usize,
slow_period: usize,
signal_period: usize,
) -> Result<Macd, TaError> {
let n = values.len();
let mut out = Macd {
macd: vec![f64::NAN; n],
signal: vec![f64::NAN; n],
hist: vec![f64::NAN; n],
};
macd_fix_with_output(values, fast_period, slow_period, signal_period, &mut out)?;
Ok(out)
}
pub fn macd_fix_with_output(
values: &[f64],
fast_period: usize,
slow_period: usize,
signal_period: usize,
out: &mut Macd,
) -> Result<(), TaError> {
check_period(fast_period)?;
check_period(slow_period)?;
check_period(signal_period)?;
if fast_period >= slow_period {
return Err(TaError::BadParam(
"fast_period must be < slow_period".into(),
));
}
let n = values.len();
if out.macd.len() != n || out.signal.len() != n || out.hist.len() != n {
return Err(TaError::BadParam(
"macd_fix_with_output: out vectors must have length == values length".into(),
));
}
let use_fixed_k = fast_period == MACD_FAST && slow_period == MACD_SLOW;
let fast_k = if use_fixed_k { 0.15 } else { 2.0 / (fast_period as f64 + 1.0) };
let slow_k = if use_fixed_k { 0.075 } else { 2.0 / (slow_period as f64 + 1.0) };
let signal_k = 2.0 / (signal_period as f64 + 1.0);
let lookback_signal = signal_period - 1;
let lookback_total = lookback_signal + (slow_period - 1); if n <= lookback_total {
return Ok(());
}
let mut today = 0usize;
let mut temp_real = 0.0_f64;
let mut i = slow_period - fast_period;
while i > 0 {
temp_real += values[today];
today += 1;
i -= 1;
}
let mut prev_fast = 0.0_f64;
i = fast_period;
while i > 0 {
prev_fast += values[today];
temp_real += values[today];
today += 1;
i -= 1;
}
let mut prev_slow = temp_real / slow_period as f64;
prev_fast = prev_fast / fast_period as f64;
while today <= lookback_total - lookback_signal {
temp_real = values[today];
today += 1;
prev_fast = (temp_real - prev_fast) * fast_k + prev_fast;
prev_slow = (temp_real - prev_slow) * slow_k + prev_slow;
}
let mut macd_value = prev_fast - prev_slow;
let mut prev_signal = 0.0_f64;
prev_signal += macd_value;
i = signal_period - 1;
while i > 0 {
temp_real = values[today];
today += 1;
prev_fast = (temp_real - prev_fast) * fast_k + prev_fast;
prev_slow = (temp_real - prev_slow) * slow_k + prev_slow;
macd_value = prev_fast - prev_slow;
prev_signal += macd_value;
i -= 1;
}
prev_signal = prev_signal / signal_period as f64;
while today <= lookback_total {
temp_real = values[today];
today += 1;
prev_fast = (temp_real - prev_fast) * fast_k + prev_fast;
prev_slow = (temp_real - prev_slow) * slow_k + prev_slow;
macd_value = prev_fast - prev_slow;
prev_signal = (macd_value - prev_signal) * signal_k + prev_signal;
}
let mut out_idx = lookback_total;
out.macd[out_idx] = macd_value;
out.signal[out_idx] = prev_signal;
out.hist[out_idx] = macd_value - prev_signal;
while today < n {
temp_real = values[today];
today += 1;
prev_fast = (temp_real - prev_fast) * fast_k + prev_fast;
prev_slow = (temp_real - prev_slow) * slow_k + prev_slow;
macd_value = prev_fast - prev_slow;
prev_signal = (macd_value - prev_signal) * signal_k + prev_signal;
out_idx += 1;
out.macd[out_idx] = macd_value;
out.signal[out_idx] = prev_signal;
out.hist[out_idx] = macd_value - prev_signal;
}
Ok(())
}
pub fn macd_fix_default(values: &[f64]) -> Result<Macd, TaError> {
macd_fix(values, MACD_FAST, MACD_SLOW, MACD_SIGNAL)
}
pub fn macd_ext(
values: &[f64],
fast_period: usize,
slow_period: usize,
signal_period: usize,
) -> Result<Macd, TaError> {
let n = values.len();
let mut out = Macd {
macd: vec![f64::NAN; n],
signal: vec![f64::NAN; n],
hist: vec![f64::NAN; n],
};
macd_ext_with_output(values, fast_period, slow_period, signal_period, &mut out)?;
Ok(out)
}
pub fn macd_ext_with_output(
values: &[f64],
fast_period: usize,
slow_period: usize,
signal_period: usize,
out: &mut Macd,
) -> Result<(), TaError> {
macd_with_output(values, fast_period, slow_period, signal_period, out)
}
pub fn macd_ext_default(values: &[f64]) -> Result<Macd, TaError> {
macd(values, MACD_FAST, MACD_SLOW, MACD_SIGNAL)
}
indicator! {
fn apo(values: &[f64], fast_period: usize, slow_period: usize) -> Vec<f64> with apo_with_output
default apo_default(values: &[f64]) => (APO_FAST, APO_SLOW)
;
}
pub fn apo_with_output(
values: &[f64],
fast_period: usize,
slow_period: usize,
out: &mut [f64],
) -> Result<(), TaError> {
check_period(fast_period)?;
check_period(slow_period)?;
if fast_period >= slow_period {
return Err(TaError::BadParam("fast_period must be < slow_period".into()));
}
if out.len() != values.len() {
return Err(TaError::BadParam(
"apo_with_output: out length must equal values length".into(),
));
}
let ef = ema(values, fast_period);
let es = ema(values, slow_period);
let n = values.len();
for i in 0..n {
if !ef[i].is_nan() && !es[i].is_nan() {
out[i] = ef[i] - es[i];
}
}
Ok(())
}
indicator! {
fn ppo(values: &[f64], fast_period: usize, slow_period: usize) -> Vec<f64> with ppo_with_output
default ppo_default(values: &[f64]) => (APO_FAST, APO_SLOW)
;
}
pub fn ppo_with_output(
values: &[f64],
fast_period: usize,
slow_period: usize,
out: &mut [f64],
) -> Result<(), TaError> {
check_period(fast_period)?;
check_period(slow_period)?;
if fast_period >= slow_period {
return Err(TaError::BadParam("fast_period must be < slow_period".into()));
}
if out.len() != values.len() {
return Err(TaError::BadParam(
"ppo_with_output: out length must equal values length".into(),
));
}
let ef = ema(values, fast_period);
let es = ema(values, slow_period);
let n = values.len();
for i in 0..n {
if !ef[i].is_nan() && !es[i].is_nan() && es[i] != 0.0 {
out[i] = 100.0 * (ef[i] - es[i]) / es[i];
}
}
Ok(())
}
indicator! {
fn cmo(values: &[f64], time_period: usize) -> Vec<f64> with cmo_with_output
default cmo_default(values: &[f64]) => (CMO_PERIOD)
;
}
pub fn cmo_with_output(
values: &[f64],
time_period: usize,
out: &mut [f64],
) -> Result<(), TaError> {
check_period(time_period)?;
let n = values.len();
if out.len() != n {
return Err(TaError::BadParam(
"cmo_with_output: out length must equal values length".into(),
));
}
if n <= time_period {
return Ok(());
}
let p = time_period as f64;
let mut prev_value = values[0];
let mut prev_gain = 0.0_f64;
let mut prev_loss = 0.0_f64;
for t in 1..=time_period {
let tv = values[t];
let delta = tv - prev_value;
prev_value = tv;
if delta < 0.0 {
prev_loss -= delta;
} else {
prev_gain += delta;
}
}
prev_gain /= p;
prev_loss /= p;
let k = 1.0 / p; let denom0 = prev_gain + prev_loss;
out[time_period] = if denom0 == 0.0 {
0.0
} else {
100.0 * (prev_gain - prev_loss) / denom0
};
for t in (time_period + 1)..n {
let tv = values[t];
let delta = tv - prev_value;
prev_value = tv;
let new_gain = if delta < 0.0 { 0.0 } else { delta };
let new_loss = if delta < 0.0 { -delta } else { 0.0 };
prev_gain = wilder_step(prev_gain, new_gain, k);
prev_loss = wilder_step(prev_loss, new_loss, k);
let denom = prev_gain + prev_loss;
out[t] = if denom == 0.0 {
0.0
} else {
100.0 * (prev_gain - prev_loss) / denom
};
}
Ok(())
}
indicator! {
fn cci(high: &[f64], low: &[f64], close: &[f64], time_period: usize) -> Vec<f64> with cci_with_output;
}
pub fn cci_with_output(
high: &[f64],
low: &[f64],
close: &[f64],
time_period: usize,
out: &mut [f64],
) -> Result<(), TaError> {
check_period(time_period)?;
check_eq_len(&[high, low, close], "cci")?;
if out.len() != close.len() {
return Err(TaError::BadParam(
"cci_with_output: out length must equal close length".into(),
));
}
let n = close.len();
let tp: Vec<f64> = (0..n).map(|i| (high[i] + low[i] + close[i]) / 3.0).collect();
let sma_tp = rolling_mean(&tp, time_period);
for i in (time_period - 1)..n {
let mean = sma_tp[i];
let mut dev = 0.0;
for j in 0..time_period {
dev += (tp[i - j] - mean).abs();
}
dev /= time_period as f64;
out[i] = if dev == 0.0 { 0.0 } else { (tp[i] - mean) / (0.015 * dev) };
}
Ok(())
}
pub fn cci_default(high: &[f64], low: &[f64], close: &[f64]) -> Result<Vec<f64>, TaError> {
cci(high, low, close, 20)
}
indicator! {
fn mfi(high: &[f64], low: &[f64], close: &[f64], volume: &[f64], time_period: usize) -> Vec<f64> with mfi_with_output;
}
pub fn mfi_with_output(
high: &[f64],
low: &[f64],
close: &[f64],
volume: &[f64],
time_period: usize,
out: &mut [f64],
) -> Result<(), TaError> {
check_period(time_period)?;
check_eq_len(&[high, low, close, volume], "mfi")?;
if out.len() != close.len() {
return Err(TaError::BadParam(
"mfi_with_output: out length must equal close length".into(),
));
}
let n = close.len();
if n == 0 {
return Ok(());
}
let p = time_period;
let mut pos_ring = vec![0.0_f64; p]; let mut neg_ring = vec![0.0_f64; p]; let mut pos_sum = 0.0_f64;
let mut neg_sum = 0.0_f64;
let mut idx = 0_usize;
let mut tp_prev = (high[0] + low[0] + close[0]) / 3.0;
for i in 1..n {
let tp = (high[i] + low[i] + close[i]) / 3.0;
let mf = tp * volume[i];
let (pos_i, neg_i) = if tp > tp_prev {
(mf, 0.0)
} else if tp < tp_prev {
(0.0, mf)
} else {
(0.0, 0.0)
};
if i >= p {
pos_sum -= pos_ring[idx];
neg_sum -= neg_ring[idx];
}
pos_sum += pos_i;
neg_sum += neg_i;
pos_ring[idx] = pos_i;
neg_ring[idx] = neg_i;
if i >= p {
out[i] = if neg_sum == 0.0 {
100.0
} else {
100.0 - 100.0 / (1.0 + pos_sum / neg_sum)
};
}
tp_prev = tp;
idx = (idx + 1) % p;
}
Ok(())
}
pub fn mfi_default(
high: &[f64],
low: &[f64],
close: &[f64],
volume: &[f64],
) -> Result<Vec<f64>, TaError> {
mfi(high, low, close, volume, MFI_PERIOD)
}
indicator! {
fn willr(high: &[f64], low: &[f64], close: &[f64], time_period: usize) -> Vec<f64> with willr_with_output;
}
pub fn willr_with_output(
high: &[f64],
low: &[f64],
close: &[f64],
time_period: usize,
out: &mut [f64],
) -> Result<(), TaError> {
check_period(time_period)?;
check_eq_len(&[high, low, close], "willr")?;
if out.len() != close.len() {
return Err(TaError::BadParam(
"willr_with_output: out length must equal close length".into(),
));
}
#[cfg(feature = "parallel")]
{
if close.len() >= 8192 {
return willr_parallel_with_output(high, low, close, time_period, out);
}
}
willr_serial_with_output(high, low, close, time_period, out)
}
fn willr_serial_with_output(
high: &[f64],
low: &[f64],
close: &[f64],
time_period: usize,
out: &mut [f64],
) -> Result<(), TaError> {
let n = close.len();
if n < time_period {
for v in out.iter_mut() {
*v = f64::NAN;
}
return Ok(());
}
let mut max_dq = crate::core::MonoQueue::with_capacity(time_period);
let mut min_dq = crate::core::MonoQueue::with_capacity(time_period);
for i in 0..n {
while !max_dq.is_empty() && max_dq.front() + time_period <= i {
max_dq.pop_front();
}
while !min_dq.is_empty() && min_dq.front() + time_period <= i {
min_dq.pop_front();
}
while !max_dq.is_empty() && high[max_dq.back()] <= high[i] {
max_dq.pop_back();
}
while !min_dq.is_empty() && low[min_dq.back()] >= low[i] {
min_dq.pop_back();
}
max_dq.push_back(i);
min_dq.push_back(i);
if i >= time_period - 1 {
let hh = high[max_dq.front()];
let ll = low[min_dq.front()];
out[i] = if hh == ll {
0.0
} else {
-100.0 * (hh - close[i]) / (hh - ll)
};
} else {
out[i] = f64::NAN;
}
}
Ok(())
}
pub fn willr_serial(
high: &[f64],
low: &[f64],
close: &[f64],
time_period: usize,
) -> Result<Vec<f64>, TaError> {
check_period(time_period)?;
check_eq_len(&[high, low, close], "willr")?;
let mut out = vec![f64::NAN; close.len()];
willr_serial_with_output(high, low, close, time_period, &mut out)?;
Ok(out)
}
#[cfg(feature = "parallel")]
pub fn willr_parallel(
high: &[f64],
low: &[f64],
close: &[f64],
time_period: usize,
) -> Result<Vec<f64>, TaError> {
check_period(time_period)?;
check_eq_len(&[high, low, close], "willr")?;
let mut out = vec![f64::NAN; close.len()];
willr_parallel_with_output(high, low, close, time_period, &mut out)?;
Ok(out)
}
#[cfg(feature = "parallel")]
fn willr_parallel_with_output(
high: &[f64],
low: &[f64],
close: &[f64],
time_period: usize,
out: &mut [f64],
) -> Result<(), TaError> {
check_period(time_period)?;
check_eq_len(&[high, low, close], "willr")?;
if out.len() != close.len() {
return Err(TaError::BadParam(
"willr_parallel_with_output: out length must equal close length".into(),
));
}
let p = time_period;
crate::parallel::parallel_index_map(close.len(), p - 1, out, |start, end| {
let mut local = vec![f64::NAN; end - start];
let _ = willr_serial_with_output(
&high[start..end],
&low[start..end],
&close[start..end],
p,
&mut local,
);
local
});
Ok(())
}
pub fn willr_default(high: &[f64], low: &[f64], close: &[f64]) -> Result<Vec<f64>, TaError> {
willr(high, low, close, ATR_PERIOD)
}
indicator! {
fn bop(open: &[f64], high: &[f64], low: &[f64], close: &[f64]) -> Vec<f64> with bop_with_output init zero;
}
pub fn bop_with_output(
open: &[f64],
high: &[f64],
low: &[f64],
close: &[f64],
out: &mut [f64],
) -> Result<(), TaError> {
check_eq_len(&[open, high, low, close], "bop")?;
if out.len() != close.len() {
return Err(TaError::BadParam(
"bop_with_output: out length must equal close length".into(),
));
}
let n = close.len();
for i in 0..n {
let range = high[i] - low[i];
out[i] = if range == 0.0 {
0.0
} else {
(close[i] - open[i]) / range
};
}
Ok(())
}
indicator! {
fn ultosc(
high: &[f64],
low: &[f64],
close: &[f64],
period1: usize,
period2: usize,
period3: usize,
) -> Vec<f64> with ultosc_with_output;
}
pub fn ultosc_with_output(
high: &[f64],
low: &[f64],
close: &[f64],
period1: usize,
period2: usize,
period3: usize,
out: &mut [f64],
) -> Result<(), TaError> {
check_period(period1)?;
check_period(period2)?;
check_period(period3)?;
check_eq_len(&[high, low, close], "ultosc")?;
if out.len() != close.len() {
return Err(TaError::BadParam(
"ultosc_with_output: out length must equal close length".into(),
));
}
let n = close.len();
if n == 0 {
return Ok(());
}
let cap = period3 + 1;
let mut ring_bp = vec![0.0_f64; cap];
let mut ring_tr = vec![0.0_f64; cap];
let mut w = 0usize;
let mut sbp1 = 0.0_f64;
let mut sbp2 = 0.0_f64;
let mut sbp3 = 0.0_f64;
let mut str1 = 0.0_f64;
let mut str2 = 0.0_f64;
let mut str3 = 0.0_f64;
for i in 0..n {
let prev = if i > 0 { close[i - 1] } else { close[i] };
let bpi = close[i] - low[i].min(prev);
let tri = high[i].max(prev) - low[i].min(prev);
ring_bp[w] = bpi;
ring_tr[w] = tri;
sbp1 += bpi;
sbp2 += bpi;
sbp3 += bpi;
str1 += tri;
str2 += tri;
str3 += tri;
if i >= period1 {
let k = (w + cap - period1) % cap;
sbp1 -= ring_bp[k];
str1 -= ring_tr[k];
}
if i >= period2 {
let k = (w + cap - period2) % cap;
sbp2 -= ring_bp[k];
str2 -= ring_tr[k];
}
if i >= period3 {
let k = (w + cap - period3) % cap;
sbp3 -= ring_bp[k];
str3 -= ring_tr[k];
if str1 == 0.0 || str2 == 0.0 || str3 == 0.0 {
out[i] = 0.0;
} else {
let a1 = sbp1 / str1;
let a2 = sbp2 / str2;
let a3 = sbp3 / str3;
out[i] = 100.0 * (4.0 * a1 + 2.0 * a2 + a3) / 7.0;
}
}
w = (w + 1) % cap;
}
Ok(())
}
pub fn ultosc_default(high: &[f64], low: &[f64], close: &[f64]) -> Result<Vec<f64>, TaError> {
ultosc(
high,
low,
close,
ULTOSC_PERIOD1,
ULTOSC_PERIOD2,
ULTOSC_PERIOD3,
)
}
#[allow(clippy::too_many_arguments)]
#[allow(clippy::too_many_arguments)]
fn dm_tr(
high: &[f64],
low: &[f64],
close: &[f64],
period: usize,
) -> (Vec<f64>, Vec<f64>, Vec<f64>) {
let n = high.len();
let mut pdm = vec![f64::NAN; n];
let mut mdm = vec![f64::NAN; n];
let mut tr = vec![f64::NAN; n];
if n < period + 1 {
return (pdm, mdm, tr);
}
let p = period as f64;
let mut today = 0usize;
let mut prev_high = high[0];
let mut prev_low = low[0];
let mut prev_close = close[0];
let mut prev_plus_dm = 0.0_f64;
let mut prev_minus_dm = 0.0_f64;
let mut prev_tr = 0.0_f64;
let mut i = period - 1;
while i > 0 {
today += 1;
let tp = high[today];
let diff_p = tp - prev_high;
prev_high = tp;
let tl = low[today];
let diff_m = prev_low - tl;
prev_low = tl;
if diff_m > 0.0 && diff_p < diff_m {
prev_minus_dm += diff_m;
} else if diff_p > 0.0 && diff_p > diff_m {
prev_plus_dm += diff_p;
}
let mut range = prev_high - prev_low;
let mut tmp = (prev_high - prev_close).abs();
if tmp > range {
range = tmp;
}
tmp = (prev_low - prev_close).abs();
if tmp > range {
range = tmp;
}
prev_tr += range;
prev_close = close[today];
i -= 1;
}
pdm[period - 1] = prev_plus_dm;
mdm[period - 1] = prev_minus_dm;
tr[period - 1] = prev_tr;
while today < n - 1 {
today += 1;
let tp = high[today];
let diff_p = tp - prev_high;
prev_high = tp;
let tl = low[today];
let diff_m = prev_low - tl;
prev_low = tl;
let pdm_add = if diff_m > 0.0 && diff_p < diff_m {
diff_m
} else {
0.0
};
let pdm_p = if diff_p > 0.0 && diff_p > diff_m {
diff_p
} else {
0.0
};
prev_minus_dm = wilder_step_sum(prev_minus_dm, pdm_add, 1.0 / p);
prev_plus_dm = wilder_step_sum(prev_plus_dm, pdm_p, 1.0 / p);
let mut range = prev_high - prev_low;
let mut tmp = (prev_high - prev_close).abs();
if tmp > range {
range = tmp;
}
tmp = (prev_low - prev_close).abs();
if tmp > range {
range = tmp;
}
prev_tr = wilder_step_sum(prev_tr, range, 1.0 / p);
prev_close = close[today];
pdm[today] = prev_plus_dm;
mdm[today] = prev_minus_dm;
tr[today] = prev_tr;
}
(pdm, mdm, tr)
}
fn di_from_dm_tr(pdm: &[f64], mdm: &[f64], tr: &[f64], period: usize) -> (Vec<f64>, Vec<f64>) {
let n = tr.len();
let mut pdi = vec![f64::NAN; n];
let mut mdi = vec![f64::NAN; n];
for i in period..n {
if !tr[i].is_nan() {
if tr[i] == 0.0 {
pdi[i] = 0.0;
mdi[i] = 0.0;
} else {
pdi[i] = 100.0 * pdm[i] / tr[i];
mdi[i] = 100.0 * mdm[i] / tr[i];
}
}
}
(pdi, mdi)
}
fn adx_adxr_fused(
high: &[f64],
low: &[f64],
close: &[f64],
period: usize,
) -> (Vec<f64>, Vec<f64>) {
let n = high.len();
let mut adx = vec![f64::NAN; n];
let mut adxr = vec![f64::NAN; n];
let dx_seed_end = 2 * period - 1;
if n < 2 * period {
return (adx, adxr);
}
let p = period as f64;
let mut today = 0usize;
let mut prev_high = high[0];
let mut prev_low = low[0];
let mut prev_close = close[0];
let mut prev_plus_dm = 0.0_f64;
let mut prev_minus_dm = 0.0_f64;
let mut prev_tr = 0.0_f64;
let mut i = period - 1;
while i > 0 {
today += 1;
let tp = high[today];
let diff_p = tp - prev_high;
prev_high = tp;
let tl = low[today];
let diff_m = prev_low - tl;
prev_low = tl;
if diff_m > 0.0 && diff_p < diff_m {
prev_minus_dm += diff_m;
} else if diff_p > 0.0 && diff_p > diff_m {
prev_plus_dm += diff_p;
}
let mut range = prev_high - prev_low;
let mut tmp = (prev_high - prev_close).abs();
if tmp > range {
range = tmp;
}
tmp = (prev_low - prev_close).abs();
if tmp > range {
range = tmp;
}
prev_tr += range;
prev_close = close[today];
i -= 1;
}
let mut sum_dx = 0.0_f64;
let mut seeded_adx = false;
let mut prev_adx = 0.0_f64;
let mut adx_ring: std::collections::VecDeque<f64> = std::collections::VecDeque::with_capacity(period);
while today < n - 1 {
today += 1;
let tp = high[today];
let diff_p = tp - prev_high;
prev_high = tp;
let tl = low[today];
let diff_m = prev_low - tl;
prev_low = tl;
let pdm_add = if diff_m > 0.0 && diff_p < diff_m {
diff_m
} else {
0.0
};
let pdm_p = if diff_p > 0.0 && diff_p > diff_m {
diff_p
} else {
0.0
};
prev_minus_dm = wilder_step_sum(prev_minus_dm, pdm_add, 1.0 / p);
prev_plus_dm = wilder_step_sum(prev_plus_dm, pdm_p, 1.0 / p);
let mut range = prev_high - prev_low;
let mut tmp = (prev_high - prev_close).abs();
if tmp > range {
range = tmp;
}
tmp = (prev_low - prev_close).abs();
if tmp > range {
range = tmp;
}
prev_tr = wilder_step_sum(prev_tr, range, 1.0 / p);
prev_close = close[today];
let (pdi, mdi) = if prev_tr == 0.0 {
(0.0, 0.0)
} else {
(
100.0 * prev_plus_dm / prev_tr,
100.0 * prev_minus_dm / prev_tr,
)
};
let denom = pdi + mdi;
let dx = if denom != 0.0 {
100.0 * (pdi - mdi).abs() / denom
} else {
0.0
};
if !seeded_adx {
sum_dx += dx;
if today >= dx_seed_end {
prev_adx = sum_dx / p;
adx[today] = prev_adx;
adx_ring.push_back(prev_adx);
seeded_adx = true;
}
} else {
prev_adx = wilder_step(prev_adx, dx, 1.0 / p);
adx[today] = prev_adx;
adx_ring.push_back(prev_adx);
if adx_ring.len() > period {
adx_ring.pop_front();
}
}
let adxr_start = 3 * period - 2;
if today >= adxr_start && adx_ring.len() == period {
let earlier = adx_ring.front().copied().unwrap();
adxr[today] = (adx[today] + earlier) / 2.0;
}
}
(adx, adxr)
}
fn dx_from_candles(
high: &[f64],
low: &[f64],
close: &[f64],
period: usize,
carry: bool,
) -> Vec<f64> {
let n = high.len();
let mut dx = vec![f64::NAN; n];
if n < period + 1 {
return dx;
}
let p = period as f64;
let mut today = 0usize;
let mut prev_high = high[0];
let mut prev_low = low[0];
let mut prev_close = close[0];
let mut prev_plus_dm = 0.0_f64;
let mut prev_minus_dm = 0.0_f64;
let mut prev_tr = 0.0_f64;
let mut i = period - 1;
while i > 0 {
today += 1;
let tp = high[today];
let diff_p = tp - prev_high;
prev_high = tp;
let tl = low[today];
let diff_m = prev_low - tl;
prev_low = tl;
if diff_m > 0.0 && diff_p < diff_m {
prev_minus_dm += diff_m;
} else if diff_p > 0.0 && diff_p > diff_m {
prev_plus_dm += diff_p;
}
let mut range = prev_high - prev_low;
let mut tmp = (prev_high - prev_close).abs();
if tmp > range {
range = tmp;
}
tmp = (prev_low - prev_close).abs();
if tmp > range {
range = tmp;
}
prev_tr += range;
prev_close = close[today];
i -= 1;
}
let mut last = f64::NAN; while today < n - 1 {
today += 1;
let tp = high[today];
let diff_p = tp - prev_high;
prev_high = tp;
let tl = low[today];
let diff_m = prev_low - tl;
prev_low = tl;
let pdm_add = if diff_m > 0.0 && diff_p < diff_m {
diff_m
} else {
0.0
};
let pdm_p = if diff_p > 0.0 && diff_p > diff_m {
diff_p
} else {
0.0
};
prev_minus_dm = wilder_step_sum(prev_minus_dm, pdm_add, 1.0 / p);
prev_plus_dm = wilder_step_sum(prev_plus_dm, pdm_p, 1.0 / p);
let mut range = prev_high - prev_low;
let mut tmp = (prev_high - prev_close).abs();
if tmp > range {
range = tmp;
}
tmp = (prev_low - prev_close).abs();
if tmp > range {
range = tmp;
}
prev_tr = wilder_step_sum(prev_tr, range, 1.0 / p);
prev_close = close[today];
let (pdi, mdi) = if prev_tr == 0.0 {
(0.0, 0.0)
} else {
(
100.0 * prev_plus_dm / prev_tr,
100.0 * prev_minus_dm / prev_tr,
)
};
let denom = pdi + mdi;
let val = if denom != 0.0 {
100.0 * (pdi - mdi).abs() / denom
} else if carry {
if last.is_nan() {
0.0
} else {
last
}
} else {
0.0
};
dx[today] = val;
last = val;
}
dx
}
indicator! {
fn plus_dm(high: &[f64], low: &[f64], close: &[f64], time_period: usize) -> Vec<f64> with plus_dm_with_output;
}
pub fn plus_dm_with_output(
high: &[f64],
low: &[f64],
close: &[f64],
time_period: usize,
out: &mut [f64],
) -> Result<(), TaError> {
check_period(time_period)?;
check_eq_len(&[high, low, close], "plus_dm")?;
if out.len() != high.len() {
return Err(TaError::BadParam(
"plus_dm_with_output: out length must equal high length".into(),
));
}
let (pdm, _, _) = dm_tr(high, low, close, time_period);
out.copy_from_slice(&pdm);
Ok(())
}
pub fn plus_dm_default(high: &[f64], low: &[f64], close: &[f64]) -> Result<Vec<f64>, TaError> {
plus_dm(high, low, close, ATR_PERIOD)
}
indicator! {
fn minus_dm(high: &[f64], low: &[f64], close: &[f64], time_period: usize) -> Vec<f64> with minus_dm_with_output;
}
pub fn minus_dm_with_output(
high: &[f64],
low: &[f64],
close: &[f64],
time_period: usize,
out: &mut [f64],
) -> Result<(), TaError> {
check_period(time_period)?;
check_eq_len(&[high, low, close], "minus_dm")?;
if out.len() != high.len() {
return Err(TaError::BadParam(
"minus_dm_with_output: out length must equal high length".into(),
));
}
let (_, mdm, _) = dm_tr(high, low, close, time_period);
out.copy_from_slice(&mdm);
Ok(())
}
pub fn minus_dm_default(high: &[f64], low: &[f64], close: &[f64]) -> Result<Vec<f64>, TaError> {
minus_dm(high, low, close, ATR_PERIOD)
}
indicator! {
fn plus_di(high: &[f64], low: &[f64], close: &[f64], time_period: usize) -> Vec<f64> with plus_di_with_output;
}
pub fn plus_di_with_output(
high: &[f64],
low: &[f64],
close: &[f64],
time_period: usize,
out: &mut [f64],
) -> Result<(), TaError> {
check_period(time_period)?;
check_eq_len(&[high, low, close], "plus_di")?;
if out.len() != high.len() {
return Err(TaError::BadParam(
"plus_di_with_output: out length must equal high length".into(),
));
}
let (pdm, mdm, tr) = dm_tr(high, low, close, time_period);
let (pdi, _) = di_from_dm_tr(&pdm, &mdm, &tr, time_period);
out.copy_from_slice(&pdi);
Ok(())
}
pub fn plus_di_default(high: &[f64], low: &[f64], close: &[f64]) -> Result<Vec<f64>, TaError> {
plus_di(high, low, close, ATR_PERIOD)
}
indicator! {
fn minus_di(high: &[f64], low: &[f64], close: &[f64], time_period: usize) -> Vec<f64> with minus_di_with_output;
}
pub fn minus_di_with_output(
high: &[f64],
low: &[f64],
close: &[f64],
time_period: usize,
out: &mut [f64],
) -> Result<(), TaError> {
check_period(time_period)?;
check_eq_len(&[high, low, close], "minus_di")?;
if out.len() != high.len() {
return Err(TaError::BadParam(
"minus_di_with_output: out length must equal high length".into(),
));
}
let (pdm, mdm, tr) = dm_tr(high, low, close, time_period);
let (_, mdi) = di_from_dm_tr(&pdm, &mdm, &tr, time_period);
out.copy_from_slice(&mdi);
Ok(())
}
pub fn minus_di_default(high: &[f64], low: &[f64], close: &[f64]) -> Result<Vec<f64>, TaError> {
minus_di(high, low, close, ATR_PERIOD)
}
indicator! {
fn adx(high: &[f64], low: &[f64], close: &[f64], time_period: usize) -> Vec<f64> with adx_with_output;
}
pub fn adx_with_output(
high: &[f64],
low: &[f64],
close: &[f64],
time_period: usize,
out: &mut [f64],
) -> Result<(), TaError> {
check_period(time_period)?;
check_eq_len(&[high, low, close], "adx")?;
if out.len() != high.len() {
return Err(TaError::BadParam(
"adx_with_output: out length must equal high length".into(),
));
}
out.copy_from_slice(&adx_adxr_fused(high, low, close, time_period).0);
Ok(())
}
pub fn adx_default(high: &[f64], low: &[f64], close: &[f64]) -> Result<Vec<f64>, TaError> {
adx(high, low, close, ATR_PERIOD)
}
indicator! {
fn adxr(high: &[f64], low: &[f64], close: &[f64], time_period: usize) -> Vec<f64> with adxr_with_output;
}
pub fn adxr_with_output(
high: &[f64],
low: &[f64],
close: &[f64],
time_period: usize,
out: &mut [f64],
) -> Result<(), TaError> {
check_period(time_period)?;
check_eq_len(&[high, low, close], "adxr")?;
if out.len() != high.len() {
return Err(TaError::BadParam(
"adxr_with_output: out length must equal high length".into(),
));
}
out.copy_from_slice(&adx_adxr_fused(high, low, close, time_period).1);
Ok(())
}
pub fn adxr_default(high: &[f64], low: &[f64], close: &[f64]) -> Result<Vec<f64>, TaError> {
adxr(high, low, close, ATR_PERIOD)
}
pub fn aroon(high: &[f64], low: &[f64], time_period: usize) -> Result<Aroon, TaError> {
check_period(time_period)?;
check_eq_len(&[high, low], "aroon")?;
let n = high.len();
let mut out = Aroon {
up: vec![f64::NAN; n],
down: vec![f64::NAN; n],
};
aroon_with_output(high, low, time_period, &mut out)?;
Ok(out)
}
pub fn aroon_with_output(
high: &[f64],
low: &[f64],
time_period: usize,
out: &mut Aroon,
) -> Result<(), TaError> {
check_period(time_period)?;
check_eq_len(&[high, low], "aroon")?;
let n = high.len();
if out.up.len() != n || out.down.len() != n {
return Err(TaError::BadParam(
"aroon_with_output: out vectors must have length == high length".into(),
));
}
let period = time_period as i64;
let mut true_up = vec![f64::NAN; n];
let mut true_down = vec![f64::NAN; n];
if n <= time_period {
return Ok(());
}
let mut today: i64 = time_period as i64; let mut trailing_idx: i64 = today - period; let mut lowest_idx: i64 = -1;
let mut highest_idx: i64 = -1;
let mut lowest: f64 = 0.0;
let mut highest: f64 = 0.0;
let factor = 100.0 / period as f64;
let end = n as i64 - 1;
while today <= end {
let mut tmp = low[today as usize];
if lowest_idx < trailing_idx {
lowest_idx = trailing_idx;
lowest = low[lowest_idx as usize];
let mut i = lowest_idx;
while i + 1 <= today {
i += 1;
tmp = low[i as usize];
if tmp <= lowest {
lowest_idx = i;
lowest = tmp;
}
}
} else if tmp <= lowest {
lowest_idx = today;
lowest = tmp;
}
tmp = high[today as usize];
if highest_idx < trailing_idx {
highest_idx = trailing_idx;
highest = high[highest_idx as usize];
let mut i = highest_idx;
while i + 1 <= today {
i += 1;
tmp = high[i as usize];
if tmp >= highest {
highest_idx = i;
highest = tmp;
}
}
} else if tmp >= highest {
highest_idx = today;
highest = tmp;
}
true_up[today as usize] = factor * (period - (today - highest_idx)) as f64;
true_down[today as usize] = factor * (period - (today - lowest_idx)) as f64;
trailing_idx += 1;
today += 1;
}
out.up = true_down;
out.down = true_up;
Ok(())
}
pub fn aroon_default(high: &[f64], low: &[f64]) -> Result<Aroon, TaError> {
aroon(high, low, AROON_PERIOD)
}
indicator! {
fn aroon_osc(high: &[f64], low: &[f64], time_period: usize) -> Vec<f64> with aroon_osc_with_output
default aroon_osc_default(high: &[f64], low: &[f64]) => (AROON_PERIOD)
;
}
pub fn aroon_osc_with_output(
high: &[f64],
low: &[f64],
time_period: usize,
out: &mut [f64],
) -> Result<(), TaError> {
check_period(time_period)?;
check_eq_len(&[high, low], "aroon_osc")?;
if out.len() != high.len() {
return Err(TaError::BadParam(
"aroon_osc_with_output: out length must equal high length".into(),
));
}
let mut a = Aroon {
up: vec![f64::NAN; high.len()],
down: vec![f64::NAN; high.len()],
};
aroon_with_output(high, low, time_period, &mut a)?;
for i in 0..out.len() {
let u = a.up[i];
let d = a.down[i];
out[i] = if u.is_nan() || d.is_nan() {
f64::NAN
} else {
d - u
};
}
Ok(())
}
fn stoch_fastk(high: &[f64], low: &[f64], close: &[f64], fast_k_period: usize) -> Vec<f64> {
let n = close.len();
let mut fastk = vec![f64::NAN; n];
if n < fast_k_period {
return fastk;
}
let mut max_dq = crate::core::MonoQueue::with_capacity(fast_k_period);
let mut min_dq = crate::core::MonoQueue::with_capacity(fast_k_period);
for i in 0..n {
while !max_dq.is_empty() && max_dq.front() + fast_k_period <= i {
max_dq.pop_front();
}
while !min_dq.is_empty() && min_dq.front() + fast_k_period <= i {
min_dq.pop_front();
}
while !max_dq.is_empty() && high[max_dq.back()] <= high[i] {
max_dq.pop_back();
}
while !min_dq.is_empty() && low[min_dq.back()] >= low[i] {
min_dq.pop_back();
}
max_dq.push_back(i);
min_dq.push_back(i);
if i >= fast_k_period - 1 {
let hh = high[max_dq.front()];
let ll = low[min_dq.front()];
fastk[i] = if hh == ll {
0.0
} else {
100.0 * (close[i] - ll) / (hh - ll)
};
}
}
fastk
}
pub fn stoch(
high: &[f64],
low: &[f64],
close: &[f64],
fast_k_period: usize,
slow_k_period: usize,
slow_d_period: usize,
) -> Result<Stoch, TaError> {
check_period(fast_k_period)?;
check_period(slow_k_period)?;
check_period(slow_d_period)?;
check_eq_len(&[high, low, close], "stoch")?;
let n = close.len();
let mut out = Stoch {
slow_k: vec![f64::NAN; n],
slow_d: vec![f64::NAN; n],
};
stoch_with_output(
high,
low,
close,
fast_k_period,
slow_k_period,
slow_d_period,
&mut out,
)?;
Ok(out)
}
pub fn stoch_with_output(
high: &[f64],
low: &[f64],
close: &[f64],
fast_k_period: usize,
slow_k_period: usize,
slow_d_period: usize,
out: &mut Stoch,
) -> Result<(), TaError> {
check_period(fast_k_period)?;
check_period(slow_k_period)?;
check_period(slow_d_period)?;
check_eq_len(&[high, low, close], "stoch")?;
let n = close.len();
if out.slow_k.len() != n || out.slow_d.len() != n {
return Err(TaError::BadParam(
"stoch_with_output: out bands must have length == close length".into(),
));
}
let fastk = stoch_fastk(high, low, close, fast_k_period);
let slow_k = rolling_mean_skip(&fastk, slow_k_period);
let slow_d = rolling_mean_skip(&slow_k, slow_d_period);
let lookback = fast_k_period + slow_k_period + slow_d_period - 3;
out.slow_k.copy_from_slice(&slow_k);
out.slow_d.copy_from_slice(&slow_d);
for i in 0..lookback.min(n) {
out.slow_k[i] = f64::NAN;
}
Ok(())
}
pub fn stoch_default(high: &[f64], low: &[f64], close: &[f64]) -> Result<Stoch, TaError> {
stoch(
high,
low,
close,
STOCH_FAST_K,
STOCH_SLOW_K,
STOCH_SLOW_D,
)
}
pub fn stoch_f(
high: &[f64],
low: &[f64],
close: &[f64],
fast_k_period: usize,
fast_d_period: usize,
) -> Result<StochF, TaError> {
check_period(fast_k_period)?;
check_period(fast_d_period)?;
check_eq_len(&[high, low, close], "stoch_f")?;
let n = close.len();
let mut out = StochF {
fast_k: vec![f64::NAN; n],
fast_d: vec![f64::NAN; n],
};
stoch_f_with_output(high, low, close, fast_k_period, fast_d_period, &mut out)?;
Ok(out)
}
pub fn stoch_f_with_output(
high: &[f64],
low: &[f64],
close: &[f64],
fast_k_period: usize,
fast_d_period: usize,
out: &mut StochF,
) -> Result<(), TaError> {
check_period(fast_k_period)?;
check_period(fast_d_period)?;
check_eq_len(&[high, low, close], "stoch_f")?;
let n = close.len();
if out.fast_k.len() != n || out.fast_d.len() != n {
return Err(TaError::BadParam(
"stoch_f_with_output: out bands must have length == close length".into(),
));
}
#[cfg(feature = "parallel")]
{
if n >= 8192 {
return stoch_f_parallel_with_output(high, low, close, fast_k_period, fast_d_period, out);
}
}
stoch_f_serial_with_output(high, low, close, fast_k_period, fast_d_period, out)
}
fn stoch_f_serial_with_output(
high: &[f64],
low: &[f64],
close: &[f64],
fast_k_period: usize,
fast_d_period: usize,
out: &mut StochF,
) -> Result<(), TaError> {
let n = close.len();
let fastk = stoch_fastk(high, low, close, fast_k_period);
let fast_d = rolling_mean_skip(&fastk, fast_d_period);
let lookback = fast_k_period + fast_d_period - 2;
out.fast_k.copy_from_slice(&fastk);
out.fast_d.copy_from_slice(&fast_d);
for i in 0..lookback.min(n) {
out.fast_k[i] = f64::NAN;
}
Ok(())
}
pub fn stoch_f_serial(
high: &[f64],
low: &[f64],
close: &[f64],
fast_k_period: usize,
fast_d_period: usize,
) -> Result<StochF, TaError> {
check_period(fast_k_period)?;
check_period(fast_d_period)?;
check_eq_len(&[high, low, close], "stoch_f")?;
let n = close.len();
let mut out = StochF {
fast_k: vec![f64::NAN; n],
fast_d: vec![f64::NAN; n],
};
stoch_f_serial_with_output(high, low, close, fast_k_period, fast_d_period, &mut out)?;
Ok(out)
}
#[cfg(feature = "parallel")]
pub fn stoch_f_parallel(
high: &[f64],
low: &[f64],
close: &[f64],
fast_k_period: usize,
fast_d_period: usize,
) -> Result<StochF, TaError> {
check_period(fast_k_period)?;
check_period(fast_d_period)?;
check_eq_len(&[high, low, close], "stoch_f")?;
let n = close.len();
let mut out = StochF {
fast_k: vec![f64::NAN; n],
fast_d: vec![f64::NAN; n],
};
stoch_f_parallel_with_output(high, low, close, fast_k_period, fast_d_period, &mut out)?;
Ok(out)
}
#[cfg(feature = "parallel")]
fn stoch_f_parallel_with_output(
high: &[f64],
low: &[f64],
close: &[f64],
fast_k_period: usize,
fast_d_period: usize,
out: &mut StochF,
) -> Result<(), TaError> {
check_period(fast_k_period)?;
check_period(fast_d_period)?;
check_eq_len(&[high, low, close], "stoch_f")?;
let n = close.len();
if out.fast_k.len() != n || out.fast_d.len() != n {
return Err(TaError::BadParam(
"stoch_f_parallel_with_output: out bands must have length == close length".into(),
));
}
let fk = fast_k_period;
let fd = fast_d_period;
let overlap = fk + fd - 2;
crate::parallel::parallel_index_map_2(
n,
overlap,
&mut out.fast_k,
&mut out.fast_d,
|start, end| {
let mut local = StochF {
fast_k: vec![f64::NAN; end - start],
fast_d: vec![f64::NAN; end - start],
};
let _ = stoch_f_serial_with_output(
&high[start..end],
&low[start..end],
&close[start..end],
fk,
fd,
&mut local,
);
(local.fast_k, local.fast_d)
},
);
Ok(())
}
pub fn stoch_f_default(high: &[f64], low: &[f64], close: &[f64]) -> Result<StochF, TaError> {
stoch_f(high, low, close, STOCH_FAST_K, STOCH_SLOW_D)
}
pub fn stoch_rsi_with_output(
close: &[f64],
rsi_period: usize,
fast_k_period: usize,
fast_d_period: usize,
out: &mut StochF,
) -> Result<(), TaError> {
check_period(rsi_period)?;
check_period(fast_k_period)?;
check_period(fast_d_period)?;
let n = close.len();
if out.fast_k.len() != n || out.fast_d.len() != n {
return Err(TaError::BadParam(
"stoch_rsi_with_output: out bands must have length == close length".into(),
));
}
for v in out.fast_k.iter_mut().chain(out.fast_d.iter_mut()) {
*v = f64::NAN;
}
let r = rsi(close, rsi_period)?;
if n <= rsi_period {
return Ok(());
}
let compact: Vec<f64> = r[rsi_period..].to_vec();
let mut local = StochF {
fast_k: vec![f64::NAN; compact.len()],
fast_d: vec![f64::NAN; compact.len()],
};
stoch_f_with_output(
&compact,
&compact,
&compact,
fast_k_period,
fast_d_period,
&mut local,
)?;
for j in 0..local.fast_k.len() {
out.fast_k[rsi_period + j] = local.fast_k[j];
out.fast_d[rsi_period + j] = local.fast_d[j];
}
Ok(())
}
pub fn stoch_rsi(
close: &[f64],
rsi_period: usize,
time_period: usize,
) -> Result<Vec<f64>, TaError> {
check_period(rsi_period)?;
check_period(time_period)?;
let n = close.len();
let mut out = StochF {
fast_k: vec![f64::NAN; n],
fast_d: vec![f64::NAN; n],
};
stoch_rsi_with_output(close, rsi_period, time_period, 3, &mut out)?;
Ok(out.fast_k)
}
pub fn stoch_rsi_default(close: &[f64]) -> Result<Vec<f64>, TaError> {
stoch_rsi(close, STOCHRSI_RSI_PERIOD, STOCHRSI_PERIOD)
}
pub fn trix(values: &[f64], time_period: usize) -> Result<Vec<f64>, TaError> {
check_period(time_period)?;
let n = values.len();
let lookback = 3 * time_period - 2;
let mut out = vec![f64::NAN; n];
if n <= lookback {
return Ok(out);
}
let mut e3 = vec![f64::NAN; n];
crate::core::nested_ema_with_output::<3, _>(values, time_period, |e: &[f64; 3]| e[2], &mut e3);
for i in lookback..n {
let cur = e3[i];
let prev = e3[i - 1];
if !cur.is_nan() && !prev.is_nan() {
out[i] = if prev == 0.0 {
0.0
} else {
100.0 * (cur - prev) / prev
};
}
}
Ok(out)
}
pub fn trix_default(values: &[f64]) -> Result<Vec<f64>, TaError> {
trix(values, TRIX_PERIOD)
}
indicator! {
fn dx(high: &[f64], low: &[f64], close: &[f64], period: usize) -> Vec<f64> with dx_with_output;
}
pub fn dx_with_output(
high: &[f64],
low: &[f64],
close: &[f64],
period: usize,
out: &mut [f64],
) -> Result<(), TaError> {
check_period(period)?;
check_eq_len(&[high, low, close], "dx")?;
if out.len() != high.len() {
return Err(TaError::BadParam(
"dx_with_output: out length must equal high length".into(),
));
}
out.copy_from_slice(&dx_from_candles(high, low, close, period, true));
Ok(())
}
pub fn dx_default(high: &[f64], low: &[f64], close: &[f64]) -> Result<Vec<f64>, TaError> {
dx(high, low, close, DX_PERIOD)
}
indicator! {
fn imi(open: &[f64], close: &[f64], period: usize) -> Vec<f64> with imi_with_output;
}
pub fn imi_with_output(
open: &[f64],
close: &[f64],
period: usize,
out: &mut [f64],
) -> Result<(), TaError> {
check_period(period)?;
check_eq_len(&[open, close], "imi")?;
let n = open.len();
let lookback = period - 1;
if out.len() != n {
return Err(TaError::BadParam(
"imi_with_output: out length must equal open length".into(),
));
}
if n <= lookback {
return Ok(());
}
let mut up = vec![0.0_f64; n];
let mut down = vec![0.0_f64; n];
for i in 0..n {
let o = open[i];
let c = close[i];
if c > o {
up[i] = c - o;
} else if c < o {
down[i] = o - c;
}
}
let mut sum_up = 0.0_f64;
let mut sum_down = 0.0_f64;
for i in 0..period {
sum_up += up[i];
sum_down += down[i];
}
for i in lookback..n {
if i >= period {
sum_up -= up[i - period];
sum_down -= down[i - period];
}
if i == lookback {
} else {
sum_up += up[i];
sum_down += down[i];
}
let total = sum_up + sum_down;
out[i] = if total == 0.0 { 50.0 } else { 100.0 * sum_up / total };
}
Ok(())
}
pub fn imi_default(open: &[f64], close: &[f64]) -> Result<Vec<f64>, TaError> {
imi(open, close, IMI_PERIOD)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn mom_basic() {
let p = [1.0, 2.0, 4.0, 8.0, 16.0];
let out = mom(&p, 2).unwrap();
assert!(out[0].is_nan() && out[1].is_nan());
assert!((out[2] - 3.0).abs() < 1e-9);
assert!((out[4] - 12.0).abs() < 1e-9);
}
#[test]
fn roc_family() {
let p = [1.0, 2.0, 4.0, 8.0, 16.0];
let r = roc(&p, 2).unwrap();
assert!((r[2] - 300.0).abs() < 1e-9);
let c = rocr(&p, 2).unwrap();
assert!((c[2] - 4.0).abs() < 1e-9);
let c100 = rocr100(&p, 2).unwrap();
assert!((c100[2] - 400.0).abs() < 1e-9);
}
#[test]
fn rsi_wilder_seed() {
let p = [1.0, 2.0, 3.0, 2.0, 4.0];
let out = rsi(&p, 2).unwrap();
assert!(out[0].is_nan() && out[1].is_nan());
assert!((out[2] - 100.0).abs() < 1e-9);
assert!((out[3] - 50.0).abs() < 1e-9);
assert!((out[4] - (100.0 - 100.0 / 6.0)).abs() < 1e-9);
}
#[test]
fn macd_shape() {
let p: Vec<f64> = (0..60).map(|i| (i as f64).sin() + 2.0).collect();
let m = macd(&p, 12, 26, 9).unwrap();
assert_eq!(m.macd.len(), p.len());
assert!(m.macd[0].is_nan() && m.signal[32].is_nan());
assert!(!m.signal[40].is_nan());
}
#[test]
fn cmo_symmetry() {
let p = [1.0, 2.0, 1.0, 2.0, 1.0, 2.0, 1.0, 2.0];
let out = cmo(&p, 4).unwrap();
assert!(out[4].abs() < 1e-9);
}
#[test]
fn adx_runs() {
let high: Vec<f64> = (0..40).map(|i| 10.0 + i as f64 * 0.1).collect();
let low: Vec<f64> = (0..40).map(|i| 9.0 + i as f64 * 0.1).collect();
let close: Vec<f64> = (0..40).map(|i| 9.5 + i as f64 * 0.1).collect();
let a = adx(&high, &low, &close, 14).unwrap();
assert!(a[26].is_nan());
assert!(!a[27].is_nan());
assert!(a[30] >= 0.0);
}
#[test]
fn stoch_shape() {
let close: Vec<f64> = (0..40).map(|i| (i as f64 * 0.3).sin()).collect();
let high: Vec<f64> = close.iter().map(|&c| c + 0.5).collect();
let low: Vec<f64> = close.iter().map(|&c| c - 0.5).collect();
let s = stoch(&high, &low, &close, 5, 3, 3).unwrap();
assert_eq!(s.slow_k.len(), 40);
assert!(s.slow_k[5].is_nan() && s.slow_d[7].is_nan());
assert!(!s.slow_k[15].is_nan());
}
#[test]
fn bop_zero_range() {
let o = [1.0; 4];
let h = [1.0; 4];
let l = [1.0; 4];
let c = [1.0; 4];
let out = bop(&o, &h, &l, &c).unwrap();
assert!(out.iter().all(|&v| v == 0.0));
}
#[test]
fn trix_shape() {
let p: Vec<f64> = (0..120).map(|i| (i as f64).sin()).collect();
let out = trix(&p, 30).unwrap();
assert!(out[87].is_nan());
assert!(!out[100].is_nan());
}
}