use crate::core::trade::PutOrCall;
use crate::core::utils::ContractStyle;
use crate::equity::utils::{Payoff, PayoffType};
#[derive(Debug)]
pub struct AutocallablePayoff {
pub exercise_style: ContractStyle,
pub autocall_barrier: f64,
pub protection_barrier: f64,
pub coupon: f64,
pub observations: usize,
pub notional: f64,
pub initial_fixing: f64,
}
impl AutocallablePayoff {
pub fn path_value(&self, path: &[f64], obs_idx: &[usize], dfs: &[f64]) -> f64 {
for (m, &idx) in obs_idx.iter().enumerate() {
if path[idx] >= self.autocall_barrier {
return (self.notional + self.coupon * (m + 1) as f64) * dfs[m];
}
}
let df_final = *dfs.last().unwrap();
let knocked_in = path.iter().any(|&s| s <= self.protection_barrier);
if knocked_in {
let terminal = *path.last().unwrap();
self.notional * (terminal / self.initial_fixing) * df_final
} else {
self.notional * df_final
}
}
}
impl Payoff for AutocallablePayoff {
fn payoff(&self, _spot: f64, _strike: f64) -> f64 {
0.0
}
fn path_payoff(&self, _path: &[f64], _strike: f64) -> f64 {
panic!(
"Autocallables pay at multiple dates and cannot be valued through \
path_payoff; the Monte Carlo engine prices them via path_value"
);
}
fn is_path_dependent(&self) -> bool {
true
}
fn payoff_kind(&self) -> PayoffType {
PayoffType::Autocallable
}
fn put_or_call(&self) -> &PutOrCall {
&PutOrCall::Call
}
fn exercise_style(&self) -> &ContractStyle {
&self.exercise_style
}
fn as_any(&self) -> &dyn std::any::Any {
self
}
}
#[cfg(test)]
mod tests {
use super::*;
fn note() -> AutocallablePayoff {
AutocallablePayoff {
exercise_style: ContractStyle::European,
autocall_barrier: 100.0,
protection_barrier: 70.0,
coupon: 5.0,
observations: 4,
notional: 100.0,
initial_fixing: 100.0,
}
}
#[test]
fn calls_at_first_breach_with_accrued_coupon() {
let payoff = note();
let obs_idx = [1, 3, 5, 7];
let dfs = [0.99, 0.98, 0.97, 0.96];
let path = [90.0, 95.0, 99.0, 101.0, 50.0, 50.0, 50.0, 50.0];
let value = payoff.path_value(&path, &obs_idx, &dfs);
assert!((value - (100.0 + 2.0 * 5.0) * 0.98).abs() < 1e-12);
}
#[test]
fn protected_redemption_when_never_called_nor_knocked() {
let payoff = note();
let path = [90.0, 92.0, 91.0, 95.0, 93.0, 92.0, 94.0, 96.0];
let value = payoff.path_value(&path, &[1, 3, 5, 7], &[0.99, 0.98, 0.97, 0.96]);
assert!((value - 100.0 * 0.96).abs() < 1e-12);
}
#[test]
fn downside_participation_after_knock_in() {
let payoff = note();
let path = [90.0, 65.0, 75.0, 80.0, 78.0, 82.0, 79.0, 80.0];
let value = payoff.path_value(&path, &[1, 3, 5, 7], &[0.99, 0.98, 0.97, 0.96]);
assert!((value - 100.0 * (80.0 / 100.0) * 0.96).abs() < 1e-12);
}
}