use crate::constants::*;
use crate::error::SolMathError;
use crate::arithmetic::{fp_mul_i, fp_div, fp_div_i, fp_sqrt};
use crate::transcendental::{ln_fixed_i, exp_fixed_i};
use crate::normal::{norm_cdf_poly, norm_cdf_and_pdf_bs_guarded};
pub fn black_scholes_price(s: u128, k: u128, r: u128, sigma: u128, t: u128) -> Result<(u128, u128), SolMathError> {
black_scholes_price_selective(s, k, r, sigma, t)
}
pub(crate) fn black_scholes_price_selective(s: u128, k: u128, r: u128, sigma: u128, t: u128) -> Result<(u128, u128), SolMathError> {
if s > i128::MAX as u128 || k > i128::MAX as u128 || r > i128::MAX as u128
|| sigma > i128::MAX as u128 || t > i128::MAX as u128
{
return Err(SolMathError::Overflow);
}
if s == 0 {
let r_t = fp_mul_i(r as i128, t as i128)?;
let k_disc = fp_mul_i(k as i128, exp_fixed_i(-r_t)?)?;
return Ok((0, if k_disc > 0 { k_disc as u128 } else { 0 }));
}
if k == 0 {
return Ok((s, 0));
}
let s_i = s as i128;
let k_i = k as i128;
let r_i = r as i128;
let sigma_i = sigma as i128;
let t_i = t as i128;
if t == 0 {
let call = if s > k { s - k } else { 0 };
let put = if k > s { k - s } else { 0 };
return Ok((call, put));
}
let sk_ratio = fp_div(s, k)?;
let ln_sk = ln_fixed_i(sk_ratio)?;
let sigma_sq = fp_mul_i(sigma_i, sigma_i)?;
let sigma_sq_half = sigma_sq / 2;
let drift = fp_mul_i(r_i + sigma_sq_half, t_i)?;
let d1_num = ln_sk + drift;
let sqrt_t = fp_sqrt(t)? as i128;
let sigma_sqrt_t = fp_mul_i(sigma_i, sqrt_t)?;
if sigma_sqrt_t <= 1 {
let r_t = fp_mul_i(r_i, t_i)?;
let discount = exp_fixed_i(-r_t)?;
let k_disc = fp_mul_i(k_i, discount)?;
let call_i = s_i - k_disc;
let put_i = k_disc - s_i;
let call = if call_i > 0 { call_i as u128 } else { 0 };
let put = if put_i > 0 { put_i as u128 } else { 0 };
return Ok((call, put));
}
let d1 = fp_div_i(d1_num, sigma_sqrt_t)?;
let d2 = d1 - sigma_sqrt_t;
let phi_d1 = norm_cdf_poly(d1)?;
let phi_neg_d1 = SCALE_I - phi_d1;
let phi_d2 = norm_cdf_poly(d2)?;
let phi_neg_d2 = SCALE_I - phi_d2;
let r_t = fp_mul_i(r_i, t_i)?;
let discount = exp_fixed_i(-r_t)?;
let k_disc = fp_mul_i(k_i, discount)?;
let term1 = fp_mul_i(s_i, phi_d1)?;
let term2 = fp_mul_i(k_disc, phi_d2)?;
let call_i = term1 - term2;
let call = if call_i > 0 { call_i as u128 } else { 0 };
let term3 = fp_mul_i(k_disc, phi_neg_d2)?;
let term4 = fp_mul_i(s_i, phi_neg_d1)?;
let put_i = term3 - term4;
let put = if put_i > 0 { put_i as u128 } else { 0 };
Ok((call, put))
}
pub(crate) fn bs_intermediates(s: u128, k: u128, r: u128, sigma: u128, t: u128) -> Result<BsIntermediates, SolMathError> {
bs_intermediates_selective(s, k, r, sigma, t)
}
pub(crate) fn bs_intermediates_selective(s: u128, k: u128, r: u128, sigma: u128, t: u128) -> Result<BsIntermediates, SolMathError> {
if s > i128::MAX as u128 || k > i128::MAX as u128 || r > i128::MAX as u128
|| sigma > i128::MAX as u128 || t > i128::MAX as u128
{
return Err(SolMathError::Overflow);
}
let k_i = k as i128;
let r_i = r as i128;
let sigma_i = sigma as i128;
let t_i = t as i128;
let sk_ratio = fp_div(s, k)?;
let ln_sk = ln_fixed_i(sk_ratio)?;
let sigma_sq = fp_mul_i(sigma_i, sigma_i)?;
let sigma_sq_half = sigma_sq / 2;
let drift = fp_mul_i(r_i + sigma_sq_half, t_i)?;
let d1_num = ln_sk + drift;
let sqrt_t = fp_sqrt(t)? as i128;
let sigma_sqrt_t = fp_mul_i(sigma_i, sqrt_t)?;
let d1 = if sigma_sqrt_t > 0 {
fp_div_i(d1_num, sigma_sqrt_t)?
} else if d1_num > 0 {
8 * SCALE_I
} else if d1_num < 0 {
-8 * SCALE_I
} else {
0
};
let d2 = d1 - sigma_sqrt_t;
let (phi_d1, pdf_d1) = norm_cdf_and_pdf_bs_guarded(d1)?;
let phi_d2 = norm_cdf_poly(d2)?;
let phi_neg_d1 = SCALE_I - phi_d1;
let phi_neg_d2 = SCALE_I - phi_d2;
let r_t = fp_mul_i(r_i, t_i)?;
let discount = exp_fixed_i(-r_t)?;
let k_disc = fp_mul_i(k_i, discount)?;
Ok(BsIntermediates {
d1,
d2,
phi_d1,
phi_d2,
phi_neg_d1,
phi_neg_d2,
pdf_d1,
discount,
sqrt_t,
sigma_sqrt_t,
k_disc,
})
}
pub fn bs_vega(s: u128, k: u128, r: u128, sigma: u128, t: u128) -> Result<i128, SolMathError> {
bs_vega_selective(s, k, r, sigma, t)
}
pub(crate) fn bs_vega_selective(s: u128, k: u128, r: u128, sigma: u128, t: u128) -> Result<i128, SolMathError> {
if sigma == 0 || t == 0 {
return Err(SolMathError::DomainError);
}
if s == 0 || k == 0 {
return Ok(0);
}
let im = bs_intermediates(s, k, r, sigma, t)?;
let s_i = s as i128;
Ok(fp_mul_i(fp_mul_i(s_i, im.pdf_d1)?, im.sqrt_t)?)
}
pub fn bs_delta(s: u128, k: u128, r: u128, sigma: u128, t: u128) -> Result<(i128, i128), SolMathError> {
if sigma == 0 || t == 0 {
return Err(SolMathError::DomainError);
}
if s == 0 {
return Ok((0, -SCALE_I));
}
if k == 0 {
return Ok((SCALE_I, 0));
}
let im = bs_intermediates(s, k, r, sigma, t)?;
Ok((im.phi_d1, im.phi_d1 - SCALE_I))
}
pub fn bs_gamma(s: u128, k: u128, r: u128, sigma: u128, t: u128) -> Result<i128, SolMathError> {
bs_gamma_selective(s, k, r, sigma, t)
}
pub(crate) fn bs_gamma_selective(s: u128, k: u128, r: u128, sigma: u128, t: u128) -> Result<i128, SolMathError> {
if sigma == 0 || t == 0 {
return Err(SolMathError::DomainError);
}
if s == 0 || k == 0 {
return Ok(0);
}
let im = bs_intermediates(s, k, r, sigma, t)?;
let s_i = s as i128;
let denom = fp_mul_i(s_i, im.sigma_sqrt_t)?;
if denom == 0 {
return Ok(0);
}
Ok(fp_div_i(im.pdf_d1, denom)?)
}
pub fn bs_theta(s: u128, k: u128, r: u128, sigma: u128, t: u128) -> Result<(i128, i128), SolMathError> {
bs_theta_selective(s, k, r, sigma, t)
}
pub(crate) fn bs_theta_selective(s: u128, k: u128, r: u128, sigma: u128, t: u128) -> Result<(i128, i128), SolMathError> {
if sigma == 0 || t == 0 {
return Err(SolMathError::DomainError);
}
if s == 0 || k == 0 {
return Ok((0, 0));
}
let im = bs_intermediates(s, k, r, sigma, t)?;
let s_i = s as i128;
let r_i = r as i128;
let sigma_i = sigma as i128;
let term1_num = fp_mul_i(fp_mul_i(s_i, im.pdf_d1)?, sigma_i)?;
let two_sqrt_t = 2 * im.sqrt_t;
let term1 = if two_sqrt_t > 0 {
-fp_div_i(term1_num, two_sqrt_t)?
} else {
0
};
let r_k_disc = fp_mul_i(r_i, im.k_disc)?;
let term2_call = fp_mul_i(r_k_disc, im.phi_d2)?;
let term2_put = fp_mul_i(r_k_disc, im.phi_neg_d2)?;
let theta_call = term1 - term2_call;
let theta_put = term1 + term2_put;
Ok((theta_call, theta_put))
}
pub fn bs_rho(s: u128, k: u128, r: u128, sigma: u128, t: u128) -> Result<(i128, i128), SolMathError> {
bs_rho_selective(s, k, r, sigma, t)
}
pub(crate) fn bs_rho_selective(s: u128, k: u128, r: u128, sigma: u128, t: u128) -> Result<(i128, i128), SolMathError> {
if sigma == 0 || t == 0 {
return Err(SolMathError::DomainError);
}
if s == 0 || k == 0 {
return Ok((0, 0));
}
let im = bs_intermediates(s, k, r, sigma, t)?;
let t_i = t as i128;
let kt_disc = fp_mul_i(im.k_disc, t_i)?;
let rho_call = fp_mul_i(kt_disc, im.phi_d2)?;
let rho_put = -fp_mul_i(kt_disc, im.phi_neg_d2)?;
Ok((rho_call, rho_put))
}
pub fn bs_full(s: u128, k: u128, r: u128, sigma: u128, t: u128) -> Result<BsFull, SolMathError> {
bs_full_selective(s, k, r, sigma, t)
}
pub(crate) fn bs_full_selective(s: u128, k: u128, r: u128, sigma: u128, t: u128) -> Result<BsFull, SolMathError> {
if s > i128::MAX as u128 || k > i128::MAX as u128 || r > i128::MAX as u128
|| sigma > i128::MAX as u128 || t > i128::MAX as u128
{
return Err(SolMathError::Overflow);
}
if sigma == 0 || t == 0 {
return Err(SolMathError::DomainError);
}
if s == 0 || k == 0 {
let r_t = fp_mul_i(r as i128, t as i128)?;
let discount = exp_fixed_i(-r_t)?;
let k_disc = fp_mul_i(k as i128, discount)?;
return Ok(BsFull {
call: if s > 0 { s } else { 0 },
put: if s == 0 { if k_disc > 0 { k_disc as u128 } else { 0 } } else { 0 },
call_delta: if s == 0 { 0 } else { SCALE_I },
put_delta: if s == 0 { -SCALE_I } else { 0 },
gamma: 0, vega: 0, call_theta: 0, put_theta: 0, call_rho: 0, put_rho: 0,
});
}
let im = bs_intermediates(s, k, r, sigma, t)?;
let s_i = s as i128;
let r_i = r as i128;
let sigma_i = sigma as i128;
let t_i = t as i128;
let call_i = fp_mul_i(s_i, im.phi_d1)? - fp_mul_i(im.k_disc, im.phi_d2)?;
let put_i = fp_mul_i(im.k_disc, im.phi_neg_d2)? - fp_mul_i(s_i, im.phi_neg_d1)?;
let call = if call_i > 0 { call_i as u128 } else { 0 };
let put = if put_i > 0 { put_i as u128 } else { 0 };
let call_delta = im.phi_d1;
let put_delta = im.phi_d1 - SCALE_I;
let denom = fp_mul_i(s_i, im.sigma_sqrt_t)?;
let gamma = if denom != 0 {
fp_div_i(im.pdf_d1, denom)?
} else {
0
};
let vega = fp_mul_i(fp_mul_i(s_i, im.pdf_d1)?, im.sqrt_t)?;
let term1_num = fp_mul_i(fp_mul_i(s_i, im.pdf_d1)?, sigma_i)?;
let two_sqrt_t = 2 * im.sqrt_t;
let term1 = if two_sqrt_t > 0 {
-fp_div_i(term1_num, two_sqrt_t)?
} else {
0
};
let r_k_disc = fp_mul_i(r_i, im.k_disc)?;
let call_theta = term1 - fp_mul_i(r_k_disc, im.phi_d2)?;
let put_theta = term1 + fp_mul_i(r_k_disc, im.phi_neg_d2)?;
let kt_disc = fp_mul_i(im.k_disc, t_i)?;
let call_rho = fp_mul_i(kt_disc, im.phi_d2)?;
let put_rho = -fp_mul_i(kt_disc, im.phi_neg_d2)?;
Ok(BsFull {
call,
put,
call_delta,
put_delta,
gamma,
vega,
call_theta,
put_theta,
call_rho,
put_rho,
})
}