use crate::errors::QlResult;
use crate::require;
use crate::time::date::Date;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum ExerciseType {
American,
Bermudan,
European,
}
pub trait Exercise {
fn exercise_type(&self) -> ExerciseType;
fn dates(&self) -> &[Date];
fn last_date(&self) -> Date {
*self
.dates()
.last()
.expect("no exercise date given (implementors guarantee at least one)")
}
fn payoff_at_expiry(&self) -> bool {
false
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct EuropeanExercise {
dates: [Date; 1],
}
impl EuropeanExercise {
pub fn new(date: Date) -> EuropeanExercise {
EuropeanExercise { dates: [date] }
}
}
impl Exercise for EuropeanExercise {
fn exercise_type(&self) -> ExerciseType {
ExerciseType::European
}
fn dates(&self) -> &[Date] {
&self.dates
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct AmericanExercise {
dates: [Date; 2],
payoff_at_expiry: bool,
}
impl AmericanExercise {
pub fn new(earliest: Date, latest: Date, payoff_at_expiry: bool) -> QlResult<AmericanExercise> {
require!(earliest <= latest, "earliest > latest exercise date");
Ok(AmericanExercise {
dates: [earliest, latest],
payoff_at_expiry,
})
}
pub fn over(earliest: Date, latest: Date) -> QlResult<AmericanExercise> {
AmericanExercise::new(earliest, latest, false)
}
}
impl Exercise for AmericanExercise {
fn exercise_type(&self) -> ExerciseType {
ExerciseType::American
}
fn dates(&self) -> &[Date] {
&self.dates
}
fn payoff_at_expiry(&self) -> bool {
self.payoff_at_expiry
}
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct BermudanExercise {
dates: Vec<Date>,
payoff_at_expiry: bool,
}
impl BermudanExercise {
pub fn new(dates: Vec<Date>, payoff_at_expiry: bool) -> QlResult<BermudanExercise> {
require!(!dates.is_empty(), "no exercise date given");
let mut dates = dates;
dates.sort_unstable();
Ok(BermudanExercise {
dates,
payoff_at_expiry,
})
}
}
impl Exercise for BermudanExercise {
fn exercise_type(&self) -> ExerciseType {
ExerciseType::Bermudan
}
fn dates(&self) -> &[Date] {
&self.dates
}
fn payoff_at_expiry(&self) -> bool {
self.payoff_at_expiry
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::time::date::Month;
#[test]
fn european_exercise_holds_the_single_expiry() {
let expiry = Date::new(17, Month::May, 2027);
let exercise = EuropeanExercise::new(expiry);
assert_eq!(exercise.exercise_type(), ExerciseType::European);
assert_eq!(exercise.dates(), &[expiry]);
assert_eq!(exercise.last_date(), expiry);
}
#[test]
fn american_exercise_holds_the_window() {
let earliest = Date::new(17, Month::May, 1998);
let latest = Date::new(17, Month::May, 1999);
let exercise = AmericanExercise::over(earliest, latest).unwrap();
assert_eq!(exercise.exercise_type(), ExerciseType::American);
assert_eq!(exercise.dates(), &[earliest, latest]);
assert_eq!(exercise.last_date(), latest);
assert!(!exercise.payoff_at_expiry(), "the C++ default is false");
}
#[test]
fn an_inverted_window_is_rejected() {
let earlier = Date::new(17, Month::May, 1998);
let later = Date::new(17, Month::May, 1999);
assert!(AmericanExercise::over(earlier, earlier).is_ok());
match AmericanExercise::over(later, earlier) {
Err(e) => assert_eq!(e.message(), "earliest > latest exercise date"),
Ok(_) => panic!("earliest after latest must be rejected"),
}
}
#[test]
fn payoff_at_expiry_round_trips_and_defaults_to_false() {
let earliest = Date::new(17, Month::May, 1998);
let latest = Date::new(17, Month::May, 1999);
assert!(
AmericanExercise::new(earliest, latest, true)
.unwrap()
.payoff_at_expiry()
);
assert!(!EuropeanExercise::new(latest).payoff_at_expiry());
}
#[test]
fn bermudan_exercise_sorts_its_dates() {
let first = Date::new(17, Month::May, 1998);
let second = Date::new(17, Month::November, 1998);
let third = Date::new(17, Month::May, 1999);
let exercise = BermudanExercise::new(vec![third, first, second], false).unwrap();
assert_eq!(exercise.exercise_type(), ExerciseType::Bermudan);
assert_eq!(exercise.dates(), &[first, second, third]);
assert_eq!(exercise.last_date(), third);
assert!(!exercise.payoff_at_expiry(), "the C++ default is false");
}
#[test]
fn bermudan_exercise_keeps_repeated_dates() {
let date = Date::new(17, Month::May, 1999);
let exercise = BermudanExercise::new(vec![date, date], false).unwrap();
assert_eq!(exercise.dates(), &[date, date]);
}
#[test]
fn an_empty_bermudan_date_set_is_rejected() {
match BermudanExercise::new(Vec::new(), false) {
Err(e) => assert_eq!(e.message(), "no exercise date given"),
Ok(_) => panic!("an empty date set must be rejected"),
}
}
#[test]
fn a_bermudan_exercise_carries_payoff_at_expiry() {
let date = Date::new(17, Month::May, 1999);
assert!(
BermudanExercise::new(vec![date], true)
.unwrap()
.payoff_at_expiry()
);
}
#[test]
fn usable_as_trait_object() {
let expiry = Date::new(31, Month::December, 2030);
let exercise: &dyn Exercise = &EuropeanExercise::new(expiry);
assert_eq!(exercise.last_date(), expiry);
assert_eq!(exercise.dates().len(), 1);
}
}