use crate::arithmetic::{fp_div, fp_div_i, fp_mul, fp_mul_i, fp_mul_i_fast, fp_sqrt};
use crate::bs::black_scholes_price;
use crate::constants::*;
use crate::error::SolMathError;
use crate::normal::{inverse_norm_cdf, norm_cdf_and_pdf, norm_cdf_poly, norm_pdf};
use crate::transcendental::{exp_fixed_i, ln_fixed_i};
#[inline(always)]
fn mul_fast(a: i128, b: i128) -> i128 {
debug_assert!(
a.checked_mul(b).is_some(),
"mul_fast precondition violated: {a} * {b} overflows i128 (IV solver bound broken)"
);
(a * b) / SCALE_I
}
#[allow(dead_code)]
pub(crate) fn newton_raphson(
f: fn(i128) -> i128,
f_prime: fn(i128) -> i128,
x0: i128,
tolerance: i128,
max_iter: u8,
) -> Result<i128, SolMathError> {
let mut x = x0;
let mut i = 0u8;
while i < max_iter {
let fx = f(x);
if fx.abs() < tolerance {
break;
}
let fpx = f_prime(x);
if fpx == 0 {
break;
}
x -= fp_div_i(fx, fpx)?;
i += 1;
}
Ok(x)
}
#[inline(never)]
#[allow(dead_code)]
pub(crate) fn li_rational_guess(x: i128, c: i128) -> Result<i128, SolMathError> {
let sc = fp_sqrt(c as u128)? as i128; let sc3 = mul_fast(sc, c); let sc4 = mul_fast(c, c);
let h0_n = mul_fast(
x,
LI_N[1] + mul_fast(x, LI_N[4] + mul_fast(x, LI_N[8] + mul_fast(x, LI_N[13]))),
);
let h0_m = mul_fast(
x,
LI_M[1] + mul_fast(x, LI_M[4] + mul_fast(x, LI_M[8] + mul_fast(x, LI_M[13]))),
);
let h1_n = mul_fast(
sc,
LI_N[0] + mul_fast(x, LI_N[3] + mul_fast(x, LI_N[7] + mul_fast(x, LI_N[12]))),
);
let h1_m = mul_fast(
sc,
LI_M[0] + mul_fast(x, LI_M[3] + mul_fast(x, LI_M[7] + mul_fast(x, LI_M[12]))),
);
let h2_n = mul_fast(c, LI_N[2] + mul_fast(x, LI_N[6] + mul_fast(x, LI_N[11])));
let h2_m = mul_fast(c, LI_M[2] + mul_fast(x, LI_M[6] + mul_fast(x, LI_M[11])));
let h3_n = mul_fast(sc3, LI_N[5] + mul_fast(x, LI_N[10]));
let h3_m = mul_fast(sc3, LI_M[5] + mul_fast(x, LI_M[10]));
let h4_n = mul_fast(sc4, LI_N[9]);
let h4_m = mul_fast(sc4, LI_M[9]);
let num = h0_n + h1_n + h2_n + h3_n + h4_n;
let den = SCALE_I + h0_m + h1_m + h2_m + h3_m + h4_m;
let linear = mul_fast(LI_P1, x) + mul_fast(LI_P2, sc) + mul_fast(LI_P3, c);
if den > 0 {
Ok(linear + fp_div_i(num, den)?)
} else {
Ok(mul_fast(
2_506_628_274_631,
fp_div_i(c, fp_sqrt(c as u128)? as i128)?,
)) }
}
#[inline(never)]
#[cfg(feature = "pade-iv")]
pub(crate) fn rational_guess_v2(x: i128, c: i128) -> Result<i128, SolMathError> {
let beta = mul_fast(c, SQRT_2PI_IV);
if beta <= 0 {
return Err(SolMathError::DomainError);
}
let z = fp_div_i(x, beta)?;
if z.abs() > 200_000_000_000_000 {
return Err(SolMathError::DomainError);
}
let mut num = PADE_P4;
num = mul_fast(num, z) + PADE_P3;
num = mul_fast(num, z) + PADE_P2;
num = mul_fast(num, z) + PADE_P1;
num = mul_fast(num, z) + PADE_P0;
let mut den = PADE_Q4;
den = mul_fast(den, z) + PADE_Q3;
den = mul_fast(den, z) + PADE_Q2;
den = mul_fast(den, z) + PADE_Q1;
den = mul_fast(den, z) + SCALE_I;
if den.abs() < 1000 {
return Ok(beta);
}
Ok(mul_fast(beta, fp_div_i(num, den)?))
}
#[inline(never)]
fn iv_price_and_greeks(
x_i: i128,
ln_fk: i128,
s_i: i128,
k_disc: i128,
solve_as_put: bool,
) -> Result<(i128, i128, i128), SolMathError> {
let d1 = fp_div_i(ln_fk, x_i)? + x_i / 2;
let d2 = d1 - x_i;
let (phi_d1, pdf_d1) = norm_cdf_and_pdf(d1)?;
let phi_d2 = norm_cdf_poly(d2)?;
let price_i = if solve_as_put {
let p = mul_fast(k_disc, SCALE_I - phi_d2) - mul_fast(s_i, SCALE_I - phi_d1);
if p > 0 {
p
} else {
0
}
} else {
let c = mul_fast(s_i, phi_d1) - mul_fast(k_disc, phi_d2);
if c > 0 {
c
} else {
0
}
};
let vega_x = mul_fast(s_i, pdf_d1);
let volga_x = if x_i > 0 && vega_x > 0 {
fp_div_i(mul_fast(vega_x, mul_fast(d1, d2)), x_i)?
} else {
0
};
Ok((price_i, vega_x, volga_x))
}
#[inline(never)]
fn halley_step_bracketed(
x_u: u128,
f: i128,
vega_x: i128,
volga_x: i128,
x_lo: u128,
x_hi: u128,
) -> Result<u128, SolMathError> {
let bisect = (x_lo + x_hi) / 2;
if vega_x <= 1_000 {
return Ok(bisect);
}
let halley_step = || -> Option<u128> {
let two_f_fp = fp_mul_i(f, vega_x).ok()?.checked_mul(2)?;
let two_vega_sq = fp_mul_i(vega_x, vega_x).ok()?.checked_mul(2)?;
let f_volga = fp_mul_i(f, volga_x).ok()?;
let denom = two_vega_sq.checked_sub(f_volga)?;
let step = if denom.abs() > 1_000 {
fp_div_i(two_f_fp, denom).ok()?
} else {
fp_div_i(f, vega_x).ok()?
};
let new_x = (x_u as i128).checked_sub(step)?;
if new_x > (x_lo as i128) && new_x < (x_hi as i128) {
Some(new_x as u128)
} else {
None
}
};
Ok(halley_step().unwrap_or(bisect))
}
#[inline(never)]
#[allow(dead_code)]
pub(crate) fn implied_vol_v1(
market_price: u128,
s: u128,
k: u128,
r: u128,
t: u128,
) -> Result<u128, SolMathError> {
if s == 0 || k == 0 || t == 0 || market_price == 0 {
return Err(SolMathError::DomainError);
}
if market_price < 100 {
return Err(SolMathError::NoConvergence);
}
let mp_i = market_price as i128;
let s_i = s as i128;
let k_i = k as i128;
let r_i = r as i128;
let t_i = t as i128;
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 sqrt_t = fp_sqrt(t)? as i128;
let ln_sk = ln_fixed_i(fp_div(s, k)?)?;
let ln_fk = ln_sk.checked_add(r_t).ok_or(SolMathError::Overflow)?;
let c_raw = fp_div_i(mp_i, s_i)?;
let (x_li, c_li) = if ln_fk > 0 {
let c_put = c_raw - SCALE_I + fp_div_i(k_disc, s_i)?;
(-ln_fk, if c_put > 0 { c_put } else { 0 })
} else {
(ln_fk, c_raw)
};
let abs_x = x_li.abs();
if abs_x >= 500_000_000_000 || c_li <= 0 {
return implied_vol_iterative(
market_price,
s,
k,
r,
t,
r_t,
discount,
k_disc,
sqrt_t,
ln_sk,
ln_fk,
);
}
let w = li_rational_guess(x_li, c_li)?;
if w <= 0 || w > SCALE_I {
return implied_vol_iterative(
market_price,
s,
k,
r,
t,
r_t,
discount,
k_disc,
sqrt_t,
ln_sk,
ln_fk,
);
}
let solve_as_put = s_i > k_disc + k_disc / 20;
let target_i = if solve_as_put {
let put_i = mp_i - s_i + k_disc;
if put_i > 0 {
put_i
} else {
1
}
} else {
mp_i
};
let mut x_lo = (mul_fast(1_000_000_000, sqrt_t)).max(1) as u128;
let mut x_hi = (mul_fast(5_000_000_000_000, sqrt_t)).max(x_lo as i128 + 1) as u128;
let mut x_u = (w as u128).clamp(x_lo, x_hi);
for _ in 0..4u8 {
let x_i = x_u as i128;
if x_i <= 1 {
break;
}
let (price_i, vega_x, volga_x) =
iv_price_and_greeks(x_i, ln_fk, s_i, k_disc, solve_as_put)?;
let f = price_i - target_i;
if f.abs() <= 100 {
if sqrt_t > 0 {
return Ok(fp_div_i(x_i, sqrt_t)? as u128);
} else {
return Err(SolMathError::NoConvergence);
}
}
if f > 0 {
if x_u < x_hi {
x_hi = x_u;
}
} else {
if x_u > x_lo {
x_lo = x_u;
}
}
x_u = halley_step_bracketed(x_u, f, vega_x, volga_x, x_lo, x_hi)?;
}
for _ in 0..4u8 {
let x_i = x_u as i128;
if x_i <= 1 {
break;
}
let (price_i, vega_x, volga_x) =
iv_price_and_greeks(x_i, ln_fk, s_i, k_disc, solve_as_put)?;
let f = price_i - target_i;
if f.abs() <= 100 {
if sqrt_t > 0 {
return Ok(fp_div_i(x_i, sqrt_t)? as u128);
} else {
return Err(SolMathError::NoConvergence);
}
}
if f > 0 {
if x_u < x_hi {
x_hi = x_u;
}
} else {
if x_u > x_lo {
x_lo = x_u;
}
}
x_u = halley_step_bracketed(x_u, f, vega_x, volga_x, x_lo, x_hi)?;
}
let x_i = x_u as i128;
if x_i > 0 && sqrt_t > 0 {
let (price_i, _, _) = iv_price_and_greeks(x_i, ln_fk, s_i, k_disc, solve_as_put)?;
let f = price_i - target_i;
if f.abs() <= 1000 {
return Ok(fp_div_i(x_i, sqrt_t)? as u128);
}
}
implied_vol_iterative(
market_price,
s,
k,
r,
t,
r_t,
discount,
k_disc,
sqrt_t,
ln_sk,
ln_fk,
)
}
#[inline(never)]
#[allow(dead_code)]
pub(crate) fn implied_vol_iterative(
market_price: u128,
s: u128,
k: u128,
_r: u128,
_t: u128,
_r_t: i128,
_discount: i128,
k_disc: i128,
sqrt_t: i128,
_ln_sk: i128,
ln_fk: i128,
) -> Result<u128, SolMathError> {
let mp_i = market_price as i128;
let s_i = s as i128;
let solve_as_put = s_i > k_disc + k_disc / 20;
let target_i = if solve_as_put {
let put_i = mp_i - s_i + k_disc;
if put_i > 0 {
put_i
} else {
1
}
} else {
mp_i
};
let sqrt_2pi: i128 = 2_506_628_274_631;
let abs_ln_fk = ln_fk.abs();
let beta_otm = if ln_fk > 0 {
let put_i = mp_i - s_i + k_disc;
if put_i <= 0 {
0
} else {
let sqrt_sk = fp_sqrt(fp_mul(s, k)?)? as i128;
fp_div_i(put_i, sqrt_sk)?
}
} else {
let sqrt_sk = fp_sqrt(fp_mul(s, k)?)? as i128;
fp_div_i(mp_i, sqrt_sk)?
};
let mut x_vol: i128;
if beta_otm <= 0 {
if solve_as_put {
return Err(SolMathError::NoConvergence);
}
x_vol = mul_fast(250_000_000_000, sqrt_t);
} else if abs_ln_fk < 50_000_000_000 {
x_vol = mul_fast(beta_otm, sqrt_2pi);
} else if beta_otm < 10_000_000_000 && abs_ln_fk > 100_000_000_000 {
let ln_beta = ln_fixed_i(beta_otm as u128)?;
let a = -2 * ln_beta - LN_2PI;
if a > SCALE_I {
let ln_a = ln_fixed_i(a as u128)?;
let a2 = a - ln_a;
if a2 > SCALE_I / 2 {
x_vol = fp_div_i(abs_ln_fk, fp_sqrt(a2 as u128)? as i128)?;
} else {
x_vol = fp_div_i(abs_ln_fk, fp_sqrt(a as u128)? as i128)?;
}
} else if a > 0 {
x_vol = fp_div_i(abs_ln_fk, fp_sqrt(a as u128)? as i128)?;
} else {
x_vol = mul_fast(250_000_000_000, sqrt_t);
}
} else {
let s_c = fp_sqrt(2 * abs_ln_fk as u128)? as i128;
let exp_neg_half_x = exp_fixed_i(-abs_ln_fk / 2)?;
let exp_half_x = fp_div_i(SCALE_I, exp_neg_half_x)?;
let b_c = {
let phi_neg_sc = norm_cdf_poly(-s_c)?;
let v = exp_neg_half_x / 2 - mul_fast(phi_neg_sc, exp_half_x);
if v > 0 {
v
} else {
0
}
};
let v_c = mul_fast(INV_SQRT_2PI, exp_neg_half_x);
if v_c > 0 {
let s0 = s_c + fp_div_i(beta_otm - b_c, v_c)?;
x_vol = if s0 > 0 { s0 } else { s_c };
} else {
x_vol = s_c;
}
}
let x_min = mul_fast(1_000_000_000, sqrt_t); let x_max = mul_fast(10_000_000_000_000, sqrt_t); x_vol = x_vol.clamp(x_min, x_max);
let mut x_u = x_vol as u128;
let mut x_lo = x_min as u128;
let mut x_hi = x_max as u128;
for iter in 0..6u8 {
let x_i = x_u as i128;
if x_i <= 1 {
break;
}
let (price_i, vega_x, volga_x) =
iv_price_and_greeks(x_i, ln_fk, s_i, k_disc, solve_as_put)?;
let f = price_i - target_i;
if f.abs() <= 1 {
if sqrt_t > 0 {
return Ok(fp_div_i(x_i, sqrt_t)? as u128);
} else {
return Err(SolMathError::NoConvergence);
}
}
if iter >= 4 && f.abs() <= 100 {
if sqrt_t > 0 {
return Ok(fp_div_i(x_i, sqrt_t)? as u128);
} else {
return Err(SolMathError::NoConvergence);
}
}
if f > 0 {
if x_u < x_hi {
x_hi = x_u;
}
} else {
if x_u > x_lo {
x_lo = x_u;
}
}
x_u = halley_step_bracketed(x_u, f, vega_x, volga_x, x_lo, x_hi)?;
}
let x_i = x_u as i128;
if x_i > 0 && sqrt_t > 0 {
let (price_i, _, _) = iv_price_and_greeks(x_i, ln_fk, s_i, k_disc, solve_as_put)?;
let f = price_i - target_i;
if f.abs() <= 1000 {
return Ok(fp_div_i(x_i, sqrt_t)? as u128);
}
}
Err(SolMathError::NoConvergence)
}
#[inline(never)]
fn normalised_intrinsic_call(x: i128) -> Result<i128, SolMathError> {
if x <= 0 {
return Ok(0);
}
let b_max = exp_fixed_i(x / 2)?;
let b_min = fp_div_i(SCALE_I, b_max)?;
let v = b_max - b_min;
Ok(if v > 0 { v } else { 0 })
}
#[inline(never)]
fn normalised_black_call(x: i128, s: i128) -> Result<i128, SolMathError> {
if s <= 0 {
return normalised_intrinsic_call(x);
}
if x > 0 {
let intrinsic = normalised_intrinsic_call(x)?;
return Ok(intrinsic + normalised_black_call(-x, s)?);
}
let h = fp_div_i(x, s)?;
let t = s / 2;
let exp_half = exp_fixed_i(x / 2)?;
let inv_exp_half = if exp_half > 0 {
fp_div_i(SCALE_I, exp_half)?
} else {
return Ok(0);
};
let b =
mul_fast(norm_cdf_poly(h + t)?, exp_half) - mul_fast(norm_cdf_poly(h - t)?, inv_exp_half);
Ok(if b > 0 { b } else { 0 })
}
#[inline(never)]
fn normalised_vega(x: i128, s: i128) -> Result<i128, SolMathError> {
if s <= 0 {
return Ok(0);
}
let ax = x.abs();
if ax > 0 && s <= ax / 1_000_000 {
return Ok(0); }
let h = fp_div_i(x, s)?;
let t = s / 2;
let sq_sum = match (fp_mul_i(h, h), fp_mul_i(t, t)) {
(Ok(hh), Ok(tt)) => match hh.checked_add(tt) {
Some(v) => v,
None => return Ok(0),
},
_ => return Ok(0),
};
let arg = -sq_sum / 2;
let e = exp_fixed_i(arg)?;
Ok(fp_mul_i_fast(INV_SQRT_2PI, e))
}
#[inline(never)]
fn householder_factor(newton: i128, halley: i128, hh3: i128) -> Result<i128, SolMathError> {
let hn = mul_fast(halley, newton);
let num = SCALE_I + hn / 2;
let den = SCALE_I + mul_fast(newton, halley + mul_fast(hh3, newton) / 6);
if den.abs() < 100 {
return Ok(SCALE_I); }
fp_div_i(num, den)
}
#[inline(never)]
fn rational_cubic_interpolation(
x: i128,
x_l: i128,
x_r: i128,
y_l: i128,
y_r: i128,
d_l: i128,
d_r: i128,
r: i128,
) -> Result<i128, SolMathError> {
let h = x_r - x_l;
if h.abs() <= 0 {
return Ok((y_l + y_r) / 2);
}
if r > 1_000_000_000_000_000_000 {
let t = fp_div_i(x - x_l, h)?;
return Ok(mul_fast(y_r, t) + mul_fast(y_l, SCALE_I - t));
}
let t = fp_div_i(x - x_l, h)?;
let omt = SCALE_I - t;
let t2 = mul_fast(t, t);
let omt2 = mul_fast(omt, omt);
let num = mul_fast(y_r, mul_fast(t2, t))
+ mul_fast(mul_fast(r, y_r) - mul_fast(h, d_r), mul_fast(t2, omt))
+ mul_fast(mul_fast(r, y_l) + mul_fast(h, d_l), mul_fast(t, omt2))
+ mul_fast(y_l, mul_fast(omt2, omt));
let den = SCALE_I + mul_fast(r - 3 * SCALE_I, mul_fast(t, omt));
if den.abs() < 100 {
return Ok((y_l + y_r) / 2);
}
fp_div_i(num, den)
}
fn rc_param_fit_2nd_deriv_left(
x_l: i128,
x_r: i128,
y_l: i128,
y_r: i128,
d_l: i128,
d_r: i128,
second_deriv: i128,
) -> Result<i128, SolMathError> {
let h = x_r - x_l;
let num = mul_fast(h, second_deriv) / 2 + (d_r - d_l);
if num.abs() < 100 {
return Ok(0);
}
let slope = if h == 0 { 0 } else { fp_div_i(y_r - y_l, h)? };
let den = slope - d_l;
if den.abs() < 100 {
return Ok(if num > 0 {
1_000_000_000_000_000_000
} else {
-SCALE_I + 1
});
}
if den == 0 {
Ok(0)
} else {
fp_div_i(num, den)
}
}
fn rc_param_fit_2nd_deriv_right(
x_l: i128,
x_r: i128,
y_l: i128,
y_r: i128,
d_l: i128,
d_r: i128,
second_deriv: i128,
) -> Result<i128, SolMathError> {
let h = x_r - x_l;
let num = mul_fast(h, second_deriv) / 2 + (d_r - d_l);
if num.abs() < 100 {
return Ok(0);
}
let slope = if h == 0 { 0 } else { fp_div_i(y_r - y_l, h)? };
let den = d_r - slope;
if den.abs() < 100 {
return Ok(if num > 0 {
1_000_000_000_000_000_000
} else {
-SCALE_I + 1
});
}
if den == 0 {
Ok(0)
} else {
fp_div_i(num, den)
}
}
const MIN_RC_PARAM: i128 = -SCALE_I + 1;
fn minimum_rc_param(
d_l: i128,
d_r: i128,
s: i128,
prefer_shape: bool,
) -> Result<i128, SolMathError> {
let monotonic = (mul_fast(d_l, s) >= 0) && (mul_fast(d_r, s) >= 0);
let convex = d_l <= s && s <= d_r;
let concave = d_l >= s && s >= d_r;
if !monotonic && !convex && !concave {
return Ok(MIN_RC_PARAM);
}
let mut r1 = i128::MIN;
let mut r2 = i128::MIN;
if monotonic {
if s.abs() > 100 {
r1 = fp_div_i(d_r + d_l, s)?;
} else if prefer_shape {
r1 = 1_000_000_000_000_000_000;
}
}
if convex || concave {
let s_m_dl = s - d_l;
let dr_m_s = d_r - s;
let dr_m_dl = d_r - d_l;
if s_m_dl.abs() > 100 && dr_m_s.abs() > 100 {
let r2a = if dr_m_s == 0 {
0
} else {
fp_div_i(dr_m_dl, dr_m_s)?.abs()
};
let r2b = if s_m_dl == 0 {
0
} else {
fp_div_i(dr_m_dl, s_m_dl)?.abs()
};
r2 = r2a.max(r2b);
} else if prefer_shape {
r2 = 1_000_000_000_000_000_000;
}
} else if monotonic && prefer_shape {
r2 = 1_000_000_000_000_000_000;
}
Ok(MIN_RC_PARAM.max(r1.max(r2)))
}
fn convex_rc_param_left(
x_l: i128,
x_r: i128,
y_l: i128,
y_r: i128,
d_l: i128,
d_r: i128,
second_deriv: i128,
prefer_shape: bool,
) -> Result<i128, SolMathError> {
let r = rc_param_fit_2nd_deriv_left(x_l, x_r, y_l, y_r, d_l, d_r, second_deriv)?;
let h = x_r - x_l;
let s = if h == 0 { 0 } else { fp_div_i(y_r - y_l, h)? };
let r_min = minimum_rc_param(d_l, d_r, s, prefer_shape)?;
Ok(r.max(r_min))
}
fn convex_rc_param_right(
x_l: i128,
x_r: i128,
y_l: i128,
y_r: i128,
d_l: i128,
d_r: i128,
second_deriv: i128,
prefer_shape: bool,
) -> Result<i128, SolMathError> {
let r = rc_param_fit_2nd_deriv_right(x_l, x_r, y_l, y_r, d_l, d_r, second_deriv)?;
let h = x_r - x_l;
let s = if h == 0 { 0 } else { fp_div_i(y_r - y_l, h)? };
let r_min = minimum_rc_param(d_l, d_r, s, prefer_shape)?;
Ok(r.max(r_min))
}
#[inline(never)]
fn compute_f_lower_map(x: i128, s: i128) -> Result<(i128, i128, i128), SolMathError> {
let ax = x.abs();
let z = mul_fast(SQRT_ONE_OVER_THREE, fp_div_i(ax, s)?);
let y = mul_fast(z, z);
let s2 = mul_fast(s, s);
let phi = norm_cdf_poly(-z)?;
let pdf = norm_pdf(z)?;
let phi2 = mul_fast(phi, phi);
let exp_y_s2 = exp_fixed_i(y + s2 / 8)?;
let fp = mul_fast(TWO_PI_SCALED, mul_fast(y, mul_fast(phi2, exp_y_s2)));
let f = if ax < 100 {
0
} else {
mul_fast(
TWO_PI_OVER_SQRT_TWENTY_SEVEN,
mul_fast(ax, mul_fast(phi2, phi)),
)
};
let exp_2y_s2 = exp_fixed_i(2 * y + s2 / 4)?;
let fpp = if pdf.abs() < 100 {
0
} else {
let inner = mul_fast(8 * SQRT_THREE_SCALED, mul_fast(s, ax))
+ mul_fast(
3 * mul_fast(s2, s2 - 8 * SCALE_I) - 8 * mul_fast(x, x),
fp_div_i(phi, pdf)?,
);
mul_fast(
PI_OVER_SIX,
mul_fast(
fp_div_i(y, mul_fast(s2, s))?,
mul_fast(phi, mul_fast(inner, exp_2y_s2)),
),
)
};
Ok((f, fp, fpp))
}
#[inline(never)]
fn compute_f_upper_map(x: i128, s: i128) -> Result<(i128, i128, i128), SolMathError> {
let f = norm_cdf_poly(-s / 2)?;
if x.abs() < 100 {
return Ok((f, -SCALE_I / 2, 0));
}
let w = mul_fast(fp_div_i(x, s)?, fp_div_i(x, s)?);
let fp = -exp_fixed_i(w / 2)? / 2;
let fpp = mul_fast(
SQRT_PI_OVER_TWO,
mul_fast(exp_fixed_i(w + s * s / 8000_000_000_000)?, fp_div_i(w, s)?),
);
Ok((f, fp, fpp))
}
#[inline(never)]
fn inverse_f_lower_map(x: i128, f: i128) -> Result<i128, SolMathError> {
if f <= 0 {
return Ok(0);
}
let ax = x.abs();
let ratio = fp_div_i(f, mul_fast(TWO_PI_OVER_SQRT_TWENTY_SEVEN, ax))?;
let ln_ratio = ln_fixed_i(ratio.unsigned_abs())?;
let cbrt = exp_fixed_i(ln_ratio / 3)?;
let inv_phi = inverse_norm_cdf(cbrt)?; if inv_phi >= 0 {
return Ok(0); }
let denom = mul_fast(SQRT_THREE_SCALED, -inv_phi);
if denom <= 0 {
return Ok(0);
}
Ok(fp_div_i(ax, denom)?.abs())
}
#[inline(never)]
fn inverse_f_upper_map(f: i128) -> Result<i128, SolMathError> {
Ok(-2 * inverse_norm_cdf(f)?)
}
#[inline(never)]
fn jaeckel_normalised_iv(beta: i128, x: i128, n_householder: u8) -> Result<i128, SolMathError> {
if beta <= 0 {
return Ok(0);
}
let b_max = exp_fixed_i(x / 2)?;
if beta >= b_max {
return Err(SolMathError::NoConvergence);
}
let ax = x.abs();
let s_c = fp_sqrt(2 * ax as u128)? as i128;
let b_c = normalised_black_call(x, s_c)?;
let v_c = normalised_vega(x, s_c)?;
let mut s: i128;
let mut s_left: i128 = 0;
let mut s_right: i128 = i128::MAX / 2;
let mut use_direct_objective = true;
if beta < b_c {
let s_l = if v_c > 100 {
s_c - fp_div_i(b_c, v_c)?
} else {
s_c / 2
};
let s_l = if s_l > 0 { s_l } else { s_c / 10 };
let b_l = normalised_black_call(x, s_l)?;
if beta < b_l {
let (f_l, dfdb_l, d2fdb2_l) = compute_f_lower_map(x, s_l)?;
let r_ll = convex_rc_param_right(0, b_l, 0, f_l, SCALE_I, dfdb_l, d2fdb2_l, true)?;
let mut f = rational_cubic_interpolation(beta, 0, b_l, 0, f_l, SCALE_I, dfdb_l, r_ll)?;
if f <= 0 {
let t = if b_l > 0 {
fp_div_i(beta, b_l)?
} else {
SCALE_I / 2
};
f = mul_fast(mul_fast(f_l, t) + mul_fast(b_l, SCALE_I - t), t);
}
s = inverse_f_lower_map(x, f)?;
s_right = s_l;
use_direct_objective = false; } else {
let v_l = normalised_vega(x, s_l)?;
let inv_v_l = if v_l > 100 {
fp_div_i(SCALE_I, v_l)?
} else {
SCALE_I * 100
};
let inv_v_c = if v_c > 100 {
fp_div_i(SCALE_I, v_c)?
} else {
SCALE_I * 100
};
let r_lm = convex_rc_param_right(b_l, b_c, s_l, s_c, inv_v_l, inv_v_c, 0, false)?;
s = rational_cubic_interpolation(beta, b_l, b_c, s_l, s_c, inv_v_l, inv_v_c, r_lm)?;
s_left = s_l;
s_right = s_c;
}
} else {
let s_h = if v_c > 100 {
s_c + fp_div_i(b_max - b_c, v_c)?
} else {
s_c * 2
};
let b_h = normalised_black_call(x, s_h)?;
if beta <= b_h {
let v_h = normalised_vega(x, s_h)?;
let inv_v_c = if v_c > 100 {
fp_div_i(SCALE_I, v_c)?
} else {
SCALE_I * 100
};
let inv_v_h = if v_h > 100 {
fp_div_i(SCALE_I, v_h)?
} else {
SCALE_I * 100
};
let r_hm = convex_rc_param_left(b_c, b_h, s_c, s_h, inv_v_c, inv_v_h, 0, false)?;
s = rational_cubic_interpolation(beta, b_c, b_h, s_c, s_h, inv_v_c, inv_v_h, r_hm)?;
s_left = s_c;
s_right = s_h;
} else {
let (f_h, dfdb_h, d2fdb2_h) = compute_f_upper_map(x, s_h)?;
let r_hh =
convex_rc_param_left(b_h, b_max, f_h, 0, dfdb_h, -SCALE_I / 2, d2fdb2_h, true)?;
let mut f =
rational_cubic_interpolation(beta, b_h, b_max, f_h, 0, dfdb_h, -SCALE_I / 2, r_hh)?;
if f <= 0 {
let h = b_max - b_h;
let t = if h > 0 {
fp_div_i(beta - b_h, h)?
} else {
SCALE_I / 2
};
f = mul_fast(mul_fast(f_h, SCALE_I - t) + mul_fast(h, t) / 2, SCALE_I - t);
}
s = inverse_f_upper_map(f)?;
s_left = s_h;
use_direct_objective = beta <= b_max / 2;
}
}
s = s.max(s_left + 1).min(if s_right < i128::MAX / 2 {
s_right
} else {
s + SCALE_I
});
let mut ds_previous: i128 = 0;
let mut direction_reversal_count = 0u8;
for _ in 0..n_householder {
if s <= 0 {
break;
}
let b = normalised_black_call(x, s)?;
let bp = normalised_vega(x, s)?;
if b > beta && s < s_right {
s_right = s;
} else if b < beta && s > s_left {
s_left = s;
}
if bp <= 100 {
s = (s_left + s_right) / 2;
continue;
}
let ds;
if use_direct_objective {
let newton = fp_div_i(beta - b, bp)?;
let x_over_s2 = fp_div_i(x, mul_fast(s, s))?;
let b_halley = fp_div_i(mul_fast(x, x), mul_fast(mul_fast(s, s), s))? - s / 4;
let b_hh3 =
mul_fast(b_halley, b_halley) - 3 * mul_fast(x_over_s2, x_over_s2) - SCALE_I / 4;
let hh_fac = householder_factor(newton, b_halley, b_hh3)?;
ds = mul_fast(newton, hh_fac);
} else if beta < b_c {
if b <= 0 {
s = (s_left + s_right) / 2;
continue;
}
let ln_b = ln_fixed_i(b as u128)?;
let ln_beta = ln_fixed_i(beta as u128)?;
if ln_b == 0 || ln_beta == 0 {
s = (s_left + s_right) / 2;
continue;
}
let bpob = fp_div_i(bp, b)?;
let b_halley = fp_div_i(mul_fast(x, x), mul_fast(mul_fast(s, s), s))? - s / 4;
let newton = mul_fast(
fp_div_i(mul_fast(ln_beta - ln_b, ln_b), ln_beta)?,
fp_div_i(b, bp)?,
);
let halley = b_halley - mul_fast(bpob, SCALE_I + fp_div_i(2 * SCALE_I, ln_b)?);
let b_hh3 = mul_fast(b_halley, b_halley)
- 3 * mul_fast(fp_div_i(x, mul_fast(s, s))?, fp_div_i(x, mul_fast(s, s))?)
- SCALE_I / 4;
let hh3 = b_hh3
+ 2 * mul_fast(
mul_fast(bpob, bpob),
SCALE_I
+ mul_fast(
fp_div_i(3 * SCALE_I, ln_b)?,
SCALE_I + fp_div_i(SCALE_I, ln_b)?,
),
)
- 3 * mul_fast(
mul_fast(b_halley, bpob),
SCALE_I + fp_div_i(2 * SCALE_I, ln_b)?,
);
let hh_fac = householder_factor(newton, halley, hh3)?;
ds = mul_fast(newton, hh_fac);
} else {
let b_max_minus_b = b_max - b;
if b_max_minus_b <= 0 || bp <= 100 {
s = (s_left + s_right) / 2;
continue;
}
let g = ln_fixed_i((b_max - beta).unsigned_abs())?
- ln_fixed_i(b_max_minus_b.unsigned_abs())?;
let gp = fp_div_i(bp, b_max_minus_b)?;
let newton = -fp_div_i(g, gp)?;
let b_halley = fp_div_i(mul_fast(x, x), mul_fast(mul_fast(s, s), s))? - s / 4;
let b_hh3 = mul_fast(b_halley, b_halley)
- 3 * mul_fast(fp_div_i(x, mul_fast(s, s))?, fp_div_i(x, mul_fast(s, s))?)
- SCALE_I / 4;
let halley = b_halley + gp;
let hh3 = b_hh3 + mul_fast(gp, 2 * gp + 3 * b_halley);
let hh_fac = householder_factor(newton, halley, hh3)?;
ds = mul_fast(newton, hh_fac);
};
if ds.signum() * ds_previous.signum() < 0 {
direction_reversal_count += 1;
}
if direction_reversal_count >= 3 || (s + ds <= s_left) || (s + ds >= s_right) {
s = (s_left + s_right) / 2;
direction_reversal_count = 0;
ds_previous = 0;
continue;
}
ds_previous = ds;
s += ds.max(-s / 2);
}
if s > 0 {
Ok(s)
} else {
Err(SolMathError::NoConvergence)
}
}
#[inline(never)]
fn iterative_initial_guess(
_market_price: u128,
s: u128,
k: u128,
mp_i: i128,
s_i: i128,
k_disc: i128,
sqrt_t: i128,
ln_fk: i128,
) -> Result<i128, SolMathError> {
let abs_ln_fk = ln_fk.abs();
let beta_otm = if ln_fk > 0 {
let put_i = mp_i - s_i + k_disc;
if put_i <= 0 {
0
} else {
let sqrt_sk = fp_sqrt(fp_mul(s, k)?)? as i128;
fp_div_i(put_i, sqrt_sk)?
}
} else {
let sqrt_sk = fp_sqrt(fp_mul(s, k)?)? as i128;
fp_div_i(mp_i, sqrt_sk)?
};
let sqrt_2pi: i128 = 2_506_628_274_631;
if beta_otm < 1000 {
return Err(SolMathError::NoConvergence);
} else if abs_ln_fk < 50_000_000_000 {
Ok(mul_fast(beta_otm, sqrt_2pi))
} else if beta_otm < 10_000_000_000 && abs_ln_fk > 100_000_000_000 {
let ln_beta = ln_fixed_i(beta_otm as u128)?;
let a = -2 * ln_beta - LN_2PI;
if a > SCALE_I {
let ln_a = ln_fixed_i(a as u128)?;
let a2 = a - ln_a;
if a2 > SCALE_I / 2 {
fp_div_i(abs_ln_fk, fp_sqrt(a2 as u128)? as i128)
} else {
fp_div_i(abs_ln_fk, fp_sqrt(a as u128)? as i128)
}
} else if a > 0 {
fp_div_i(abs_ln_fk, fp_sqrt(a as u128)? as i128)
} else {
Ok(mul_fast(250_000_000_000, sqrt_t))
}
} else {
let s_c = fp_sqrt(2 * abs_ln_fk as u128)? as i128;
let exp_neg_half_x = exp_fixed_i(-abs_ln_fk / 2)?;
let exp_half_x = fp_div_i(SCALE_I, exp_neg_half_x)?;
let b_c = {
let phi_neg_sc = norm_cdf_poly(-s_c)?;
let v = exp_neg_half_x / 2 - mul_fast(phi_neg_sc, exp_half_x);
if v > 0 {
v
} else {
0
}
};
let v_c = mul_fast(INV_SQRT_2PI, exp_neg_half_x);
if v_c > 0 {
let s0 = s_c + fp_div_i(beta_otm - b_c, v_c)?;
Ok(if s0 > 0 { s0 } else { s_c })
} else {
Ok(s_c)
}
}
}
#[inline(never)]
pub fn implied_vol(
market_price: u128,
s: u128,
k: u128,
r: u128,
t: u128,
) -> Result<u128, SolMathError> {
const MAX_PRICE: u128 = 100_000 * SCALE;
if market_price > i128::MAX as u128
|| s > i128::MAX as u128
|| k > i128::MAX as u128
|| r > i128::MAX as u128
|| t > i128::MAX as u128
{
return Err(SolMathError::Overflow);
}
if s > MAX_PRICE || k > MAX_PRICE || market_price > MAX_PRICE {
return Err(SolMathError::Overflow);
}
if s == 0 || k == 0 || t == 0 || market_price == 0 {
return Err(SolMathError::DomainError);
}
if market_price < 100 {
return Err(SolMathError::NoConvergence);
}
let mp_i = market_price as i128;
let s_i = s as i128;
let k_i = k as i128;
let r_i = r as i128;
let t_i = t as i128;
let r_t = fp_mul_i(r_i, t_i)?;
let sqrt_t = fp_sqrt(t)? as i128;
if sqrt_t <= 0 {
return Err(SolMathError::DomainError);
}
let ln_sk = ln_fixed_i(fp_div(s, k)?)?;
let ln_fk = ln_sk.checked_add(r_t).ok_or(SolMathError::Overflow)?;
let discount = exp_fixed_i(-r_t)?;
let k_disc = fp_mul_i(k_i, discount)?;
let lower = s_i.saturating_sub(k_disc);
if mp_i < lower || mp_i > s_i {
return Err(SolMathError::DomainError);
}
let c_raw = fp_div_i(mp_i, s_i)?;
let (x_li, c_li) = if ln_fk > 0 {
let c_put = c_raw - SCALE_I + fp_div_i(k_disc, s_i)?;
(-ln_fk, if c_put > 0 { c_put } else { 0 })
} else {
(ln_fk, c_raw)
};
let abs_x = x_li.abs();
if abs_x < 500_000_000_000 && c_li > 0 {
#[cfg(feature = "pade-iv")]
let guess_result = rational_guess_v2(x_li, c_li);
#[cfg(not(feature = "pade-iv"))]
let guess_result = li_rational_guess(x_li, c_li);
if let Ok(w) = guess_result {
if w > 0 && w <= SCALE_I {
let solve_as_put = s_i > k_disc + k_disc / 20;
let target_i = if solve_as_put {
let put_i = mp_i - s_i + k_disc;
if put_i > 0 {
put_i
} else {
1
}
} else {
mp_i
};
let mut x_lo = (mul_fast(1_000_000_000, sqrt_t)).max(1) as u128;
let mut x_hi = (mul_fast(5_000_000_000_000, sqrt_t)).max(x_lo as i128 + 1) as u128;
let mut x_u = (w as u128).clamp(x_lo, x_hi);
for iter in 0..4u8 {
let x_i = x_u as i128;
if x_i <= 1 {
break;
}
let (price_i, vega_x, volga_x) =
iv_price_and_greeks(x_i, ln_fk, s_i, k_disc, solve_as_put)?;
let f = price_i - target_i;
let tol = if iter < 3 { 100 } else { 1000 };
if f.abs() <= tol {
return Ok(fp_div_i(x_i, sqrt_t)? as u128);
}
if f > 0 {
if x_u < x_hi {
x_hi = x_u;
}
} else {
if x_u > x_lo {
x_lo = x_u;
}
}
x_u = halley_step_bracketed(x_u, f, vega_x, volga_x, x_lo, x_hi)?;
}
let x_i = x_u as i128;
if x_i > 0 {
let (price_i, _, _) =
iv_price_and_greeks(x_i, ln_fk, s_i, k_disc, solve_as_put)?;
if (price_i - target_i).abs() <= 1000 {
return Ok(fp_div_i(x_i, sqrt_t)? as u128);
}
}
}
}
}
{
let solve_as_put = s_i > k_disc + k_disc / 20;
let target_i = if solve_as_put {
let put_i = mp_i - s_i + k_disc;
if put_i > 0 {
put_i
} else {
1
}
} else {
mp_i
};
let x_vol = iterative_initial_guess(market_price, s, k, mp_i, s_i, k_disc, sqrt_t, ln_fk)?;
let x_min = (mul_fast(1_000_000_000, sqrt_t)).max(1) as u128;
let x_max = (mul_fast(10_000_000_000_000, sqrt_t)).max(x_min as i128 + 1) as u128;
let mut x_u = (x_vol as u128).clamp(x_min, x_max);
let mut x_lo = x_min;
let mut x_hi = x_max;
for iter in 0..5u8 {
let x_i = x_u as i128;
if x_i <= 1 {
break;
}
let (price_i, vega_x, volga_x) =
iv_price_and_greeks(x_i, ln_fk, s_i, k_disc, solve_as_put)?;
let f = price_i - target_i;
if f.abs() <= 1 {
return Ok(fp_div_i(x_i, sqrt_t)? as u128);
}
if iter >= 3 && f.abs() <= 100 {
return Ok(fp_div_i(x_i, sqrt_t)? as u128);
}
if f > 0 {
if x_u < x_hi {
x_hi = x_u;
}
} else {
if x_u > x_lo {
x_lo = x_u;
}
}
x_u = halley_step_bracketed(x_u, f, vega_x, volga_x, x_lo, x_hi)?;
}
let x_i = x_u as i128;
if x_i > 0 {
let (price_i, _, _) = iv_price_and_greeks(x_i, ln_fk, s_i, k_disc, solve_as_put)?;
if (price_i - target_i).abs() <= 2000 {
return Ok(fp_div_i(x_i, sqrt_t)? as u128);
}
}
}
let exp_rt = if discount > 0 {
fp_div_i(SCALE_I, discount)?
} else {
exp_fixed_i(r_t)?
};
let sqrt_sk = fp_sqrt(fp_mul(s, k)?)? as i128;
let exp_half_rt = exp_fixed_i(r_t / 2)?;
let sqrt_fk = mul_fast(sqrt_sk, exp_half_rt);
if sqrt_fk <= 0 {
return Err(SolMathError::DomainError);
}
let x = ln_fk;
let (x_otm, beta) = if x > 0 {
let undiscounted_call = mul_fast(mp_i, exp_rt);
let forward = mul_fast(s_i, exp_rt);
let intrinsic = forward - k_i;
let put_undiscounted = undiscounted_call - (if intrinsic > 0 { intrinsic } else { 0 });
let put_undiscounted = if put_undiscounted > 0 {
put_undiscounted
} else {
0
};
(-x, fp_div_i(put_undiscounted, sqrt_fk)?)
} else {
let undiscounted_call = mul_fast(mp_i, exp_rt);
(x, fp_div_i(undiscounted_call, sqrt_fk)?)
};
let s_norm = jaeckel_normalised_iv(beta, x_otm, 2)?;
if s_norm <= 0 {
return Err(SolMathError::NoConvergence);
}
let sigma = fp_div_i(s_norm, sqrt_t)?;
if sigma <= 0 {
return Err(SolMathError::NoConvergence);
}
let sigma_u = sigma as u128;
if let Ok((check_call, _)) = black_scholes_price(s, k, r, sigma_u, t) {
let price_err = if check_call > market_price {
check_call - market_price
} else {
market_price - check_call
};
if price_err <= 1000 {
return Ok(sigma_u);
}
}
Err(SolMathError::NoConvergence)
}
#[inline(never)]
#[allow(dead_code)]
pub(crate) fn implied_vol_jaeckel(
market_price: u128,
s: u128,
k: u128,
r: u128,
t: u128,
) -> Result<u128, SolMathError> {
let s_i = s as i128;
let k_i = k as i128;
let r_i = r as i128;
let t_i = t as i128;
let r_t = fp_mul_i(r_i, t_i)?;
let exp_rt = exp_fixed_i(r_t)?;
let sqrt_t = fp_sqrt(t)? as i128;
if sqrt_t <= 0 {
return Err(SolMathError::DomainError);
}
let sqrt_sk = fp_sqrt(fp_mul(s, k)?)? as i128;
let exp_half_rt = exp_fixed_i(r_t / 2)?;
let sqrt_fk = mul_fast(sqrt_sk, exp_half_rt);
if sqrt_fk <= 0 {
return Err(SolMathError::DomainError);
}
let ln_sk = ln_fixed_i(fp_div(s, k)?)?;
let x = ln_sk + r_t;
let mp_i = market_price as i128;
let (x_otm, beta) = if x > 0 {
let undiscounted_call = mul_fast(mp_i, exp_rt);
let forward = mul_fast(s_i, exp_rt);
let intrinsic = forward - k_i;
let put_undiscounted = undiscounted_call - (if intrinsic > 0 { intrinsic } else { 0 });
let put_undiscounted = if put_undiscounted > 0 {
put_undiscounted
} else {
0
};
(-x, fp_div_i(put_undiscounted, sqrt_fk)?)
} else {
let undiscounted_call = mul_fast(mp_i, exp_rt);
(x, fp_div_i(undiscounted_call, sqrt_fk)?)
};
let s_norm = jaeckel_normalised_iv(beta, x_otm, 2)?;
if s_norm <= 0 {
return Err(SolMathError::NoConvergence);
}
let sigma = fp_div_i(s_norm, sqrt_t)?;
if sigma <= 0 {
return Err(SolMathError::NoConvergence);
}
let sigma_u = sigma as u128;
if let Ok((check_call, _)) = black_scholes_price(s, k, r, sigma_u, t) {
let price_err = if check_call > market_price {
check_call - market_price
} else {
market_price - check_call
};
if price_err <= 1000 {
return Ok(sigma_u);
}
}
Err(SolMathError::NoConvergence)
}
#[cfg(test)]
mod adversarial_tests {
use super::*;
#[test]
fn advertised_cap_cannot_reach_unchecked_multiply_overflow() {
let s = 100_000_000 * SCALE;
let call = black_scholes_price(s, s, 0, 200_000_000_000, SCALE)
.unwrap()
.0;
assert_eq!(
implied_vol(call, s, s, 0, SCALE),
Err(SolMathError::Overflow)
);
}
}