1fn roots(r: f64, b: f64, sigma: f64) -> (f64, f64) {
21 let v2 = sigma * sigma;
22 let half_shift = 0.5 - b / v2;
23 let disc = ((b / v2 - 0.5).powi(2) + 2.0 * r / v2).sqrt();
24 (half_shift + disc, half_shift - disc)
25}
26
27pub fn perpetual_call(s: f64, k: f64, r: f64, q: f64, sigma: f64) -> f64 {
34 assert!(s > 0.0 && k > 0.0 && sigma > 0.0, "need positive spot, strike and vol");
35 let b = r - q;
36 if b > r {
37 return f64::INFINITY; }
39 if b == r {
40 return s; }
42 let (y1, _) = roots(r, b, sigma);
43 let boundary = y1 / (y1 - 1.0) * k;
44 if s >= boundary {
45 return s - k;
46 }
47 k / (y1 - 1.0) * (((y1 - 1.0) / y1) * (s / k)).powf(y1)
48}
49
50pub fn perpetual_put(s: f64, k: f64, r: f64, q: f64, sigma: f64) -> f64 {
53 assert!(s > 0.0 && k > 0.0 && sigma > 0.0, "need positive spot, strike and vol");
54 assert!(r > 0.0, "the perpetual put needs a positive risk-free rate");
55 let b = r - q;
56 let (_, y2) = roots(r, b, sigma);
57 let boundary = y2 / (y2 - 1.0) * k;
58 if s <= boundary {
59 return k - s;
60 }
61 k / (1.0 - y2) * (((y2 - 1.0) / y2) * (s / k)).powf(y2)
62}
63
64pub fn exercise_boundary(
67 k: f64,
68 r: f64,
69 q: f64,
70 sigma: f64,
71 put_or_call: crate::core::trade::PutOrCall,
72) -> f64 {
73 let b = r - q;
74 let (y1, y2) = roots(r, b, sigma);
75 match put_or_call {
76 crate::core::trade::PutOrCall::Call => {
77 assert!(b < r, "the perpetual call is never exercised when b >= r");
78 y1 / (y1 - 1.0) * k
79 }
80 crate::core::trade::PutOrCall::Put => y2 / (y2 - 1.0) * k,
81 }
82}
83
84#[cfg(test)]
85mod tests {
86 use super::*;
87 use crate::core::trade::PutOrCall;
88
89 const S: f64 = 100.0;
90 const K: f64 = 100.0;
91 const R: f64 = 0.05;
92 const Q: f64 = 0.03;
93 const V: f64 = 0.25;
94
95 #[test]
96 fn golden_values_match_the_validated_reference() {
97 assert!((perpetual_call(S, K, R, Q, V) - 40.3730823948).abs() < 1e-9);
98 assert!((perpetual_put(S, K, R, Q, V) - 23.4169723789).abs() < 1e-9);
99 assert!((exercise_boundary(K, R, Q, V, PutOrCall::Call) - 318.505635).abs() < 1e-5);
100 assert!((exercise_boundary(K, R, Q, V, PutOrCall::Put) - 52.327698).abs() < 1e-5);
101 }
102
103 #[test]
104 fn solves_the_stationary_ode_exactly() {
105 let b = R - Q;
107 for f in [
108 (|s: f64| perpetual_call(s, K, R, Q, V)) as fn(f64) -> f64,
109 |s: f64| perpetual_put(s, K, R, Q, V),
110 ] {
111 for s in [60.0, 80.0, 100.0, 150.0] {
112 let h = s * 1e-4;
113 let (v0, vp, vm) = (f(s), f(s + h), f(s - h));
114 let d1 = (vp - vm) / (2.0 * h);
115 let d2 = (vp - 2.0 * v0 + vm) / (h * h);
116 let residual = 0.5 * V * V * s * s * d2 + b * s * d1 - R * v0;
117 assert!(residual.abs() < 1e-5 * (1.0 + v0), "S = {s}: residual {residual}");
118 }
119 }
120 }
121
122 #[test]
123 fn value_matching_and_smooth_pasting_at_the_boundary() {
124 let call_boundary = exercise_boundary(K, R, Q, V, PutOrCall::Call);
125 let put_boundary = exercise_boundary(K, R, Q, V, PutOrCall::Put);
126 assert!((perpetual_call(call_boundary, K, R, Q, V) - (call_boundary - K)).abs() < 1e-9);
128 assert!((perpetual_put(put_boundary, K, R, Q, V) - (K - put_boundary)).abs() < 1e-9);
129 let h = 1e-5;
131 let call_slope =
132 (perpetual_call(call_boundary - h, K, R, Q, V)
133 - perpetual_call(call_boundary - 2.0 * h, K, R, Q, V))
134 / h;
135 assert!((call_slope - 1.0).abs() < 1e-4, "call slope {call_slope}");
136 let put_slope = (perpetual_put(put_boundary + 2.0 * h, K, R, Q, V)
137 - perpetual_put(put_boundary + h, K, R, Q, V))
138 / h;
139 assert!((put_slope + 1.0).abs() < 1e-4, "put slope {put_slope}");
140 }
141
142 #[test]
143 fn put_call_duality_holds() {
144 let p = perpetual_put(S, K, R, Q, V);
146 let c = perpetual_call(K, S, Q, R, V);
147 assert!((p - c).abs() < 1e-12, "{p} vs {c}");
148 }
149
150 #[test]
151 fn degenerate_carry_cases() {
152 assert_eq!(perpetual_call(100.0, 80.0, 0.05, 0.0, 0.3), 100.0);
154 assert!(perpetual_call(100.0, 80.0, 0.05, -0.01, 0.3).is_infinite());
156 assert_eq!(perpetual_call(500.0, 100.0, R, Q, V), 400.0);
158 assert_eq!(perpetual_put(30.0, 100.0, R, Q, V), 70.0);
159 }
160
161 #[test]
162 fn finite_maturity_american_prices_increase_toward_the_perpetual() {
163 use crate::equity::bjerksund_stensland;
169 let perpetual = perpetual_put(S, K, R, Q, V);
170 let mut last = 0.0;
171 for t in [1.0, 5.0, 15.0, 40.0] {
172 let finite = bjerksund_stensland::price(S, K, R, Q, V, t, PutOrCall::Put);
173 assert!(finite > last, "not increasing at T = {t}");
174 assert!(finite <= perpetual + 1e-9, "T = {t}: {finite} above perpetual {perpetual}");
175 last = finite;
176 }
177 assert!(perpetual - last < 0.10 * perpetual, "T=40 {last} vs perpetual {perpetual}");
179 }
180}