pub mod single {
use crate::basic_indicators::single::mean;
use crate::validation::{assert_non_empty, assert_positive_usize, unsupported_type};
use crate::MovingAverageType;
#[inline]
pub fn moving_average(
prices: &[f64],
moving_average_type: MovingAverageType,
) -> crate::Result<f64> {
assert_non_empty("prices", prices)?;
match moving_average_type {
MovingAverageType::Simple => mean(prices),
MovingAverageType::Smoothed => personalised_moving_average(prices, 1.0, 0.0),
MovingAverageType::Exponential => personalised_moving_average(prices, 2.0, 1.0),
MovingAverageType::Personalised {
alpha_num,
alpha_den,
} => personalised_moving_average(prices, alpha_num, alpha_den),
_ => Err(unsupported_type("MovingAverageType")),
}
}
#[inline]
fn personalised_moving_average(
prices: &[f64],
alpha_numerator: f64,
alpha_denominator: f64,
) -> crate::Result<f64> {
let length = prices.len() as f64;
if length == 1.0 {
return Ok(prices[0]);
};
if length + alpha_denominator == 0.0 {
return Err(crate::TechnicalIndicatorError::InvalidValue {
name: "alpha_denominator".to_string(),
value: alpha_denominator,
reason: format!(
"The length of prices ({}) and the alpha_denominator ({}) add up to 0",
length, alpha_denominator
),
});
};
let alpha: f64 = alpha_numerator / (length + alpha_denominator);
let multiplicator = 1.0 - alpha;
let mut price_sum: f64 = 0.0;
let mut denominator_sum: f64 = 0.0;
for (index, price) in prices.iter().rev().enumerate() {
let multiplactor_powd = multiplicator.powi(index as i32);
denominator_sum += multiplactor_powd;
price_sum += price * multiplactor_powd;
}
Ok(price_sum / denominator_sum)
}
#[inline]
pub fn mcginley_dynamic(
latest_price: f64,
previous_mcginley_dynamic: f64,
period: usize,
) -> crate::Result<f64> {
assert_positive_usize("period", period)?;
if previous_mcginley_dynamic == 0.0 {
return Ok(latest_price);
};
let base = latest_price / previous_mcginley_dynamic;
Ok(previous_mcginley_dynamic
+ ((latest_price - previous_mcginley_dynamic) / (period as f64 * base.powi(4))))
}
}
pub mod bulk {
use crate::moving_average::single;
use crate::validation::assert_period;
use crate::MovingAverageType;
#[inline]
pub fn moving_average(
prices: &[f64],
moving_average_type: MovingAverageType,
period: usize,
) -> crate::Result<Vec<f64>> {
let length = prices.len();
assert_period(period, length)?;
let mut moving_averages = Vec::with_capacity(length - period + 1);
for window in prices.windows(period) {
moving_averages.push(single::moving_average(window, moving_average_type)?);
}
Ok(moving_averages)
}
#[inline]
pub fn mcginley_dynamic(
prices: &[f64],
previous_mcginley_dynamic: f64,
period: usize,
) -> crate::Result<Vec<f64>> {
let length = prices.len();
assert_period(period, length)?;
let mut mcginley_dynamics = Vec::with_capacity(length - period + 1);
let mut mcginley_dynamic =
single::mcginley_dynamic(prices[period - 1], previous_mcginley_dynamic, period)?;
mcginley_dynamics.push(mcginley_dynamic);
for &price in prices.iter().skip(period) {
mcginley_dynamic = single::mcginley_dynamic(price, mcginley_dynamic, period)?;
mcginley_dynamics.push(mcginley_dynamic);
}
Ok(mcginley_dynamics)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn single_simple_moving_average() {
let prices = vec![100.2, 100.46, 100.53, 100.38, 100.19];
let simple_ma = single::moving_average(&prices, crate::MovingAverageType::Simple).unwrap();
assert_eq!(100.352, simple_ma);
}
#[test]
fn single_exponential_moving_average() {
let prices = vec![100.2, 100.46, 100.53, 100.38, 100.19];
let exponential_ma =
single::moving_average(&prices, crate::MovingAverageType::Exponential).unwrap();
assert_eq!(100.32810426540287, exponential_ma);
}
#[test]
fn single_smoothed_moving_average() {
let prices = vec![100.2, 100.46, 100.53, 100.38, 100.19];
let smoothed_ma =
single::moving_average(&prices, crate::MovingAverageType::Smoothed).unwrap();
assert_eq!(100.34228938600666, smoothed_ma);
}
#[test]
fn single_personalised_moving_average() {
let prices = vec![100.2, 100.46, 100.53, 100.38, 100.19];
let personalised_ma = single::moving_average(
&prices,
crate::MovingAverageType::Personalised {
alpha_num: 5.0,
alpha_den: 3.0,
},
)
.unwrap();
assert_eq!(100.27405995388162, personalised_ma)
}
#[test]
fn single_moving_average_error() {
let prices = Vec::new();
let result = single::moving_average(&prices, crate::MovingAverageType::Simple);
assert!(result.is_err());
}
#[test]
fn single_moving_average_personalised_ma_error() {
let prices = vec![100.2, 100.46, 100.53, 100.38, 100.19];
let result = single::moving_average(
&prices,
crate::MovingAverageType::Personalised {
alpha_num: 5.0,
alpha_den: -5.0,
},
);
assert!(result.is_err());
}
#[test]
fn bulk_simlpe_moving_average() {
let prices = vec![100.2, 100.46, 100.53, 100.38, 100.19];
let period: usize = 3;
let simple_ma =
bulk::moving_average(&prices, crate::MovingAverageType::Simple, period).unwrap();
assert_eq!(
vec![100.39666666666666, 100.456666666666666, 100.36666666666667],
simple_ma
);
}
#[test]
fn bulk_exponential_moving_average() {
let prices = vec![100.2, 100.46, 100.53, 100.38, 100.19];
let period: usize = 3;
let exponential_ma =
bulk::moving_average(&prices, crate::MovingAverageType::Exponential, period).unwrap();
assert_eq!(
vec![100.46285714285715, 100.4342857142857, 100.29285714285713],
exponential_ma
);
}
#[test]
fn bulk_smoothed_moving_average() {
let prices = vec![100.2, 100.46, 100.53, 100.38, 100.19];
let period: usize = 3;
let smoothed_ma =
bulk::moving_average(&prices, crate::MovingAverageType::Smoothed, period).unwrap();
assert_eq!(
vec![100.43842105263158, 100.4442105263158, 100.32157894736842],
smoothed_ma
);
}
#[test]
fn bulk_personalised_moving_average() {
let prices = vec![100.2, 100.46, 100.53, 100.38, 100.19];
let period: usize = 3;
let personalised_ma = bulk::moving_average(
&prices,
crate::MovingAverageType::Personalised {
alpha_num: 5.0,
alpha_den: 3.0,
},
period,
)
.unwrap();
assert_eq!(
vec![100.5125581395349, 100.40279069767443, 100.22441860465118],
personalised_ma
);
}
#[test]
fn bulk_moving_average_error() {
let prices = vec![100.2, 100.46, 100.53, 100.38, 100.19];
let period: usize = 30;
let result = bulk::moving_average(&prices, crate::MovingAverageType::Simple, period);
assert!(result.is_err());
}
#[test]
fn single_mcginley_dynamic_no_previous() {
let mcginley_dynamic = single::mcginley_dynamic(100.19, 0.0_f64, 5_usize).unwrap();
assert_eq!(100.19, mcginley_dynamic);
}
#[test]
fn single_mcginley_dynamic_previous() {
let mcginley_dynamic = single::mcginley_dynamic(100.21, 100.19, 5_usize).unwrap();
assert_eq!(100.19399680766176, mcginley_dynamic);
}
#[test]
fn single_mcginley_dynamic_error() {
let result = single::mcginley_dynamic(100.0, 0.0_f64, 0_usize);
assert!(result.is_err());
}
#[test]
fn bulk_mcginley_dynamic() {
let prices = vec![100.2, 100.46, 100.53, 100.38, 100.19];
let period: usize = 3;
assert_eq!(
vec![100.53, 100.47970046511769, 100.38201189376744],
bulk::mcginley_dynamic(&prices, 0.0_f64, period).unwrap()
);
assert_eq!(
vec![100.47970046511769, 100.38201189376744],
bulk::mcginley_dynamic(&prices[1..], 100.53, period).unwrap()
);
}
#[test]
fn bulk_mcginley_dynamic_error() {
let prices = vec![100.2, 100.46, 100.53, 100.38, 100.19];
let period: usize = 30;
let result = bulk::mcginley_dynamic(&prices, 0.0_f64, period);
assert!(result.is_err());
}
}