stochastic_rs/quant/pricing/
finitie_difference.rs1use impl_new_derive::ImplNew;
2use ndarray::{s, Array1};
3
4use crate::quant::{
5 r#trait::{PricerExt, TimeExt},
6 OptionStyle, OptionType,
7};
8
9#[derive(Default)]
10pub enum FiniteDifferenceMethod {
11 Explicit,
12 Implicit,
13 #[default]
14 CrankNicolson,
15}
16
17#[derive(ImplNew)]
18pub struct FiniteDifferencePricer {
19 pub s: f64,
21 pub v: f64,
23 pub k: f64,
25 pub r: f64,
27 pub t_n: usize,
29 pub s_n: usize,
31 pub tau: Option<f64>,
33 pub eval: Option<chrono::NaiveDate>,
35 pub expiration: Option<chrono::NaiveDate>,
37 pub option_style: OptionStyle,
39 pub option_type: OptionType,
41 pub method: FiniteDifferenceMethod,
43}
44
45impl PricerExt for FiniteDifferencePricer {
46 #[must_use]
48 fn calculate_price(&self) -> f64 {
49 match self.method {
50 FiniteDifferenceMethod::Explicit => self.explicit(),
51 FiniteDifferenceMethod::Implicit => self.implicit(),
52 FiniteDifferenceMethod::CrankNicolson => self.crank_nicolson(),
53 }
54 }
55}
56
57impl TimeExt for FiniteDifferencePricer {
58 fn tau(&self) -> Option<f64> {
59 self.tau
60 }
61
62 fn eval(&self) -> chrono::NaiveDate {
63 self.eval.unwrap()
64 }
65
66 fn expiration(&self) -> chrono::NaiveDate {
67 self.expiration.unwrap()
68 }
69}
70
71impl FiniteDifferencePricer {
72 fn explicit(&self) -> f64 {
73 let (dt, ds, s_values, time_steps) = self.calculate_grid();
74 let mut option_values = Array1::<f64>::zeros(self.s_n + 1);
75
76 for (i, &s_i) in s_values.iter().enumerate() {
77 option_values[i] = self.payoff(s_i);
78 }
79
80 for _ in 0..time_steps {
81 let mut new_option_values = option_values.clone();
82
83 for i in 1..self.s_n {
84 let s_i = s_values[i];
85
86 let delta = (option_values[i + 1] - option_values[i - 1]) / (2.0 * ds);
87 let gamma =
88 (option_values[i + 1] - 2.0 * option_values[i] + option_values[i - 1]) / (ds.powi(2));
89
90 new_option_values[i] = option_values[i]
91 + dt
92 * (0.5 * self.v.powi(2) * s_i.powi(2) * gamma + self.r * s_i * delta
93 - self.r * option_values[i]);
94
95 if let OptionStyle::American = self.option_style {
96 let intrinsic_value = self.payoff(s_i);
97 new_option_values[i] = new_option_values[i].max(intrinsic_value);
98 }
99 }
100
101 new_option_values[0] = self.boundary_condition(s_values[0], 0.0);
102 new_option_values[self.s_n] = self.boundary_condition(s_values[self.s_n], 0.0);
103
104 option_values = new_option_values;
105 }
106
107 self.interpolate(&s_values, &option_values, self.s)
108 }
109
110 fn implicit(&self) -> f64 {
111 let (dt, ds, s_values, time_steps) = self.calculate_grid();
112
113 let mut a = Array1::<f64>::zeros(self.s_n - 1);
114 let mut b = Array1::<f64>::zeros(self.s_n - 1);
115 let mut c = Array1::<f64>::zeros(self.s_n - 1);
116
117 let mut option_values = Array1::<f64>::zeros(self.s_n + 1);
118 for (i, &s_i) in s_values.iter().enumerate() {
119 option_values[i] = self.payoff(s_i);
120 }
121
122 for _ in 0..time_steps {
123 for i in 1..self.s_n {
124 let s_i = s_values[i];
125 let sigma_sq = self.v.powi(2);
126
127 a[i - 1] = -0.5 * dt * (sigma_sq * s_i.powi(2) / ds.powi(2) - self.r * s_i / ds);
128 b[i - 1] = 1.0 + dt * (sigma_sq * s_i.powi(2) / ds.powi(2) + self.r);
129 c[i - 1] = -0.5 * dt * (sigma_sq * s_i.powi(2) / ds.powi(2) + self.r * s_i / ds);
130 }
131
132 let mut d = option_values.slice(s![1..self.s_n]).to_owned();
133
134 d[0] -= a[0] * self.boundary_condition(0.0, dt);
135 d[self.s_n - 2] -= c[self.s_n - 2] * self.boundary_condition(s_values[self.s_n], dt);
136
137 let new_option_values_inner = self.solve_tridiagonal(&a, &b, &c, &d);
138
139 for i in 1..self.s_n {
140 option_values[i] = new_option_values_inner[i - 1];
141
142 if let OptionStyle::American = self.option_style {
143 let intrinsic_value = self.payoff(s_values[i]);
144 option_values[i] = option_values[i].max(intrinsic_value);
145 }
146 }
147
148 option_values[0] = self.boundary_condition(0.0, dt);
149 option_values[self.s_n] = self.boundary_condition(s_values[self.s_n], dt);
150 }
151
152 self.interpolate(&s_values, &option_values, self.s)
153 }
154
155 fn crank_nicolson(&self) -> f64 {
156 let (dt, ds, s_values, time_steps) = self.calculate_grid();
157
158 let mut a = Array1::<f64>::zeros(self.s_n - 1);
159 let mut b = Array1::<f64>::zeros(self.s_n - 1);
160 let mut c = Array1::<f64>::zeros(self.s_n - 1);
161
162 let mut option_values = Array1::<f64>::zeros(self.s_n + 1);
163 for (i, &s_i) in s_values.iter().enumerate() {
164 option_values[i] = self.payoff(s_i);
165 }
166
167 for _ in 0..time_steps {
168 for i in 1..self.s_n {
169 let s_i = s_values[i];
170 let sigma_sq = self.v.powi(2);
171
172 a[i - 1] = -0.25 * dt * (sigma_sq * s_i.powi(2) / ds.powi(2) - self.r * s_i / ds);
173 b[i - 1] = 1.0 + 0.5 * dt * (sigma_sq * s_i.powi(2) / ds.powi(2) + self.r);
174 c[i - 1] = -0.25 * dt * (sigma_sq * s_i.powi(2) / ds.powi(2) + self.r * s_i / ds);
175 }
176
177 let mut d = Array1::<f64>::zeros(self.s_n - 1);
178 for i in 1..self.s_n {
179 let s_i = s_values[i];
180 let sigma_sq = self.v.powi(2);
181
182 let a_past = 0.25 * dt * (sigma_sq * s_i.powi(2) / ds.powi(2) - self.r * s_i / ds);
183 let b_past = 1.0 - 0.5 * dt * (sigma_sq * s_i.powi(2) / ds.powi(2) + self.r);
184 let c_past = 0.25 * dt * (sigma_sq * s_i.powi(2) / ds.powi(2) + self.r * s_i / ds);
185
186 d[i - 1] =
187 a_past * option_values[i - 1] + b_past * option_values[i] + c_past * option_values[i + 1];
188 }
189
190 d[0] -= a[0] * self.boundary_condition(0.0, dt);
191 d[self.s_n - 2] -= c[self.s_n - 2] * self.boundary_condition(s_values[self.s_n], dt);
192
193 let new_option_values_inner = self.solve_tridiagonal(&a, &b, &c, &d);
194
195 for i in 1..self.s_n {
196 option_values[i] = new_option_values_inner[i - 1];
197
198 if let OptionStyle::American = self.option_style {
199 let intrinsic_value = self.payoff(s_values[i]);
200 option_values[i] = option_values[i].max(intrinsic_value);
201 }
202 }
203
204 option_values[0] = self.boundary_condition(0.0, dt);
205 option_values[self.s_n] = self.boundary_condition(s_values[self.s_n], dt);
206 }
207
208 self.interpolate(&s_values, &option_values, self.s)
209 }
210
211 fn calculate_grid(&self) -> (f64, f64, Array1<f64>, usize) {
212 let tau = self.tau.unwrap_or(1.0);
213 let dt = tau / self.t_n as f64;
214 let s_max = self.s * 3.0;
215 let ds = s_max / self.s_n as f64;
216 let s_values = Array1::linspace(0.0, s_max, self.s_n + 1);
217 let time_steps = self.t_n;
218 (dt, ds, s_values, time_steps)
219 }
220
221 fn payoff(&self, s: f64) -> f64 {
222 match self.option_type {
223 OptionType::Call => (s - self.k).max(0.0),
224 OptionType::Put => (self.k - s).max(0.0),
225 }
226 }
227
228 fn boundary_condition(&self, s: f64, tau: f64) -> f64 {
229 match self.option_type {
230 OptionType::Call => {
231 if s == 0.0 {
232 0.0
233 } else {
234 s - self.k * (-self.r * (self.tau.unwrap_or(1.0) - tau)).exp()
235 }
236 }
237 OptionType::Put => {
238 if s == 0.0 {
239 self.k * (-self.r * (self.tau.unwrap_or(1.0) - tau)).exp()
240 } else {
241 0.0
242 }
243 }
244 }
245 }
246
247 fn interpolate(&self, s_values: &Array1<f64>, option_values: &Array1<f64>, s: f64) -> f64 {
248 for i in 0..s_values.len() - 1 {
249 if s_values[i] <= s && s <= s_values[i + 1] {
250 let weight = (s - s_values[i]) / (s_values[i + 1] - s_values[i]);
251 return option_values[i] * (1.0 - weight) + option_values[i + 1] * weight;
252 }
253 }
254 0.0
255 }
256
257 fn solve_tridiagonal(
258 &self,
259 a: &Array1<f64>,
260 b: &Array1<f64>,
261 c: &Array1<f64>,
262 d: &Array1<f64>,
263 ) -> Array1<f64> {
264 let n = d.len();
265 let mut c_star = Array1::<f64>::zeros(n);
266 let mut d_star = Array1::<f64>::zeros(n);
267
268 c_star[0] = c[0] / b[0];
269 d_star[0] = d[0] / b[0];
270
271 for i in 1..n {
272 let m = b[i] - a[i] * c_star[i - 1];
273 c_star[i] = c[i] / m;
274 d_star[i] = (d[i] - a[i] * d_star[i - 1]) / m;
275 }
276
277 let mut x = Array1::<f64>::zeros(n);
278 x[n - 1] = d_star[n - 1];
279 for i in (0..n - 1).rev() {
280 x[i] = d_star[i] - c_star[i] * x[i + 1];
281 }
282
283 x
284 }
285}
286
287#[cfg(test)]
288mod tests {
289 use crate::{
290 quant::{r#trait::PricerExt, OptionStyle, OptionType},
291 stochastic::{K, S0},
292 };
293
294 use super::{FiniteDifferenceMethod, FiniteDifferencePricer};
295
296 fn atm_pricer(style: OptionStyle, r#type: OptionType, method: FiniteDifferenceMethod) -> f64 {
297 let pricer = FiniteDifferencePricer::new(
298 S0,
299 0.1,
300 K,
301 0.05,
302 10000,
303 250,
304 Some(1.0),
305 None,
306 None,
307 style,
308 r#type,
309 method,
310 );
311
312 pricer.calculate_price()
313 }
314
315 #[test]
316 fn eu_explicit_call() {
317 let call = atm_pricer(
318 OptionStyle::European,
319 OptionType::Call,
320 FiniteDifferenceMethod::Explicit,
321 );
322 println!("Call: {}", call);
323 }
324
325 #[test]
326 fn eu_implicit_call() {
327 let call = atm_pricer(
328 OptionStyle::European,
329 OptionType::Call,
330 FiniteDifferenceMethod::Implicit,
331 );
332 println!("Call: {}", call);
333 }
334
335 #[test]
336 fn eu_crank_nicolson_call() {
337 let call = atm_pricer(
338 OptionStyle::European,
339 OptionType::Call,
340 FiniteDifferenceMethod::CrankNicolson,
341 );
342 println!("Call: {}", call);
343 }
344
345 #[test]
346 fn am_explicit_call() {
347 let call = atm_pricer(
348 OptionStyle::American,
349 OptionType::Call,
350 FiniteDifferenceMethod::Explicit,
351 );
352 println!("Call: {}", call);
353 }
354
355 #[test]
356 fn am_implicit_call() {
357 let call = atm_pricer(
358 OptionStyle::American,
359 OptionType::Call,
360 FiniteDifferenceMethod::Implicit,
361 );
362 println!("Call: {}", call);
363 }
364
365 #[test]
366 fn am_crank_nicolson_call() {
367 let call = atm_pricer(
368 OptionStyle::American,
369 OptionType::Call,
370 FiniteDifferenceMethod::CrankNicolson,
371 );
372 println!("Call: {}", call);
373 }
374
375 #[test]
376 fn eu_explicit_put() {
377 let put = atm_pricer(
378 OptionStyle::European,
379 OptionType::Put,
380 FiniteDifferenceMethod::Explicit,
381 );
382 println!("Put: {}", put);
383 }
384
385 #[test]
386 fn eu_implicit_put() {
387 let put = atm_pricer(
388 OptionStyle::European,
389 OptionType::Put,
390 FiniteDifferenceMethod::Implicit,
391 );
392 println!("Put: {}", put);
393 }
394
395 #[test]
396 fn eu_crank_nicolson_put() {
397 let put = atm_pricer(
398 OptionStyle::European,
399 OptionType::Put,
400 FiniteDifferenceMethod::CrankNicolson,
401 );
402 println!("Put: {}", put);
403 }
404
405 #[test]
406 fn am_explicit_put() {
407 let put = atm_pricer(
408 OptionStyle::American,
409 OptionType::Put,
410 FiniteDifferenceMethod::Explicit,
411 );
412 println!("Put: {}", put);
413 }
414
415 #[test]
416 fn am_implicit_put() {
417 let put = atm_pricer(
418 OptionStyle::American,
419 OptionType::Put,
420 FiniteDifferenceMethod::Implicit,
421 );
422 println!("Put: {}", put);
423 }
424
425 #[test]
426 fn am_crank_nicolson_put() {
427 let put = atm_pricer(
428 OptionStyle::American,
429 OptionType::Put,
430 FiniteDifferenceMethod::CrankNicolson,
431 );
432 println!("Put: {}", put);
433 }
434}