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::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,
) -> f64 {
if prices_asset_a.is_empty() || prices_asset_b.is_empty() {
panic!("Prices cannot be empty")
};
let length = prices_asset_a.len();
if length != prices_asset_b.len() {
panic!(
"Length of asset a ({}) must match length of asset b ({})",
length,
prices_asset_b.len()
)
};
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),
_ => panic!("Unsupported 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),
_ => panic!("Unsupported 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)]
_ => panic!("Unsupported 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)]
_ => panic!("Unsupported DeviationModel"),
};
covariance / (asset_a_deviation * asset_b_deviation)
}
}
pub mod bulk {
use crate::correlation_indicators::single;
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,
) -> Vec<f64> {
let length = prices_asset_a.len();
if length != prices_asset_b.len() {
panic!(
"Length of asset a ({}) must match length of asset b ({})",
length,
prices_asset_b.len()
)
};
if period > length {
panic!(
"Period ({}) cannot be longer than length of prices ({})",
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
)
);
}
#[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
)
);
}
#[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
)
);
}
#[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
)
);
}
#[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
)
);
}
#[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
)
);
}
#[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
)
);
}
#[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
)
);
}
#[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
)
);
}
#[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
)
);
}
#[test]
#[should_panic]
fn single_correlation_empty_a_panic() {
let prices_a = vec![];
let prices_b = vec![74.71, 71.98, 68.33, 63.6, 65.92];
single::correlate_asset_prices(
&prices_a,
&prices_b,
crate::ConstantModelType::SimpleMovingAverage,
crate::DeviationModel::StandardDeviation,
);
}
#[test]
#[should_panic]
fn single_correlation_empty_b_panic() {
let prices_a = vec![100.46, 100.53, 100.38, 100.19, 100.21];
let prices_b = Vec::new();
single::correlate_asset_prices(
&prices_a,
&prices_b,
crate::ConstantModelType::SimpleMovingAverage,
crate::DeviationModel::StandardDeviation,
);
}
#[test]
#[should_panic]
fn single_correlation_a_length_panic() {
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];
single::correlate_asset_prices(
&prices_a,
&prices_b,
crate::ConstantModelType::SimpleMovingAverage,
crate::DeviationModel::StandardDeviation,
);
}
#[test]
#[should_panic]
fn single_correlation_b_length_panic() {
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];
single::correlate_asset_prices(
&prices_a,
&prices_b,
crate::ConstantModelType::SimpleMovingAverage,
crate::DeviationModel::StandardDeviation,
);
}
#[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
)
);
}
#[test]
#[should_panic]
fn bulk_correlation_period_panic() {
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,
50_usize
)
);
}
#[test]
#[should_panic]
fn bulk_correlation_size_a_panic() {
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];
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
)
);
}
#[test]
#[should_panic]
fn bulk_correlation_size_b_panic() {
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];
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
)
);
}
#[test]
fn test_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,
);
assert_eq!(10299.383075453763, result);
}
#[test]
fn test_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 },
);
assert_eq!(0.6, result);
}
#[test]
fn test_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,
);
assert_eq!(0.9999999999999998, result);
}
#[test]
fn test_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,
);
assert_eq!(0.8888888888888888, result);
}
}