Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 11 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,8 @@ Quantrs supports options pricing with various models for both vanilla and exotic
| ²Double Barrier | ❌ (mod. BSM) | ❌ | ⏳ | ⏳ | ❌ (complex) | ⏳ |
| ²Asian (fixed strike) | ❌ (mod. BSM) | ❌ | ❌ | ✅ | ⏳ | ⏳ |
| ²Asian (floating strike) | ❌ (mod. BSM) | ❌ | ❌ | ✅ | ⏳ | ⏳ |
| ²Lookback (fixed strike) | | ❌ | ❌ | | ⏳ | ⏳ |
| ²Lookback (floating strike) | | ❌ | ❌ | | ⏳ | ⏳ |
| ²Lookback (fixed strike) | | ❌ | ❌ | | ⏳ | ⏳ |
| ²Lookback (floating strike) | | ❌ | ❌ | | ⏳ | ⏳ |
| ²Binary Cash-or-Nothing | ✅ | ❌ | ✅ | ✅ | ❌ (mod. PDE) | ⏳ |
| ²Binary Asset-or-Nothing | ✅ | ❌ | ✅ | ✅ | ❌ (mod. PDE) | ⏳ |
| Greeks (Δ,ν,Θ,ρ,Γ) | ✅ | ✅ | ⏳ | ❌ | ❌ | ❌ |
Expand Down Expand Up @@ -95,7 +95,7 @@ Add this to your `Cargo.toml`:
quantrs = "0.1.6"
```

Now if you want to e.g., model binary call options using the Black-Scholes model, you can:
Now if you want to e.g., calculate the arbitrage-free price of a binary cash-or-nothing call using the Black-Scholes model, you can:

```rust
use quantrs::options::*;
Expand Down Expand Up @@ -131,7 +131,7 @@ Greeks { delta: 0.013645840354947947, gamma: -0.0008813766475726433, theta: 0.17

Quantrs also supports plotting option prices and strategies using the `plotters` backend.

E.g., Plot the P/L of a slightly skewed Condor spread using the Monte-Carlo model:
E.g., Plot the P/L of a slightly skewed Condor spread consisting of fixed-strike Asian calls using the Monte-Carlo model with the geometric average price path:

<details>
<summary><i>Click to see example code</i></summary>
Expand All @@ -142,20 +142,20 @@ use quantrs::options::*;
// Create a new instrument with a spot price of 100 and a dividend yield of 2%
let instrument = Instrument::new().with_spot(100.0).with_cont_yield(0.02);

// Create a vector of European call options with different strike prices
// Create a vector of fixed-strike Asian calls options with different strike prices
let options = vec![
EuropeanOption::new(instrument.clone(), 85.0, 1.0, Call),
EuropeanOption::new(instrument.clone(), 95.0, 1.0, Call),
EuropeanOption::new(instrument.clone(), 102.0, 1.0, Call),
EuropeanOption::new(instrument.clone(), 115.0, 1.0, Call),
AsianOption::fixed(instrument.clone(), 85.0, 1.0, Call),
AsianOption::fixed(instrument.clone(), 95.0, 1.0, Call),
AsianOption::fixed(instrument.clone(), 102.0, 1.0, Call),
AsianOption::fixed(instrument.clone(), 115.0, 1.0, Call),
];

// Create a new Monte-Carlo model with:
// - Risk-free interest rate (r) = 5%
// - Volatility (σ) = 20%
// - Number of simulations = 10,000
// - Number of time steps = 365
let model = MonteCarloModel::geometric(0.05, 0.2, 10_000, 365);
// - Number of time steps = 252
let model = MonteCarloModel::geometric(0.05, 0.2, 10_000, 252);

// Plot a breakdown of the Condor spread with a spot price range of [80,120]
model.plot_strategy_breakdown(
Expand Down
Binary file modified examples/images/strategy.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
24 changes: 12 additions & 12 deletions examples/options_pricing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -711,24 +711,24 @@ fn example_strategy() {
}

fn example_plots() {
// Create a new Monte-Carlo model with:
// - Risk-free interest rate (r) = 5%
// - Volatility (σ) = 20%
// - Number of simulations = 10,000
// - Number of time steps = 365
let model = MonteCarloModel::geometric(0.05, 0.2, 10_000, 365);

// Create a new instrument with a spot price of 100 and a dividend yield of 2%
let instrument = Instrument::new().with_spot(100.0).with_cont_yield(0.02);

// Create a vector of European call options with different strike prices
// Create a vector of fixed-strike Asian calls options with different strike prices
let options = vec![
EuropeanOption::new(instrument.clone(), 85.0, 1.0, Call),
EuropeanOption::new(instrument.clone(), 95.0, 1.0, Call),
EuropeanOption::new(instrument.clone(), 102.0, 1.0, Call),
EuropeanOption::new(instrument.clone(), 115.0, 1.0, Call),
AsianOption::fixed(instrument.clone(), 85.0, 1.0, Call),
AsianOption::fixed(instrument.clone(), 95.0, 1.0, Call),
AsianOption::fixed(instrument.clone(), 102.0, 1.0, Call),
AsianOption::fixed(instrument.clone(), 115.0, 1.0, Call),
];

// Create a new Monte-Carlo model with:
// - Risk-free interest rate (r) = 5%
// - Volatility (σ) = 20%
// - Number of simulations = 10,000
// - Number of time steps = 252
let model = MonteCarloModel::geometric(0.05, 0.2, 10_000, 252);

// Plot a breakdown of the Condor spread with a spot price range of [80,120]
let _ = model.plot_strategy_breakdown(
"Condor Example",
Expand Down
32 changes: 8 additions & 24 deletions src/options/instrument.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,6 @@ use rand_distr::{Distribution, Normal};
pub struct Instrument {
/// Current price of the underlying asset or future price at time 0.
pub spot: Vec<f64>,
/// Maximum spot price of the underlying asset.
pub max_spot: f64,
/// Minimum spot price of the underlying asset.
pub min_spot: f64,
/// Continuous dividend yield where the dividend amount is proportional to the level of the underlying asset (e.g., 0.02 for 2%).
pub continuous_dividend_yield: f64,
/// Discrete proportional dividend yield (e.g., 0.02 for 2%).
Expand All @@ -55,8 +51,6 @@ impl Instrument {
pub fn new() -> Self {
Self {
spot: vec![0.0],
max_spot: 0.0,
min_spot: 0.0,
continuous_dividend_yield: 0.0,
discrete_dividend_yield: 0.0,
dividend_times: Vec::new(),
Expand Down Expand Up @@ -93,32 +87,22 @@ impl Instrument {
self
}

/// Set the maximum spot price of the instrument.
/// Get the maximum spot price of the instrument.
///
/// # Arguments
///
/// * `max_spot` - The maximum spot price of the instrument.
///
/// # Returns
///
/// The instrument with the maximum spot price set.
pub fn with_max_spot(mut self, max_spot: f64) -> Self {
self.max_spot = max_spot;
self
/// The maximum spot price of the instrument.
pub fn max_spot(&self) -> f64 {
*self.spot.iter().max_by(|x, y| x.total_cmp(y)).unwrap()
}

/// Set the minimum spot price of the instrument.
///
/// # Arguments
///
/// * `min_spot` - The minimum spot price of the instrument.
/// Get the minimum spot price of the instrument.
///
/// # Returns
///
/// The instrument with the minimum spot price set.
pub fn with_min_spot(mut self, min_spot: f64) -> Self {
self.min_spot = min_spot;
self
/// The minimum spot price of the instrument.
pub fn min_spot(&self) -> f64 {
*self.spot.iter().min_by(|x, y| x.total_cmp(y)).unwrap()
}

/// Set the continuous dividend yield of the instrument.
Expand Down
93 changes: 56 additions & 37 deletions src/options/models/black_scholes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,48 +15,14 @@
//! including the current price of the underlying asset, the strike price of the option, the time to expiration, the risk-free interest rate,
//! and the volatility of the underlying asset.
//!
//! ## Formula
//!
//! The price of an option using the Black-Scholes model is calculated as follows:
//!
//! ```text
//! C = S * N(d1) - X * e^(-rT) * N(d2) for a call option
//! P = X * e^(-rT) * N(-d2) - S * N(-d1) for a put option
//! ```
//!
//! where:
//! - `C` is the price of the call option.
//! - `P` is the price of the put option.
//! - `S` is the current price of the underlying asset.
//! - `X` is the strike price of the option.
//! - `r` is the risk-free interest rate.
//! - `T` is the time to maturity.
//! - `N` is the cumulative distribution function of the standard normal distribution.
//! - `d1` and `d2` are calculated as follows:
//! ```text
//! d1 = (ln(S / X) + (r + 0.5 * σ^2) * T) / (σ * sqrt(T))
//! d2 = d1 - σ * sqrt(T)
//! ```
//! - `σ` is the volatility of the underlying asset.
//!
//! The payoff of the option is calculated as:
//!
//! ```text
//! payoff = max(ST - K, 0) for a call option
//! payoff = max(K - ST, 0) for a put option
//! ```
//!
//! where:
//! - `ST` is the price of the underlying asset at maturity.
//! - `K` is the strike price of the option.
//! - `max` is the maximum function.
//!
//! ## References
//!
//! - [Wikipedia - Black-Scholes model](https://en.wikipedia.org/wiki/Black%E2%80%93Scholes_model)
//! - [Black-Scholes Calculator](https://www.math.drexel.edu/~pg/fin/VanillaCalculator.html)
//! - [Cash or Nothing Options' Greeks](https://quantpie.co.uk/bsm_bin_c_formula/bs_bin_c_summary.php)
//! - [Asset or Nothing Options' Greeks](https://quantpie.co.uk/bsm_bin_a_formula/bs_bin_a_summary.php)
//! - Musiela, M., Rutkowski, M. Martingale Methods in Financial Modelling, 2nd Ed Springer, 2007
//! - Joshi, M. The Concepts and Practice of Mathematical Finance, 2nd Ed Cambridge University Press, 2008
//!
//! ## Example
//!
Expand All @@ -74,8 +40,9 @@
use crate::options::{
types::BinaryType::{AssetOrNothing, CashOrNothing},
Instrument, Option, OptionGreeks, OptionPricing, OptionStrategy, OptionStyle, OptionType,
RainbowType,
Permutation, RainbowType,
};
use rand_distr::num_traits::Pow;
use statrs::distribution::{Continuous, ContinuousCDF, Normal};

/// A struct representing a Black-Scholes model.
Expand Down Expand Up @@ -297,6 +264,57 @@ impl BlackScholesModel {
)
}

/// Calculate the price of a lookback option using the Black-Scholes formula.
///
/// # Arguments
///
/// * `option` - The option to price.
/// * `normal` - The standard normal distribution.
///
/// # Returns
///
/// The price of the option.
pub fn price_lookback<T: Option>(&self, option: &T, normal: &Normal) -> f64 {
let max = option.instrument().max_spot();
let min = option.instrument().min_spot();
let sqrt_t = option.time_to_maturity().sqrt();
let s = option.instrument().spot();
let r = self.risk_free_rate;
let t = option.time_to_maturity();
let vola = self.volatility;

assert!(s > 0.0 && max > 0.0 && min > 0.0, "Spot prices must be > 0");

println!("max: {}, min: {}", max, min);

let a1 = |s: f64, h: f64| ((s / h).ln() + (r + 0.5 * vola.powi(2)) * t) / (vola * sqrt_t);
let a2 = |s: f64, h: f64| a1(s, h) - vola * sqrt_t;
let a3 = |s: f64, h: f64| a1(s, h) - 2.0 * r * sqrt_t / vola;

let phi = |x: f64| normal.cdf(x);

match option.option_type() {
OptionType::Call => {
s * phi(a1(s, min))
- min * (-r * t).exp() * phi(a2(s, min))
- (0.5 * s * vola.powi(2)) / (r)
* (phi(-a1(s, min))
- (-r * t).exp()
* (min / s).pow((2f64 * r) / (vola.powi(2)))
* phi(-a3(s, min)))
}
OptionType::Put => {
-s * phi(-a1(s, max))
+ max * (-r * t).exp() * phi(-a2(s, max))
+ (0.5 * s * vola.powi(2)) / (r)
* (phi(a1(s, max))
- (-r * t).exp()
* (max / s).pow((2f64 * r) / (vola.powi(2)))
* phi(a3(s, max)))
}
}
}

/// Calculate the option price using the Black-Scholes formula with a given volatility.
///
/// # Arguments
Expand Down Expand Up @@ -369,6 +387,7 @@ impl OptionPricing for BlackScholesModel {
(_, OptionStyle::Binary(AssetOrNothing)) => self.price_asset_or_nothing(option, &normal),
(OptionType::Call, OptionStyle::Rainbow(_)) => self.price_rainbow_call(option, &normal),
(OptionType::Put, OptionStyle::Rainbow(_)) => self.price_rainbow_put(option, &normal),
(_, OptionStyle::Lookback(Permutation::Floating)) => self.price_lookback(option, &normal),
_ => panic!("BlackScholesModel does not support this option type or style"),
}
}
Expand Down
Loading