Skip to content

Commit

Permalink
refactor equity option and added volterm structure
Browse files Browse the repository at this point in the history
  • Loading branch information
siddharthqs committed Oct 25, 2023
1 parent 8656f07 commit aa9a5a8
Show file tree
Hide file tree
Showing 10 changed files with 158 additions and 214 deletions.
1 change: 1 addition & 0 deletions derivatives/src/core/quotes.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
#[derive(Debug)]
pub struct Quote{
pub value: f64,
pub bid: f64,
Expand Down
1 change: 1 addition & 0 deletions derivatives/src/core/termstructure.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use chrono::{DateTime, Local, NaiveDate};
#[derive(Debug)]
pub struct YieldTermStructure<T> {
pub date: Vec<T>,
pub rates: Vec<f64>
Expand Down
3 changes: 2 additions & 1 deletion derivatives/src/core/trade.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
#[derive(Debug,Clone)]
pub enum Transection {
Buy,
Sell,
}

#[derive(PartialEq, Debug)]
#[derive(PartialEq, Debug,Clone)]
pub enum OptionType {
Call,
Put,
Expand Down
4 changes: 2 additions & 2 deletions derivatives/src/equity/binomial.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@ use crate::core::utils::{ContractStyle};
use ndarray::Array2;
pub fn npv(option: &&EquityOption) -> f64 {
assert!(option.volatility >= 0.0);
assert!(option.time_to_maturity >= 0.0);
assert!(option.time_to_maturity() >= 0.0);
assert!(option.current_price.value >= 0.0);
let num_steps = 1000;

let dt = option.time_to_maturity / num_steps as f64;
let dt = option.time_to_maturity() / num_steps as f64;
let discount_factor = (-option.risk_free_rate * dt).exp();
// Calculate parameters for the binomial tree
let u = (option.volatility*dt.sqrt()).exp(); //up movement
Expand Down
75 changes: 41 additions & 34 deletions derivatives/src/equity/blackscholes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use libm::{exp, log};
use std::f64::consts::{PI, SQRT_2};
use std::{io, thread};
use crate::core::quotes::Quote;
use chrono::{Datelike, Local, NaiveDate};
//use utils::{N,dN};
//use vanila_option::{EquityOption,OptionType};
use crate::core::utils::{ContractStyle, dN, N};
Expand All @@ -13,23 +14,23 @@ use super::super::core::traits::{Instrument,Greeks};
use super::super::core::interpolation;

pub fn npv(bsd_option: &&EquityOption) -> f64 {
assert!(bsd_option.volatility >= 0.0);
assert!(bsd_option.time_to_maturity >= 0.0);
//assert!(bsd_option.volatility >= 0.0);
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)
* 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)
* exp(-bsd_option.risk_free_rate * bsd_option.time_to_maturity())
* N(bsd_option.d2());
return option_price;
} else {
let option_price = -bsd_option.underlying_price.value()
* N(-bsd_option.d1())
* exp(-bsd_option.dividend_yield * bsd_option.time_to_maturity)
* 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)
* exp(-bsd_option.risk_free_rate * bsd_option.time_to_maturity())
* N(-bsd_option.d2());
return option_price;
}
Expand All @@ -39,7 +40,7 @@ impl Greeks for EquityOption{
fn delta(&self) -> f64 {
let mut delta = N(self.d1());
if self.option_type == OptionType::Call {
delta = delta * exp(-self.dividend_yield * self.time_to_maturity);
delta = delta * exp(-self.dividend_yield * self.time_to_maturity());
} else if self.option_type == OptionType::Put {
delta = delta - 1.0;
}
Expand All @@ -48,32 +49,32 @@ impl Greeks for EquityOption{
fn gamma(&self) -> f64 {
let gamma = dN(self.d1());
//(St * sigma * math.sqrt(T - t))
let var_sqrt = self.volatility * (self.time_to_maturity.sqrt());
let var_sqrt = self.volatility * (self.time_to_maturity().sqrt());
return gamma / (self.current_price.value() * var_sqrt);
}
fn vega(&self) -> f64 {
//St * dN(d1) * math.sqrt(T - t)
let vega = self.current_price.value() * dN(self.d1()) * self.time_to_maturity.sqrt();
let vega = self.underlying_price.value * dN(self.d1()) * self.time_to_maturity().sqrt();
return vega;
}
fn theta(&self) -> f64 {
let mut theta = 0.0;
if self.option_type == OptionType::Call {
//-(St * dN(d1) * sigma / (2 * math.sqrt(T - t)) + r * K * math.exp(-r * (T - t)) * N(d2))
let t1 = -self.current_price.value() * dN(self.d1()) * self.volatility
/ (2.0 * self.time_to_maturity.sqrt());
/ (2.0 * self.time_to_maturity().sqrt());
let t2 = (self.risk_free_rate - self.dividend_yield)
* self.strike_price
* exp(-(self.risk_free_rate - self.dividend_yield) * self.time_to_maturity)
* exp(-(self.risk_free_rate - self.dividend_yield) * self.time_to_maturity())
* N(self.d2());
theta = t1 + t2;
} else if self.option_type == OptionType::Put {
//-(St * dN(d1) * sigma / (2 * math.sqrt(T - t)) - r * K * math.exp(-r * (T - t)) * N(d2))
let t1 = -self.current_price.value() * dN(self.d1()) * self.volatility
/ (2.0 * self.time_to_maturity.sqrt());
/ (2.0 * self.time_to_maturity().sqrt());
let t2 = (self.risk_free_rate - self.dividend_yield)
* self.strike_price
* exp(-(self.risk_free_rate - self.dividend_yield) * self.time_to_maturity)
* exp(-(self.risk_free_rate - self.dividend_yield) * self.time_to_maturity())
* N(self.d2());
theta = t1 - t2;
}
Expand All @@ -85,14 +86,14 @@ impl Greeks for EquityOption{
let mut rho = 0.0;
if self.option_type == OptionType::Call {
rho = self.strike_price
* self.time_to_maturity
* exp(-(self.risk_free_rate - self.dividend_yield) * self.time_to_maturity)
* self.time_to_maturity()
* exp(-(self.risk_free_rate - self.dividend_yield) * self.time_to_maturity())
* N(self.d2());
} else if self.option_type == OptionType::Put {
//put_rho = -K * (T - t) * math.exp(-r * (T - t)) * N(-d2)
rho = -self.strike_price
* self.time_to_maturity
* exp(-(self.risk_free_rate - self.dividend_yield) * self.time_to_maturity)
* self.time_to_maturity()
* exp(-(self.risk_free_rate - self.dividend_yield) * self.time_to_maturity())
* N(-self.d2());
}

Expand All @@ -103,7 +104,7 @@ impl Greeks for EquityOption{
impl EquityOption {
pub fn set_risk_free_rate(&mut self){
let model = interpolation::CubicSpline::new(&self.term_structure.date, &self.term_structure.rates);
let r = model.interpolation(self.time_to_maturity);
let r = model.interpolation(self.time_to_maturity());
self.risk_free_rate = r;
}
pub fn get_premium_at_risk(&self) -> f64 {
Expand All @@ -122,15 +123,15 @@ impl EquityOption {
}
pub fn d1(&self) -> f64 {
//Black-Scholes-Merton d1 function Parameters
let tmp1 = (self.current_price.value() / self.strike_price).ln()
let tmp1 = (self.underlying_price.value() / self.strike_price).ln()
+ (self.risk_free_rate - self.dividend_yield + 0.5 * self.volatility.powi(2))
* self.time_to_maturity;
* self.time_to_maturity();

let tmp2 = self.volatility * (self.time_to_maturity.sqrt());
let tmp2 = self.volatility * (self.time_to_maturity().sqrt());
return tmp1 / tmp2;
}
pub fn d2(&self) -> f64 {
let d2 = self.d1() - self.volatility * self.time_to_maturity.powf(0.5);
let d2 = self.d1() - self.volatility * self.time_to_maturity().powf(0.5);
return d2;
}
pub fn imp_vol(&mut self,option_price:f64) -> f64 {
Expand All @@ -140,10 +141,18 @@ impl EquityOption {
}
self.volatility
}
pub fn get_imp_vol(&mut self) -> f64 {
for i in 0..100{
let d_sigma = (self.npv()-self.current_price.value)/self.vega();
self.volatility -= d_sigma
}
self.volatility
}
}
pub fn option_pricing() {
println!("Welcome to the Black-Scholes Option pricer.");
println!("(Step 1/7) What is the current price of the underlying asset?");
print!(">>");
let mut curr_price = String::new();
io::stdin()
.read_line(&mut curr_price)
Expand Down Expand Up @@ -174,12 +183,13 @@ pub fn option_pricing() {
println!("Risk-free rate in %:");
let mut rf = String::new();
io::stdin().read_line(&mut rf).expect("Failed to read line");
println!("Time to maturity in years");
println!(" Maturity date in YYYY-MM-DD format:");

let mut expiry = String::new();
io::stdin()
.read_line(&mut expiry)
.expect("Failed to read line");

let future_date = NaiveDate::parse_from_str(&expiry, "%Y-%m-%d").expect("Invalid date format");
println!("Dividend yield on this stock:");
let mut div = String::new();
io::stdin()
Expand All @@ -201,14 +211,15 @@ pub fn option_pricing() {
current_price: Quote::new(0.0),
strike_price: strike.trim().parse::<f64>().unwrap(),
volatility: vol.trim().parse::<f64>().unwrap(),
time_to_maturity: expiry.trim().parse::<f64>().unwrap(),
maturity_date: future_date,
risk_free_rate: rf.trim().parse::<f64>().unwrap(),
dividend_yield: div.trim().parse::<f64>().unwrap(),
transection_price: 0.0,
term_structure: ts,
engine: Engine::BlackScholes,
simulation: None,
style: ContractStyle::European,
valuation_date: Local::today().naive_local(),
};
option.set_risk_free_rate();
println!("Theoretical Price ${}", option.npv());
Expand Down Expand Up @@ -260,12 +271,12 @@ pub fn implied_volatility() {
let mut rf = String::new();
io::stdin().read_line(&mut rf).expect("Failed to read line");

println!("Time to maturity in years");
println!(" Maturity date in YYYY-MM-DD format:");
let mut expiry = String::new();
io::stdin()
.read_line(&mut expiry)
.expect("Failed to read line");

let future_date = NaiveDate::parse_from_str(&expiry.trim(), "%Y-%m-%d").expect("Invalid date format");
println!("Dividend yield on this stock:");
let mut div = String::new();
io::stdin()
Expand All @@ -288,7 +299,7 @@ pub fn implied_volatility() {
current_price: Quote::new(0.0),
strike_price: strike.trim().parse::<f64>().unwrap(),
volatility: 0.20,
time_to_maturity: expiry.trim().parse::<f64>().unwrap(),
maturity_date: future_date,
risk_free_rate: rf.trim().parse::<f64>().unwrap(),
dividend_yield: div.trim().parse::<f64>().unwrap(),
transection_price: 0.0,
Expand All @@ -297,15 +308,11 @@ pub fn implied_volatility() {
simulation:sim,
//style:Option::from("European".to_string()),
style: ContractStyle::European,
valuation_date: Local::today().naive_utc(),
};
option.set_risk_free_rate();
println!("Implied Volatility {}%", 100.0*option.imp_vol(option_price.trim().parse::<f64>().unwrap()));
// println!("Premium at risk ${}", option.get_premium_at_risk());
// println!("Delata {}", 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();
io::stdin()
.read_line(&mut div)
Expand Down
95 changes: 11 additions & 84 deletions derivatives/src/equity/build_contracts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,98 +12,25 @@ use crate::rates::utils::{DayCountConvention};
use crate::core::quotes::Quote;
use crate::core::utils::{Contract,ContractStyle};
use crate::equity::utils::{Engine};
use std::collections::HashMap;
pub fn build_eq_contracts(data: Contract)-> Box<EquityOption>{
let market_data = data.market_data.clone().unwrap();
let underlying_quote = Quote::new( market_data.underlying_price);
let date = vec![0.01,0.02,0.05,0.1,0.5,1.0,2.0,3.0];
let rates = vec![0.01,0.02,0.05,0.07,0.08,0.1,0.11,0.12];
let ts = YieldTermStructure::new(date,rates);
let option_type = &market_data.option_type;
let side: trade::OptionType;
match option_type.trim() {
"C" | "c" | "Call" | "call" => side = trade::OptionType::Call,
"P" | "p" | "Put" | "put" => side = trade::OptionType::Put,
_ => panic!("Invalide side argument! Side has to be either 'C' or 'P'."),
}
let maturity_date = &market_data.maturity;
let today = Local::today();
let future_date = NaiveDate::parse_from_str(&maturity_date, "%Y-%m-%d").expect("Invalid date format");
let duration = future_date.signed_duration_since(today.naive_utc());
let year_fraction = duration.num_days() as f64 / 365.0;
let rf = Some(market_data.risk_free_rate).unwrap();
let div = Some(market_data.dividend).unwrap();
let price = Some(market_data.option_price).unwrap();
let option_price = Quote::new(price.unwrap());
let mut option = EquityOption {
option_type: side,
transection: trade::Transection::Buy,
underlying_price: underlying_quote,
current_price: option_price,
strike_price: market_data.strike_price,
volatility: 0.2,
time_to_maturity: year_fraction,
risk_free_rate: rf.unwrap_or(0.0),
dividend_yield: div.unwrap_or(0.0),
transection_price: 0.0,
term_structure: ts,
engine: Engine::BlackScholes,
simulation: None,
style: ContractStyle::European,
//style: Option::from(data.style.as_ref().unwrap_or(&default_style)).map(|x| &**x),
};
match data.pricer.trim() {
"Analytical" |"analytical" => {
option.engine = Engine::BlackScholes;
}
"MonteCarlo" |"montecarlo"|"MC" => {
option.engine = Engine::MonteCarlo;
}
"Binomial"|"binomial" => {
option.engine = Engine::Binomial;
}
_ => {
panic!("Invalid pricer");}
}
match data.style.as_ref().unwrap_or(&"European".to_string()).trim() {
"European" |"european" => {
option.style = ContractStyle::European;
}
"American" |"american" => {
option.style = ContractStyle::American;
}
_ => {
option.style = ContractStyle::European;}
}
option.set_risk_free_rate();
option.volatility = option.imp_vol(option.current_price.value);
return Box::new(option);
}
use std::collections::BTreeMap;

pub fn build_eq_contracts_from_json(data: Vec<Contract>) -> Vec<Box<EquityOption>> {
let mut derivatives:Vec<Box<EquityOption>> = Vec::new();
for contract in data {
let eq = build_eq_contracts(contract);
derivatives.push(eq);
}
let derivatives:Vec<Box<EquityOption>> = data.iter().map(|x| EquityOption::equityoption_from_json(x.clone())).collect();
return derivatives;
}
pub fn build_vol_surface(mut contracts:Vec<Box<EquityOption>>) -> VolSurface {
//let mut ts:rates::utils::TermStructure = rates::utils::TermStructure::new(vec![],vec![],vec![],
// rates::utils::DayCountConvention::Act360);
// let mut vol_surface:VolSurface = VolSurface::new(Default::default(), 0.0, spot_date: NaiveDate::from_ymd(, 2020),
// DayCountConvention::Act365);
let mut hash_map:HashMap<NaiveDate,Vec<(f64,f64)>> = HashMap::new();
let spot_date = Local::today();
pub fn build_volatility_surface(mut contracts:Vec<Box<EquityOption>>) -> VolSurface {

let mut vol_tree:BTreeMap<NaiveDate,Vec<(f64,f64)>> = BTreeMap::new();
let spot_date = contracts[0].valuation_date;
let spot_price = contracts[0].underlying_price.value;
for i in 0..contracts.len(){
let mut contract = contracts[i].as_mut();
let stike = contract.underlying_price.value / contract.strike_price as f64;
let vol = contract.volatility;
let maturity = contract.time_to_maturity;
hash_map.entry(maturity).or_insert(Vec::new()).push((stike,vol));
let moneyness = contract.underlying_price.value / contract.strike_price as f64;
let volatility = contract.get_imp_vol();
let maturity = contract.maturity_date;
vol_tree.entry(maturity).or_insert(Vec::new()).push((moneyness,volatility));
}
let vol_surface:VolSurface = VolSurface::new(hash_map, spot_price, spot_date,
let vol_surface:VolSurface = VolSurface::new(vol_tree, spot_price, spot_date,
DayCountConvention::Act365);
return vol_surface;
}
Loading

0 comments on commit aa9a5a8

Please sign in to comment.