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
}
}
#[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 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);
}
}