use crate::constants::TOLERANCE;
use crate::error::{DecimalError, Error};
use itertools::Itertools;
use num_traits::{FromPrimitive, ToPrimitive};
use positive::Positive;
use rand::{Rng, RngExt, rng};
use rayon::prelude::*;
use rust_decimal::Decimal;
use std::collections::BTreeSet;
#[allow(dead_code)]
pub fn approx_equal(a: f64, b: f64) -> bool {
(a - b).abs() < TOLERANCE.to_f64().unwrap()
}
pub fn get_random_element<T>(set: &BTreeSet<T>) -> Option<&T> {
if set.is_empty() {
return None;
}
let mut thread_rng = rng();
let random_index = thread_rng.random_range(0..set.len());
set.iter().nth(random_index)
}
pub fn random_decimal(rng: &mut impl Rng) -> Result<Decimal, DecimalError> {
Decimal::from_f64(rng.random::<f64>()).ok_or(DecimalError::ConversionError {
from_type: "f64".to_string(),
to_type: "Decimal".to_string(),
reason: "Failed to convert f64 to Decimal".to_string(),
})
}
pub fn process_n_times_iter<T, Y, F>(
positions: &[T],
n: usize,
process_combination: F,
) -> Result<Vec<Y>, Error>
where
F: FnMut(&[&T]) -> Vec<Y> + Send + Sync,
T: Clone + Send + Sync,
Y: Send,
{
if positions.is_empty() {
return Err(Error::Other("Vector empty".to_string()));
}
let combinations: Vec<_> = positions.iter().combinations_with_replacement(n).collect();
let process_combination = std::sync::Mutex::new(process_combination);
Ok(combinations
.par_iter()
.flat_map(|combination| {
let mut closure = process_combination.lock().unwrap();
closure(combination)
})
.collect())
}
pub fn calculate_log_returns(close_prices: &[Positive]) -> Result<Vec<Positive>, DecimalError> {
if close_prices.len() < 2 {
return Ok(Vec::new());
}
let mut log_returns = Vec::with_capacity(close_prices.len() - 1);
for i in 1..close_prices.len() {
let current_price = close_prices[i];
let previous_price = close_prices[i - 1];
let ratio = current_price / previous_price;
log_returns.push(ratio.ln());
}
Ok(log_returns)
}
#[cfg(test)]
mod tests_approx_equal {
use super::*;
#[test]
fn test_approx_equal_exact_values() {
assert!(approx_equal(1.0, 1.0));
}
#[test]
fn test_approx_equal_within_tolerance() {
let a = 1.00000001;
let b = 1.0;
assert!(approx_equal(a, b));
}
#[test]
fn test_approx_equal_outside_tolerance() {
let a = 1.0001;
let b = 1.0;
assert!(!approx_equal(a, b));
}
#[test]
fn test_approx_equal_negative_values() {
let a = -1.00000001;
let b = -1.0;
assert!(approx_equal(a, b));
}
#[test]
fn test_approx_equal_large_values_within_tolerance() {
let a = 1000000.000000001;
let b = 1000000.0;
assert!(approx_equal(a, b));
}
#[test]
fn test_approx_equal_large_values_outside_tolerance() {
let a = 1000000.1;
let b = 1000000.0;
assert!(!approx_equal(a, b));
}
#[test]
fn test_approx_equal_zero() {
let a = 0.0;
let b = 0.0;
assert!(approx_equal(a, b));
}
#[test]
fn test_approx_equal_zero_with_small_value() {
let a = 0.000000001;
let b = 0.0;
assert!(approx_equal(a, b));
}
#[test]
fn test_approx_equal_zero_outside_tolerance() {
let a = 0.01;
let b = 0.0;
assert!(!approx_equal(a, b));
}
}
#[cfg(test)]
mod tests_get_random_element {
use super::*;
use crate::chains::OptionData;
use positive::pos_or_panic;
use std::collections::BTreeSet;
#[test]
fn test_get_random_element_empty_set() {
let set: BTreeSet<i32> = BTreeSet::new();
assert!(get_random_element(&set).is_none());
}
#[test]
fn test_get_random_element_single_element() {
let mut set = BTreeSet::new();
set.insert(42);
assert_eq!(get_random_element(&set), Some(&42));
}
#[test]
fn test_get_random_element_multiple_elements() {
let mut set = BTreeSet::new();
for i in 0..5 {
set.insert(i);
}
let random_element = get_random_element(&set);
assert!(random_element.is_some());
assert!((0..5).contains(random_element.unwrap()));
}
#[test]
fn test_get_random_element_with_option_data() {
let mut set = BTreeSet::new();
for i in 0..5 {
let option_data = OptionData::new(
pos_or_panic!(100.0 + i as f64), None, None, None, None, pos_or_panic!(0.2), None, None, None, None,
None,
None,
None,
None,
None,
None,
None,
None,
);
set.insert(option_data);
}
let random_option = get_random_element(&set);
assert!(random_option.is_some());
let strike = random_option.unwrap().strike_price;
assert!(strike >= Positive::HUNDRED && strike <= pos_or_panic!(104.0));
}
#[test]
fn test_get_random_element_distribution() {
let mut set = BTreeSet::new();
for i in 0..3 {
set.insert(i);
}
let mut counts = vec![0; 3];
for _ in 0..1000 {
if let Some(&value) = get_random_element(&set) {
counts[value as usize] += 1;
}
}
for count in counts {
assert!(count > 200); }
}
}
#[cfg(test)]
mod tests_process_n_times_iter {
use super::*;
#[test]
fn test_empty_vector() {
let empty_vec: Vec<i32> = vec![];
let result = process_n_times_iter(&empty_vec, 1, |_| vec![42]);
assert!(result.is_err());
assert_eq!(result.unwrap_err().to_string(), "Vector empty");
}
#[test]
fn test_single_element_single_combination() {
let vec = vec![1];
let result = process_n_times_iter(&vec, 1, |combination| vec![*combination[0] * 2]);
assert!(result.is_ok());
assert_eq!(result.unwrap(), vec![2]);
}
#[test]
fn test_multiple_elements_single_output() {
let vec = vec![1, 2, 3];
let result =
process_n_times_iter(&vec, 2, |combination| vec![combination[0] + combination[1]]);
assert!(result.is_ok());
let result = result.unwrap();
assert_eq!(result.len(), 6);
assert!(result.contains(&2)); assert!(result.contains(&3)); assert!(result.contains(&4)); }
#[test]
fn test_type_conversion() {
let vec = vec![1, 2];
let result = process_n_times_iter(&vec, 1, |combination| vec![combination[0].to_string()]);
assert!(result.is_ok());
let result = result.unwrap();
assert_eq!(result, vec!["1", "2"]);
}
#[test]
fn test_multiple_outputs_per_combination() {
let vec = vec![1, 2];
let result = process_n_times_iter(&vec, 1, |combination| {
vec![combination[0] * 2, combination[0] * 3]
});
assert!(result.is_ok());
let result = result.unwrap();
assert_eq!(result, vec![2, 3, 4, 6]);
}
#[test]
fn test_empty_output() {
let vec = vec![1, 2];
let result = process_n_times_iter(&vec, 1, |_| Vec::<i32>::new());
assert!(result.is_ok());
assert!(result.unwrap().is_empty());
}
#[test]
fn test_with_custom_struct() {
#[derive(Clone, Debug, PartialEq)]
struct TestStruct {
value: i32,
}
let vec = vec![TestStruct { value: 1 }, TestStruct { value: 2 }];
let result = process_n_times_iter(&vec, 2, |combination| {
vec![TestStruct {
value: combination[0].value + combination[1].value,
}]
});
assert!(result.is_ok());
let result = result.unwrap();
assert!(result.contains(&TestStruct { value: 2 })); assert!(result.contains(&TestStruct { value: 3 })); assert!(result.contains(&TestStruct { value: 4 })); }
#[test]
fn test_combination_size_larger_than_input() {
let vec = vec![1, 2];
let result = process_n_times_iter(&vec, 3, |combination| {
let sum = combination.iter().copied().sum::<i32>();
vec![sum]
});
assert!(result.is_ok());
let result = result.unwrap();
assert!(!result.is_empty());
let expected_sums = vec![3, 4, 5, 6]; for sum in expected_sums {
assert!(result.contains(&sum));
}
}
#[test]
fn test_mutable_state() {
let vec = vec![1, 2];
let mut sum = 0;
let result = process_n_times_iter(&vec, 1, |combination| {
sum += combination[0];
vec![sum]
});
assert!(result.is_ok());
}
#[test]
fn test_filter_combinations() {
let vec = vec![1, 2, 3, 4];
let result = process_n_times_iter(&vec, 2, |combination| {
if combination[0] + combination[1] > 5 {
vec![combination[0] + combination[1]]
} else {
vec![]
}
});
assert!(result.is_ok());
let result = result.unwrap();
assert!(result.iter().all(|&x| x > 5));
}
}
#[cfg(test)]
mod tests_random_decimal {
use super::*;
use rand::SeedableRng;
use rand::rngs::SmallRng;
use tracing::info;
#[test]
fn test_random_decimal_generates_valid_value() {
let mut t_rng = SmallRng::seed_from_u64(42);
let result = random_decimal(&mut t_rng);
assert!(result.is_ok());
let decimal = result.unwrap();
assert!(decimal >= Decimal::ZERO);
assert!(decimal < Decimal::ONE);
}
#[test]
fn test_random_decimal_different_calls_different_values() {
let mut t_rng = SmallRng::seed_from_u64(42);
let decimal1 = random_decimal(&mut t_rng).unwrap();
let decimal2 = random_decimal(&mut t_rng).unwrap();
assert_ne!(decimal1, decimal2);
}
#[test]
fn test_random_decimal_reproduces_expected_values() {
let mut t_rng = SmallRng::seed_from_u64(12345);
let decimal = random_decimal(&mut t_rng).unwrap();
info!("Generated decimal: {}", decimal);
let mut rng2 = SmallRng::seed_from_u64(12345);
let decimal2 = random_decimal(&mut rng2).unwrap();
assert_eq!(decimal, decimal2);
}
#[test]
fn test_random_decimal_with_multiple_rng_types() {
{
let mut t_rng = SmallRng::seed_from_u64(1);
assert!(random_decimal(&mut t_rng).is_ok());
}
{
let mut t_rng = rng();
assert!(random_decimal(&mut t_rng).is_ok());
}
{
let mut t_rng = SmallRng::seed_from_u64(42);
assert!(random_decimal(&mut t_rng).is_ok());
}
}
#[test]
fn test_multiple_random_decimals() {
let mut t_rng = SmallRng::seed_from_u64(42);
let decimals: Vec<Decimal> = (0..10)
.map(|_| random_decimal(&mut t_rng).unwrap())
.collect();
assert_eq!(decimals.len(), 10);
for i in 0..9 {
assert_ne!(decimals[i], decimals[i + 1]);
}
}
}
#[cfg(test)]
mod tests_log_returns {
use super::*;
use approx::assert_relative_eq;
use positive::pos_or_panic;
#[test]
fn test_empty_input() {
let prices: Vec<Positive> = Vec::new();
let result = calculate_log_returns(&prices);
assert!(result.is_ok());
assert_eq!(result.unwrap().len(), 0);
}
#[test]
fn test_single_input() {
let prices = vec![Positive::HUNDRED];
let result = calculate_log_returns(&prices);
assert!(result.is_ok());
assert_eq!(result.unwrap().len(), 0);
}
#[test]
fn test_basic_calculation() {
let prices = vec![
Positive::HUNDRED,
pos_or_panic!(110.0),
pos_or_panic!(105.0),
];
let result = calculate_log_returns(&prices).unwrap();
assert_eq!(result.len(), 2);
assert_relative_eq!(result[0].to_f64(), 0.09531018, epsilon = 0.00001);
assert_relative_eq!(result[1].to_f64(), -0.04652, epsilon = 0.00001);
}
#[test]
#[should_panic]
fn test_zero_price() {
let prices = vec![Positive::HUNDRED, Positive::ZERO, pos_or_panic!(105.0)];
let _ = calculate_log_returns(&prices);
}
#[test]
#[should_panic]
fn test_negative_price() {
let prices = vec![
Positive::HUNDRED,
pos_or_panic!(-50.0),
pos_or_panic!(105.0),
];
let _ = calculate_log_returns(&prices);
}
#[test]
fn test_realistic_stock_prices() {
let prices = vec![
pos_or_panic!(150.25),
pos_or_panic!(151.50),
pos_or_panic!(149.75),
pos_or_panic!(152.25),
pos_or_panic!(153.00),
];
let result = calculate_log_returns(&prices).unwrap();
assert_eq!(result.len(), 4);
assert_relative_eq!(result[0].to_f64(), 0.00829, epsilon = 0.00001);
assert_relative_eq!(result[1].to_f64(), -0.01161, epsilon = 0.00001);
assert_relative_eq!(result[2].to_f64(), 0.01656, epsilon = 0.00001);
assert_relative_eq!(result[3].to_f64(), 0.00491, epsilon = 0.00001);
}
#[test]
fn test_large_price_movements() {
let prices = vec![
Positive::HUNDRED, pos_or_panic!(200.0), pos_or_panic!(50.0), pos_or_panic!(300.0), ];
let result = calculate_log_returns(&prices).unwrap();
assert_eq!(result.len(), 3);
assert_relative_eq!(result[0].to_f64(), std::f64::consts::LN_2, epsilon = 0.0001);
assert_relative_eq!(result[1].to_f64(), -1.3863, epsilon = 0.0001);
assert_relative_eq!(result[2].to_f64(), 1.7918, epsilon = 0.0001);
}
#[test]
fn test_no_change_prices() {
let prices = vec![Positive::HUNDRED, Positive::HUNDRED, Positive::HUNDRED];
let result = calculate_log_returns(&prices).unwrap();
assert_eq!(result.len(), 2);
assert_relative_eq!(result[0].to_f64(), 0.0, epsilon = 0.00001);
assert_relative_eq!(result[1].to_f64(), 0.0, epsilon = 0.00001);
}
#[test]
fn test_very_small_price_changes() {
let prices = vec![
pos_or_panic!(100.000),
pos_or_panic!(100.001),
pos_or_panic!(100.002),
];
let result = calculate_log_returns(&prices).unwrap();
assert_eq!(result.len(), 2);
assert!(result[0].to_f64() > 0.0);
assert!(result[0].to_f64() < 0.0001);
assert!(result[1].to_f64() > 0.0);
assert!(result[1].to_f64() < 0.0001);
}
}