pub mod single {
use crate::validation::{
assert_min_length, assert_min_value, assert_non_empty, assert_positive, unsupported_type,
};
use crate::{AbsDevConfig, CentralPoint, DeviationAggregate};
use std::cmp::Ordering;
use std::collections::HashMap;
#[inline]
pub fn mean(prices: &[f64]) -> crate::Result<f64> {
assert_non_empty("prices", prices)?;
Ok(prices.iter().sum::<f64>() / prices.len() as f64)
}
#[inline]
pub fn median(prices: &[f64]) -> crate::Result<f64> {
assert_non_empty("prices", 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().is_multiple_of(2) {
Ok((values[mid - 1] + values[mid]) / 2.0)
} else {
Ok(values[mid])
}
}
#[inline]
pub fn mode(prices: &[f64]) -> crate::Result<f64> {
assert_non_empty("prices", 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();
Ok(modes.iter().sum::<i64>() as f64 / modes.len() as f64)
}
#[inline]
pub fn log_difference(price_t: f64, price_t_1: f64) -> crate::Result<f64> {
if price_t <= 0.0 || price_t_1 <= 0.0 {
return Err(crate::TechnicalIndicatorError::InvalidValue {
name: "price".to_string(),
value: if price_t <= 0.0 { price_t } else { price_t_1 },
reason: "price_t and price_t_1 must be greater than 0.0".to_string(),
});
}
Ok(price_t.ln() - price_t_1.ln())
}
#[inline]
pub fn variance(prices: &[f64]) -> crate::Result<f64> {
assert_non_empty("prices", 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]) -> crate::Result<f64> {
Ok(variance(prices)?.sqrt())
}
#[inline]
pub fn absolute_deviation(prices: &[f64], config: AbsDevConfig) -> crate::Result<f64> {
assert_non_empty("prices", prices)?;
let mid_point = match config.center {
CentralPoint::Mean => mean(prices)?,
CentralPoint::Median => median(prices)?,
CentralPoint::Mode => mode(prices)?,
_ => return Err(unsupported_type("CentralPoint")),
};
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]) -> crate::Result<f64> {
assert_non_empty("prices", prices)?;
let mut logs = Vec::with_capacity(prices.len());
for &x in prices {
if x <= 0.0 {
return Err(crate::TechnicalIndicatorError::InvalidValue {
name: "prices".to_string(),
value: x,
reason: "requires all positive values".to_string(),
});
}
logs.push(x.ln());
}
standard_deviation(&logs)
}
#[inline]
pub fn student_t_adjusted_std(prices: &[f64], df: f64) -> crate::Result<f64> {
assert_min_value("degrees_of_freedom", df, 2.0)?;
let s = standard_deviation(prices)?;
Ok(s * (df / (df - 2.0)).sqrt())
}
#[inline]
pub fn laplace_std_equivalent(prices: &[f64]) -> crate::Result<f64> {
let mad = absolute_deviation(
prices,
AbsDevConfig {
center: CentralPoint::Median,
aggregate: DeviationAggregate::Median,
},
)?;
Ok(mad * 2.0f64.sqrt())
}
#[inline]
pub fn cauchy_iqr_scale(prices: &[f64]) -> crate::Result<f64> {
assert_min_length("prices", 4, 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.is_multiple_of(2) {
(&v[..mid], &v[mid..])
} else {
(&v[..mid], &v[mid + 1..])
};
let q1 = percentile50(lower); let q3 = percentile50(upper); Ok((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]) -> crate::Result<f64> {
assert_non_empty("prices", prices)?;
Ok(prices
.iter()
.copied()
.filter(|f| !f.is_nan())
.fold(f64::NAN, f64::max))
}
#[inline]
pub fn min(prices: &[f64]) -> crate::Result<f64> {
assert_non_empty("prices", prices)?;
Ok(prices
.iter()
.copied()
.filter(|f| !f.is_nan())
.fold(f64::NAN, f64::min))
}
#[inline]
pub fn price_distribution(prices: &[f64], precision: f64) -> crate::Result<Vec<(f64, usize)>> {
assert_non_empty("prices", prices)?;
assert_positive("precision", 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));
Ok(result)
}
#[inline]
fn empirical_quantile_from_distribution(
prices: &[f64],
precision: f64,
q: f64,
) -> crate::Result<f64> {
if !(q > 0.0 && q < 1.0) {
panic!("quantile ({}) must be in range (0, 1)", q);
}
let hist = price_distribution(prices, precision)?;
let n: usize = hist.iter().map(|(_, c)| *c).sum();
if n == 0 {
return Ok(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 Ok(price + within.clamp(0.0, 1.0) * (next_price - price));
} else {
return Ok(*price);
}
}
}
Ok(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,
) -> crate::Result<f64> {
assert_positive("precision", precision)?;
if !(low > 0.0 && low < 1.0 && high > 0.0 && high < 1.0 && low < high) {
return Err(crate::TechnicalIndicatorError::InvalidValue {
name: "quantile bounds".to_string(),
value: low,
reason: format!(
"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)?;
Ok(qh - ql)
}
}
pub mod bulk {
use crate::basic_indicators::single;
use crate::validation::{assert_min_period, assert_non_empty, assert_period};
use crate::AbsDevConfig;
#[inline]
pub fn mean(prices: &[f64], period: usize) -> crate::Result<Vec<f64>> {
assert_period(period, prices.len())?;
let mut result = Vec::with_capacity(prices.len());
for window in prices.windows(period) {
result.push(single::mean(window)?)
}
Ok(result)
}
#[inline]
pub fn median(prices: &[f64], period: usize) -> crate::Result<Vec<f64>> {
assert_period(period, prices.len())?;
let mut result = Vec::with_capacity(prices.len());
for window in prices.windows(period) {
result.push(single::median(window)?)
}
Ok(result)
}
#[inline]
pub fn mode(prices: &[f64], period: usize) -> crate::Result<Vec<f64>> {
assert_period(period, prices.len())?;
let mut result = Vec::with_capacity(prices.len());
for window in prices.windows(period) {
result.push(single::mode(window)?)
}
Ok(result)
}
#[inline]
pub fn log(prices: &[f64]) -> crate::Result<Vec<f64>> {
assert_non_empty("prices", prices)?;
Ok(prices.iter().map(|&p| p.ln()).collect())
}
#[inline]
pub fn log_difference(prices: &[f64]) -> crate::Result<Vec<f64>> {
assert_non_empty("prices", prices)?;
prices
.windows(2)
.map(|w| single::log_difference(w[1], w[0]))
.collect()
}
#[inline]
pub fn variance(prices: &[f64], period: usize) -> crate::Result<Vec<f64>> {
assert_period(period, prices.len())?;
let mut result = Vec::with_capacity(prices.len());
for window in prices.windows(period) {
result.push(single::variance(window)?)
}
Ok(result)
}
#[inline]
pub fn standard_deviation(prices: &[f64], period: usize) -> crate::Result<Vec<f64>> {
assert_period(period, prices.len())?;
let mut result = Vec::with_capacity(prices.len());
for window in prices.windows(period) {
result.push(single::standard_deviation(window)?);
}
Ok(result)
}
#[inline]
pub fn absolute_deviation(
prices: &[f64],
period: usize,
config: AbsDevConfig,
) -> crate::Result<Vec<f64>> {
assert_period(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,
) -> crate::Result<Vec<Vec<(f64, usize)>>> {
assert_period(period, prices.len())?;
prices
.windows(period)
.map(|w| single::price_distribution(w, precision))
.collect()
}
#[inline]
pub fn log_standard_deviation(prices: &[f64], period: usize) -> crate::Result<Vec<f64>> {
assert_period(period, prices.len())?;
let mut result = Vec::with_capacity(prices.len());
for window in prices.windows(period) {
result.push(single::log_standard_deviation(window)?)
}
Ok(result)
}
#[inline]
pub fn student_t_adjusted_std(
prices: &[f64],
period: usize,
df: f64,
) -> crate::Result<Vec<f64>> {
assert_period(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)?)
}
Ok(result)
}
#[inline]
pub fn laplace_std_equivalent(prices: &[f64], period: usize) -> crate::Result<Vec<f64>> {
assert_period(period, prices.len())?;
let mut result = Vec::with_capacity(prices.len());
for window in prices.windows(period) {
result.push(single::laplace_std_equivalent(window)?)
}
Ok(result)
}
#[inline]
pub fn cauchy_iqr_scale(prices: &[f64], period: usize) -> crate::Result<Vec<f64>> {
assert_min_period(period, 4, prices.len())?;
let mut result = Vec::with_capacity(prices.len());
for window in prices.windows(period) {
result.push(single::cauchy_iqr_scale(window)?)
}
Ok(result)
}
#[inline]
pub fn empirical_quantile_range_from_distribution(
prices: &[f64],
period: usize,
precision: f64,
low: f64,
high: f64,
) -> crate::Result<Vec<f64>> {
assert_period(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).unwrap());
}
#[test]
fn single_mean_identical_prices() {
let prices = vec![100.0, 100.0, 100.0];
assert_eq!(100.0, single::mean(&prices).unwrap());
}
#[test]
fn single_mean_empty_prices() {
let prices = Vec::new();
let result = single::mean(&prices);
assert!(result.is_err());
}
#[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).unwrap()
);
}
#[test]
fn bulk_mean_long_period_error() {
let prices = vec![100.2, 100.46, 100.53, 100.38, 100.19];
let period: usize = 30;
let result = bulk::mean(&prices, period);
assert!(result.is_err());
}
#[test]
fn bulk_mean_no_period_error() {
let prices = vec![100.2, 100.46, 100.53, 100.38, 100.19];
let period: usize = 0;
let result = bulk::mean(&prices, period);
assert!(result.is_err());
}
#[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).unwrap());
}
#[test]
fn single_median_even() {
let prices = vec![100.2, 100.46, 100.53, 100.38];
assert_eq!(100.41999999999999, single::median(&prices).unwrap());
}
#[test]
fn single_median_error() {
let prices = Vec::new();
let result = single::median(&prices);
assert!(result.is_err());
}
#[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).unwrap()
);
}
#[test]
fn bulk_median_long_period_error() {
let prices = vec![100.2, 100.46, 100.53, 100.38, 100.19];
let period: usize = 30;
let result = bulk::median(&prices, period);
assert!(result.is_err());
}
#[test]
fn bulk_median_no_period_error() {
let prices = vec![100.2, 100.46, 100.53, 100.38, 100.19];
let period: usize = 0;
let result = bulk::median(&prices, period);
assert!(result.is_err());
}
#[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).unwrap());
}
#[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).unwrap());
}
#[test]
fn single_mode_average() {
let prices = vec![100.46, 100.35, 101.08, 101.19];
assert_eq!(100.5, single::mode(&prices).unwrap());
}
#[test]
fn single_mode_error() {
let prices = Vec::new();
let result = single::mode(&prices);
assert!(result.is_err());
}
#[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).unwrap()
);
}
#[test]
fn bulk_mode_long_period_error() {
let prices = vec![100.2, 100.46, 100.53, 101.08, 101.19];
let period: usize = 30;
let result = bulk::mode(&prices, period);
assert!(result.is_err());
}
#[test]
fn bulk_mode_no_period_error() {
let prices = vec![100.2, 100.46, 100.53, 101.08, 101.19];
let period: usize = 0;
let result = bulk::mode(&prices, period);
assert!(result.is_err());
}
#[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).unwrap()
);
}
#[test]
fn bulk_log_error() {
let prices = Vec::new();
let result = bulk::log(&prices);
assert!(result.is_err());
}
#[test]
fn single_log_difference() {
assert_eq!(
-0.0018946009556159993,
single::log_difference(100.19, 100.38).unwrap()
);
}
#[test]
fn single_log_difference_error() {
let result = single::log_difference(0.0, 100.38);
assert!(result.is_err());
}
#[test]
fn single_log_difference_error_2() {
let result = single::log_difference(100.19, -100.38);
assert!(result.is_err());
}
#[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).unwrap()
);
}
#[test]
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).unwrap());
}
#[test]
fn single_variance_error() {
let prices = Vec::new();
let result = single::variance(&prices);
assert!(result.is_err());
}
#[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).unwrap()
);
}
#[test]
fn bulk_variance_long_period_error() {
let prices = vec![100.2, 100.46, 100.53, 100.38, 100.19];
let period = 30;
let result = bulk::variance(&prices, period);
assert!(result.is_err());
}
#[test]
fn bulk_variance_no_period_error() {
let prices = vec![100.2, 100.46, 100.53, 100.38, 100.19];
let period = 0;
let result = bulk::variance(&prices, period);
assert!(result.is_err());
}
#[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).unwrap()
);
}
#[test]
fn single_standard_deviation_error() {
let prices = Vec::new();
let result = single::standard_deviation(&prices);
assert!(result.is_err());
}
#[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).unwrap()
);
}
#[test]
fn bulk_standard_deviation_long_period_error() {
let prices = vec![100.2, 100.46, 100.53, 100.38, 100.19];
let period = 30;
let result = bulk::standard_deviation(&prices, period);
assert!(result.is_err());
}
#[test]
fn bulk_standard_deviation_no_period_error() {
let prices = vec![100.2, 100.46, 100.53, 100.38, 100.19];
let period = 0;
let result = bulk::standard_deviation(&prices, period);
assert!(result.is_err());
}
#[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
}
)
.unwrap()
);
assert_eq!(
0.15000000000000568,
single::absolute_deviation(
&prices,
crate::AbsDevConfig {
center: crate::CentralPoint::Median,
aggregate: crate::DeviationAggregate::Median
}
)
.unwrap()
);
assert_eq!(
0.0,
single::absolute_deviation(
&prices,
crate::AbsDevConfig {
center: crate::CentralPoint::Mode,
aggregate: crate::DeviationAggregate::Mode
}
)
.unwrap()
);
}
#[test]
fn single_absolute_deviation_error() {
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
}
)
.unwrap()
);
assert_eq!(
vec![
0.07000000000000739,
0.07000000000000739,
0.15000000000000568
],
bulk::absolute_deviation(
&prices,
period,
crate::AbsDevConfig {
center: crate::CentralPoint::Median,
aggregate: crate::DeviationAggregate::Median
}
)
.unwrap()
);
assert_eq!(
vec![0.0, 0.0, 0.0],
bulk::absolute_deviation(
&prices,
period,
crate::AbsDevConfig {
center: crate::CentralPoint::Mode,
aggregate: crate::DeviationAggregate::Mode
}
)
.unwrap()
);
}
#[test]
fn bulk_absolute_deviation_long_period_error() {
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 bulk_absolute_deviation_no_period_error() {
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 single_max() {
let prices = vec![100.2, 100.46, 100.53, 100.38, 100.19];
assert_eq!(100.53, single::max(&prices).unwrap());
}
#[test]
fn single_max_error() {
let prices = Vec::new();
let result = single::max(&prices);
assert!(result.is_err());
}
#[test]
fn single_min() {
let prices = vec![100.2, 100.46, 100.53, 100.38, 100.19];
assert_eq!(100.19, single::min(&prices).unwrap());
}
#[test]
fn single_min_error() {
let prices = Vec::new();
let result = single::min(&prices);
assert!(result.is_err());
}
#[test]
fn 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).unwrap();
assert_eq!(vec![(100.0, 3), (102.0, 2), (103.0, 1)], distribution);
}
#[test]
fn single_price_distribution_unique() {
let prices = vec![100.0, 101.0, 102.0, 103.0];
let distribution = single::price_distribution(&prices, 1.0).unwrap();
assert_eq!(
vec![(100.0, 1), (101.0, 1), (102.0, 1), (103.0, 1)],
distribution
);
}
#[test]
fn single_price_distribution_same() {
let prices = vec![100.0, 100.0, 100.0];
let distribution = single::price_distribution(&prices, 1.0).unwrap();
assert_eq!(vec![(100.0, 3)], distribution);
}
#[test]
fn single_price_distribution_error() {
let prices = Vec::new();
let result = single::price_distribution(&prices, 1.0);
assert!(result.is_err());
}
#[test]
fn single_price_distribution_bad_precision() {
let result = single::price_distribution(&[1.0], 0.0);
assert!(result.is_err());
}
#[test]
fn single_price_distribution_precision_examples() {
let prices = vec![5949.41];
assert_eq!(
vec![(6000.0, 1)],
single::price_distribution(&prices, 1000.0).unwrap()
);
assert_eq!(
vec![(5900.0, 1)],
single::price_distribution(&prices, 100.0).unwrap()
);
assert_eq!(
vec![(5950.0, 1)],
single::price_distribution(&prices, 10.0).unwrap()
);
assert_eq!(
vec![(5949.0, 1)],
single::price_distribution(&prices, 1.0).unwrap()
);
assert_eq!(
vec![(5949.400000000001, 1)],
single::price_distribution(&prices, 0.1).unwrap()
);
}
#[test]
fn 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).unwrap()
);
assert_eq!(
vec![(100.0, 1), (100.5, 2), (101.0, 2)],
single::price_distribution(&prices, 0.5).unwrap()
);
}
#[test]
fn 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).unwrap()
);
}
#[test]
fn 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).unwrap();
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 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).unwrap();
assert_eq!(
vec![vec![(100.0, 1), (100.5, 2)], vec![(100.5, 2), (101.0, 1)],],
distribution
);
}
#[test]
fn bulk_price_distribution_period_too_long() {
let prices = vec![100.0, 102.0, 100.0];
let result = bulk::price_distribution(&prices, 5, 1.0);
assert!(result.is_err());
}
#[test]
fn bulk_price_distribution_zero_period() {
let prices = vec![100.0, 102.0, 100.0];
let result = bulk::price_distribution(&prices, 0, 1.0);
assert!(result.is_err());
}
#[test]
fn bulk_price_distribution_bad_precision() {
let prices = vec![100.0, 101.0, 102.0];
let result = bulk::price_distribution(&prices, 2, -1.0);
assert!(result.is_err());
}
#[test]
fn log_standard_deviation_simple_series() {
let prices = vec![1.0, E, E.powi(2)];
let s = single::log_standard_deviation(&prices).unwrap();
assert_eq!(0.816496580927726, s);
}
#[test]
fn log_standard_deviation_errors_on_non_positive() {
let prices = vec![1.0, 0.0];
let _ = single::log_standard_deviation(&prices);
}
#[test]
fn 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).unwrap();
assert_eq!(1.0540925533894598, s);
}
#[test]
fn student_t_adjusted_std_errors_on_low_df() {
let prices = vec![1.0, 2.0, 3.0];
let _ = single::student_t_adjusted_std(&prices, 2.0);
}
#[test]
fn 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).unwrap();
let expected = 2.0_f64.sqrt();
assert!(
(s - expected).abs() < 1e-12,
"expected {}, got {}",
expected,
s
);
}
#[test]
fn cauchy_iqr_scale_basic() {
let prices = vec![1.0, 2.0, 3.0, 4.0];
let s = single::cauchy_iqr_scale(&prices).unwrap();
assert!((s - 1.0).abs() < 1e-12, "expected 1.0, got {}", s);
}
#[test]
fn cauchy_iqr_scale_errors_on_short_input() {
let prices = vec![1.0, 2.0, 3.0];
let _ = single::cauchy_iqr_scale(&prices);
}
#[test]
fn 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).unwrap();
assert_eq!(3, log_std.len());
assert!(log_std[0] > 0.0);
}
#[test]
fn bulk_log_standard_deviation_zero_period() {
let prices = vec![1.0, 2.0, 3.0];
let _ = bulk::log_standard_deviation(&prices, 0);
}
#[test]
fn bulk_log_standard_deviation_period_too_long() {
let prices = vec![1.0, 2.0, 3.0];
let _ = bulk::log_standard_deviation(&prices, 5);
}
#[test]
fn bulk_log_standard_deviation_errors_on_non_positive() {
let prices = vec![1.0, 0.0, 2.0, 3.0];
let _ = bulk::log_standard_deviation(&prices, 2);
}
#[test]
fn 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).unwrap();
assert_eq!(3, student_std.len());
assert!(student_std[0] > 0.0);
}
#[test]
fn 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]
fn 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]
fn bulk_student_t_adjusted_std_errors_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 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).unwrap();
assert_eq!(3, laplace_std.len());
assert!(laplace_std[0] > 0.0);
}
#[test]
fn bulk_laplace_std_equivalent_zero_period() {
let prices = vec![1.0, 2.0, 3.0];
let _ = bulk::laplace_std_equivalent(&prices, 0);
}
#[test]
fn 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 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).unwrap();
assert_eq!(3, cauchy_scale.len());
assert!(cauchy_scale[0] > 0.0);
}
#[test]
fn bulk_cauchy_iqr_scale_period_less_than_four() {
let prices = vec![1.0, 2.0, 3.0, 4.0, 5.0];
let result = bulk::cauchy_iqr_scale(&prices, 3);
assert!(result.is_err());
}
#[test]
fn bulk_cauchy_iqr_scale_period_too_long() {
let prices = vec![1.0, 2.0, 3.0, 4.0];
let result = bulk::cauchy_iqr_scale(&prices, 5);
assert!(result.is_err());
}
#[test]
fn 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).unwrap();
assert_eq!(2.0, iqr,);
}
#[test]
fn 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).unwrap();
assert_eq!(vec![1.0, 1.0], v);
}
#[test]
fn 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);
}
}