use crate::Options;
use crate::error::greeks::GreeksError;
use crate::model::decimal::{d_add, d_div, d_mul, d_sub};
use crate::pricing::unified::{Priceable, PricingEngine};
use positive::Positive;
use rust_decimal::Decimal;
use rust_decimal_macros::dec;
const H: Decimal = dec!(0.01);
pub fn numerical_delta(option: &Options) -> Result<Decimal, GreeksError> {
let mut opt_plus = option.clone();
opt_plus.underlying_price =
Positive::new_decimal((option.underlying_price.to_dec() + H).abs())?;
let mut opt_minus = option.clone();
opt_minus.underlying_price =
Positive::new_decimal((option.underlying_price.to_dec() - H).abs())?;
let p_plus = opt_plus.price(&PricingEngine::ClosedFormBS)?;
let p_minus = opt_minus.price(&PricingEngine::ClosedFormBS)?;
let diff = d_sub(
p_plus.to_dec(),
p_minus.to_dec(),
"greeks::numerical::delta::diff",
)?;
Ok(d_div(
diff,
dec!(2.0) * H,
"greeks::numerical::delta::scaled",
)?)
}
pub fn numerical_gamma(option: &Options) -> Result<Decimal, GreeksError> {
let mut opt_plus = option.clone();
opt_plus.underlying_price =
Positive::new_decimal((option.underlying_price.to_dec() + H).abs())?;
let mut opt_minus = option.clone();
opt_minus.underlying_price =
Positive::new_decimal((option.underlying_price.to_dec() - H).abs())?;
let p_plus = opt_plus.price(&PricingEngine::ClosedFormBS)?;
let p_minus = opt_minus.price(&PricingEngine::ClosedFormBS)?;
let p = option.price(&PricingEngine::ClosedFormBS)?;
let two_p = d_mul(dec!(2.0), p.to_dec(), "greeks::numerical::gamma::two_p")?;
let step = d_sub(p_plus.to_dec(), two_p, "greeks::numerical::gamma::step")?;
let numer = d_add(step, p_minus.to_dec(), "greeks::numerical::gamma::numer")?;
let h_squared = d_mul(H, H, "greeks::numerical::gamma::h_squared")?;
Ok(d_div(numer, h_squared, "greeks::numerical::gamma::scaled")?)
}
pub fn numerical_vega(option: &Options) -> Result<Decimal, GreeksError> {
let mut opt_plus = option.clone();
opt_plus.implied_volatility =
Positive::new_decimal((option.implied_volatility.to_dec() + H).abs())?;
let mut opt_minus = option.clone();
opt_minus.implied_volatility =
Positive::new_decimal((option.implied_volatility.to_dec() - H).abs())?;
let p_plus = opt_plus.price(&PricingEngine::ClosedFormBS)?;
let p_minus = opt_minus.price(&PricingEngine::ClosedFormBS)?;
let diff = d_sub(
p_plus.to_dec(),
p_minus.to_dec(),
"greeks::numerical::vega::diff",
)?;
Ok(d_div(
diff,
dec!(2.0) * H,
"greeks::numerical::vega::scaled",
)?)
}
pub fn numerical_theta(option: &Options) -> Result<Decimal, GreeksError> {
let t = option.expiration_date.get_years()?;
if t < H {
return Ok(Decimal::ZERO);
}
let _opt_plus = option.clone();
Err(GreeksError::CalculationError(crate::error::greeks::CalculationErrorKind::ThetaError { reason: "Numerical theta not yet implemented for exotics due to ExpirationDate mutation complexity".to_string() }))
}
pub fn numerical_rho(option: &Options) -> Result<Decimal, GreeksError> {
let mut opt_plus = option.clone();
opt_plus.risk_free_rate += H;
let mut opt_minus = option.clone();
opt_minus.risk_free_rate -= H;
let p_plus = opt_plus.price(&PricingEngine::ClosedFormBS)?;
let p_minus = opt_minus.price(&PricingEngine::ClosedFormBS)?;
let diff = d_sub(
p_plus.to_dec(),
p_minus.to_dec(),
"greeks::numerical::rho::diff",
)?;
Ok(d_div(
diff,
dec!(2.0) * H,
"greeks::numerical::rho::scaled",
)?)
}