Skip to content

Commit a289d4c

Browse files
committed
bug fix in binomial
1 parent 85f15b7 commit a289d4c

File tree

7 files changed

+150
-41
lines changed

7 files changed

+150
-41
lines changed

Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -37,3 +37,4 @@ strum = "0.25"
3737
strum_macros = "0.25"
3838
ndarray = "0.15"
3939
assert_approx_eq = "1.1.0"
40+
rayon = "1.5.1"

README.md

+7-14
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
[![Build and Tests](https://github.com/siddharthqs/RustyQLib/actions/workflows/rust.yml/badge.svg)](https://github.com/siddharthqs/RustyQLib/actions/workflows/rust.yml)
22
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
3-
3+
![Crates.io](https://img.shields.io/crates/dr/rustyqlib)
4+
![Crates.io](https://img.shields.io/crates/v/rustyqlib)
45
# RUSTYQLib :Pricing Options with Confidence using JSON
56
RustyQLib is a lightweight yet robust quantitative finance library designed for pricing options.
67
Built entirely in Rust, it offers a unique combination of safety, performance, and expressiveness that is crucial
7-
for handling financial data and complex calculations. RustyQlib simplifies equity option pricing without compromising
8-
on safety, speed, or usability.
8+
for handling financial data and complex calculations. RustyQlib simplifies option pricing without compromising
9+
on safety, speed, or usability. It uses JSON to make distributed computing easier and integration with other systems or your websites.
910
## License
1011
RustyQlib is distributed under the terms of both the MIT license and the Apache License (Version 2.0).
1112
See LICENSE-APACHE and LICENSE-MIT for details.
@@ -30,20 +31,13 @@ Sample input file is provided in the repository (src\input\equity_option.json)
3031
Files are in JSON format and can be easily edited with any text editor.
3132
## Features
3233

33-
### JSON Input Simplicity:
34+
### JSON Simplicity:
3435

3536
- Ease of Use: Providing input data in JSON format is straightforward and human-readable.
36-
You can specify the parameters of your options with ease, making complex financial modeling accessible to all.
37+
- Portability: JSON is a platform-independent format, so you can use it on any operating system.
3738
- Flexibility: JSON accommodates various data types and structures, enabling you to define not only the option details but also additional market data, historical information, and risk parameters as needed.
38-
- Integration-Ready: RustQuant's JSON input is integration-friendly. You can seamlessly connect it to data sources, trading platforms, or other financial systems, simplifying your workflow and enhancing automation.
39-
40-
### JSON Output Clarity:
41-
JSON Output Clarity
42-
- Structured Results: RustQuant produces JSON output, that is your provided input with pricing results, Greeks, and risk profiles.
39+
- Integration-Ready: You can seamlessly connect it to data sources, trading platforms, or other financial systems, simplifying your workflow and enhancing automation.
4340

44-
- Scalability: JSON output is highly scalable.
45-
You can process large batches of option pricing requests and obtain results in a structured format, streamlining portfolio management.
46-
- Interoperability: JSON output integrates seamlessly with data visualization tools, databases, and reporting systems, enabling you to present and share your derivative pricing results effectively.
4741
### Stypes:
4842
- [x] European
4943
- [x] American
@@ -70,7 +64,6 @@ JSON Output Clarity
7064
- [ ] Commodity Barrier
7165
- [ ] Commodity Lookback
7266

73-
7467
### Pricing engines:
7568
- [x] Black Scholes
7669
- [x] Binomial Tree

src/equity/binomial.rs

+22-2
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ pub fn npv(option: &&EquityOption) -> f64 {
2424
//println!("{:?}",tree);
2525
// Calculate option prices at the final time step (backward induction)
2626
let multiplier = if option.option_type == OptionType::Call { 1.0 } else { -1.0 };
27+
2728
for j in 0..=num_steps {
2829
let spot_price_j = option.underlying_price.value * u.powi(num_steps as i32 - j as i32) * d.powi(j as i32);
2930
tree[[j,num_steps]] = (multiplier*(spot_price_j - option.strike_price)).max(0.0);
@@ -33,7 +34,7 @@ pub fn npv(option: &&EquityOption) -> f64 {
3334
ContractStyle::European => {
3435
for i in (0..num_steps).rev() {
3536
for j in 0..=i {
36-
let spot_price_i = option.underlying_price.value * u.powi(i as i32 - j as i32) * d.powi(j as i32);
37+
//let spot_price_i = option.underlying_price.value * u.powi(i as i32 - j as i32) * d.powi(j as i32);
3738
let discounted_option_price = discount_factor * (p * tree[[ j,i+1]] + (1.0 - p) * tree[[ j + 1,i+1]]);
3839
//tree[[j,i]] = (multiplier*(spot_price_i - option.strike_price)).max(discounted_option_price);
3940
tree[[j,i]] = discounted_option_price;
@@ -42,7 +43,7 @@ pub fn npv(option: &&EquityOption) -> f64 {
4243

4344
}
4445
ContractStyle::American => {
45-
println!("American");
46+
4647
for i in (0..num_steps).rev() {
4748
for j in 0..=i {
4849
let spot_price_i = option.underlying_price.value * u.powi(i as i32 - j as i32) * d.powi(j as i32);
@@ -99,7 +100,26 @@ mod tests {
99100
};
100101
let mut option = EquityOption::from_json(&data);
101102
option.valuation_date = NaiveDate::from_ymd(2023, 11, 06);
103+
//Call European test
104+
let npv = option.npv();
105+
assert_approx_eq!(npv, 5.058163, 1e-6);
106+
//Call American test
107+
option.option_type = OptionType::Call;
108+
option.style = ContractStyle::American;
102109
let npv = option.npv();
103110
assert_approx_eq!(npv, 5.058163, 1e-6);
111+
112+
//Put European test
113+
option.option_type = OptionType::Put;
114+
option.style = ContractStyle::European;
115+
option.valuation_date = NaiveDate::from_ymd(2023, 11, 07);
116+
let npv = option.npv();
117+
assert_approx_eq!(npv, 4.259022688, 1e-6);
118+
119+
//Put American test
120+
option.option_type = OptionType::Put;
121+
option.style = ContractStyle::American;
122+
let npv = option.npv();
123+
assert_approx_eq!(npv, 4.315832381, 1e-6);
104124
}
105125
}

src/equity/blackscholes.rs

+49-5
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,6 @@ pub fn npv(bsd_option: &&EquityOption) -> f64 {
2525
- bsd_option.strike_price
2626
* exp(-bsd_option.risk_free_rate * bsd_option.time_to_maturity())
2727
* N(bsd_option.d2());
28-
let a = N(bsd_option.d1());
29-
let b = N(bsd_option.d2());
30-
println!("a = {:?} b = {:?}",a,b);
31-
println!("{:?}",bsd_option);
3228
return option_price;
3329
} else {
3430
let option_price = -bsd_option.underlying_price.value()
@@ -335,4 +331,52 @@ pub fn implied_volatility() {
335331
io::stdin()
336332
.read_line(&mut div)
337333
.expect("Failed to read line");
338-
}
334+
}
335+
336+
#[cfg(test)]
337+
mod tests {
338+
339+
use assert_approx_eq::assert_approx_eq;
340+
use super::*;
341+
use crate::core::utils::{Contract,MarketData};
342+
use crate::core::trade::{OptionType,Transection};
343+
use crate::core::utils::{ContractStyle};
344+
use crate::equity::vanila_option::{EquityOption};
345+
346+
#[test]
347+
fn test_black_scholes() {
348+
let mut data = Contract {
349+
action: "PV".to_string(),
350+
market_data: Some(MarketData {
351+
underlying_price: 100.0,
352+
strike_price: 100.0,
353+
volatility: Some(0.3),
354+
option_price: Some(10.0),
355+
risk_free_rate: Some(0.05),
356+
dividend: Some(0.0),
357+
maturity: "2024-01-01".to_string(),
358+
option_type: "C".to_string(),
359+
simulation: None
360+
}),
361+
pricer: "Analytical".to_string(),
362+
asset: "".to_string(),
363+
style: Some("European".to_string()),
364+
rate_data: None
365+
};
366+
367+
let mut option = EquityOption::from_json(&data);
368+
option.valuation_date = NaiveDate::from_ymd(2023, 11, 06);
369+
//Call European test
370+
let npv = option.npv();
371+
assert_approx_eq!(npv, 5.05933313, 1e-6);
372+
373+
//Put European test
374+
option.option_type = OptionType::Put;
375+
option.style = ContractStyle::European;
376+
option.valuation_date = NaiveDate::from_ymd(2023, 11, 07);
377+
let npv = option.npv();
378+
assert_approx_eq!(npv,4.2601813, 1e-6);
379+
380+
}
381+
}
382+

src/equity/vanila_option.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,12 @@ impl Instrument for EquityOption {
1818
value
1919
}
2020
Engine::MonteCarlo => {
21-
println!("Using MonteCarlo Engine ");
21+
2222
let value = montecarlo::npv(&self,false);
2323
value
2424
}
2525
Engine::Binomial => {
26-
println!("Using Binomial Engine ");
26+
2727
let value = binomial::npv(&self);
2828
value
2929
}

src/examples/EQ/eq2.json

+49
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
{"asset":"EQ",
2+
"contracts" : [
3+
{
4+
"action":"PV",
5+
"style":"American",
6+
"pricer":"Binomial",
7+
"asset":"EQ",
8+
"market_data":{
9+
"underlying_price":100,
10+
"option_type":"P",
11+
"strike_price":100,
12+
"volatility":0.3,
13+
"risk_free_rate":0.05,
14+
"maturity":"2024-01-01",
15+
"dividend": 0.0
16+
}
17+
},
18+
{
19+
"action":"PV",
20+
"pricer":"Binomial",
21+
"asset":"EQ",
22+
"style":"American",
23+
"market_data":{
24+
"underlying_price":100,
25+
"option_type":"P",
26+
"strike_price":105,
27+
"volatility":0.3,
28+
"risk_free_rate":0.05,
29+
"maturity":"2024-01-01",
30+
"dividend": 0.0
31+
}
32+
},
33+
{
34+
"action":"PV",
35+
"pricer":"Binomial",
36+
"asset":"EQ",
37+
"style":"American",
38+
"market_data":{
39+
"underlying_price":100,
40+
"option_type":"P",
41+
"strike_price":110,
42+
"volatility":0.3,
43+
"risk_free_rate":0.05,
44+
"maturity":"2024-01-01",
45+
"dividend": 0.0
46+
}
47+
}
48+
]
49+
}

src/utils/parse_json.rs

+20-18
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,7 @@ use crate::cmdty::cmdty_option::{CmdtyOption};
1414
use crate::core::trade;
1515
use crate::cmdty::cmdty_option;
1616
use crate::core::traits::{Instrument, Rates};
17-
use crate::core::utils;
18-
use crate::core::utils::{CombinedContract, ContractOutput, Contracts, OutputJson,EngineType};
17+
use crate::core::utils::{Contract,CombinedContract, ContractOutput, Contracts, OutputJson,EngineType};
1918
use crate::core::utils::ContractStyle;
2019
use crate::core::traits::Greeks;
2120
use std::io::Write;
@@ -26,7 +25,7 @@ use crate::rates::deposits::Deposit;
2625
use crate::rates::build_contracts::{build_ir_contracts, build_ir_contracts_from_json, build_term_structure};
2726
use crate::equity::build_contracts::{build_eq_contracts_from_json};
2827
use crate::equity::vol_surface::VolSurface;
29-
28+
use rayon::prelude::*;
3029
/// This function saves the output to a file and returns the path to the file.
3130
pub fn save_to_file<'a>(output_folder: &'a str, subfolder: &'a str, filename: &'a str, output: &'a str) -> String {
3231
let mut dir = std::path::PathBuf::from(output_folder);
@@ -88,35 +87,38 @@ pub fn parse_contract(mut file: &mut File,output_filename: &str) {
8887

8988
let list_contracts: Contracts = serde_json::from_str(&contents).expect("Failed to deserialize JSON");
9089

91-
//let data: utils::Contract = serde_json::from_str(&contents).expect("Failed to deserialize JSON");
92-
//let mut output: String = String::new();
93-
let mut output_vec:Vec<String> = Vec::new();
94-
for data in list_contracts.contracts.into_iter() {
95-
output_vec.push(process_contract(data));
90+
if list_contracts.contracts.len() == 0 {
91+
println!("No contracts found in JSON file");
92+
return;
9693
}
94+
// parallel processing of each contract
95+
let mut output_vec: Vec<_> = list_contracts.contracts.par_iter().enumerate()
96+
.map(|(index,data)| (index,process_contract(data)))
97+
.collect();
98+
output_vec.sort_by_key(|k| k.0);
9799

98-
let mut file = File::create(output_filename).expect("Failed to create file");
99-
//let mut output:OutputJson = OutputJson{contracts:output_vec};
100+
let output_vec: Vec<String> = output_vec.into_iter().map(|(_,v)| v).collect();
100101
let output_str = output_vec.join(",");
101-
//let output_json = serde_json::to_string(&output_vec).expect("Failed to generate output");
102+
//Write to file
103+
let mut file = File::create(output_filename).expect("Failed to create file");
102104
file.write_all(output_str.as_bytes()).expect("Failed to write to file");
103105
}
104-
pub fn process_contract(data: utils::Contract) -> String {
106+
pub fn process_contract(data: &Contract) -> String {
105107
//println!("Processing {:?}",data);
106108
let date = vec![0.01,0.02,0.05,0.1,0.5,1.0,2.0,3.0];
107109
let rates = vec![0.05,0.05,0.05,0.05,0.05,0.05,0.05,0.05];
108110
let ts = YieldTermStructure::new(date,rates);
109111

110-
111112
if data.action=="PV" && data.asset=="EQ"{
112113
//let market_data = data.market_data.clone().unwrap();
113114
let option = EquityOption::from_json(&data);
114115

115-
let contract_output = ContractOutput{pv:option.npv(),delta:option.delta(),gamma:option.gamma(),vega:option.vega(),theta:option.theta(),rho:option.rho(), error: None };
116+
let contract_output = ContractOutput{pv:option.npv(),delta:option.delta(),gamma:option.gamma(),
117+
vega:option.vega(),theta:option.theta(),rho:option.rho(), error: None };
116118
println!("Theoretical Price ${}", contract_output.pv);
117119
println!("Delta ${}", contract_output.delta);
118120
let combined_ = CombinedContract{
119-
contract: data,
121+
contract: data.clone(),
120122
output:contract_output
121123
};
122124
let output_json = serde_json::to_string(&combined_).expect("Failed to generate output");
@@ -155,11 +157,11 @@ pub fn process_contract(data: utils::Contract) -> String {
155157
time_to_future_maturity: None,
156158
risk_free_rate: None
157159
};
158-
let contract_output = utils::ContractOutput{pv:option.npv(),delta:option.delta(),gamma:option.gamma(),vega:option.vega(),theta:option.theta(),rho:option.rho(), error: None };
160+
let contract_output = ContractOutput{pv:option.npv(),delta:option.delta(),gamma:option.gamma(),vega:option.vega(),theta:option.theta(),rho:option.rho(), error: None };
159161
println!("Theoretical Price ${}", contract_output.pv);
160162
println!("Delta ${}", contract_output.delta);
161-
let combined_ = utils::CombinedContract{
162-
contract: data,
163+
let combined_ = CombinedContract{
164+
contract: data.clone(),
163165
output:contract_output
164166
};
165167
let output_json = serde_json::to_string(&combined_).expect("Failed to generate output");

0 commit comments

Comments
 (0)