use crate::error::PricingError;
use crate::model::types::{OptionStyle, Side};
use num_traits::ToPrimitive;
use positive::Positive;
use rust_decimal::Decimal;
use tracing::{trace, warn};
pub trait Payoff {
fn payoff(&self, info: &PayoffInfo) -> f64;
}
#[derive(Debug, Clone)]
pub struct PayoffInfo {
pub spot: Positive,
pub strike: Positive,
pub style: OptionStyle,
pub side: Side,
pub spot_prices: Option<Vec<f64>>, pub spot_min: Option<f64>, pub spot_max: Option<f64>, }
impl Default for PayoffInfo {
fn default() -> Self {
PayoffInfo {
spot: Positive::ZERO,
strike: Positive::ZERO,
style: OptionStyle::Call,
side: Side::Long,
spot_prices: None,
spot_min: None,
spot_max: None,
}
}
}
impl PayoffInfo {
#[inline]
#[must_use]
pub fn spot_prices_len(&self) -> Option<usize> {
self.spot_prices.as_ref().map(|vec| vec.len())
}
}
#[inline]
pub(crate) fn standard_payoff(info: &PayoffInfo) -> f64 {
trace!("standard_payoff - spot: {}", info.spot);
trace!("standard_payoff - info.strike: {}", info.strike);
trace!(
"standard_payoff - (info.spot - info.strike): {}",
info.spot - info.strike
);
let spot: Decimal = info.spot.into();
let strike: Decimal = info.strike.into();
let payoff = match info.style {
OptionStyle::Call => (spot - strike)
.max(Decimal::ZERO)
.to_f64()
.unwrap_or_else(|| {
warn!(
spot = %spot,
strike = %strike,
"standard_payoff(Call): to_f64 returned None; defaulting to 0.0"
);
0.0
}),
OptionStyle::Put => (strike - spot)
.max(Decimal::ZERO)
.to_f64()
.unwrap_or_else(|| {
warn!(
spot = %spot,
strike = %strike,
"standard_payoff(Put): to_f64 returned None; defaulting to 0.0"
);
0.0
}),
};
match info.side {
Side::Long => payoff,
Side::Short => -payoff,
}
}
pub trait Profit {
fn calculate_profit_at(&self, price: &Positive) -> Result<Decimal, PricingError>;
fn get_point_at_price(&self, _price: &Positive) -> Result<(Decimal, Decimal), PricingError> {
let profit = self.calculate_profit_at(_price)?;
let price: Decimal = _price.into();
let point = (price, profit);
trace!("get_point_at_price - point: {:?}", point);
Ok(point)
}
}
#[cfg(test)]
mod tests_standard_payoff {
use super::*;
use crate::model::types::OptionType;
use positive::pos_or_panic;
#[test]
fn test_call_option_in_the_money() {
let option_type = OptionType::European;
let info = PayoffInfo {
spot: pos_or_panic!(110.0),
strike: Positive::HUNDRED,
style: OptionStyle::Call,
side: Side::Long,
spot_prices: None,
spot_min: None,
spot_max: None,
};
assert_eq!(option_type.payoff(&info), 10.0);
}
#[test]
fn test_call_option_at_the_money() {
let option_type = OptionType::European;
let info = PayoffInfo {
spot: Positive::HUNDRED,
strike: Positive::HUNDRED,
style: OptionStyle::Call,
side: Side::Long,
spot_prices: None,
spot_min: None,
spot_max: None,
};
assert_eq!(option_type.payoff(&info), 0.0);
}
#[test]
fn test_call_option_out_of_the_money() {
let option_type = OptionType::European;
let info = PayoffInfo {
spot: pos_or_panic!(90.0),
strike: Positive::HUNDRED,
style: OptionStyle::Call,
side: Side::Long,
spot_prices: None,
spot_min: None,
spot_max: None,
};
assert_eq!(option_type.payoff(&info), 0.0);
}
#[test]
fn test_put_option_in_the_money() {
let option_type = OptionType::European;
let info = PayoffInfo {
spot: pos_or_panic!(90.0),
strike: Positive::HUNDRED,
style: OptionStyle::Put,
side: Side::Long,
spot_prices: None,
spot_min: None,
spot_max: None,
};
assert_eq!(option_type.payoff(&info), 10.0);
}
#[test]
fn test_put_option_at_the_money() {
let option_type = OptionType::European;
let info = PayoffInfo {
spot: Positive::HUNDRED,
strike: Positive::HUNDRED,
style: OptionStyle::Put,
side: Side::Long,
spot_prices: None,
spot_min: None,
spot_max: None,
};
assert_eq!(option_type.payoff(&info), 0.0);
}
#[test]
fn test_put_option_out_of_the_money() {
let option_type = OptionType::European;
let info = PayoffInfo {
spot: pos_or_panic!(110.0),
strike: Positive::HUNDRED,
style: OptionStyle::Put,
side: Side::Long,
spot_prices: None,
spot_min: None,
spot_max: None,
};
assert_eq!(option_type.payoff(&info), 0.0);
}
}