Skip to content

Commit

Permalink
add binomial tree models docs && add unit test
Browse files Browse the repository at this point in the history
  • Loading branch information
Liberxue committed Jul 27, 2024
1 parent ed8d068 commit 1247051
Show file tree
Hide file tree
Showing 4 changed files with 303 additions and 21 deletions.
214 changes: 197 additions & 17 deletions core/src/models/binomial_tree.rs
Original file line number Diff line number Diff line change
@@ -1,34 +1,214 @@
use crate::models::{OptionParameters, OptionPricingModel};
use rand_distr::num_traits::real::Real;

pub struct BinomialTreeModel;
// <https://www.kent.ac.uk/learning/documents/slas-documents/Binomial_models.pdf >
// <https://www.le.ac.uk/users/dsgp1/COURSES/DERIVATE/BINOPTION.PDF >
pub struct BinomialTreeModel {
/// Number of steps in the binomial tree model.
pub steps: usize,
}

impl BinomialTreeModel {
/// Creates a new `BinomialTreeModel` with a specified number of steps.
///
/// # Arguments
///
/// * `steps` - Number of steps in the binomial tree model.
pub fn new(steps: usize) -> Self {
Self { steps }
}
}

impl Default for BinomialTreeModel {
fn default() -> Self {
Self { steps: 100 } // Default number of steps is 100
}
}

