pub mod single {
use crate::{AbsDevConfig, CentralPoint, DeviationAggregate};
use std::cmp::Ordering;
use std::collections::HashMap;
#[inline]
pub fn mean(prices: &[f64]) -> f64 {
if prices.is_empty() {
panic!("Prices ({:?}) is empty", prices);
};
prices.iter().sum::<f64>() / prices.len() as f64
}
#[inline]
pub fn median(prices: &[f64]) -> f64 {
if prices.is_empty() {
panic!("Prices ({:?}) is empty", prices);
};
let mut values: Vec<f64> = prices.iter().copied().filter(|f| !f.is_nan()).collect();
values.sort_by(|a, b| a.partial_cmp(b).unwrap_or(Ordering::Equal));
let mid = values.len() / 2;
if values.len() % 2 == 0 {
(values[mid - 1] + values[mid]) / 2.0
} else {
values[mid]
}
}
#[inline]
pub fn mode(prices: &[f64]) -> f64 {
if prices.is_empty() {
panic!("Prices ({:?}) is empty", prices);
};
let mut frequency: HashMap<i64, usize> = HashMap::new();
for &price in prices {
*frequency.entry(price.round() as i64).or_insert(0) += 1;
}
let max_count = frequency.values().copied().max().unwrap();
let modes: Vec<i64> = frequency
.iter()
.filter_map(|(&value, &count)| {
if count == max_count {
Some(value)
} else {
None
}
})
.collect();
modes.iter().sum::<i64>() as f64 / modes.len() as f64
}
#[inline]
pub fn log_difference(price_t: f64, price_t_1: f64) -> f64 {
if price_t <= 0.0 || price_t_1 <= 0.0 {
panic!(
"price_t ({}) and price_t_1 ({}) need to be greater than 0.0",
price_t, price_t_1
);
}
price_t.ln() - price_t_1.ln()
}
#[inline]
pub fn variance(prices: &[f64]) -> f64 {
if prices.is_empty() {
panic!("Prices ({:?}) is empty", prices);
}
let prices_mean = mean(prices);
let mean_diff_sq: Vec<f64> = prices.iter().map(|x| (x - prices_mean).powi(2)).collect();
mean(&mean_diff_sq)
}
#[inline]
pub fn standard_deviation(prices: &[f64]) -> f64 {
variance(prices).sqrt()
}
#[inline]
pub fn absolute_deviation(prices: &[f64], config: AbsDevConfig) -> f64 {
if prices.is_empty() {
panic!("Prices is empty")
};
let mid_point = match config.center {
CentralPoint::Mean => mean(prices),
CentralPoint::Median => median(prices),
CentralPoint::Mode => mode(prices),
_ => panic!("Unsupported central_point {:?}", config.center),
};
let devs: Vec<f64> = prices.iter().map(|&x| (x - mid_point).abs()).collect();
match config.aggregate {
DeviationAggregate::Mean => mean(&devs),
DeviationAggregate::Median => median(&devs),
DeviationAggregate::Mode => mode(&devs),
}
}
#[inline]
pub fn log_standard_deviation(prices: &[f64]) -> f64 {
if prices.is_empty() {
panic!("Prices ({:?}) is empty", prices);
}
let mut logs = Vec::with_capacity(prices.len());
for &x in prices {
if x <= 0.0 {
panic!("LogStandardDeviation requires positive prices; found {}", x);
}
logs.push(x.ln());
}
standard_deviation(&logs)
}
#[inline]
pub fn student_t_adjusted_std(prices: &[f64], df: f64) -> f64 {
if df <= 2.0 {
panic!("Degrees of freedom ({}) must be greater than 2", df);
}
let s = standard_deviation(prices);
s * (df / (df - 2.0)).sqrt()
}
#[inline]
pub fn laplace_std_equivalent(prices: &[f64]) -> f64 {
let mad = absolute_deviation(
prices,
AbsDevConfig {
center: CentralPoint::Median,
aggregate: DeviationAggregate::Median,
},
);
mad * 2.0f64.sqrt()
}
#[inline]
pub fn cauchy_iqr_scale(prices: &[f64]) -> f64 {
if prices.len() < 4 {
panic!(
"CauchyIQRScale requires at least 4 values; received {}",
prices.len()
);
}
let mut v = prices.to_vec();
v.sort_by(|a, b| a.partial_cmp(b).unwrap());
let n = v.len();
let mid = n / 2;
let (lower, upper) = if n % 2 == 0 {
(&v[..mid], &v[mid..])
} else {
(&v[..mid], &v[mid + 1..])
};
let q1 = percentile50(lower); let q3 = percentile50(upper); (q3 - q1) / 2.0
}
#[inline]
fn percentile50(slice: &[f64]) -> f64 {
let m = slice.len();
if m == 0 {
return f64::NAN;
}
if m % 2 == 1 {
slice[m / 2]
} else {
0.5 * (slice[m / 2 - 1] + slice[m / 2])
}
}
#[inline]
pub fn max(prices: &[f64]) -> f64 {
if prices.is_empty() {
panic!("Prices is empty")
};
prices
.iter()
.copied()
.filter(|f| !f.is_nan())
.fold(f64::NAN, f64::max)
}
#[inline]
pub fn min(prices: &[f64]) -> f64 {
if prices.is_empty() {
panic!("Prices is empty")
};
prices
.iter()
.copied()
.filter(|f| !f.is_nan())
.fold(f64::NAN, f64::min)
}
#[inline]
pub fn price_distribution(prices: &[f64], precision: f64) -> Vec<(f64, usize)> {
if prices.is_empty() {
panic!("Prices ({:?}) is empty", prices);
}
if !(precision > 0.0) {
panic!("precision ({}) must be > 0.0 and not NaN", precision);
}
let mut frequency: HashMap<i64, usize> = HashMap::new();
for &price in prices {
if !price.is_nan() {
let key = (price / precision).round() as i64;
*frequency.entry(key).or_insert(0) += 1;
}
}
let mut result: Vec<(f64, usize)> = frequency
.into_iter()
.map(|(key, count)| ((key as f64) * precision, count))
.collect();
result.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap_or(Ordering::Equal));
result
}
#[inline]
fn empirical_quantile_from_distribution(
prices: &[f64],
precision: f64,
q: f64,
) -> f64 {
if !(q > 0.0 && q < 1.0) {
panic!("Quantile ({}) must be in (0,1)", q);
}
let hist = price_distribution(prices, precision);
let n: usize = hist.iter().map(|(_, c)| *c).sum();
if n == 0 {
return f64::NAN;
}
let target = q * (n.saturating_sub(1)) as f64;
let mut cum = 0usize;
for (i, (price, count)) in hist.iter().enumerate() {
let prev_cum = cum;
cum += *count;
if (target as usize) < cum {
let within = if *count > 1 {
(target - prev_cum as f64) / (*count as f64)
} else {
0.0
};
if i + 1 < hist.len() {
let (next_price, _) = hist[i + 1];
return price + within.clamp(0.0, 1.0) * (next_price - price);
} else {
return *price;
}
}
}
hist.last().map(|(p, _)| *p).unwrap_or(f64::NAN)
}
#[inline]
pub fn empirical_quantile_range_from_distribution(
prices: &[f64],
precision: f64,
low: f64,
high: f64,
) -> f64 {
if !(precision > 0.0) {
panic!("precision ({}) must be > 0.0 and not NaN", precision);
}
if !(low > 0.0 && low < 1.0 && high > 0.0 && high < 1.0 && low < high) {
panic!(
"Invalid quantile bounds: low ({}) and high ({}) must be in (0,1) and low < high",
low, high
);
}
let ql = empirical_quantile_from_distribution(prices, precision, low);
let qh = empirical_quantile_from_distribution(prices, precision, high);
qh - ql
}
}
pub mod bulk {
use crate::basic_indicators::single;
use crate::AbsDevConfig;
#[inline]
pub fn mean(prices: &[f64], period: usize) -> Vec<f64> {
if period == 0 {
panic!("Period ({}) must be greater than 0", period);
}
if period > prices.len() {
panic!(
"Period ({}) cannot be longer than the length of prices ({})",
period,
prices.len()
);
};
let mut result = Vec::with_capacity(prices.len());
for window in prices.windows(period) {
result.push(single::mean(window))
}
result
}
#[inline]
pub fn median(prices: &[f64], period: usize) -> Vec<f64> {
if period == 0 {
panic!("Period ({}) must be greater than 0", period);
};
if period > prices.len() {
panic!(
"Period ({}) cannot be longer than the length of provided prices ({})",
period,
prices.len()
);
};
let mut result = Vec::with_capacity(prices.len());
for window in prices.windows(period) {
result.push(single::median(window))
}
result
}
#[inline]
pub fn mode(prices: &[f64], period: usize) -> Vec<f64> {
if period == 0 {
panic!("Period ({}) must be greater than 0", period);
};
if period > prices.len() {
panic!(
"Period ({}) cannot be longer than the length of provided prices ({})",
period,
prices.len()
);
};
let mut result = Vec::with_capacity(prices.len());
for window in prices.windows(period) {
result.push(single::mode(window))
}
result
}
#[inline]
pub fn log(prices: &[f64]) -> Vec<f64> {
if prices.is_empty() {
panic!("Prices ({:?}) is empty", prices);
}
prices.iter().map(|&p| p.ln()).collect()
}
#[inline]
pub fn log_difference(prices: &[f64]) -> Vec<f64> {
if prices.is_empty() {
panic!("Prices ({:?}) is empty", prices);
}
prices
.windows(2)
.map(|w| single::log_difference(w[1], w[0]))
.collect()
}
#[inline]
pub fn variance(prices: &[f64], period: usize) -> Vec<f64> {
if period == 0 {
panic!("Period ({}) must be greater than 0", period)
};
if period > prices.len() {
panic!(
"Period ({}) cannot be longer than the length of provided prices ({})",
period,
prices.len()
);
};
let mut result = Vec::with_capacity(prices.len());
for window in prices.windows(period) {
result.push(single::variance(window))
}
result
}
#[inline]
pub fn standard_deviation(prices: &[f64], period: usize) -> Vec<f64> {
if period == 0 {
panic!("Period ({}) must be greater than 0", period)
};
if period > prices.len() {
panic!(
"Period ({}) cannot be longer than the length of provided prices ({})",
period,
prices.len()
);
};
let mut result = Vec::with_capacity(prices.len());
for window in prices.windows(period) {
result.push(single::standard_deviation(window));
}
result
}
#[inline]
pub fn absolute_deviation(prices: &[f64], period: usize, config: AbsDevConfig) -> Vec<f64> {
if period == 0 {
panic!("Period ({}) must be greater than 0", period)
};
if period > prices.len() {
panic!(
"Period ({}) cannot be longer than the length of provided prices ({})",
period,
prices.len()
);
};
prices
.windows(period)
.map(|w| single::absolute_deviation(w, config))
.collect()
}
#[inline]
pub fn price_distribution(
prices: &[f64],
period: usize,
precision: f64,
) -> Vec<Vec<(f64, usize)>> {
if period == 0 {
panic!("Period ({}) must be greater than 0", period);
}
if period > prices.len() {
panic!(
"Period ({}) cannot be longer than the length of provided prices ({})",
period,
prices.len()
);
}
prices
.windows(period)
.map(|w| single::price_distribution(w, precision))
.collect()
}
#[inline]
pub fn log_standard_deviation(prices: &[f64], period: usize) -> Vec<f64> {
if period == 0 {
panic!("Period ({}) must be greater than 0", period);
}
if period > prices.len() {
panic!(
"Period ({}) cannot be longer than the length of prices ({})",
period,
prices.len()
);
}
let mut result = Vec::with_capacity(prices.len());
for window in prices.windows(period) {
result.push(single::log_standard_deviation(window))
}
result
}
#[inline]
pub fn student_t_adjusted_std(prices: &[f64], period: usize, df: f64) -> Vec<f64> {
if period == 0 {
panic!("Period ({}) must be greater than 0", period);
}
if period > prices.len() {
panic!(
"Period ({}) cannot be longer than the length of prices ({})",
period,
prices.len()
);
}
let mut result = Vec::with_capacity(prices.len());
for window in prices.windows(period) {
result.push(single::student_t_adjusted_std(window, df))
}
result
}
#[inline]
pub fn laplace_std_equivalent(prices: &[f64], period: usize) -> Vec<f64> {
if period == 0 {
panic!("Period ({}) must be greater than 0", period);
}
if period > prices.len() {
panic!(
"Period ({}) cannot be longer than the length of prices ({})",
period,
prices.len()
);
}
let mut result = Vec::with_capacity(prices.len());
for window in prices.windows(period) {
result.push(single::laplace_std_equivalent(window))
}
result
}
#[inline]
pub fn cauchy_iqr_scale(prices: &[f64], period: usize) -> Vec<f64> {
if period < 4 {
panic!(
"Period ({}) must be at least 4 for Cauchy IQR scale",
period
);
}
if period > prices.len() {
panic!(
"Period ({}) cannot be longer than the length of prices ({})",
period,
prices.len()
);
}
let mut result = Vec::with_capacity(prices.len());
for window in prices.windows(period) {
result.push(single::cauchy_iqr_scale(window))
}
result
}
#[inline]
pub fn empirical_quantile_range_from_distribution(
prices: &[f64],
period: usize,
precision: f64,
low: f64,
high: f64,
) -> Vec<f64> {
if period == 0 {
panic!("Period ({}) must be greater than 0", period);
}
if period > prices.len() {
panic!(
"Period ({}) cannot be longer than the length of provided prices ({})",
period,
prices.len()
);
}
prices
.windows(period)
.map(|w| single::empirical_quantile_range_from_distribution(w, precision, low, high))
.collect()
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::f64::consts::E;
#[test]
fn single_mean() {
let prices = vec![100.2, 100.46, 100.53, 100.38, 100.19];
assert_eq!(100.352, single::mean(&prices));
}
#[test]
fn single_mean_identical_prices() {
let prices = vec![100.0, 100.0, 100.0];
assert_eq!(100.0, single::mean(&prices));
}
#[test]
#[should_panic]
fn single_mean_empty_prices() {
let prices = Vec::new();
single::mean(&prices);
}
#[test]
fn bulk_mean() {
let prices = vec![100.2, 100.46, 100.53, 100.38, 100.19];
let period: usize = 3;
assert_eq!(
vec![100.39666666666666, 100.45666666666666, 100.36666666666667],
bulk::mean(&prices, period)
);
}
#[test]
#[should_panic]
fn bulk_mean_long_period_panic() {
let prices = vec![100.2, 100.46, 100.53, 100.38, 100.19];
let period: usize = 30;
bulk::mean(&prices, period);
}
#[test]
#[should_panic]
fn bulk_mean_no_period_panic() {
let prices = vec![100.2, 100.46, 100.53, 100.38, 100.19];
let period: usize = 0;
bulk::mean(&prices, period);
}
#[test]
fn single_median_odd() {
let prices = vec![100.2, 100.46, 100.53, 100.38, 100.19];
assert_eq!(100.38, single::median(&prices));
}
#[test]
fn single_median_even() {
let prices = vec![100.2, 100.46, 100.53, 100.38];
assert_eq!(100.41999999999999, single::median(&prices));
}
#[test]
#[should_panic]
fn single_median_panic() {
let prices = Vec::new();
single::median(&prices);
}
#[test]
fn bulk_median() {
let prices = vec![100.2, 100.46, 100.53, 100.38, 100.19];
let period: usize = 3;
assert_eq!(vec![100.46, 100.46, 100.38], bulk::median(&prices, period));
}
#[test]
#[should_panic]
fn bulk_median_long_period_panic() {
let prices = vec![100.2, 100.46, 100.53, 100.38, 100.19];
let period: usize = 30;
bulk::median(&prices, period);
}
#[test]
#[should_panic]
fn bulk_median_no_period_panic() {
let prices = vec![100.2, 100.46, 100.53, 100.38, 100.19];
let period: usize = 0;
bulk::median(&prices, period);
}
#[test]
fn single_mode_round_up() {
let prices = vec![100.2, 100.46, 100.53, 101.08, 101.19];
assert_eq!(101.0, single::mode(&prices));
}
#[test]
fn single_mode_round_down() {
let prices = vec![100.2, 100.46, 100.35, 101.08, 101.19];
assert_eq!(100.0, single::mode(&prices));
}
#[test]
fn single_mode_average() {
let prices = vec![100.46, 100.35, 101.08, 101.19];
assert_eq!(100.5, single::mode(&prices));
}
#[test]
#[should_panic]
fn single_mode_panic() {
let prices = Vec::new();
single::mode(&prices);
}
#[test]
fn bulk_mode() {
let prices = vec![100.2, 100.46, 100.53, 101.08, 101.19];
let period: usize = 3;
assert_eq!(vec![100.0, 101.0, 101.0], bulk::mode(&prices, period));
}
#[test]
#[should_panic]
fn bulk_mode_long_period_panic() {
let prices = vec![100.2, 100.46, 100.53, 101.08, 101.19];
let period: usize = 30;
bulk::mode(&prices, period);
}
#[test]
#[should_panic]
fn bulk_mode_no_period_panic() {
let prices = vec![100.2, 100.46, 100.53, 101.08, 101.19];
let period: usize = 0;
bulk::mode(&prices, period);
}
#[test]
fn bulk_log() {
let prices = vec![100.2, 100.46, 100.53, 100.38, 100.19];
assert_eq!(
vec![
4.607168188650764,
4.609759638321899,
4.610456190417329,
4.608962984226787,
4.607068383271171
],
bulk::log(&prices)
);
}
#[test]
#[should_panic]
fn bulk_log_panic() {
let prices = Vec::new();
bulk::log(&prices);
}
#[test]
fn single_log_difference() {
assert_eq!(
-0.0018946009556159993,
single::log_difference(100.19, 100.38)
);
}
#[test]
#[should_panic]
fn single_log_difference_panic() {
single::log_difference(0.0, 100.38);
}
#[test]
#[should_panic]
fn single_log_difference_panic_2() {
single::log_difference(100.19, -100.38);
}
#[test]
fn bulk_log_difference() {
let prices = vec![100.2, 100.46, 100.53, 100.38, 100.19];
assert_eq!(
vec![
0.0025914496711347823,
0.0006965520954302917,
-0.0014932061905419403,
-0.0018946009556159993
],
bulk::log_difference(&prices)
);
}
#[test]
#[should_panic]
fn bulk_log_difference_difference() {
bulk::log_difference(&Vec::new());
}
#[test]
fn single_variance() {
let prices = vec![100.2, 100.46, 100.53, 100.38, 100.19];
assert_eq!(0.018695999999999734, single::variance(&prices));
}
#[test]
#[should_panic]
fn single_variance_panic() {
let prices = Vec::new();
single::variance(&prices);
}
#[test]
fn bulk_variance() {
let prices = vec![100.2, 100.46, 100.53, 100.38, 100.19];
let period = 3;
assert_eq!(
vec![
0.02015555555555502,
0.0037555555555558295,
0.019355555555555907
],
bulk::variance(&prices, period)
);
}
#[test]
#[should_panic]
fn bulk_variance_long_period_panic() {
let prices = vec![100.2, 100.46, 100.53, 100.38, 100.19];
let period = 30;
bulk::variance(&prices, period);
}
#[test]
#[should_panic]
fn bulk_variance_no_period_panic() {
let prices = vec![100.2, 100.46, 100.53, 100.38, 100.19];
let period = 0;
bulk::variance(&prices, period);
}
#[test]
fn single_standard_deviation() {
let prices = vec![100.2, 100.46, 100.53, 100.38, 100.19];
assert_eq!(0.1367333170810967, single::standard_deviation(&prices));
}
#[test]
#[should_panic]
fn single_standard_deviation_panic() {
let prices = Vec::new();
single::standard_deviation(&prices);
}
#[test]
fn bulk_standard_deviation() {
let prices = vec![100.2, 100.46, 100.53, 100.38, 100.19];
let period = 3;
assert_eq!(
vec![
0.14197026292697715,
0.06128258770283635,
0.13912424503139598
],
bulk::standard_deviation(&prices, period)
);
}
#[test]
#[should_panic]
fn bulk_standard_deviation_long_period_panic() {
let prices = vec![100.2, 100.46, 100.53, 100.38, 100.19];
let period = 30;
bulk::standard_deviation(&prices, period);
}
#[test]
#[should_panic]
fn bulk_standard_deviation_no_period_panic() {
let prices = vec![100.2, 100.46, 100.53, 100.38, 100.19];
let period = 0;
bulk::standard_deviation(&prices, period);
}
#[test]
fn single_absolute_deviation() {
let prices = vec![100.2, 100.46, 100.53, 100.38, 100.19];
assert_eq!(
0.12559999999999719,
single::absolute_deviation(
&prices,
crate::AbsDevConfig {
center: crate::CentralPoint::Mean,
aggregate: crate::DeviationAggregate::Mean
}
)
);
assert_eq!(
0.15000000000000568,
single::absolute_deviation(
&prices,
crate::AbsDevConfig {
center: crate::CentralPoint::Median,
aggregate: crate::DeviationAggregate::Median
}
)
);
assert_eq!(
0.0,
single::absolute_deviation(
&prices,
crate::AbsDevConfig {
center: crate::CentralPoint::Mode,
aggregate: crate::DeviationAggregate::Mode
}
)
);
}
#[test]
#[should_panic]
fn singe_absolute_deviation_panic() {
let prices = Vec::new();
single::absolute_deviation(
&prices,
crate::AbsDevConfig {
center: crate::CentralPoint::Mean,
aggregate: crate::DeviationAggregate::Mean,
},
);
}
#[test]
fn bulk_absolute_deviation() {
let prices = vec![100.2, 100.46, 100.53, 100.38, 100.19];
let period: usize = 3;
assert_eq!(
vec![
0.1311111111111103,
0.051111111111111995,
0.11777777777777487
],
bulk::absolute_deviation(
&prices,
period,
crate::AbsDevConfig {
center: crate::CentralPoint::Mean,
aggregate: crate::DeviationAggregate::Mean
}
)
);
assert_eq!(
vec![
0.07000000000000739,
0.07000000000000739,
0.15000000000000568
],
bulk::absolute_deviation(
&prices,
period,
crate::AbsDevConfig {
center: crate::CentralPoint::Median,
aggregate: crate::DeviationAggregate::Median
}
)
);
assert_eq!(
vec![0.0, 0.0, 0.0],
bulk::absolute_deviation(
&prices,
period,
crate::AbsDevConfig {
center: crate::CentralPoint::Mode,
aggregate: crate::DeviationAggregate::Mode
}
)
);
}
#[test]
#[should_panic]
fn bulk_absolute_deviation_long_period_panic() {
let prices = vec![100.2, 100.46, 100.53, 100.38, 100.19];
let period: usize = 30;
bulk::absolute_deviation(
&prices,
period,
crate::AbsDevConfig {
center: crate::CentralPoint::Median,
aggregate: crate::DeviationAggregate::Median,
},
);
}
#[test]
#[should_panic]
fn bulk_absolute_deviation_no_period_panic() {
let prices = vec![100.2, 100.46, 100.53, 100.38, 100.19];
let period: usize = 30;
bulk::absolute_deviation(
&prices,
period,
crate::AbsDevConfig {
center: crate::CentralPoint::Median,
aggregate: crate::DeviationAggregate::Median,
},
);
}
#[test]
fn test_single_max() {
let prices = vec![100.2, 100.46, 100.53, 100.38, 100.19];
assert_eq!(100.53, single::max(&prices));
}
#[test]
#[should_panic]
fn test_single_max_panic() {
let prices = Vec::new();
single::max(&prices);
}
#[test]
fn test_single_min() {
let prices = vec![100.2, 100.46, 100.53, 100.38, 100.19];
assert_eq!(100.19, single::min(&prices));
}
#[test]
#[should_panic]
fn test_single_min_panic() {
let prices = Vec::new();
single::min(&prices);
}
#[test]
fn test_single_price_distribution() {
let prices = vec![100.0, 102.0, 100.0, 103.0, 102.0, 100.0];
let distribution = single::price_distribution(&prices, 1.0);
assert_eq!(vec![(100.0, 3), (102.0, 2), (103.0, 1)], distribution);
}
#[test]
fn test_single_price_distribution_unique() {
let prices = vec![100.0, 101.0, 102.0, 103.0];
let distribution = single::price_distribution(&prices, 1.0);
assert_eq!(
vec![(100.0, 1), (101.0, 1), (102.0, 1), (103.0, 1)],
distribution
);
}
#[test]
fn test_single_price_distribution_same() {
let prices = vec![100.0, 100.0, 100.0];
let distribution = single::price_distribution(&prices, 1.0);
assert_eq!(vec![(100.0, 3)], distribution);
}
#[test]
#[should_panic]
fn test_single_price_distribution_panic() {
let prices = Vec::new();
single::price_distribution(&prices, 1.0);
}
#[test]
#[should_panic]
fn test_single_price_distribution_bad_precision() {
single::price_distribution(&[1.0], 0.0);
}
#[test]
fn test_single_price_distribution_precision_examples() {
let prices = vec![5949.41];
assert_eq!(
vec![(6000.0, 1)],
single::price_distribution(&prices, 1000.0)
);
assert_eq!(
vec![(5900.0, 1)],
single::price_distribution(&prices, 100.0)
);
assert_eq!(vec![(5950.0, 1)], single::price_distribution(&prices, 10.0));
assert_eq!(vec![(5949.0, 1)], single::price_distribution(&prices, 1.0));
assert_eq!(
vec![(5949.400000000001, 1)],
single::price_distribution(&prices, 0.1)
);
}
#[test]
fn test_single_price_distribution_half_precision() {
let prices = vec![100.2, 100.46, 100.53, 101.08, 101.19];
assert_eq!(
vec![(100.0, 2), (101.0, 3)],
single::price_distribution(&prices, 1.0)
);
assert_eq!(
vec![(100.0, 1), (100.5, 2), (101.0, 2)],
single::price_distribution(&prices, 0.5)
);
}
#[test]
fn test_single_price_distribution_nan_ignored() {
let prices = vec![100.0, f64::NAN, 100.4, 100.49, 100.51];
assert_eq!(
vec![(100.0, 1), (100.5, 3)],
single::price_distribution(&prices, 0.5)
);
}
#[test]
fn test_bulk_price_distribution() {
let prices = vec![100.0, 102.0, 100.0, 103.0, 102.0];
let distribution = bulk::price_distribution(&prices, 3, 1.0);
assert_eq!(
vec![
vec![(100.0, 2), (102.0, 1)],
vec![(100.0, 1), (102.0, 1), (103.0, 1)],
vec![(100.0, 1), (102.0, 1), (103.0, 1)]
],
distribution
);
}
#[test]
fn test_bulk_price_distribution_half_precision() {
let prices = vec![100.2, 100.46, 100.53, 101.08];
let distribution = bulk::price_distribution(&prices, 3, 0.5);
assert_eq!(
vec![vec![(100.0, 1), (100.5, 2)], vec![(100.5, 2), (101.0, 1)],],
distribution
);
}
#[test]
#[should_panic]
fn test_bulk_price_distribution_period_too_long() {
let prices = vec![100.0, 102.0, 100.0];
bulk::price_distribution(&prices, 5, 1.0);
}
#[test]
#[should_panic]
fn test_bulk_price_distribution_zero_period() {
let prices = vec![100.0, 102.0, 100.0];
bulk::price_distribution(&prices, 0, 1.0);
}
#[test]
#[should_panic]
fn test_bulk_price_distribution_bad_precision() {
let prices = vec![100.0, 101.0, 102.0];
bulk::price_distribution(&prices, 2, -1.0);
}
#[test]
fn test_log_standard_deviation_simple_series() {
let prices = vec![1.0, E, E.powi(2)];
let s = single::log_standard_deviation(&prices);
assert_eq!(0.816496580927726, s);
}
#[test]
#[should_panic]
fn test_log_standard_deviation_panics_on_non_positive() {
let prices = vec![1.0, 0.0];
let _ = single::log_standard_deviation(&prices);
}
#[test]
fn test_student_t_adjusted_std_factor_works() {
let prices = vec![1.0, 2.0, 3.0];
let df = 5.0;
let s = single::student_t_adjusted_std(&prices, df);
assert_eq!(1.0540925533894598, s);
}
#[test]
#[should_panic]
fn test_student_t_adjusted_std_panics_on_low_df() {
let prices = vec![1.0, 2.0, 3.0];
let _ = single::student_t_adjusted_std(&prices, 2.0);
}
#[test]
fn test_laplace_std_equivalent_matches_sqrt2_mad() {
let prices = vec![0.0, 0.0, 0.0, 1.0, 2.0, 2.0, 2.0];
let s = single::laplace_std_equivalent(&prices);
let expected = 2.0_f64.sqrt();
assert!(
(s - expected).abs() < 1e-12,
"expected {}, got {}",
expected,
s
);
}
#[test]
fn test_cauchy_iqr_scale_basic() {
let prices = vec![1.0, 2.0, 3.0, 4.0];
let s = single::cauchy_iqr_scale(&prices);
assert!((s - 1.0).abs() < 1e-12, "expected 1.0, got {}", s);
}
#[test]
#[should_panic]
fn test_cauchy_iqr_scale_panics_on_short_input() {
let prices = vec![1.0, 2.0, 3.0];
let _ = single::cauchy_iqr_scale(&prices);
}
#[test]
fn test_bulk_log_standard_deviation() {
let prices = vec![1.0, E, E.powi(2), E.powi(3), E.powi(4)];
let log_std = bulk::log_standard_deviation(&prices, 3);
assert_eq!(3, log_std.len());
assert!(log_std[0] > 0.0);
}
#[test]
#[should_panic]
fn test_bulk_log_standard_deviation_zero_period() {
let prices = vec![1.0, 2.0, 3.0];
let _ = bulk::log_standard_deviation(&prices, 0);
}
#[test]
#[should_panic]
fn test_bulk_log_standard_deviation_period_too_long() {
let prices = vec![1.0, 2.0, 3.0];
let _ = bulk::log_standard_deviation(&prices, 5);
}
#[test]
#[should_panic]
fn test_bulk_log_standard_deviation_panics_on_non_positive() {
let prices = vec![1.0, 0.0, 2.0, 3.0];
let _ = bulk::log_standard_deviation(&prices, 2);
}
#[test]
fn test_bulk_student_t_adjusted_std() {
let prices = vec![1.0, 2.0, 3.0, 4.0, 5.0];
let df = 5.0;
let student_std = bulk::student_t_adjusted_std(&prices, 3, df);
assert_eq!(3, student_std.len());
assert!(student_std[0] > 0.0);
}
#[test]
#[should_panic]
fn test_bulk_student_t_adjusted_std_zero_period() {
let prices = vec![1.0, 2.0, 3.0];
let _ = bulk::student_t_adjusted_std(&prices, 0, 5.0);
}
#[test]
#[should_panic]
fn test_bulk_student_t_adjusted_std_period_too_long() {
let prices = vec![1.0, 2.0, 3.0];
let _ = bulk::student_t_adjusted_std(&prices, 5, 5.0);
}
#[test]
#[should_panic]
fn test_bulk_student_t_adjusted_std_panics_on_low_df() {
let prices = vec![1.0, 2.0, 3.0, 4.0];
let _ = bulk::student_t_adjusted_std(&prices, 2, 2.0);
}
#[test]
fn test_bulk_laplace_std_equivalent() {
let prices = vec![0.0, 1.0, 2.0, 3.0, 4.0];
let laplace_std = bulk::laplace_std_equivalent(&prices, 3);
assert_eq!(3, laplace_std.len());
assert!(laplace_std[0] > 0.0);
}
#[test]
#[should_panic]
fn test_bulk_laplace_std_equivalent_zero_period() {
let prices = vec![1.0, 2.0, 3.0];
let _ = bulk::laplace_std_equivalent(&prices, 0);
}
#[test]
#[should_panic]
fn test_bulk_laplace_std_equivalent_period_too_long() {
let prices = vec![1.0, 2.0, 3.0];
let _ = bulk::laplace_std_equivalent(&prices, 5);
}
#[test]
fn test_bulk_cauchy_iqr_scale() {
let prices = vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0];
let cauchy_scale = bulk::cauchy_iqr_scale(&prices, 4);
assert_eq!(3, cauchy_scale.len());
assert!(cauchy_scale[0] > 0.0);
}
#[test]
#[should_panic]
fn test_bulk_cauchy_iqr_scale_period_less_than_four() {
let prices = vec![1.0, 2.0, 3.0, 4.0, 5.0];
let _ = bulk::cauchy_iqr_scale(&prices, 3);
}
#[test]
#[should_panic]
fn test_bulk_cauchy_iqr_scale_period_too_long() {
let prices = vec![1.0, 2.0, 3.0, 4.0];
let _ = bulk::cauchy_iqr_scale(&prices, 5);
}
#[test]
fn test_single_empirical_quantile_range_from_distribution_simple() {
let prices = vec![1.0, 2.0, 3.0, 4.0];
let iqr = single::empirical_quantile_range_from_distribution(&prices, 1.0, 0.25, 0.75);
assert_eq!(2.0, iqr,);
}
#[test]
fn test_bulk_empirical_quantile_range_from_distribution() {
let prices = vec![1.0, 2.0, 3.0, 4.0];
let v = bulk::empirical_quantile_range_from_distribution(&prices, 3, 1.0, 0.25, 0.75);
assert_eq!(vec![1.0, 1.0], v);
}
#[test]
#[should_panic]
fn test_single_empirical_quantile_invalid_bounds() {
let prices = vec![1.0, 2.0, 3.0];
let _ = single::empirical_quantile_range_from_distribution(&prices, 1.0, 0.8, 0.2);
}
}