Skip to content

Commit

Permalink
fixed bug in binomical tree
Browse files Browse the repository at this point in the history
  • Loading branch information
siddharthqs committed Nov 7, 2023
1 parent 8787fcb commit 36467bb
Show file tree
Hide file tree
Showing 6 changed files with 72 additions and 16 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,4 @@ bincode = "1.3.1"
strum = "0.25"
strum_macros = "0.25"
ndarray = "0.15"
assert_approx_eq = "1.1.0"
54 changes: 49 additions & 5 deletions src/equity/binomial.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use ndarray::Array2;
pub fn npv(option: &&EquityOption) -> f64 {
assert!(option.volatility >= 0.0);
assert!(option.time_to_maturity() >= 0.0);
assert!(option.current_price.value >= 0.0);
assert!(option.underlying_price.value >= 0.0);
let num_steps = 1000;

let dt = option.time_to_maturity() / num_steps as f64;
Expand All @@ -25,31 +25,33 @@ pub fn npv(option: &&EquityOption) -> f64 {
// 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.current_price.value * u.powi(num_steps as i32 - j as i32) * d.powi(j as i32);
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);
}

match option.style {
ContractStyle::European => {
for i in (0..num_steps).rev() {
for j in 0..=i {
let spot_price_i = option.current_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;
}
}

}
ContractStyle::American => {
println!("American");
for i in (0..num_steps).rev() {
for j in 0..=i {
let spot_price_i = option.current_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 intrinsic_value = (multiplier*(spot_price_i - option.strike_price)).max(0.0);
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);
}
}

}
_ => {
panic!("Invalid option style");
Expand All @@ -58,4 +60,46 @@ pub fn npv(option: &&EquityOption) -> f64 {


return tree[[0,0]];
}
}

// Write a unit test for the binomial tree model

#[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};

use chrono::{NaiveDate};
use crate::core::traits::Instrument;


#[test]
fn test_binomial_tree() {
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: "Binomial".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);
let npv = option.npv();
assert_approx_eq!(npv, 5.058163, 1e-6);
}
}
16 changes: 12 additions & 4 deletions src/equity/blackscholes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,17 @@ pub fn npv(bsd_option: &&EquityOption) -> f64 {
assert!(bsd_option.time_to_maturity() >= 0.0);
assert!(bsd_option.underlying_price.value >= 0.0);
if bsd_option.option_type == OptionType::Call {

let option_price = bsd_option.underlying_price.value()
* N(bsd_option.d1())
* exp(-bsd_option.dividend_yield * bsd_option.time_to_maturity())
- 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 @@ -152,6 +157,7 @@ impl EquityOption {
}
pub fn option_pricing() {
println!("Welcome to the Black-Scholes Option pricer.");
print!(">>");
println!(" What is the current price of the underlying asset?");
print!(">>");
let mut curr_price = String::new();
Expand Down Expand Up @@ -199,6 +205,7 @@ pub fn option_pricing() {
println!("{:?}", expiry.trim());
let _d = expiry.trim();
let future_date = NaiveDate::parse_from_str(&_d, "%Y-%m-%d").expect("Invalid date format");
//println!("{:?}", future_date);
println!("Dividend yield on this stock:");
print!(">>");
let mut div = String::new();
Expand All @@ -211,7 +218,7 @@ pub fn option_pricing() {
// rates: vec![0.01,0.02,0.05,0.07,0.08,0.1,0.11,0.12]
//};
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.06,0.07,0.08,0.9,0.9,0.10];
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);
let curr_quote = Quote::new( curr_price.trim().parse::<f64>().unwrap());
let mut option = EquityOption {
Expand All @@ -231,17 +238,18 @@ pub fn option_pricing() {
style: ContractStyle::European,
valuation_date: Local::today().naive_local(),
};
println!("{:?}", option.time_to_maturity());
option.set_risk_free_rate();
println!("Theoretical Price ${}", option.npv());
println!("Premium at risk ${}", option.get_premium_at_risk());
println!("Delata {}", option.delta());
println!("Delta {}", option.delta());
println!("Gamma {}", option.gamma());
println!("Vega {}", option.vega() * 0.01);
println!("Theta {}", option.theta() * (1.0 / 365.0));
println!("Rho {}", option.rho() * 0.01);
let mut div1 = String::new();
let mut wait = String::new();
io::stdin()
.read_line(&mut div)
.read_line(&mut wait)
.expect("Failed to read line");
}
pub fn implied_volatility() {
Expand Down
8 changes: 5 additions & 3 deletions src/equity/vanila_option.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ impl EquityOption {
let underlying_quote = Quote::new(market_data.underlying_price);
//TODO: Add term structure
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.055, 0.06, 0.07, 0.07, 0.08, 0.1, 0.1];
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);
let option_type = &market_data.option_type;
let side: trade::OptionType;
Expand All @@ -79,8 +79,10 @@ impl EquityOption {

let risk_free_rate = Some(market_data.risk_free_rate).unwrap();
let dividend = Some(market_data.dividend).unwrap();
let option_price = Quote::new(match Some(market_data.option_price) {
Some(x) => x.unwrap(),
let mut op = 0.0;

let option_price = Quote::new(match market_data.option_price {
Some(x) => x,
None => 0.0,
});
//let volatility = Some(market_data.volatility);
Expand Down
1 change: 1 addition & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ fn main() {
let interactive_matches = matches.subcommand_matches("interactive");
match matches.subcommand(){
("build",Some(build_matches)) => {

let input_file = build_matches.value_of("input").unwrap();
let output_file = build_matches.value_of("output").unwrap();
let mut file = File::open(input_file).expect("Failed to open JSON file");
Expand Down
8 changes: 4 additions & 4 deletions src/utils/parse_json.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ pub fn parse_contract(mut file: &mut File,output_filename: &str) {
file.read_to_string(&mut contents)
.expect("Failed to read JSON file");

let list_contracts: utils::Contracts = serde_json::from_str(&contents).expect("Failed to deserialize JSON");
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();
Expand All @@ -104,18 +104,18 @@ pub fn parse_contract(mut file: &mut File,output_filename: &str) {
pub fn process_contract(data: utils::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.06,0.07,0.08,0.9,0.9,0.10];
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 = 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{
let combined_ = CombinedContract{
contract: data,
output:contract_output
};
Expand Down

0 comments on commit 36467bb

Please sign in to comment.