use std::any::Any;
use crate::option::OptionType;
use crate::payoff::Payoff;
use crate::types::Real;
pub trait TypePayoff: Payoff {
fn option_type(&self) -> OptionType;
}
pub trait StrikedTypePayoff: TypePayoff + Any {
fn strike(&self) -> Real;
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct PlainVanillaPayoff {
option_type: OptionType,
strike: Real,
}
impl PlainVanillaPayoff {
pub fn new(option_type: OptionType, strike: Real) -> PlainVanillaPayoff {
PlainVanillaPayoff {
option_type,
strike,
}
}
}
impl Payoff for PlainVanillaPayoff {
fn name(&self) -> String {
"Vanilla".to_string()
}
fn description(&self) -> String {
format!(
"{} {}, {} strike",
self.name(),
self.option_type,
self.strike
)
}
fn value(&self, price: Real) -> Real {
let intrinsic = match self.option_type {
OptionType::Call => price - self.strike,
OptionType::Put => self.strike - price,
};
if intrinsic < 0.0 { 0.0 } else { intrinsic }
}
}
impl TypePayoff for PlainVanillaPayoff {
fn option_type(&self) -> OptionType {
self.option_type
}
}
impl StrikedTypePayoff for PlainVanillaPayoff {
fn strike(&self) -> Real {
self.strike
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn call_pays_excess_over_strike() {
let payoff = PlainVanillaPayoff::new(OptionType::Call, 100.0);
assert_eq!(payoff.value(110.0), 10.0);
assert_eq!(payoff.value(100.0), 0.0);
assert_eq!(payoff.value(90.0), 0.0);
}
#[test]
fn put_pays_shortfall_under_strike() {
let payoff = PlainVanillaPayoff::new(OptionType::Put, 100.0);
assert_eq!(payoff.value(90.0), 10.0);
assert_eq!(payoff.value(100.0), 0.0);
assert_eq!(payoff.value(110.0), 0.0);
}
#[test]
fn nan_price_propagates_like_cpp_std_max() {
let call = PlainVanillaPayoff::new(OptionType::Call, 100.0);
assert!(call.value(Real::NAN).is_nan());
let put = PlainVanillaPayoff::new(OptionType::Put, 100.0);
assert!(put.value(Real::NAN).is_nan());
}
#[test]
fn accessors_expose_type_and_strike() {
let payoff = PlainVanillaPayoff::new(OptionType::Put, 32.5);
assert_eq!(payoff.option_type(), OptionType::Put);
assert_eq!(payoff.strike(), 32.5);
}
#[test]
fn name_and_description_match_quantlib() {
let call = PlainVanillaPayoff::new(OptionType::Call, 100.0);
assert_eq!(call.name(), "Vanilla");
assert_eq!(call.description(), "Vanilla Call, 100 strike");
let put = PlainVanillaPayoff::new(OptionType::Put, 32.5);
assert_eq!(put.description(), "Vanilla Put, 32.5 strike");
}
#[test]
fn usable_as_trait_object() {
let payoff = PlainVanillaPayoff::new(OptionType::Call, 100.0);
let dynamic: &dyn StrikedTypePayoff = &payoff;
assert_eq!(dynamic.value(107.0), 7.0);
assert_eq!(dynamic.option_type(), OptionType::Call);
assert_eq!(dynamic.strike(), 100.0);
}
}