pub mod single {
use crate::basic_indicators::single::{
absolute_deviation, cauchy_iqr_scale, laplace_std_equivalent, log_standard_deviation,
median, mode, standard_deviation, student_t_adjusted_std,
};
use crate::moving_average::single::moving_average;
use crate::validation::{assert_non_empty, assert_same_len, unsupported_type};
use crate::volatility_indicators::single::ulcer_index;
use crate::{
AbsDevConfig, CentralPoint, ConstantModelType, DeviationAggregate, DeviationModel,
MovingAverageType,
};
pub fn correlate_asset_prices(
prices_asset_a: &[f64],
prices_asset_b: &[f64],
constant_model_type: ConstantModelType,
deviation_model: DeviationModel,
) -> crate::Result<f64> {
let length = prices_asset_a.len();
assert_same_len(&[
("prices_asset_a", prices_asset_a),
("prices_asset_b", prices_asset_b),
])?;
assert_non_empty("prices_asset_a", prices_asset_a)?;
let asset_a_average = match constant_model_type {
ConstantModelType::SimpleMovingAverage => {
moving_average(prices_asset_a, MovingAverageType::Simple)?
}
ConstantModelType::SmoothedMovingAverage => {
moving_average(prices_asset_a, MovingAverageType::Smoothed)?
}
ConstantModelType::ExponentialMovingAverage => {
moving_average(prices_asset_a, MovingAverageType::Exponential)?
}
ConstantModelType::PersonalisedMovingAverage {
alpha_num,
alpha_den,
} => moving_average(
prices_asset_a,
MovingAverageType::Personalised {
alpha_num,
alpha_den,
},
)?,
ConstantModelType::SimpleMovingMedian => median(prices_asset_a)?,
ConstantModelType::SimpleMovingMode => mode(prices_asset_a)?,
_ => return Err(unsupported_type("ConstantModelType")),
};
let asset_b_average = match constant_model_type {
ConstantModelType::SimpleMovingAverage => {
moving_average(prices_asset_b, MovingAverageType::Simple)?
}
ConstantModelType::SmoothedMovingAverage => {
moving_average(prices_asset_b, MovingAverageType::Smoothed)?
}
ConstantModelType::ExponentialMovingAverage => {
moving_average(prices_asset_b, MovingAverageType::Exponential)?
}
ConstantModelType::PersonalisedMovingAverage {
alpha_num,
alpha_den,
} => moving_average(
prices_asset_b,
MovingAverageType::Personalised {
alpha_num,
alpha_den,
},
)?,
ConstantModelType::SimpleMovingMedian => median(prices_asset_b)?,
ConstantModelType::SimpleMovingMode => mode(prices_asset_b)?,
_ => return Err(unsupported_type("ConstantModelType")),
};
let joint_average_return: f64 = (0..length)
.map(|i| (prices_asset_a[i] - asset_a_average) * (prices_asset_b[i] - asset_b_average))
.sum();
let covariance = joint_average_return / length as f64;
let asset_a_deviation = match deviation_model {
DeviationModel::StandardDeviation => standard_deviation(prices_asset_a)?,
DeviationModel::MeanAbsoluteDeviation => absolute_deviation(
prices_asset_a,
AbsDevConfig {
center: CentralPoint::Mean,
aggregate: DeviationAggregate::Mean,
},
)?,
DeviationModel::MedianAbsoluteDeviation => absolute_deviation(
prices_asset_a,
AbsDevConfig {
center: CentralPoint::Median,
aggregate: DeviationAggregate::Median,
},
)?,
DeviationModel::ModeAbsoluteDeviation => absolute_deviation(
prices_asset_a,
AbsDevConfig {
center: CentralPoint::Mode,
aggregate: DeviationAggregate::Mode,
},
)?,
DeviationModel::CustomAbsoluteDeviation { config } => {
absolute_deviation(prices_asset_a, config)?
}
DeviationModel::UlcerIndex => ulcer_index(prices_asset_a)?,
DeviationModel::LogStandardDeviation => log_standard_deviation(prices_asset_a)?,
DeviationModel::StudentT { df } => student_t_adjusted_std(prices_asset_a, df)?,
DeviationModel::LaplaceStdEquivalent => laplace_std_equivalent(prices_asset_a)?,
DeviationModel::CauchyIQRScale => cauchy_iqr_scale(prices_asset_a)?,
DeviationModel::EmpiricalQuantileRange {
low,
high,
precision,
} => crate::basic_indicators::single::empirical_quantile_range_from_distribution(
prices_asset_a,
precision,
low,
high,
)?,
#[allow(unreachable_patterns)]
_ => return Err(unsupported_type("DeviationModel")),
};
let asset_b_deviation = match deviation_model {
DeviationModel::StandardDeviation => standard_deviation(prices_asset_b)?,
DeviationModel::MeanAbsoluteDeviation => absolute_deviation(
prices_asset_b,
AbsDevConfig {
center: CentralPoint::Mean,
aggregate: DeviationAggregate::Mean,
},
)?,
DeviationModel::MedianAbsoluteDeviation => absolute_deviation(
prices_asset_b,
AbsDevConfig {
center: CentralPoint::Median,
aggregate: DeviationAggregate::Median,
},
)?,
DeviationModel::ModeAbsoluteDeviation => absolute_deviation(
prices_asset_b,
AbsDevConfig {
center: CentralPoint::Mode,
aggregate: DeviationAggregate::Mode,
},
)?,
DeviationModel::CustomAbsoluteDeviation { config } => {
absolute_deviation(prices_asset_b, config)?
}
DeviationModel::UlcerIndex => ulcer_index(prices_asset_b)?,
DeviationModel::LogStandardDeviation => log_standard_deviation(prices_asset_b)?,
DeviationModel::StudentT { df } => student_t_adjusted_std(prices_asset_b, df)?,
DeviationModel::LaplaceStdEquivalent => laplace_std_equivalent(prices_asset_b)?,
DeviationModel::CauchyIQRScale => cauchy_iqr_scale(prices_asset_b)?,
DeviationModel::EmpiricalQuantileRange {
low,
high,
precision,
} => crate::basic_indicators::single::empirical_quantile_range_from_distribution(
prices_asset_b,
precision,
low,
high,
)?,
#[allow(unreachable_patterns)]
_ => return Err(unsupported_type("DeviationModel")),
};
Ok(covariance / (asset_a_deviation * asset_b_deviation))
}
}
pub mod bulk {
use crate::correlation_indicators::single;
use crate::validation::{assert_period, assert_same_len};
use crate::{ConstantModelType, DeviationModel};
#[inline]
pub fn correlate_asset_prices(
prices_asset_a: &[f64],
prices_asset_b: &[f64],
constant_model_type: ConstantModelType,
deviation_model: DeviationModel,
period: usize,
) -> crate::Result<Vec<f64>> {
let length = prices_asset_a.len();
assert_same_len(&[
("prices_asset_a", prices_asset_a),
("prices_asset_b", prices_asset_b),
])?;
assert_period(period, length)?;
(0..=length - period)
.map(|i| {
single::correlate_asset_prices(
&prices_asset_a[i..i + period],
&prices_asset_b[i..i + period],
constant_model_type,
deviation_model,
)
})
.collect()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn single_correlation_ma_std_dev() {
let prices_a = vec![100.46, 100.53, 100.38, 100.19, 100.21];
let prices_b = vec![74.71, 71.98, 68.33, 63.6, 65.92];
assert_eq!(
0.9042213658878326,
single::correlate_asset_prices(
&prices_a,
&prices_b,
crate::ConstantModelType::SimpleMovingAverage,
crate::DeviationModel::StandardDeviation
)
.unwrap()
);
}
#[test]
fn single_correlation_sma_std_dev() {
let prices_a = vec![100.46, 100.53, 100.38, 100.19, 100.21];
let prices_b = vec![74.71, 71.98, 68.33, 63.6, 65.92];
assert_eq!(
0.9791080346628417,
single::correlate_asset_prices(
&prices_a,
&prices_b,
crate::ConstantModelType::SmoothedMovingAverage,
crate::DeviationModel::StandardDeviation
)
.unwrap()
);
}
#[test]
fn single_correlation_ema_std_dev() {
let prices_a = vec![100.46, 100.53, 100.38, 100.19, 100.21];
let prices_b = vec![74.71, 71.98, 68.33, 63.6, 65.92];
assert_eq!(
1.121845018991745,
single::correlate_asset_prices(
&prices_a,
&prices_b,
crate::ConstantModelType::ExponentialMovingAverage,
crate::DeviationModel::StandardDeviation
)
.unwrap()
);
}
#[test]
fn single_correlation_pma_std_dev() {
let prices_a = vec![100.46, 100.53, 100.38, 100.19, 100.21];
let prices_b = vec![74.71, 71.98, 68.33, 63.6, 65.92];
assert_eq!(
1.468978482735914,
single::correlate_asset_prices(
&prices_a,
&prices_b,
crate::ConstantModelType::PersonalisedMovingAverage {
alpha_num: 5.0,
alpha_den: 4.0
},
crate::DeviationModel::StandardDeviation
)
.unwrap()
);
}
#[test]
fn single_correlation_median_std_dev() {
let prices_a = vec![100.46, 100.53, 100.38, 100.19, 100.21];
let prices_b = vec![74.71, 71.98, 68.33, 63.6, 65.92];
assert_eq!(
0.8763922185678863,
single::correlate_asset_prices(
&prices_a,
&prices_b,
crate::ConstantModelType::SimpleMovingMedian,
crate::DeviationModel::StandardDeviation
)
.unwrap()
);
}
#[test]
fn single_correlation_mode_std_dev() {
let prices_a = vec![100.46, 100.53, 100.38, 100.19, 100.21];
let prices_b = vec![74.71, 71.98, 68.33, 63.6, 65.92];
assert_eq!(
0.8439113000163907,
single::correlate_asset_prices(
&prices_a,
&prices_b,
crate::ConstantModelType::SimpleMovingMode,
crate::DeviationModel::StandardDeviation
)
.unwrap()
);
}
#[test]
fn single_correlation_ma_mean_ad_dev() {
let prices_a = vec![100.46, 100.53, 100.38, 100.19, 100.21];
let prices_b = vec![74.71, 71.98, 68.33, 63.6, 65.92];
assert_eq!(
1.1165699299573548,
single::correlate_asset_prices(
&prices_a,
&prices_b,
crate::ConstantModelType::SimpleMovingAverage,
crate::DeviationModel::MeanAbsoluteDeviation
)
.unwrap()
);
}
#[test]
fn single_correlation_ma_median_ad_dev() {
let prices_a = vec![100.46, 100.53, 100.38, 100.19, 100.21];
let prices_b = vec![74.71, 71.98, 68.33, 63.6, 65.92];
assert_eq!(
0.8918502283104672,
single::correlate_asset_prices(
&prices_a,
&prices_b,
crate::ConstantModelType::SimpleMovingAverage,
crate::DeviationModel::MedianAbsoluteDeviation
)
.unwrap()
);
}
#[test]
fn single_correlation_ma_mode_ad_dev() {
let prices_a = vec![100.46, 100.53, 100.38, 100.19, 100.21];
let prices_b = vec![74.71, 71.98, 68.33, 63.6, 65.92];
assert_eq!(
f64::INFINITY,
single::correlate_asset_prices(
&prices_a,
&prices_b,
crate::ConstantModelType::SimpleMovingAverage,
crate::DeviationModel::ModeAbsoluteDeviation
)
.unwrap()
);
}
#[test]
fn single_correlation_ulcer_index_ad_dev() {
let prices_a = vec![100.46, 100.53, 100.38, 100.19, 100.21];
let prices_b = vec![74.71, 71.98, 68.33, 63.6, 65.92];
assert_eq!(
0.23702330943345767,
single::correlate_asset_prices(
&prices_a,
&prices_b,
crate::ConstantModelType::SimpleMovingAverage,
crate::DeviationModel::UlcerIndex
)
.unwrap()
);
}
#[test]
fn single_correlation_empty_a_error() {
let prices_a = vec![];
let prices_b = vec![74.71, 71.98, 68.33, 63.6, 65.92];
let result = single::correlate_asset_prices(
&prices_a,
&prices_b,
crate::ConstantModelType::SimpleMovingAverage,
crate::DeviationModel::StandardDeviation,
);
assert!(result.is_err());
}
#[test]
fn single_correlation_empty_b_error() {
let prices_a = vec![100.46, 100.53, 100.38, 100.19, 100.21];
let prices_b = Vec::new();
let result = single::correlate_asset_prices(
&prices_a,
&prices_b,
crate::ConstantModelType::SimpleMovingAverage,
crate::DeviationModel::StandardDeviation,
);
assert!(result.is_err());
}
#[test]
fn single_correlation_a_length_error() {
let prices_a = vec![100.46, 100.53, 100.38, 100.19];
let prices_b = vec![74.71, 71.98, 68.33, 63.6, 65.92];
let result = single::correlate_asset_prices(
&prices_a,
&prices_b,
crate::ConstantModelType::SimpleMovingAverage,
crate::DeviationModel::StandardDeviation,
);
assert!(result.is_err());
}
#[test]
fn single_correlation_b_length_error() {
let prices_a = vec![100.46, 100.53, 100.38, 100.19, 100.21];
let prices_b = vec![74.71, 71.98, 68.33, 63.6];
let result = single::correlate_asset_prices(
&prices_a,
&prices_b,
crate::ConstantModelType::SimpleMovingAverage,
crate::DeviationModel::StandardDeviation,
);
assert!(result.is_err());
}
#[test]
fn bulk_correlation() {
let prices_a = vec![100.46, 100.53, 100.38, 100.19, 100.21, 100.32, 100.28];
let prices_b = vec![74.71, 71.98, 68.33, 63.6, 65.92, 69.54, 73.81];
assert_eq!(
vec![0.9042213658878326, 0.9268640506930989, 0.5300870380836703],
bulk::correlate_asset_prices(
&prices_a,
&prices_b,
crate::ConstantModelType::SimpleMovingAverage,
crate::DeviationModel::StandardDeviation,
5_usize
)
.unwrap()
);
}
#[test]
fn bulk_correlation_period_error() {
let prices_a = vec![100.46, 100.53, 100.38, 100.19, 100.21, 100.32, 100.28];
let prices_b = vec![74.71, 71.98, 68.33, 63.6, 65.92, 69.54, 73.81];
let result = bulk::correlate_asset_prices(
&prices_a,
&prices_b,
crate::ConstantModelType::SimpleMovingAverage,
crate::DeviationModel::StandardDeviation,
50_usize,
);
assert!(result.is_err());
}
#[test]
fn bulk_correlation_size_a_error() {
let prices_a = vec![100.46, 100.53, 100.38, 100.19, 100.21, 100.32];
let prices_b = vec![74.71, 71.98, 68.33, 63.6, 65.92, 69.54, 73.81];
let result = bulk::correlate_asset_prices(
&prices_a,
&prices_b,
crate::ConstantModelType::SimpleMovingAverage,
crate::DeviationModel::StandardDeviation,
5_usize,
);
assert!(result.is_err());
}
#[test]
fn bulk_correlation_size_b_error() {
let prices_a = vec![100.46, 100.53, 100.38, 100.19, 100.21, 100.32, 100.28];
let prices_b = vec![74.71, 71.98, 68.33, 63.6, 65.92, 69.54];
let result = bulk::correlate_asset_prices(
&prices_a,
&prices_b,
crate::ConstantModelType::SimpleMovingAverage,
crate::DeviationModel::StandardDeviation,
5_usize,
);
assert!(result.is_err());
}
#[test]
fn correlate_asset_prices_log_std() {
let prices_a = vec![100.0, 102.0, 103.0, 101.0, 99.0];
let prices_b = vec![101.0, 103.0, 104.0, 102.0, 100.0];
let result = single::correlate_asset_prices(
&prices_a,
&prices_b,
crate::ConstantModelType::SimpleMovingAverage,
crate::DeviationModel::LogStandardDeviation,
)
.unwrap();
assert_eq!(10299.383075453763, result);
}
#[test]
fn correlate_asset_prices_student_t() {
let prices_a = vec![100.0, 102.0, 103.0, 101.0, 99.0];
let prices_b = vec![101.0, 103.0, 104.0, 102.0, 100.0];
let result = single::correlate_asset_prices(
&prices_a,
&prices_b,
crate::ConstantModelType::SimpleMovingAverage,
crate::DeviationModel::StudentT { df: 5.0 },
)
.unwrap();
assert_eq!(0.6, result);
}
#[test]
fn correlate_asset_prices_laplace_std() {
let prices_a = vec![100.0, 102.0, 103.0, 101.0, 99.0];
let prices_b = vec![101.0, 103.0, 104.0, 102.0, 100.0];
let result = single::correlate_asset_prices(
&prices_a,
&prices_b,
crate::ConstantModelType::SimpleMovingAverage,
crate::DeviationModel::LaplaceStdEquivalent,
)
.unwrap();
assert_eq!(0.9999999999999998, result);
}
#[test]
fn correlate_asset_prices_cauchy_iqr() {
let prices_a = vec![100.0, 102.0, 103.0, 101.0, 99.0];
let prices_b = vec![101.0, 103.0, 104.0, 102.0, 100.0];
let result = single::correlate_asset_prices(
&prices_a,
&prices_b,
crate::ConstantModelType::SimpleMovingAverage,
crate::DeviationModel::CauchyIQRScale,
)
.unwrap();
assert_eq!(0.8888888888888888, result);
}
}