use crate::Options;
use crate::error::greeks::GreeksError;
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)
.map_err(|e| GreeksError::StdError(e.to_string()))?;
let p_minus = opt_minus
.price(&PricingEngine::ClosedFormBS)
.map_err(|e| GreeksError::StdError(e.to_string()))?;
Ok((p_plus.to_dec() - p_minus.to_dec()) / (dec!(2.0) * H))
}
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)
.map_err(|e| GreeksError::StdError(e.to_string()))?;
let p_minus = opt_minus
.price(&PricingEngine::ClosedFormBS)
.map_err(|e| GreeksError::StdError(e.to_string()))?;
let p = option
.price(&PricingEngine::ClosedFormBS)
.map_err(|e| GreeksError::StdError(e.to_string()))?;
Ok((p_plus.to_dec() - dec!(2.0) * p.to_dec() + p_minus.to_dec()) / (H * H))
}
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)
.map_err(|e| GreeksError::StdError(e.to_string()))?;
let p_minus = opt_minus
.price(&PricingEngine::ClosedFormBS)
.map_err(|e| GreeksError::StdError(e.to_string()))?;
Ok((p_plus.to_dec() - p_minus.to_dec()) / (dec!(2.0) * H))
}
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)
.map_err(|e| GreeksError::StdError(e.to_string()))?;
let p_minus = opt_minus
.price(&PricingEngine::ClosedFormBS)
.map_err(|e| GreeksError::StdError(e.to_string()))?;
Ok((p_plus.to_dec() - p_minus.to_dec()) / (dec!(2.0) * H))
}