Skip to content

Commit

Permalink
bug fix in binomial
Browse files Browse the repository at this point in the history
  • Loading branch information
siddharthqs committed Nov 10, 2023
1 parent 85f15b7 commit a289d4c
Show file tree
Hide file tree
Showing 7 changed files with 150 additions and 41 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,4 @@ strum = "0.25"
strum_macros = "0.25"
ndarray = "0.15"
assert_approx_eq = "1.1.0"
rayon = "1.5.1"
21 changes: 7 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
[![Build and Tests](https://github.com/siddharthqs/RustyQLib/actions/workflows/rust.yml/badge.svg)](https://github.com/siddharthqs/RustyQLib/actions/workflows/rust.yml)
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)

![Crates.io](https://img.shields.io/crates/dr/rustyqlib)
![Crates.io](https://img.shields.io/crates/v/rustyqlib)
# RUSTYQLib :Pricing Options with Confidence using JSON
RustyQLib is a lightweight yet robust quantitative finance library designed for pricing options.
Built entirely in Rust, it offers a unique combination of safety, performance, and expressiveness that is crucial
for handling financial data and complex calculations. RustyQlib simplifies equity option pricing without compromising
on safety, speed, or usability.
for handling financial data and complex calculations. RustyQlib simplifies option pricing without compromising
on safety, speed, or usability. It uses JSON to make distributed computing easier and integration with other systems or your websites.
## License
RustyQlib is distributed under the terms of both the MIT license and the Apache License (Version 2.0).
See LICENSE-APACHE and LICENSE-MIT for details.
Expand All @@ -30,20 +31,13 @@ Sample input file is provided in the repository (src\input\equity_option.json)
Files are in JSON format and can be easily edited with any text editor.
## Features

### JSON Input Simplicity:
### JSON Simplicity:

- Ease of Use: Providing input data in JSON format is straightforward and human-readable.
You can specify the parameters of your options with ease, making complex financial modeling accessible to all.
- Portability: JSON is a platform-independent format, so you can use it on any operating system.
- 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.
- 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.
### JSON Output Clarity:
JSON Output Clarity
- Structured Results: RustQuant produces JSON output, that is your provided input with pricing results, Greeks, and risk profiles.
- Integration-Ready: You can seamlessly connect it to data sources, trading platforms, or other financial systems, simplifying your workflow and enhancing automation.

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

### Pricing engines:
- [x] Black Scholes
- [x] Binomial Tree
Expand Down
24 changes: 22 additions & 2 deletions src/equity/binomial.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ pub fn npv(option: &&EquityOption) -> f64 {
//println!("{:?}",tree);
// Calculate option prices at the final time step (backward induction)
let multiplier = if option.option_type == OptionType::Call { 1.0 } else { -1.0 };

for j in 0..=num_steps {
let spot_price_j = option.underlying_price.value * u.powi(num_steps as i32 - j as i32) * d.powi(j as i32);
tree[[j,num_steps]] = (multiplier*(spot_price_j - option.strike_price)).max(0.0);
Expand All @@ -33,7 +34,7 @@ pub fn npv(option: &&EquityOption) -> f64 {
ContractStyle::European => {
for i in (0..num_steps).rev() {
for j in 0..=i {
let spot_price_i = option.underlying_price.value * u.powi(i as i32 - j as i32) * d.powi(j as i32);
//let spot_price_i = option.underlying_price.value * u.powi(i as i32 - j as i32) * d.powi(j as i32);
let discounted_option_price = discount_factor * (p * tree[[ j,i+1]] + (1.0 - p) * tree[[ j + 1,i+1]]);
//tree[[j,i]] = (multiplier*(spot_price_i - option.strike_price)).max(discounted_option_price);
tree[[j,i]] = discounted_option_price;
Expand All @@ -42,7 +43,7 @@ pub fn npv(option: &&EquityOption) -> f64 {

}
ContractStyle::American => {
println!("American");

for i in (0..num_steps).rev() {
for j in 0..=i {
let spot_price_i = option.underlying_price.value * u.powi(i as i32 - j as i32) * d.powi(j as i32);
Expand Down Expand Up @@ -99,7 +100,26 @@ mod tests {
};
let mut option = EquityOption::from_json(&data);
option.valuation_date = NaiveDate::from_ymd(2023, 11, 06);
//Call European test
let npv = option.npv();
assert_approx_eq!(npv, 5.058163, 1e-6);
//Call American test
option.option_type = OptionType::Call;
option.style = ContractStyle::American;
let npv = option.npv();
assert_approx_eq!(npv, 5.058163, 1e-6);

//Put European test
option.option_type = OptionType::Put;
option.style = ContractStyle::European;
option.valuation_date = NaiveDate::from_ymd(2023, 11, 07);
let npv = option.npv();
assert_approx_eq!(npv, 4.259022688, 1e-6);

//Put American test
option.option_type = OptionType::Put;
option.style = ContractStyle::American;
let npv = option.npv();
assert_approx_eq!(npv, 4.315832381, 1e-6);
}
}
54 changes: 49 additions & 5 deletions src/equity/blackscholes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,6 @@ pub fn npv(bsd_option: &&EquityOption) -> f64 {
- bsd_option.strike_price
* exp(-bsd_option.risk_free_rate * bsd_option.time_to_maturity())
* N(bsd_option.d2());
let a = N(bsd_option.d1());
let b = N(bsd_option.d2());
println!("a = {:?} b = {:?}",a,b);
println!("{:?}",bsd_option);
return option_price;
} else {
let option_price = -bsd_option.underlying_price.value()
Expand Down Expand Up @@ -335,4 +331,52 @@ pub fn implied_volatility() {
io::stdin()
.read_line(&mut div)
.expect("Failed to read line");
}
}

#[cfg(test)]
mod tests {

use assert_approx_eq::assert_approx_eq;
use super::*;
use crate::core::utils::{Contract,MarketData};
use crate::core::trade::{OptionType,Transection};
use crate::core::utils::{ContractStyle};
use crate::equity::vanila_option::{EquityOption};

#[test]
fn test_black_scholes() {
let mut data = Contract {
action: "PV".to_string(),
market_data: Some(MarketData {
underlying_price: 100.0,
strike_price: 100.0,
volatility: Some(0.3),
option_price: Some(10.0),
risk_free_rate: Some(0.05),
dividend: Some(0.0),
maturity: "2024-01-01".to_string(),
option_type: "C".to_string(),
simulation: None
}),
pricer: "Analytical".to_string(),
asset: "".to_string(),
style: Some("European".to_string()),
rate_data: None
};

let mut option = EquityOption::from_json(&data);
option.valuation_date = NaiveDate::from_ymd(2023, 11, 06);
//Call European test
let npv = option.npv();
assert_approx_eq!(npv, 5.05933313, 1e-6);

//Put European test
option.option_type = OptionType::Put;
option.style = ContractStyle::European;
option.valuation_date = NaiveDate::from_ymd(2023, 11, 07);
let npv = option.npv();
assert_approx_eq!(npv,4.2601813, 1e-6);

}
}

4 changes: 2 additions & 2 deletions src/equity/vanila_option.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,12 @@ impl Instrument for EquityOption {
value
}
Engine::MonteCarlo => {
println!("Using MonteCarlo Engine ");

let value = montecarlo::npv(&self,false);
value
}
Engine::Binomial => {
println!("Using Binomial Engine ");

let value = binomial::npv(&self);
value
}
Expand Down
49 changes: 49 additions & 0 deletions src/examples/EQ/eq2.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
{"asset":"EQ",
"contracts" : [
{
"action":"PV",
"style":"American",
"pricer":"Binomial",
"asset":"EQ",
"market_data":{
"underlying_price":100,
"option_type":"P",
"strike_price":100,
"volatility":0.3,
"risk_free_rate":0.05,
"maturity":"2024-01-01",
"dividend": 0.0
}
},
{
"action":"PV",
"pricer":"Binomial",
"asset":"EQ",
"style":"American",
"market_data":{
"underlying_price":100,
"option_type":"P",
"strike_price":105,
"volatility":0.3,
"risk_free_rate":0.05,
"maturity":"2024-01-01",
"dividend": 0.0
}
},
{
"action":"PV",
"pricer":"Binomial",
"asset":"EQ",
"style":"American",
"market_data":{
"underlying_price":100,
"option_type":"P",
"strike_price":110,
"volatility":0.3,
"risk_free_rate":0.05,
"maturity":"2024-01-01",
"dividend": 0.0
}
}
]
}
38 changes: 20 additions & 18 deletions src/utils/parse_json.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,7 @@ use crate::cmdty::cmdty_option::{CmdtyOption};
use crate::core::trade;
use crate::cmdty::cmdty_option;
use crate::core::traits::{Instrument, Rates};
use crate::core::utils;
use crate::core::utils::{CombinedContract, ContractOutput, Contracts, OutputJson,EngineType};
use crate::core::utils::{Contract,CombinedContract, ContractOutput, Contracts, OutputJson,EngineType};
use crate::core::utils::ContractStyle;
use crate::core::traits::Greeks;
use std::io::Write;
Expand All @@ -26,7 +25,7 @@ use crate::rates::deposits::Deposit;
use crate::rates::build_contracts::{build_ir_contracts, build_ir_contracts_from_json, build_term_structure};
use crate::equity::build_contracts::{build_eq_contracts_from_json};
use crate::equity::vol_surface::VolSurface;

use rayon::prelude::*;
/// This function saves the output to a file and returns the path to the file.
pub fn save_to_file<'a>(output_folder: &'a str, subfolder: &'a str, filename: &'a str, output: &'a str) -> String {
let mut dir = std::path::PathBuf::from(output_folder);
Expand Down Expand Up @@ -88,35 +87,38 @@ pub fn parse_contract(mut file: &mut File,output_filename: &str) {

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

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

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


if data.action=="PV" && data.asset=="EQ"{
//let market_data = data.market_data.clone().unwrap();
let option = EquityOption::from_json(&data);

let contract_output = ContractOutput{pv:option.npv(),delta:option.delta(),gamma:option.gamma(),vega:option.vega(),theta:option.theta(),rho:option.rho(), error: None };
let contract_output = ContractOutput{pv:option.npv(),delta:option.delta(),gamma:option.gamma(),
vega:option.vega(),theta:option.theta(),rho:option.rho(), error: None };
println!("Theoretical Price ${}", contract_output.pv);
println!("Delta ${}", contract_output.delta);
let combined_ = CombinedContract{
contract: data,
contract: data.clone(),
output:contract_output
};
let output_json = serde_json::to_string(&combined_).expect("Failed to generate output");
Expand Down Expand Up @@ -155,11 +157,11 @@ pub fn process_contract(data: utils::Contract) -> String {
time_to_future_maturity: None,
risk_free_rate: None
};
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 };
let contract_output = ContractOutput{pv:option.npv(),delta:option.delta(),gamma:option.gamma(),vega:option.vega(),theta:option.theta(),rho:option.rho(), error: None };
println!("Theoretical Price ${}", contract_output.pv);
println!("Delta ${}", contract_output.delta);
let combined_ = utils::CombinedContract{
contract: data,
let combined_ = CombinedContract{
contract: data.clone(),
output:contract_output
};
let output_json = serde_json::to_string(&combined_).expect("Failed to generate output");
Expand Down

0 comments on commit a289d4c

Please sign in to comment.