use crate::error::greeks::{DeltaNeutralityErrorKind, GreeksError};
use optionstratlib_core::model::Positive;
use optionstratlib_core::model::decimal::{d_add, d_div, d_exp, d_mul, d_powd, d_sub};
use rust_decimal::Decimal;
use rust_decimal_macros::dec;
pub const DELTA_THRESHOLD: Decimal = dec!(0.0001);
#[inline]
pub fn n(x: Decimal) -> Result<Decimal, GreeksError> {
const NORM_FACTOR: Decimal = dec!(0.3989422804014326779399461);
if x.abs() > dec!(5) {
return Ok(Decimal::ZERO);
}
let x_squared = d_powd(x, Decimal::TWO, "greeks::n::x_squared")?;
let pre_pdf = -d_div(x_squared, Decimal::TWO, "greeks::n::half_x_squared")?;
if pre_pdf < dec!(-11.7) {
return Ok(Decimal::ZERO);
}
let pdf = d_exp(pre_pdf, "greeks::n::exp")?;
Ok(d_mul(NORM_FACTOR, pdf, "greeks::n::normalisation")?) }
#[allow(dead_code)]
#[inline]
pub(crate) fn n_prime(x: Decimal) -> Result<Decimal, GreeksError> {
Ok(d_mul(-x, n(x)?, "greeks::n_prime")?) }
pub fn calculate_delta_neutral_sizes(
delta1: Decimal,
delta2: Decimal,
total_size: Positive,
) -> Result<(Positive, Positive), GreeksError> {
if delta1.is_zero() || delta2.is_zero() {
return Err(DeltaNeutralityErrorKind::ZeroDelta.into());
}
if delta1 == delta2 {
return Err(DeltaNeutralityErrorKind::EqualDeltas.into());
}
if delta1.is_sign_positive() == delta2.is_sign_positive() {
return Err(DeltaNeutralityErrorKind::SameSignDeltas.into());
}
let weighted = d_mul(
-total_size.to_dec(),
delta2,
"greeks::delta_neutral::size1::weighted",
)?;
let spread = d_sub(delta1, delta2, "greeks::delta_neutral::size1::spread")?;
let size1 = Positive::new_decimal(d_div(weighted, spread, "greeks::delta_neutral::size1")?)?;
let Ok(size2) = total_size.checked_sub(&size1) else {
return Err(DeltaNeutralityErrorKind::NegativePositionSize.into());
};
if size1 < Decimal::ZERO || size2 < Decimal::ZERO {
return Err(DeltaNeutralityErrorKind::NegativePositionSize.into());
}
let total_delta: Decimal = d_add(
d_mul(size1.to_dec(), delta1, "greeks::delta_neutral::check::leg1")?,
d_mul(size2.to_dec(), delta2, "greeks::delta_neutral::check::leg2")?,
"greeks::delta_neutral::check::total_delta",
)?;
if total_delta.abs() > DELTA_THRESHOLD {
return Err(DeltaNeutralityErrorKind::NotAchievable.into());
}
let total_size_check = size1.checked_add(&size2)?;
let size_gap = d_sub(
total_size_check.to_dec(),
total_size.to_dec(),
"greeks::delta_neutral::check::size_gap",
)?;
if size_gap.abs() > DELTA_THRESHOLD {
return Err(DeltaNeutralityErrorKind::SizeMismatch {
calculated: total_size_check,
expected: total_size,
}
.into());
}
Ok((size1, size2))
}
#[cfg(test)]
mod tests_calculate_delta_neutral_sizes {
use super::*;
use optionstratlib_core::assert_decimal_eq;
use optionstratlib_core::{assert_pos_relative_eq, pos_or_panic};
use rust_decimal_macros::dec;
#[test]
fn test_valid_delta_neutral_calculation() {
let result = calculate_delta_neutral_sizes(
dec!(-0.30), dec!(0.20), pos_or_panic!(7.0), )
.unwrap();
let (size1, size2) = result;
assert_pos_relative_eq!(size1 + size2, pos_or_panic!(7.0), pos_or_panic!(0.0001));
let total_delta = size1.to_dec() * dec!(-0.30) + size2.to_dec() * dec!(0.20);
assert_decimal_eq!(total_delta, Decimal::ZERO, dec!(0.0001));
}
#[test]
fn test_equal_deltas() {
let result = calculate_delta_neutral_sizes(dec!(0.25), dec!(0.25), pos_or_panic!(10.0));
assert!(result.is_err());
}
#[test]
fn test_impossible_neutrality() {
let result = calculate_delta_neutral_sizes(dec!(-0.95), dec!(-0.90), pos_or_panic!(10.0));
assert!(result.is_err());
}
}
#[cfg(test)]
mod calculate_n_values {
use super::*;
use approx::assert_relative_eq;
use num_traits::{FloatConst, ToPrimitive};
use rust_decimal_macros::dec;
use std::f64::consts::PI;
#[test]
fn test_n_zero() {
let x = Decimal::ZERO;
let expected_n = 1.0f64 / (2.0 * PI).sqrt();
let calculated_n = n(x).unwrap().to_f64().unwrap();
assert_relative_eq!(calculated_n, expected_n, epsilon = 1e-8);
}
#[test]
fn test_n_one() {
let x = Decimal::ONE;
let expected_n = 0.24197072535043143;
let calculated_n = n(x).unwrap().to_f64().unwrap();
assert_relative_eq!(calculated_n, expected_n, epsilon = 1e-8);
}
#[test]
fn test_n_two() {
let x = Decimal::TWO;
let expected_n = 0.05399096672219953;
let calculated_n = n(x).unwrap().to_f64().unwrap();
assert_relative_eq!(calculated_n, expected_n, epsilon = 1e-8);
}
#[test]
fn test_n_positive_small_value() {
let x = dec!(0.5);
let expected_n = 1.0f64 / (2.0 * PI).sqrt() * (-0.5f64 * 0.5f64 / 2.0f64).exp();
let calculated_n = n(x).unwrap().to_f64().unwrap();
assert_relative_eq!(calculated_n, expected_n, epsilon = 1e-8);
}
#[test]
fn test_n_negative_small_value() {
let x = dec!(-0.5);
let expected_n = 1.0f64 / (2.0 * PI).sqrt() * (-0.5f64 * 0.5f64 / 2.0f64).exp();
let calculated_n = n(x).unwrap().to_f64().unwrap();
assert_relative_eq!(calculated_n, expected_n, epsilon = 1e-8);
}
#[test]
fn test_n_large_positive_value() {
let x = dec!(5.0);
let calculated_n = n(x).unwrap().to_f64().unwrap();
assert_relative_eq!(calculated_n, 0.0, epsilon = 1e-8);
}
#[test]
fn test_n_large_negative_value() {
let x = dec!(-5.0);
let calculated_n = n(x).unwrap().to_f64().unwrap();
assert_relative_eq!(calculated_n, 0.0, epsilon = 1e-8);
}
#[test]
fn test_n_extreme_positive_value() {
let x = dec!(100.0);
let expected_n = 1.0f64 / (2.0 * PI).sqrt() * (-100.0f64 * 100.0f64 / 2.0f64).exp();
let calculated_n = n(x).unwrap().to_f64().unwrap();
assert_relative_eq!(calculated_n, expected_n, epsilon = 1e-100);
}
#[test]
fn test_n_extreme_negative_value() {
let x = dec!(-100.0);
let expected_n = 1.0f64 / (2.0 * PI).sqrt() * (-100.0f64 * 100.0f64 / 2.0f64).exp();
let calculated_n = n(x).unwrap().to_f64().unwrap();
assert_relative_eq!(calculated_n, expected_n, epsilon = 1e-100);
}
#[test]
fn test_n() {
let x = Decimal::ZERO;
let expected_n = 1.0 / (2.0 * f64::PI()).sqrt();
let computed_n = n(x).unwrap().to_f64().unwrap();
assert_relative_eq!(computed_n, expected_n, epsilon = 1e-8);
let x = Decimal::ONE;
let expected_n = 1.0 / (2.0 * f64::PI()).sqrt() * (-0.5f64).exp();
let computed_n = n(x).unwrap().to_f64().unwrap();
assert_relative_eq!(computed_n, expected_n, epsilon = 1e-8);
}
#[test]
fn test_precision_limits() {
let x = dec!(15.0);
let n_result = n(x).unwrap();
assert!(n_result.to_f64().unwrap() == 0.0);
}
}
#[cfg(test)]
mod calculate_n_prime_values {
use super::*;
use approx::assert_relative_eq;
use num_traits::ToPrimitive;
use rust_decimal_macros::dec;
#[test]
fn test_n_prime_zero() {
let x = dec!(0.0);
let expected_n_prime = 0.0f64;
let calculated_n_prime = n_prime(x).unwrap().to_f64().unwrap();
assert_relative_eq!(calculated_n_prime, expected_n_prime, epsilon = 1e-8);
}
#[test]
fn test_n_prime_one() {
let x = Decimal::ONE;
let expected_n_prime = -0.24197072535043143f64;
let calculated_n_prime = n_prime(x).unwrap().to_f64().unwrap();
assert_relative_eq!(calculated_n_prime, expected_n_prime, epsilon = 1e-8);
}
#[test]
fn test_n_prime_two() {
let x = Decimal::TWO;
let expected_n_prime = -0.10798193344439906;
let calculated_n_prime = n_prime(x).unwrap().to_f64().unwrap();
assert_relative_eq!(calculated_n_prime, expected_n_prime, epsilon = 1e-8);
}
#[test]
fn test_n_prime_positive_small_value() {
let x = dec!(0.5);
let expected_n_prime = -x.to_f64().unwrap() * n(x).unwrap().to_f64().unwrap();
let calculated_n_prime = n_prime(x).unwrap().to_f64().unwrap();
assert_relative_eq!(calculated_n_prime, expected_n_prime, epsilon = 1e-8);
}
#[test]
fn test_n_prime_negative_small_value() {
let x = dec!(-0.5);
let expected_n_prime = (-x * n(x).unwrap()).to_f64().unwrap();
let calculated_n_prime = n_prime(x).unwrap().to_f64().unwrap();
assert_relative_eq!(calculated_n_prime, expected_n_prime, epsilon = 1e-8);
}
#[test]
fn test_n_prime_large_positive_value() {
let x = dec!(5.0);
let expected_n_prime = -x.to_f64().unwrap() * n(x).unwrap().to_f64().unwrap();
let calculated_n_prime = n_prime(x).unwrap().to_f64().unwrap();
assert_relative_eq!(calculated_n_prime, expected_n_prime, epsilon = 1e-8);
}
#[test]
fn test_n_prime_large_negative_value() {
let x = -dec!(5.0);
let expected_n_prime = (-x * n(x).unwrap()).to_f64().unwrap();
let calculated_n_prime = n_prime(x).unwrap().to_f64().unwrap();
assert_relative_eq!(calculated_n_prime, expected_n_prime, epsilon = 1e-8);
}
#[test]
fn test_n_prime_extreme_positive_value() {
let x = dec!(100.0);
let expected_n_prime = (-x * n(x).unwrap()).to_f64().unwrap();
let calculated_n_prime = n_prime(x).unwrap().to_f64().unwrap();
assert_relative_eq!(calculated_n_prime, expected_n_prime, epsilon = 1e-100);
}
#[test]
fn test_n_prime_extreme_negative_value() {
let x = -dec!(100.0);
let expected_n_prime = (-x * n(x).unwrap()).to_f64().unwrap();
let calculated_n_prime = n_prime(x).unwrap().to_f64().unwrap();
assert_relative_eq!(calculated_n_prime, expected_n_prime, epsilon = 1e-100);
}
}
#[cfg(test)]
mod tests_probability_density {
use super::*;
use rust_decimal_macros::dec;
#[test]
fn test_n_prime_symmetry() {
let x = dec!(1.5);
let n_prime_pos = n_prime(x).unwrap();
let n_prime_neg = n_prime(-x).unwrap();
assert_eq!(n_prime_pos, -n_prime_neg);
}
#[test]
fn test_n_integration_limits() {
let x_very_large = dec!(10.0);
let result = n(x_very_large).unwrap();
assert!(result < dec!(0.0001));
}
#[test]
fn test_n_prime_zero_crossing() {
let result = n_prime(dec!(0.0)).unwrap();
assert_eq!(result, dec!(0.0));
}
}
#[cfg(test)]
mod tests_delta_neutral_extremes {
use super::*;
use rust_decimal_macros::dec;
#[test]
fn test_large_deltas_on_a_large_size_report_the_overflow() {
let result = calculate_delta_neutral_sizes(dec!(10), dec!(-10), Positive::MAX);
assert!(result.is_err(), "expected an error, got {result:?}");
}
#[test]
fn test_ordinary_deltas_still_size_the_pair() {
let (size1, size2) = match calculate_delta_neutral_sizes(
dec!(0.5),
dec!(-0.5),
Positive::new(10.0).expect("literal is positive"),
) {
Ok(sizes) => sizes,
Err(e) => panic!("sizing should succeed: {e:?}"),
};
assert_eq!(size1, size2);
assert_eq!(
size1 + size2,
Positive::new(10.0).expect("literal is positive")
);
}
}