use crate::core::trade::PutOrCall;
use crate::core::utils::ContractStyle;
use crate::equity::blackscholes::bs_price;
use crate::equity::utils::{Payoff, PayoffType};
#[derive(Debug)]
pub struct ForwardStartPayoff {
pub put_or_call: PutOrCall,
pub exercise_style: ContractStyle,
pub strike_fraction: f64,
pub start_fraction: f64,
}
impl Payoff for ForwardStartPayoff {
fn payoff(&self, _spot: f64, _strike: f64) -> f64 {
0.0
}
fn path_payoff(&self, path: &[f64], _strike: f64) -> f64 {
let n = path.len();
let idx = ((self.start_fraction * n as f64).round() as usize).clamp(1, n - 1) - 1;
let strike = self.strike_fraction * path[idx];
let terminal = path[n - 1];
match self.put_or_call {
PutOrCall::Call => (terminal - strike).max(0.0),
PutOrCall::Put => (strike - terminal).max(0.0),
}
}
fn is_path_dependent(&self) -> bool {
true
}
fn payoff_kind(&self) -> PayoffType {
PayoffType::ForwardStart
}
fn put_or_call(&self) -> &PutOrCall {
&self.put_or_call
}
fn exercise_style(&self) -> &ContractStyle {
&self.exercise_style
}
fn as_any(&self) -> &dyn std::any::Any {
self
}
}
#[allow(clippy::too_many_arguments)]
pub fn forward_start_price(
s: f64,
strike_fraction: f64,
r: f64,
q: f64,
sigma: f64,
start_t: f64,
t: f64,
put_or_call: PutOrCall,
) -> f64 {
assert!(start_t > 0.0 && start_t < t, "fixing must lie inside the option life");
s * (-q * start_t).exp() * bs_price(1.0, strike_fraction, r, q, sigma, t - start_t, put_or_call)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn reduces_to_vanilla_when_fixing_is_immediate() {
let fs = forward_start_price(100.0, 1.0, 0.05, 0.02, 0.3, 1e-6, 1.0, PutOrCall::Call);
let vanilla = bs_price(100.0, 100.0, 0.05, 0.02, 0.3, 1.0, PutOrCall::Call);
assert!((fs - vanilla).abs() < 1e-3, "{fs} vs {vanilla}");
}
#[test]
fn price_is_homogeneous_in_spot() {
let p1 = forward_start_price(100.0, 1.0, 0.05, 0.02, 0.3, 0.5, 1.0, PutOrCall::Call);
let p2 = forward_start_price(200.0, 1.0, 0.05, 0.02, 0.3, 0.5, 1.0, PutOrCall::Call);
assert!((p2 - 2.0 * p1).abs() < 1e-12);
}
#[test]
fn path_payoff_reads_fixing_off_the_path() {
let payoff = ForwardStartPayoff {
put_or_call: PutOrCall::Call,
exercise_style: ContractStyle::European,
strike_fraction: 1.0,
start_fraction: 0.5,
};
let path = [100.0, 90.0, 110.0, 120.0];
assert!((payoff.path_payoff(&path, 0.0) - 30.0).abs() < 1e-12); }
}