Skip to content

Commit f837623

Browse files
committed
added finite difference for call option
1 parent a289d4c commit f837623

File tree

6 files changed

+157
-15
lines changed

6 files changed

+157
-15
lines changed

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
33
![Crates.io](https://img.shields.io/crates/dr/rustyqlib)
44
![Crates.io](https://img.shields.io/crates/v/rustyqlib)
5+
[![codecov](https://codecov.io/gh/siddharthqs/RustyQLib/graph/badge.svg?token=879K6LTTR4)](https://codecov.io/gh/siddharthqs/RustyQLib)
56
# RUSTYQLib :Pricing Options with Confidence using JSON
67
RustyQLib is a lightweight yet robust quantitative finance library designed for pricing options.
78
Built entirely in Rust, it offers a unique combination of safety, performance, and expressiveness that is crucial

src/equity/finite_difference.rs

+136
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
use super::vanila_option::{EquityOption};
2+
use super::utils::{Engine};
3+
4+
use crate::core::trade::{OptionType,Transection};
5+
use crate::core::utils::{ContractStyle};
6+
use ndarray::{Array, Array2,Array1, ArrayBase, Ix1, OwnedRepr, s};
7+
//use num_integer::Integer;
8+
/// finite difference model for European and American options
9+
pub fn npv(option: &&EquityOption) -> f64 {
10+
assert!(option.volatility >= 0.0);
11+
assert!(option.time_to_maturity() >= 0.0);
12+
assert!(option.underlying_price.value >= 0.0);
13+
let strike_price = option.strike_price;
14+
let time_to_maturity = option.time_to_maturity();
15+
let underlying_price = option.underlying_price.value;
16+
let volatility = option.volatility;
17+
let risk_free_rate = option.risk_free_rate;
18+
let dividend_yield = option.dividend_yield;
19+
let time_steps:f64 = 1000.0;
20+
//let time_steps:f64 = time_to_maturity/0.001 as f64;
21+
22+
let mut spot_steps = (time_steps / 50.0) as usize; //should be even number
23+
//let spot_steps:usize = 20;
24+
if spot_steps % 2 != 0{
25+
spot_steps = spot_steps + 1;
26+
}// should be even number
27+
28+
if option.option_type == OptionType::Call {
29+
return fd(underlying_price,strike_price,risk_free_rate,dividend_yield,volatility,
30+
time_to_maturity,spot_steps,time_steps);
31+
} else {
32+
//TODO implement for put option
33+
//println!("Not implemented"");
34+
return 0.0;
35+
}
36+
37+
}
38+
fn fd(s0:f64,k:f64,risk_free_rate:f64,dividend_yield:f64,sigma:f64,time_to_mat:f64,spot_steps:usize,time_steps:f64)->f64{
39+
let ds = 2.0*s0 / spot_steps as f64;
40+
41+
let M:i32 = (spot_steps as f64+(spot_steps as f64)/2.0) as i32; // 40 +20 = 60-20 = 40
42+
//let ds = 2.0*s0 / spot_steps as f64;
43+
//let M:i32 = spot_steps as i32;
44+
let dt = time_to_mat/(time_steps as f64);
45+
let time_steps = time_steps as i32;
46+
// convert float to nearest integer
47+
48+
//println!(" h {:?}",dt / (ds*ds));
49+
//let sigma:f64 = 0.3;
50+
//let r = 0.05;
51+
//let q = 0.01;
52+
let r = risk_free_rate-dividend_yield;
53+
let mut price_grid:Array2<f64> = Array2::zeros((M as usize +1,time_steps as usize+1));
54+
// Underlying price Grid
55+
for j in 0..time_steps+1 {
56+
for i in 0..M+1{
57+
price_grid[[(M-i) as usize,j as usize]] = (i as f64)*ds as f64;
58+
}
59+
}
60+
let mm = M as usize - ii as usize;
61+
println!("price_ {:?}",price_grid[[mm as usize as usize,0]]);
62+
let mut v_grid:Array2<f64> = Array2::zeros((M as usize +1,time_steps as usize+1));
63+
// Boundary condition
64+
// for j in 0..time_steps+1 {
65+
// for i in 0..M+1{
66+
// v_grid[[(M-i) as usize,j as usize]] = (price_grid[[(M-i) as usize,j as usize]]-k).max(0.0);
67+
// }
68+
// }
69+
// Boundary condition
70+
for i in 0..M+1{
71+
v_grid[[(M-i) as usize,time_steps as usize]] = (price_grid[[(M-i) as usize,time_steps as usize]]-k).max(0.0);
72+
}
73+
74+
let mut pd:Vec<f64> = Vec::with_capacity((M + 1) as usize);
75+
let mut b:Vec<f64> = Vec::with_capacity((M + 1) as usize);
76+
let mut x_:Vec<f64> = Vec::with_capacity((M + 1) as usize);
77+
let mut y_:Vec<f64> = Vec::with_capacity((M + 1) as usize);
78+
let mut pu:Vec<f64> = Vec::with_capacity((M + 1) as usize);
79+
//let ss = price_grid.slice(s![0..M+1,time_steps]).to_vec();
80+
//let mut xx_:Vec<f64> = Vec::with_capacity((M + 1) as usize);
81+
for j in 0..M + 1 {
82+
// ssj = (j as f64)*ds;
83+
//let x = r * ss[j as usize] / ds;
84+
let x = r*((M-j) as f64);
85+
//let y = sigma.powi(2) * (ss[j as usize].powi(2)) / ds.powi(2);
86+
let y = sigma.powi(2) * ((M-j) as f64).powi(2);
87+
x_.push(x);
88+
y_.push(y);
89+
//xx_.push(ss[j as usize] / ds);
90+
b.push(1.0 + dt * r + y * dt*0.5); //0.5 * dt * (j as f64)*((r-q) - sigma.powi(2)*(j as f64));
91+
pu.push(-0.25 * dt * (x + y)); //0.5 * dt * (j as f64)*((r-q) + sigma.powi(2)*(j as f64))
92+
pd.push(0.25 * dt * (x - y)); //-0.5*dt*(j as f64)*((r-q) + sigma.powi(2)*(j as f64))
93+
}
94+
95+
for i in (1..time_steps+1).rev(){
96+
let mut d = v_grid.slice(s![0..M as usize+1,i]).to_vec();
97+
d[0] = d[0]*(1.0-pu[0]);
98+
for j in 1..M{
99+
d[j as usize] = d[j as usize] +0.25*x_[j as usize]*dt*(d[(j-1) as usize]-d[(j+1) as usize]) +
100+
0.25*y_[j as usize]*dt*(d[(j-1) as usize]+d[(j+1) as usize] - 2.0*d[j as usize] );
101+
}
102+
let x = thomas_algorithm(&pu[0..M as usize], &b, &pd[1..(M+1) as usize], &d);
103+
for j in 0..M+1{
104+
v_grid[[j as usize,(i-1) as usize]] = x[j as usize];
105+
}
106+
}
107+
108+
return v_grid[[spot_steps as usize,0]];
109+
110+
}
111+
pub fn thomas_algorithm(a: &[f64], b: &[f64], c: &[f64], d: &[f64]) -> Vec<f64> {
112+
///https://en.wikipedia.org/wiki/Tridiagonal_matrix_algorithm
113+
/// Solves Ax = d where A is a tridiagonal matrix consisting of vectors a, b, c
114+
let n = d.len();
115+
let mut c_ = c.to_vec();
116+
let mut d_ = d.to_vec();
117+
let mut x: Vec<f64> = vec![0.0; n];
118+
119+
// Adjust for the upper boundary condition
120+
//d_[0] = d_[0]*(1.0-a[0]);
121+
122+
c_[0] = c_[0] / b[0];
123+
d_[0] = d_[0] / b[0];
124+
for i in 1..n-1 {
125+
let id = 1.0 / (b[i] - a[i-1] * c_[i - 1]);
126+
c_[i] = c_[i] * id;
127+
d_[i] = (d_[i] - a[i-1] * d_[i - 1]) * id;
128+
}
129+
d_[n - 1] = (d_[n - 1] - a[n - 2] * d_[n - 2]) / (b[n - 1] - a[n - 2] * c_[n - 2]);
130+
131+
x[n - 1] = d_[n - 1];
132+
for i in (0..n - 1).rev() {
133+
x[i] = d_[i] - c_[i] * x[i + 1];
134+
}
135+
x
136+
}

src/equity/mod.rs

+1
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,4 @@ pub mod utils;
66
pub mod binomial;
77
pub mod build_contracts;
88
pub mod vol_surface;
9+
pub mod finite_difference;

src/equity/utils.rs

+1
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,5 @@ pub enum Engine{
44
BlackScholes,
55
MonteCarlo,
66
Binomial,
7+
FiniteDifference
78
}

src/equity/vanila_option.rs

+15-11
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
use chrono::{Datelike, Local, NaiveDate};
2-
use crate::equity::montecarlo;
3-
use crate::equity::binomial;
2+
use crate::equity::{binomial,finite_difference,montecarlo};
43
use super::super::core::termstructure::YieldTermStructure;
54
use super::super::core::quotes::Quote;
65
use super::super::core::traits::{Instrument,Greeks};
@@ -27,9 +26,11 @@ impl Instrument for EquityOption {
2726
let value = binomial::npv(&self);
2827
value
2928
}
30-
_ => {
31-
0.0
29+
Engine::FiniteDifference => {
30+
let value = finite_difference::npv(&self);
31+
value
3232
}
33+
3334
}
3435
}
3536
}
@@ -67,10 +68,10 @@ impl EquityOption {
6768
let rates = vec![0.05,0.05,0.05,0.05,0.05,0.05,0.05,0.05];
6869
let ts = YieldTermStructure::new(date, rates);
6970
let option_type = &market_data.option_type;
70-
let side: trade::OptionType;
71+
let side: OptionType;
7172
match option_type.trim() {
72-
"C" | "c" | "Call" | "call" => side = trade::OptionType::Call,
73-
"P" | "p" | "Put" | "put" => side = trade::OptionType::Put,
73+
"C" | "c" | "Call" | "call" => side = OptionType::Call,
74+
"P" | "p" | "Put" | "put" => side = OptionType::Put,
7475
_ => panic!("Invalide side argument! Side has to be either 'C' or 'P'."),
7576
}
7677
let maturity_date = &market_data.maturity;
@@ -79,7 +80,7 @@ impl EquityOption {
7980

8081
let risk_free_rate = Some(market_data.risk_free_rate).unwrap();
8182
let dividend = Some(market_data.dividend).unwrap();
82-
let mut op = 0.0;
83+
//let mut op = 0.0;
8384

8485
let option_price = Quote::new(match market_data.option_price {
8586
Some(x) => x,
@@ -110,15 +111,18 @@ impl EquityOption {
110111
valuation_date: today.naive_utc(),
111112
};
112113
match data.pricer.trim() {
113-
"Analytical" | "analytical" => {
114+
"Analytical" | "analytical"|"bs" => {
114115
option.engine = Engine::BlackScholes;
115116
}
116-
"MonteCarlo" | "montecarlo" | "MC" => {
117+
"MonteCarlo" | "montecarlo" | "MC"|"mc" => {
117118
option.engine = Engine::MonteCarlo;
118119
}
119-
"Binomial" | "binomial" => {
120+
"Binomial" | "binomial"|"bino" => {
120121
option.engine = Engine::Binomial;
121122
}
123+
"FiniteDifference" | "finitdifference" |"FD" |"fd" => {
124+
option.engine = Engine::FiniteDifference;
125+
}
122126
_ => {
123127
panic!("Invalid pricer");
124128
}

src/utils/parse_json.rs

+3-4
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ pub fn parse_contract(mut file: &mut File,output_filename: &str) {
9191
println!("No contracts found in JSON file");
9292
return;
9393
}
94-
// parallel processing of each contract
94+
// parallel processing of each contract using rayon
9595
let mut output_vec: Vec<_> = list_contracts.contracts.par_iter().enumerate()
9696
.map(|(index,data)| (index,process_contract(data)))
9797
.collect();
@@ -104,15 +104,14 @@ pub fn parse_contract(mut file: &mut File,output_filename: &str) {
104104
file.write_all(output_str.as_bytes()).expect("Failed to write to file");
105105
}
106106
pub fn process_contract(data: &Contract) -> String {
107-
//println!("Processing {:?}",data);
107+
108108
let date = vec![0.01,0.02,0.05,0.1,0.5,1.0,2.0,3.0];
109109
let rates = vec![0.05,0.05,0.05,0.05,0.05,0.05,0.05,0.05];
110110
let ts = YieldTermStructure::new(date,rates);
111111

112112
if data.action=="PV" && data.asset=="EQ"{
113113
//let market_data = data.market_data.clone().unwrap();
114114
let option = EquityOption::from_json(&data);
115-
116115
let contract_output = ContractOutput{pv:option.npv(),delta:option.delta(),gamma:option.gamma(),
117116
vega:option.vega(),theta:option.theta(),rho:option.rho(), error: None };
118117
println!("Theoretical Price ${}", contract_output.pv);
@@ -180,7 +179,7 @@ pub fn process_contract(data: &Contract) -> String {
180179
let maturity_date = rates::utils::convert_mm_to_date(maturity_date_str);
181180
let start_date = rates::utils::convert_mm_to_date(start_date_str);
182181
println!("Maturity Date {:?}",maturity_date);
183-
let mut deposit = rates::deposits::Deposit {
182+
let mut deposit = Deposit {
184183
start_date: start_date,
185184
maturity_date: maturity_date,
186185
valuation_date: current_date.naive_utc(),

0 commit comments

Comments
 (0)