diff --git a/Cargo.toml b/Cargo.toml index 49dc246..b61f76e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,3 +36,4 @@ bincode = "1.3.1" strum = "0.25" strum_macros = "0.25" ndarray = "0.15" +assert_approx_eq = "1.1.0" diff --git a/src/equity/binomial.rs b/src/equity/binomial.rs index 52ff7cf..902455a 100644 --- a/src/equity/binomial.rs +++ b/src/equity/binomial.rs @@ -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; @@ -25,7 +25,7 @@ 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); } @@ -33,23 +33,25 @@ 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.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"); @@ -58,4 +60,46 @@ pub fn npv(option: &&EquityOption) -> f64 { return tree[[0,0]]; -} \ No newline at end of file +} + +// 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); + } +} diff --git a/src/equity/blackscholes.rs b/src/equity/blackscholes.rs index 417ac2f..5debcfc 100644 --- a/src/equity/blackscholes.rs +++ b/src/equity/blackscholes.rs @@ -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() @@ -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(); @@ -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(); @@ -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::().unwrap()); let mut option = EquityOption { @@ -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() { diff --git a/src/equity/vanila_option.rs b/src/equity/vanila_option.rs index 1a6b716..7d312eb 100644 --- a/src/equity/vanila_option.rs +++ b/src/equity/vanila_option.rs @@ -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; @@ -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); diff --git a/src/main.rs b/src/main.rs index aa4117d..611ae49 100644 --- a/src/main.rs +++ b/src/main.rs @@ -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"); diff --git a/src/utils/parse_json.rs b/src/utils/parse_json.rs index cbe2e73..5fc7d75 100644 --- a/src/utils/parse_json.rs +++ b/src/utils/parse_json.rs @@ -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(); @@ -104,7 +104,7 @@ 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); @@ -112,10 +112,10 @@ pub fn process_contract(data: utils::Contract) -> String { //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 };