use crate::arithmetic::{fp_div_i, fp_mul_i, isqrt_u128};
use crate::constants::*;
use crate::error::SolMathError;
use crate::hp::{
black_scholes_price_hp, downscale_hp_to_std, exp_fixed_hp, fp_div_hp_safe, fp_mul_hp_i,
ln_fixed_hp, norm_cdf_poly_hp, upscale_std_to_hp,
};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BarrierType {
DownAndOut,
DownAndIn,
UpAndOut,
UpAndIn,
}
#[derive(Debug, Clone, Copy)]
pub struct BarrierResult {
pub price: u128,
pub vanilla: u128,
}
struct HaugIntermediates {
s_hp: i128,
k_disc_hp: i128,
x1_hp: i128,
y1_hp: i128,
d1_hp: i128,
y_hp: i128,
sigma_sqrt_t_hp: i128,
discount_hp: i128,
pow_2l_hp: i128,
pow_2lm2_hp: i128,
phi: i128,
}
#[inline(never)]
fn compute_intermediates(
s: u128,
k: u128,
h: u128,
r: u128,
sigma: u128,
t: u128,
is_call: bool,
) -> Result<HaugIntermediates, SolMathError> {
let s_hp = upscale_std_to_hp(s)?;
let k_hp = upscale_std_to_hp(k)?;
let h_hp = upscale_std_to_hp(h)?;
let r_hp = upscale_std_to_hp(r)?;
let sigma_hp = upscale_std_to_hp(sigma)?;
let t_hp = upscale_std_to_hp(t)?;
let sqrt_t_hp = isqrt_u128(
(t_hp as u128)
.checked_mul(SCALE_HP_U)
.ok_or(SolMathError::Overflow)?,
) as i128;
let sigma_sqrt_t_hp = fp_mul_hp_i(sigma_hp, sqrt_t_hp)?;
let r_t_hp = fp_mul_hp_i(r_hp, t_hp)?;
let discount_hp = exp_fixed_hp(-r_t_hp)?;
let k_disc_hp = fp_mul_hp_i(k_hp, discount_hp)?;
let sigma_sq_hp = fp_mul_hp_i(sigma_hp, sigma_hp)?;
let drift_rate_hp = r_hp
.checked_add(sigma_sq_hp / 2)
.ok_or(SolMathError::Overflow)?;
let drift_hp = fp_mul_hp_i(drift_rate_hp, t_hp)?;
let lambda_sst = if sigma_sqrt_t_hp > 0 {
fp_div_hp_safe(drift_hp, sigma_sqrt_t_hp)?
} else {
0
};
let ln_sk = ln_fixed_hp(fp_div_hp_safe(s_hp, k_hp)?)?;
let ln_sh = ln_fixed_hp(fp_div_hp_safe(s_hp, h_hp)?)?;
let ln_hk = ln_fixed_hp(fp_div_hp_safe(h_hp, k_hp)?)?;
let mk = |log_val: i128| -> Result<i128, SolMathError> {
if sigma_sqrt_t_hp > 0 {
fp_div_hp_safe(log_val, sigma_sqrt_t_hp)?
.checked_add(lambda_sst)
.ok_or(SolMathError::Overflow)
} else {
Ok(0)
}
};
let d1_hp = mk(ln_sk)?;
let x1_hp = mk(ln_sh)?;
let y1_hp = mk(-ln_sh)?;
let y_hp = mk(ln_sh
.checked_neg()
.and_then(|v| v.checked_add(ln_hk))
.ok_or(SolMathError::Overflow)?)?;
let sigma_sq_std = fp_mul_i(sigma as i128, sigma as i128)?;
let lambda_num = (r as i128)
.checked_add(sigma_sq_std / 2)
.and_then(|v| v.checked_mul(2))
.ok_or(SolMathError::Overflow)?;
let two_lambda_std = fp_div_i(lambda_num, sigma_sq_std)?;
let two_lambda_hp = upscale_std_to_hp(two_lambda_std as u128)?;
let two_lambda_m2_hp = two_lambda_hp - 2 * SCALE_HP;
let ln_h_over_s_hp = -ln_sh;
let pow_2l_hp = if ln_h_over_s_hp == 0 {
SCALE_HP
} else {
exp_fixed_hp(fp_mul_hp_i(two_lambda_hp, ln_h_over_s_hp)?)?
};
let pow_2lm2_hp = if ln_h_over_s_hp == 0 {
SCALE_HP
} else {
exp_fixed_hp(fp_mul_hp_i(two_lambda_m2_hp, ln_h_over_s_hp)?)?
};
Ok(HaugIntermediates {
s_hp,
k_disc_hp,
x1_hp,
y1_hp,
d1_hp,
y_hp,
sigma_sqrt_t_hp,
discount_hp,
pow_2l_hp,
pow_2lm2_hp,
phi: if is_call { 1 } else { -1 },
})
}
#[inline(never)]
fn block_hp(phi: i128, z: i128, s_eff: i128, k_eff: i128, sst: i128) -> Result<i128, SolMathError> {
Ok(phi
* (fp_mul_hp_i(s_eff, norm_cdf_poly_hp(phi * z)?)?
- fp_mul_hp_i(k_eff, norm_cdf_poly_hp(phi * (z - sst))?)?))
}
#[inline(never)]
fn all_blocks(im: &HaugIntermediates) -> Result<(i128, i128, i128, i128), SolMathError> {
let s_pow = fp_mul_hp_i(im.s_hp, im.pow_2l_hp)?;
let k_pow = fp_mul_hp_i(im.k_disc_hp, im.pow_2lm2_hp)?;
let a = block_hp(im.phi, im.x1_hp, im.s_hp, im.k_disc_hp, im.sigma_sqrt_t_hp)?;
let b = block_hp(im.phi, im.d1_hp, im.s_hp, im.k_disc_hp, im.sigma_sqrt_t_hp)?;
let c = block_hp(im.phi, im.y1_hp, s_pow, k_pow, im.sigma_sqrt_t_hp)?;
let d = block_hp(im.phi, im.y_hp, s_pow, k_pow, im.sigma_sqrt_t_hp)?;
Ok((a, b, c, d))
}
pub fn barrier_option(
s: u128,
k: u128,
h: u128,
r: u128,
sigma: u128,
t: u128,
is_call: bool,
barrier_type: BarrierType,
) -> Result<BarrierResult, SolMathError> {
if s == 0 || k == 0 || sigma == 0 || t == 0 || h == 0 {
return Err(SolMathError::DomainError);
}
let is_down = matches!(
barrier_type,
BarrierType::DownAndOut | BarrierType::DownAndIn
);
let is_out = matches!(
barrier_type,
BarrierType::DownAndOut | BarrierType::UpAndOut
);
if (is_down && s <= h) || (!is_down && s >= h) {
let (call, put) = black_scholes_price_hp(s, k, r, sigma, t)?;
let vanilla = if is_call { call } else { put };
return Ok(BarrierResult {
price: if is_out { 0 } else { vanilla },
vanilla,
});
}
if is_call && !is_down && k >= h {
let (call, _) = black_scholes_price_hp(s, k, r, sigma, t)?;
return Ok(BarrierResult {
price: if is_out { 0 } else { call },
vanilla: call,
});
}
if !is_call && is_down && k <= h {
let (_, put) = black_scholes_price_hp(s, k, r, sigma, t)?;
return Ok(BarrierResult {
price: if is_out { 0 } else { put },
vanilla: put,
});
}
let im = compute_intermediates(s, k, h, r, sigma, t, is_call)?;
let (a, b, c, d) = all_blocks(&im)?;
let vanilla_hp = b;
let out_hp = if !is_down && !is_call && k > h {
let im_h = compute_intermediates(s, h, h, r, sigma, t, false)?;
let (_, b_h, _, d_h) = all_blocks(&im_h)?;
let p_uo_h_hp = b_h - d_h;
let digital_hp = fp_mul_hp_i(
im.discount_hp,
norm_cdf_poly_hp(im.sigma_sqrt_t_hp - im.x1_hp)?
- fp_mul_hp_i(
im.pow_2lm2_hp,
norm_cdf_poly_hp(im.sigma_sqrt_t_hp - im.y1_hp)?,
)?,
)?;
p_uo_h_hp + fp_mul_hp_i(upscale_std_to_hp(k - h)?, digital_hp)?
} else if is_down && is_call && k < h {
a - c
} else if (is_down && !is_call && k > h) || (!is_down && is_call) {
b - a + c - d
} else {
b - d
};
let vanilla = downscale_hp_to_std(vanilla_hp);
let out_price = core::cmp::min(downscale_hp_to_std(out_hp), vanilla);
let price = if is_out {
out_price
} else {
vanilla - out_price
};
Ok(BarrierResult { price, vanilla })
}
pub fn barrier_option_with_state(
s: u128,
k: u128,
h: u128,
r: u128,
sigma: u128,
t: u128,
is_call: bool,
barrier_type: BarrierType,
barrier_was_breached: bool,
) -> Result<BarrierResult, SolMathError> {
if !barrier_was_breached {
return barrier_option(s, k, h, r, sigma, t, is_call, barrier_type);
}
if s == 0 || k == 0 || h == 0 || sigma == 0 || t == 0 {
return Err(SolMathError::DomainError);
}
let (call, put) = black_scholes_price_hp(s, k, r, sigma, t)?;
let vanilla = if is_call { call } else { put };
let knocked_out = matches!(
barrier_type,
BarrierType::DownAndOut | BarrierType::UpAndOut
);
Ok(BarrierResult {
price: if knocked_out { 0 } else { vanilla },
vanilla,
})
}
#[cfg(test)]
mod path_state_tests {
use super::*;
#[test]
fn historical_breach_overrides_current_safe_spot() {
let out = barrier_option_with_state(
100 * SCALE,
100 * SCALE,
90 * SCALE,
50_000_000_000,
200_000_000_000,
SCALE,
true,
BarrierType::DownAndOut,
true,
)
.unwrap();
let knocked_in = barrier_option_with_state(
100 * SCALE,
100 * SCALE,
90 * SCALE,
50_000_000_000,
200_000_000_000,
SCALE,
true,
BarrierType::DownAndIn,
true,
)
.unwrap();
assert_eq!(out.price, 0);
assert_eq!(knocked_in.price, knocked_in.vanilla);
}
}