From ed8d0680b15d18fc8c1a772046d5066b4cdb6832 Mon Sep 17 00:00:00 2001 From: Liberxue Date: Fri, 26 Jul 2024 16:25:38 +0800 Subject: [PATCH 1/3] #binomial tree Model init --- .github/workflows/rust.yml | 3 +- Cargo.lock | 116 +--------------------- Cargo.toml | 8 ++ README.md | 161 ++----------------------------- core/src/models/binomial_tree.rs | 34 +++++++ core/src/models/mod.rs | 5 + 6 files changed, 55 insertions(+), 272 deletions(-) create mode 100644 core/src/models/binomial_tree.rs diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 31419f7..501d96e 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -19,8 +19,7 @@ jobs: override: true - name: Check - run: cargo check --workspace --verbose + run: cargo check --workspace --verbose - name: Run tests run: cargo test --workspace --verbose - diff --git a/Cargo.lock b/Cargo.lock index 970c946..c39b284 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -70,12 +70,6 @@ dependencies = [ "libc", ] -[[package]] -name = "anes" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" - [[package]] name = "anstream" version = "0.6.14" @@ -216,9 +210,6 @@ name = "bench" version = "0.1.0" dependencies = [ "assert_cmd", - "criterion", - "serde", - "serde_json", ] [[package]] @@ -647,12 +638,6 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53" -[[package]] -name = "cast" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" - [[package]] name = "cc" version = "1.1.6" @@ -689,33 +674,6 @@ dependencies = [ "windows-targets 0.52.6", ] -[[package]] -name = "ciborium" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" -dependencies = [ - "ciborium-io", - "ciborium-ll", - "serde", -] - -[[package]] -name = "ciborium-io" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" - -[[package]] -name = "ciborium-ll" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" -dependencies = [ - "ciborium-io", - "half", -] - [[package]] name = "cipher" version = "0.4.4" @@ -931,42 +889,6 @@ dependencies = [ "cfg-if", ] -[[package]] -name = "criterion" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2b12d017a929603d80db1831cd3a24082f8137ce19c69e6447f54f5fc8d692f" -dependencies = [ - "anes", - "cast", - "ciborium", - "clap", - "criterion-plot", - "is-terminal", - "itertools 0.10.5", - "num-traits", - "once_cell", - "oorandom", - "plotters", - "rayon", - "regex", - "serde", - "serde_derive", - "serde_json", - "tinytemplate", - "walkdir", -] - -[[package]] -name = "criterion-plot" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1" -dependencies = [ - "cast", - "itertools 0.10.5", -] - [[package]] name = "crossbeam-channel" version = "0.5.13" @@ -2152,32 +2074,12 @@ version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" -[[package]] -name = "is-terminal" -version = "0.4.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f23ff5ef2b80d608d61efee834934d862cd92461afc0560dedf493e4c033738b" -dependencies = [ - "hermit-abi", - "libc", - "windows-sys 0.52.0", -] - [[package]] name = "is_terminal_polyfill" version = "1.70.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800" -[[package]] -name = "itertools" -version = "0.10.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" -dependencies = [ - "either", -] - [[package]] name = "itertools" version = "0.12.1" @@ -2650,12 +2552,6 @@ version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" -[[package]] -name = "oorandom" -version = "11.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b410bbe7e14ab526a0e86877eb47c6996a2bd7746f027ba551028c925390e4e9" - [[package]] name = "openssl" version = "0.10.66" @@ -3050,7 +2946,7 @@ dependencies = [ "cassowary", "crossterm", "indoc", - "itertools 0.12.1", + "itertools", "lru", "paste", "stability", @@ -3907,16 +3803,6 @@ dependencies = [ "time-core", ] -[[package]] -name = "tinytemplate" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" -dependencies = [ - "serde", - "serde_json", -] - [[package]] name = "tinyvec" version = "1.8.0" diff --git a/Cargo.toml b/Cargo.toml index d8235dd..3448f3a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,7 +4,15 @@ version = "0.1.0" edition = "2021" [workspace] +resolver = "2" + members = ["core", "bench", "cli", "providers"] +[workspace.package] +edition = "2021" +version = "0.14.0" +readme = "README.md" +license = "Apache-2.0" [dependencies] + diff --git a/README.md b/README.md index 7ba7d75..255745a 100644 --- a/README.md +++ b/README.md @@ -1,161 +1,12 @@ - The Certificate in Quantitative Finance | CQF For Rust Example +
+[![Rust Version](https://img.shields.io/badge/Rust-1.79.0+-blue)](https://releases.rs/docs/1.79.0) +--- +**Custom Neuron Decision-Making and Visual Workflow Orchestration Quantitative** -# Example -> Option Strategies +
+
-## Single Leg Option - -**Documentation:** -[Investopedia - Single Leg Option](https://www.investopedia.com/terms/s/single-option.aspx) - -**Example:** - -```rust -let call_price = SingleLegOption::call(&model, s, k, r, sigma, t); -let put_price = SingleLegOption::put(&model, s, k, r, sigma, t); -``` - -## Butterfly Spread - -**Documentation:** -[Investopedia - Butterfly Spread](https://www.investopedia.com/terms/b/butterflyspread.asp) - -**Example:** - -```rust -let butterfly_spread = ButterflySpread::new(&model, s, k1, k2, k3, r, sigma, t); -let price = butterfly_spread.price(); -``` - -## Vertical Spread - -**Documentation:** -[Investopedia - Vertical Spread](https://www.investopedia.com/terms/v/verticalspread.asp) - -**Example:** - -```rust -let bull_call_spread = VerticalSpread::new(&model, s, k1, k2, r, sigma, t, true); -let bear_put_spread = VerticalSpread::new(&model, s, k1, k2, r, sigma, t, false); -``` -## Covered Call - -**Documentation:** -[Investopedia - Covered Call](https://www.investopedia.com/terms/c/coveredcall.asp) - -**Example:** - -```rust -let covered_call = CoveredCall::new(&model, s, k, r, sigma, t); -let price = covered_call.price(); -``` -## Collar - -**Documentation:** -[Investopedia - Collar](https://www.investopedia.com/terms/c/collar.asp) - -**Example:** - -```rust -let collar = Collar::new(&model, s, k1, k2, r, sigma, t); -let price = collar.price(); -``` - -## Straddle - -**Documentation:** -[Investopedia - Straddle](https://www.investopedia.com/terms/s/straddle.asp) - -**Example:** - -```rust -let straddle = Straddle::new(&model, s, k, r, sigma, t); -let price = straddle.price(); -``` - -## Strangle - -**Documentation:** -[Investopedia - Strangle](https://www.investopedia.com/terms/s/strangle.asp) - -**Example:** - -```rust -let strangle = Strangle::new(&model, s, k1, k2, r, sigma, t); -let price = strangle.price(); -``` - -## Calendar Spread - -**Documentation:** -[Investopedia - Calendar Spread](https://www.investopedia.com/terms/c/calendarspread.asp) - -**Example:** - -```rust -let calendar_spread = CalendarSpread::new(&model, s, k, r, sigma, t1, t2); -let price = calendar_spread.price(); -``` - -## Diagonal Spread - -**Documentation:** -[Investopedia - Diagonal Spread](https://www.investopedia.com/terms/d/diagonalspread.asp) - -**Example:** - -```rust -let diagonal_spread = DiagonalSpread::new(&model, s, k1, k2, r, sigma, t1, t2); -let price = diagonal_spread.price(); -``` - -## Condor - -**Documentation:** -[Investopedia - Condor](https://www.investopedia.com/terms/c/condor.asp) - -**Example:** - -```rust -let condor = Condor::new(&model, s, k1, k2, k3, k4, r, sigma, t); -let price = condor.price(); -``` - -## Iron Butterfly - -**Documentation:** -[Investopedia - Iron Butterfly](https://www.investopedia.com/terms/i/ironbutterfly.asp) - -**Example:** - -```rust -let iron_butterfly = IronButterfly::new(&model, s, k1, k2, k3, r, sigma, t); -let price = iron_butterfly.price(); -``` - -## Iron Condor - -**Documentation:** -[Investopedia - Iron Condor](https://www.investopedia.com/terms/i/ironcondor.asp) - -**Example:** - -```rust -let iron_condor = IronCondor::new(&model, s, k1, k2, k3, k4, r, sigma, t); -let price = iron_condor.price(); -``` - -## Dance - -**Documentation:** -[Investopedia - Dance](https://www.investopedia.com/terms/d/dance.asp) - -**Example:** - -```rust -let dance = Dance::new(&model, s, k1, k2, k3, r, sigma, t); -let price = dance.price(); -``` # Contributing Contributions are welcome! Please open an issue or submit a pull request for any improvements or new features. diff --git a/core/src/models/binomial_tree.rs b/core/src/models/binomial_tree.rs new file mode 100644 index 0000000..2b60b54 --- /dev/null +++ b/core/src/models/binomial_tree.rs @@ -0,0 +1,34 @@ +use crate::models::{OptionParameters, OptionPricingModel}; + +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 +impl OptionPricingModel for BinomialTreeModel { + fn call_price(&self, _params: &OptionParameters) -> f64 { + unimplemented!() + } + fn put_price(&self, _params: &OptionParameters) -> f64 { + unimplemented!() + } + + fn delta(&self, _params: &OptionParameters) -> f64 { + unimplemented!() + } + + fn gamma(&self, _params: &OptionParameters) -> f64 { + unimplemented!() + } + + fn theta(&self, _params: &OptionParameters) -> f64 { + unimplemented!() + } + + fn vega(&self, _params: &OptionParameters) -> f64 { + unimplemented!() + } + + fn rho(&self, _params: &OptionParameters) -> f64 { + unimplemented!() + } +} diff --git a/core/src/models/mod.rs b/core/src/models/mod.rs index 07a39c5..20c85e4 100644 --- a/core/src/models/mod.rs +++ b/core/src/models/mod.rs @@ -1,5 +1,8 @@ +pub mod binomial_tree; pub mod black_scholes; 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/ @@ -18,6 +21,8 @@ pub struct OptionParameters { pub r: f64, pub sigma: f64, pub t: f64, + // pub steps: Option, // BinomialTreeModel + // pub is_call: Option, // BinomialTreeModel } /// A trait for option pricing models. From 1247051a4abe4ee00554fcb7fa76abdafc2e7d21 Mon Sep 17 00:00:00 2001 From: Liberxue Date: Sat, 27 Jul 2024 11:49:27 +0800 Subject: [PATCH 2/3] add binomial tree models docs && add unit test --- core/src/models/binomial_tree.rs | 214 ++++++++++++++++++++++++++++--- core/src/models/black_scholes.rs | 2 +- core/src/models/mod.rs | 4 +- core/tests/binmoia_tree_tests.rs | 104 +++++++++++++++ 4 files changed, 303 insertions(+), 21 deletions(-) create mode 100644 core/tests/binmoia_tree_tests.rs diff --git a/core/src/models/binomial_tree.rs b/core/src/models/binomial_tree.rs index 2b60b54..5e1e3cc 100644 --- a/core/src/models/binomial_tree.rs +++ b/core/src/models/binomial_tree.rs @@ -1,34 +1,214 @@ use crate::models::{OptionParameters, OptionPricingModel}; +use rand_distr::num_traits::real::Real; -pub struct BinomialTreeModel; +// +// +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 } } diff --git a/core/src/models/black_scholes.rs b/core/src/models/black_scholes.rs index 1467e76..8beea73 100644 --- a/core/src/models/black_scholes.rs +++ b/core/src/models/black_scholes.rs @@ -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: pub struct BlackScholesModel; impl OptionPricingModel for BlackScholesModel { diff --git a/core/src/models/mod.rs b/core/src/models/mod.rs index 20c85e4..0f216db 100644 --- a/core/src/models/mod.rs +++ b/core/src/models/mod.rs @@ -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 /// @@ -21,8 +21,6 @@ pub struct OptionParameters { pub r: f64, pub sigma: f64, pub t: f64, - // pub steps: Option, // BinomialTreeModel - // pub is_call: Option, // BinomialTreeModel } /// A trait for option pricing models. diff --git a/core/tests/binmoia_tree_tests.rs b/core/tests/binmoia_tree_tests.rs new file mode 100644 index 0000000..7e2e529 --- /dev/null +++ b/core/tests/binmoia_tree_tests.rs @@ -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(¶ms); + 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(¶ms); + 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(¶ms); + 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(¶ms); + 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(¶ms); + 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(¶ms); + 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(¶ms); + assert!(rho >= 0.0); +} From fbb14ec450d7a45cea7c48a29adc0f7d0ad2db04 Mon Sep 17 00:00:00 2001 From: Liberxue Date: Sat, 27 Jul 2024 13:54:00 +0800 Subject: [PATCH 3/3] Refactor binomial tree model to use enum for price initialization --- core/src/models/binomial_tree.rs | 104 +++++++++++++++++++------------ 1 file changed, 63 insertions(+), 41 deletions(-) diff --git a/core/src/models/binomial_tree.rs b/core/src/models/binomial_tree.rs index 5e1e3cc..48752c7 100644 --- a/core/src/models/binomial_tree.rs +++ b/core/src/models/binomial_tree.rs @@ -1,5 +1,4 @@ use crate::models::{OptionParameters, OptionPricingModel}; -use rand_distr::num_traits::real::Real; // // @@ -8,6 +7,11 @@ pub struct BinomialTreeModel { pub steps: usize, } +enum OptionType { + Call, + Put, +} + impl BinomialTreeModel { /// Creates a new `BinomialTreeModel` with a specified number of steps. /// @@ -17,47 +21,81 @@ impl BinomialTreeModel { pub fn new(steps: usize) -> Self { Self { steps } } -} -impl Default for BinomialTreeModel { - fn default() -> Self { - Self { steps: 100 } // Default number of steps is 100 - } -} - -impl OptionPricingModel for BinomialTreeModel { - /// Calculates the call option price using the binomial tree model. + /// Initializes the prices vector for call or put options. /// /// # Arguments /// /// * `params` - A reference to `OptionParameters` containing the parameters for the option. + /// * `option_type` - A value indicating the type of option (`Call` or `Put`). /// /// # Returns /// - /// The calculated call option price. - fn call_price(&self, params: &OptionParameters) -> f64 { + /// A vector containing the prices of the option at each node. + fn initialize_prices(&self, params: &OptionParameters, option_type: OptionType) -> Vec { 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 u = f64::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); - } + (0..=n) + .map(|i| { + let price = params.s * u.powi((n - i) as i32) * d.powi(i as i32); + match option_type { + OptionType::Call => (price - params.k).max(0.0), + OptionType::Put => (params.k - price).max(0.0), + } + }) + .collect() + } + /// Performs backward induction to calculate option price. + /// + /// # Arguments + /// + /// * `prices` - A mutable vector containing the prices of the option at each node. + /// * `params` - A reference to `OptionParameters` containing the parameters for the option. + /// + /// # Returns + /// + /// The calculated option price. + fn backward_induction(&self, prices: &mut Vec, 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 = f64::exp(params.sigma * (dt as f64).sqrt()); // Up factor + let d = 1.0 / u; // Down factor + let q = (f64::exp(params.r * dt as f64) - d) / (u - d); // Risk-neutral probability - // 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]); + f64::exp(-params.r * dt as f64) * (q * prices[i] + (1.0 - q) * prices[i + 1]); } } - prices[0] } +} + +impl Default for BinomialTreeModel { + fn default() -> Self { + Self { steps: 100 } // Default number of steps is 100 + } +} + +impl OptionPricingModel for BinomialTreeModel { + /// 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 mut prices = self.initialize_prices(params, OptionType::Call); + self.backward_induction(&mut prices, params) + } /// Calculates the put option price using the binomial tree model. /// @@ -69,24 +107,8 @@ impl OptionPricingModel for BinomialTreeModel { /// /// 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] + let mut prices = self.initialize_prices(params, OptionType::Put); + self.backward_induction(&mut prices, params) } /// Calculates the delta of the option using the binomial tree model. @@ -101,7 +123,7 @@ impl OptionPricingModel for BinomialTreeModel { 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 u = f64::exp(params.sigma * (dt as f64).sqrt()); let d = 1.0 / u; let up_params = OptionParameters { @@ -136,7 +158,7 @@ impl OptionPricingModel for BinomialTreeModel { 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 u = f64::exp(params.sigma * (dt as f64).sqrt()); let d = 1.0 / u; let delta_up = self.delta(&OptionParameters {