pub mod single {
use crate::basic_indicators::single::max;
#[inline]
pub fn ulcer_index(prices: &[f64]) -> f64 {
if prices.is_empty() {
panic!("Prices cannot be empty")
};
let mut sum_sq = 0.0;
for (i, price) in prices.iter().enumerate().skip(1) {
let period_max = max(&prices[..=i]);
let percentage_drawdown = ((price - period_max) / period_max) * 100.0;
sum_sq += percentage_drawdown.powi(2);
}
(sum_sq / prices.len() as f64).sqrt()
}
}
pub mod bulk {
use crate::basic_indicators::single::{max, min};
use crate::chart_trends::overall_trend;
use crate::other_indicators::bulk::average_true_range;
use crate::volatility_indicators::single;
use crate::{ConstantModelType, Position};
#[inline]
pub fn ulcer_index(prices: &[f64], period: usize) -> Vec<f64> {
let length = prices.len();
if period > length {
panic!(
"Period ({}) cannot be longer than length of prices ({})",
period, length
)
};
let mut ulcer_indexes = Vec::with_capacity(length - period + 1);
for window in prices.windows(period) {
ulcer_indexes.push(single::ulcer_index(window));
}
ulcer_indexes
}
pub fn volatility_system(
high: &[f64],
low: &[f64],
close: &[f64],
period: usize,
constant_multiplier: f64,
constant_model_type: ConstantModelType,
) -> Vec<f64> {
let length = close.len();
if length != high.len() || length != low.len() {
panic!(
"Lengths of close ({}), high ({}), and low ({}) must be equal",
length,
high.len(),
low.len()
)
};
if close.is_empty() {
panic!("Prices cannot be empty");
};
if length < period {
panic!(
"Period ({}) must be less than or equal to length of prices ({})",
period, length
)
};
let typical_price: Vec<f64> = (0..length)
.map(|i| (high[i] + low[i] + close[i]) / 3.0)
.collect();
let mut sars = Vec::with_capacity(length - period + 1);
let mut position;
let mut significant_close;
let mut previous_period = period;
let trend = overall_trend(&typical_price[..previous_period]);
let atr = average_true_range(close, high, low, constant_model_type, period);
let arc: Vec<f64> = atr.iter().map(|x| x * constant_multiplier).collect();
if trend.0 < 0.0 {
significant_close = min(&close[..previous_period]);
position = Position::Short;
sars.push(significant_close + arc[0]);
sars.push(significant_close + arc[1]);
} else {
significant_close = max(&close[..previous_period]);
position = Position::Long;
sars.push(significant_close - arc[0]);
sars.push(significant_close - arc[1]);
};
for i in 2..arc.len() {
let max_period = i + period - 1;
if position == Position::Short {
if close[max_period] > sars[i - 1] {
position = Position::Long;
significant_close = max(&close[previous_period..max_period]);
previous_period = max_period;
sars.push(significant_close - arc[i]);
} else {
sars.push(significant_close + arc[i]);
}
} else if position == Position::Long {
if close[max_period] < sars[i - 1] {
position = Position::Short;
significant_close = min(&close[previous_period..max_period]);
previous_period = max_period;
sars.push(significant_close + arc[i]);
} else {
sars.push(significant_close - arc[i]);
}
} else {
panic!("Invalid position {:?}", position);
}
}
sars
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn single_ulcer_index() {
let prices = vec![100.46, 100.53, 100.38, 100.19, 100.21];
assert_eq!(0.21816086938686668, single::ulcer_index(&prices));
}
#[test]
#[should_panic]
fn single_ucler_index_panic() {
let prices = Vec::new();
single::ulcer_index(&prices);
}
#[test]
fn bulk_ulcer_index() {
let prices = vec![100.46, 100.53, 100.38, 100.19, 100.21, 100.32, 100.28];
assert_eq!(
vec![0.21816086938686668, 0.2373213243162752, 0.12490478596260104],
bulk::ulcer_index(&prices, 5_usize)
);
}
#[test]
#[should_panic]
fn bulk_ulcer_index_panic() {
let prices = vec![100.46, 100.53, 100.38, 100.19, 100.21, 100.32, 100.28];
bulk::ulcer_index(&prices, 50_usize);
}
#[test]
fn bulk_volatility_system_long_start() {
let highs = vec![100.83, 100.91, 101.03, 101.27, 100.52];
let lows = vec![100.59, 100.72, 100.84, 100.91, 99.85];
let close = vec![100.76, 100.88, 100.96, 101.14, 100.01];
let period: usize = 3;
assert_eq!(
vec![100.54666666666667, 100.46666666666667, 101.95333333333333],
bulk::volatility_system(
&highs,
&lows,
&close,
period,
2.0,
crate::ConstantModelType::SimpleMovingAverage
)
);
}
#[test]
fn bulk_volatility_system_short_start() {
let highs = vec![101.27, 101.03, 100.91, 100.83, 101.54];
let lows = vec![100.91, 100.84, 100.72, 100.59, 100.68];
let close = vec![101.14, 100.96, 100.88, 100.76, 101.37];
let period: usize = 3;
assert_eq!(
vec![101.37333333333332, 101.29333333333332, 99.9],
bulk::volatility_system(
&highs,
&lows,
&close,
period,
2.0,
crate::ConstantModelType::SimpleMovingAverage
)
);
}
#[test]
#[should_panic]
fn bulk_volatility_system_panic_high_length() {
let highs = vec![101.27, 101.03, 100.83, 101.54];
let lows = vec![100.91, 100.84, 100.72, 100.59, 100.68];
let close = vec![101.14, 100.96, 100.88, 100.76, 101.37];
let period: usize = 3;
bulk::volatility_system(
&highs,
&lows,
&close,
period,
2.0,
crate::ConstantModelType::SimpleMovingAverage,
);
}
#[test]
#[should_panic]
fn bulk_volatility_system_panic_low_length() {
let highs = vec![101.27, 101.03, 100.91, 100.83, 101.54];
let lows = vec![100.91, 100.84, 100.72, 100.68];
let close = vec![101.14, 100.96, 100.88, 100.76, 101.37];
let period: usize = 3;
bulk::volatility_system(
&highs,
&lows,
&close,
period,
2.0,
crate::ConstantModelType::SimpleMovingAverage,
);
}
#[test]
#[should_panic]
fn bulk_volatility_system_panic_close_length() {
let highs = vec![101.27, 101.03, 100.91, 100.83, 101.54];
let lows = vec![100.91, 100.84, 100.72, 100.59, 100.68];
let close = vec![101.14, 100.96, 100.88, 101.37];
let period: usize = 3;
bulk::volatility_system(
&highs,
&lows,
&close,
period,
2.0,
crate::ConstantModelType::SimpleMovingAverage,
);
}
#[test]
#[should_panic]
fn bulk_volatility_system_panic_empty() {
let highs = Vec::new();
let lows = Vec::new();
let close = Vec::new();
let period: usize = 3;
bulk::volatility_system(
&highs,
&lows,
&close,
period,
2.0,
crate::ConstantModelType::SimpleMovingAverage,
);
}
#[test]
#[should_panic]
fn bulk_volatility_system_panic_period() {
let highs = vec![101.27, 101.03, 100.91, 100.83, 101.54];
let lows = vec![100.91, 100.84, 100.72, 100.59, 100.68];
let close = vec![101.14, 100.96, 100.88, 100.76, 101.37];
let period: usize = 30;
bulk::volatility_system(
&highs,
&lows,
&close,
period,
2.0,
crate::ConstantModelType::SimpleMovingAverage,
);
}
}