stochastic_rs/quant/pricing/
finitie_difference.rs

1use 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  /// Underlying price
20  pub s: f64,
21  /// Volatility
22  pub v: f64,
23  /// Strike price
24  pub k: f64,
25  /// Risk-free rate
26  pub r: f64,
27  /// Time steps
28  pub t_n: usize,
29  /// Price steps
30  pub s_n: usize,
31  /// Time to maturity in years
32  pub tau: Option<f64>,
33  /// Evaluation date
34  pub eval: Option<chrono::NaiveDate>,
35  /// Expiration date
36  pub expiration: Option<chrono::NaiveDate>,
37  /// Otpion style
38  pub option_style: OptionStyle,
39  /// Option type
40  pub option_type: OptionType,
41  /// Pricing method
42  pub method: FiniteDifferenceMethod,
43}
44
45impl PricerExt for FiniteDifferencePricer {
46  /// Calculate the option price
47  #[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}