rustyqlib/equity/
forward_start_option.rs1use crate::core::trade::PutOrCall;
13use crate::core::utils::ContractStyle;
14use crate::equity::blackscholes::bs_price;
15use crate::equity::utils::{Payoff, PayoffType};
16
17#[derive(Debug)]
18pub struct ForwardStartPayoff {
19 pub put_or_call: PutOrCall,
20 pub exercise_style: ContractStyle,
21 pub strike_fraction: f64,
23 pub start_fraction: f64,
25}
26
27impl Payoff for ForwardStartPayoff {
28 fn payoff(&self, _spot: f64, _strike: f64) -> f64 {
30 0.0
31 }
32 fn path_payoff(&self, path: &[f64], _strike: f64) -> f64 {
33 let n = path.len();
34 let idx = ((self.start_fraction * n as f64).round() as usize).clamp(1, n - 1) - 1;
36 let strike = self.strike_fraction * path[idx];
37 let terminal = path[n - 1];
38 match self.put_or_call {
39 PutOrCall::Call => (terminal - strike).max(0.0),
40 PutOrCall::Put => (strike - terminal).max(0.0),
41 }
42 }
43 fn is_path_dependent(&self) -> bool {
44 true
45 }
46 fn payoff_kind(&self) -> PayoffType {
47 PayoffType::ForwardStart
48 }
49 fn put_or_call(&self) -> &PutOrCall {
50 &self.put_or_call
51 }
52 fn exercise_style(&self) -> &ContractStyle {
53 &self.exercise_style
54 }
55 fn as_any(&self) -> &dyn std::any::Any {
56 self
57 }
58}
59
60#[allow(clippy::too_many_arguments)]
64pub fn forward_start_price(
65 s: f64,
66 strike_fraction: f64,
67 r: f64,
68 q: f64,
69 sigma: f64,
70 start_t: f64,
71 t: f64,
72 put_or_call: PutOrCall,
73) -> f64 {
74 assert!(start_t > 0.0 && start_t < t, "fixing must lie inside the option life");
75 s * (-q * start_t).exp() * bs_price(1.0, strike_fraction, r, q, sigma, t - start_t, put_or_call)
76}
77
78#[cfg(test)]
79mod tests {
80 use super::*;
81
82 #[test]
83 fn reduces_to_vanilla_when_fixing_is_immediate() {
84 let fs = forward_start_price(100.0, 1.0, 0.05, 0.02, 0.3, 1e-6, 1.0, PutOrCall::Call);
87 let vanilla = bs_price(100.0, 100.0, 0.05, 0.02, 0.3, 1.0, PutOrCall::Call);
88 assert!((fs - vanilla).abs() < 1e-3, "{fs} vs {vanilla}");
89 }
90
91 #[test]
92 fn price_is_homogeneous_in_spot() {
93 let p1 = forward_start_price(100.0, 1.0, 0.05, 0.02, 0.3, 0.5, 1.0, PutOrCall::Call);
94 let p2 = forward_start_price(200.0, 1.0, 0.05, 0.02, 0.3, 0.5, 1.0, PutOrCall::Call);
95 assert!((p2 - 2.0 * p1).abs() < 1e-12);
96 }
97
98 #[test]
99 fn path_payoff_reads_fixing_off_the_path() {
100 let payoff = ForwardStartPayoff {
101 put_or_call: PutOrCall::Call,
102 exercise_style: ContractStyle::European,
103 strike_fraction: 1.0,
104 start_fraction: 0.5,
105 };
106 let path = [100.0, 90.0, 110.0, 120.0];
108 assert!((payoff.path_payoff(&path, 0.0) - 30.0).abs() < 1e-12); }
110}