// https://www.kent.ac.uk/learning/documents/slas-documents/Binomial_models.pdf
// https://www.le.ac.uk/users/dsgp1/COURSES/DERIVATE/BINOPTION.PDF
impl OptionPricingModel for BinomialTreeModel {
fn call_price(&self, _params: &OptionParameters) -> f64 {
unimplemented!()
/// Calculates the call option price using the binomial tree model.
///
/// # Arguments
///
/// * `params` - A reference to `OptionParameters` containing the parameters for the option.
///
/// # Returns
///
/// The calculated call option price.
fn call_price(&self, params: &OptionParameters) -> f64 {
let n = self.steps; // Number of steps in the binomial tree
let dt = params.t / (n as f64); // Time step size
let u = Real::exp(params.sigma * (dt as f64).sqrt()); // Up factor
let d = 1.0 / u; // Down factor
let q = (Real::exp(params.r * dt as f64) - d) / (u - d); // Risk-neutral probability
let mut prices = vec![0.0; n + 1];

// Terminal prices
for i in 0..=n {
prices[i] = (params.s * u.powi((n - i) as i32) * d.powi(i as i32) - params.k).max(0.0);
}

// Backward induction
for j in (0..n).rev() {
for i in 0..=j {
prices[i] =
Real::exp(-params.r * dt as f64) * (q * prices[i] + (1.0 - q) * prices[i + 1]);
}
}

prices[0]
}
fn put_price(&self, _params: &OptionParameters) -> f64 {
unimplemented!()

/// Calculates the put option price using the binomial tree model.
///
/// # Arguments
///
/// * `params` - A reference to `OptionParameters` containing the parameters for the option.
///
/// # Returns
///
/// The calculated put option price.
fn put_price(&self, params: &OptionParameters) -> f64 {
let n = self.steps; // Number of steps in the binomial tree
let dt = params.t / (n as f64); // Time step size
let u = Real::exp(params.sigma * (dt as f64).sqrt()); // Up factor
let d = 1.0 / u; // Down factor
let q = (Real::exp(params.r * dt as f64) - d) / (u - d); // Risk-neutral probability
let mut prices = vec![0.0; n + 1];
for i in 0..=n {
prices[i] = (params.k - params.s * u.powi((n - i) as i32) * d.powi(i as i32)).max(0.0);
}
// Backward induction
for j in (0..n).rev() {
for i in 0..=j {
prices[i] =
Real::exp(-params.r * dt as f64) * (q * prices[i] + (1.0 - q) * prices[i + 1]);
}
}

prices[0]
}

fn delta(&self, _params: &OptionParameters) -> f64 {
unimplemented!()
/// Calculates the delta of the option using the binomial tree model.
///
/// # Arguments
///
/// * `params` - A reference to `OptionParameters` containing the parameters for the option.
///
/// # Returns
///
/// The calculated delta.
fn delta(&self, params: &OptionParameters) -> f64 {
let n = self.steps;
let dt = params.t / (n as f64);
let u = Real::exp(params.sigma * (dt as f64).sqrt());
let d = 1.0 / u;

let up_params = OptionParameters {
s: params.s * u,
..params.clone()
};
let params = OptionParameters {
s: params.s * d,
..params.clone()
};

let down_params = OptionParameters {
s: params.s * d,
..params.clone()
};

let delta_up = self.call_price(&up_params);
let delta_down = self.call_price(&down_params);

(delta_up - delta_down) / (params.s * (u - d))
}

fn gamma(&self, _params: &OptionParameters) -> f64 {
unimplemented!()
/// Calculates the gamma of the option using the binomial tree model.
///
/// # Arguments
///
/// * `params` - A reference to `OptionParameters` containing the parameters for the option.
///
/// # Returns
///
/// The calculated gamma.
fn gamma(&self, params: &OptionParameters) -> f64 {
let n = self.steps;
let dt = params.t / (n as f64);
let u = Real::exp(params.sigma * (dt as f64).sqrt());
let d = 1.0 / u;

let delta_up = self.delta(&OptionParameters {
s: params.s * u,
..params.clone()
});
let delta_down = self.delta(&OptionParameters {
s: params.s * d,
..params.clone()
});

(delta_up - delta_down) / (0.5 * params.s * (u - d))
}
/// Calculates the theta of the option using the binomial tree model.
///
/// # Arguments
///
/// * `params` - A reference to `OptionParameters` containing the parameters for the option.
///
/// # Returns
///
/// The calculated theta.
fn theta(&self, params: &OptionParameters) -> f64 {
let epsilon = 1e-5;
let new_params = OptionParameters {
t: params.t - epsilon,
..params.clone()
};
let call_price_t1 = self.call_price(params);
let call_price_t2 = self.call_price(&new_params);

fn theta(&self, _params: &OptionParameters) -> f64 {
unimplemented!()
(call_price_t2 - call_price_t1) / epsilon
}
/// Calculates the vega of the option using the binomial tree model.
///
/// # Arguments
///
/// * `params` - A reference to `OptionParameters` containing the parameters for the option.
///
/// # Returns
///
/// The calcula ted vega.
fn vega(&self, params: &OptionParameters) -> f64 {
let epsilon = 1e-5;

fn vega(&self, _params: &OptionParameters) -> f64 {
unimplemented!()
let call_price_sigma1 = self.call_price(params);
let call_price_sigma2 = self.call_price(&OptionParameters {
sigma: params.sigma + epsilon,
..params.clone()
});

(call_price_sigma2 - call_price_sigma1) / epsilon
}

fn rho(&self, _params: &OptionParameters) -> f64 {
unimplemented!()
/// Calculates the rho of the option using the binomial tree model.
///
/// # Arguments
///
/// * `params` - A reference to `OptionParameters` containing the parameters for the option.
///
/// # Returns
///
/// The calculated rho.
fn rho(&self, params: &OptionParameters) -> f64 {
let epsilon = 1e-5;
let new_params = OptionParameters {
r: params.r + epsilon,
..params.clone()
};
let call_price_r1 = self.call_price(params);
let call_price_r2 = self.call_price(&new_params);

(call_price_r2 - call_price_r1) / epsilon
}
}
2 changes: 1 addition & 1 deletion core/src/models/black_scholes.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use crate::models::{OptionParameters, OptionPricingModel};

/// A Black-Scholes model for pricing European call and put options.
/// ref: https://en.wikipedia.org/wiki/Black–Scholes_model
/// ref:<https://en.wikipedia.org/wiki/Black–Scholes_model>
pub struct BlackScholesModel;

impl OptionPricingModel for BlackScholesModel {
Expand Down
4 changes: 1 addition & 3 deletions core/src/models/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ pub mod monte_carlo;
pub use binomial_tree::BinomialTreeModel;
pub use black_scholes::BlackScholesModel;
pub use monte_carlo::MonteCarloModel;
/// Parameters for option pricing models. ref: https://www.macroption.com/option-greeks-excel/
/// Parameters for option pricing models
///
/// # Fields
///
Expand All @@ -21,8 +21,6 @@ pub struct OptionParameters {
pub r: f64,
pub sigma: f64,
pub t: f64,
// pub steps: Option<usize>, // BinomialTreeModel
// pub is_call: Option<bool>, // BinomialTreeModel
}

/// A trait for option pricing models.
Expand Down
104 changes: 104 additions & 0 deletions core/tests/binmoia_tree_tests.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
extern crate core;
use core::models::{BinomialTreeModel, OptionParameters, OptionPricingModel};

#[test]
fn test_call_price() {
let params = OptionParameters {
s: 100.0,
k: 100.0,
r: 0.05,
sigma: 0.2,
t: 1.0,
};
let model = BinomialTreeModel::default();
let price = model.call_price(&params);
println!("Call Price: {}", price);
assert!(price >= 0.0);
}

#[test]
fn test_put_price() {
let params = OptionParameters {
s: 100.0,
k: 100.0,
r: 0.05,
sigma: 0.2,
t: 1.0,
};
let model = BinomialTreeModel::default();
let price = model.put_price(&params);
println!("Put Price: {}", price);
assert!(price >= 0.0);
}

#[test]
fn test_delta() {
let params = OptionParameters {
s: 100.0,
k: 100.0,
r: 0.05,
sigma: 0.2,
t: 1.0,
};
let model = BinomialTreeModel::default();
let delta = model.delta(&params);
println!("Delta: {}", delta);
assert!(delta >= -1.0 && delta <= 1.0);
}

#[test]
fn test_gamma() {
let params = OptionParameters {
s: 100.0,
k: 100.0,
r: 0.05,
sigma: 0.2,
t: 1.0,
};
let model = BinomialTreeModel::default();
let gamma = model.gamma(&params);
assert!(gamma >= 0.0);
}

#[test]
fn test_theta() {
let params = OptionParameters {
s: 100.0,
k: 100.0,
r: 0.05,
sigma: 0.2,
t: 1.0,
};
let model = BinomialTreeModel::default();
let theta = model.theta(&params);
println!("Theta: {}", theta);
assert!(theta <= 0.0);
}

#[test]
fn test_vega() {
let params = OptionParameters {
s: 100.0,
k: 100.0,
r: 0.05,
sigma: 0.2,
t: 1.0,
};
let model = BinomialTreeModel::default();
let vega = model.vega(&params);
assert!(vega >= 0.0);
}

#[test]
fn test_rho() {
let params = OptionParameters {
s: 100.0,
k: 100.0,
r: 0.05,
sigma: 0.2,
t: 1.0,
};
let model = BinomialTreeModel::default();
let rho = model.rho(&params);
assert!(rho >= 0.0);
}

0 comments on commit 1247051

Please sign in to comment.