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
14 changes: 10 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ Quantrs supports options pricing with various models for both vanilla and exotic

| | Black-Scholes | Black-76 | Lattice | ³Monte-Carlo | Finite Diff | Heston |
| --------------------------- | --------------- | -------- | ------------ | ------------ | ------------- | ------ |
| European | ✅ | | ✅ | ✅ | ⏳ | ⏳ |
| European | ✅ | | ✅ | ✅ | ⏳ | ⏳ |
| American | ❌ | ❌ | ✅ | ❌ (L. Sq.) | ⏳ | ❌ |
| Bermudan | ❌ | ❌ | ⏳ | ❌ (L. Sq.) | ❌ (complex) | ❌ |
| ¹Basket | ⏳ (∀component) | ❌ | ⏳ (approx.) | ⏳ | ❌ | ❌ |
Expand All @@ -47,16 +47,18 @@ Quantrs supports options pricing with various models for both vanilla and exotic
| ²Asian (floating strike) | ❌ (mod. BSM) | ❌ | ❌ | ✅ | ⏳ | ⏳ |
| ²Lookback (fixed strike) | ⏳ | ❌ | ❌ | ⏳ | ⏳ | ⏳ |
| ²Lookback (floating strike) | ⏳ | ❌ | ❌ | ⏳ | ⏳ | ⏳ |
| ²Binary Cash-or-Nothing | ✅ | | ✅ | ✅ | ❌ (mod. PDE) | ⏳ |
| ²Binary Asset-or-Nothing | ✅ | | ✅ | ✅ | ❌ (mod. PDE) | ⏳ |
| Greeks (Δ,ν,Θ,ρ,Γ) | ✅ | | ⏳ | ❌ | ❌ | ❌ |
| ²Binary Cash-or-Nothing | ✅ | | ✅ | ✅ | ❌ (mod. PDE) | ⏳ |
| ²Binary Asset-or-Nothing | ✅ | | ✅ | ✅ | ❌ (mod. PDE) | ⏳ |
| Greeks (Δ,ν,Θ,ρ,Γ) | ✅ | | ⏳ | ❌ | ❌ | ❌ |
| Implied Volatility | ✅ | ⏳ | ⏳ | ❌ | ❌ | ❌ |

> ¹ _"Exotic" options with standard exercise style; only differ in their payoff value_\
> ² _Non-vanilla path-dependent "exotic" options_\
> ³ _MC simulates underlying price paths based on geometric Brownian motion for Black-Scholes models and geometric average price paths for Asian and Lookback options_\
> ✅ = Supported, ⏳ = Planned / In progress, ❌ = Not supported / Not applicable

<!--Bachelier and Modified Bachelier-->

</details>

<details>
Expand Down Expand Up @@ -211,6 +213,10 @@ See [OUTLOOK.md](OUTLOOK.md) for a list of planned features and improvements.

If you find any bugs or have suggestions for improvement, please open a new issue or submit a pull request.

## Disclaimer

This library is not intended for professional use. It is a hobby project and should be treated as such.

## License

This project is licensed under either of:
Expand Down
39 changes: 28 additions & 11 deletions examples/options_pricing.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
// Run: cargo run --release --example options_pricing

use quantrs::options::{
AsianOption, BinaryOption, BinomialTreeModel, BlackScholesModel, EuropeanOption, Greeks,
Instrument, MonteCarloModel, Option, OptionGreeks, OptionPricing, OptionStrategy,
AsianOption, BinaryOption, BinomialTreeModel, Black76Model, BlackScholesModel, EuropeanOption,
Greeks, Instrument, MonteCarloModel, Option, OptionGreeks, OptionPricing, OptionStrategy,
OptionType::*, RainbowOption,
};

Expand All @@ -11,6 +11,7 @@ fn main() {
example_black_scholes();
example_binomial_tree();
example_monte_carlo();
example_black_76();
example_greeks();
example_asian();
example_rainbow();
Expand Down Expand Up @@ -58,15 +59,15 @@ fn example_black_scholes() {
}

fn example_binomial_tree() {
//let instrument = Instrument::new().with_spot(100.0);
//let option = EuropeanOption::new(instrument, 100.0, 1.0, Call);
//let model = BinomialTreeModel::new(0.05, 0.2, 100);
//
//let call_price = model.price(&option);
//println!("Binomial Tree Call Price: {}", call_price);
//
//let put_price = model.price(&option.flip());
//println!("Binomial Tree Put Price: {}", put_price);
let instrument = Instrument::new().with_spot(100.0);
let option = EuropeanOption::new(instrument, 100.0, 1.0, Call);
let model = BinomialTreeModel::new(0.05, 0.2, 100);

let call_price = model.price(&option);
println!("Binomial Tree Call Price: {}", call_price);

let put_price = model.price(&option.flip());
println!("Binomial Tree Put Price: {}", put_price);
//
//let market_price = 10.0; // Example market price
//let implied_volatility = model.implied_volatility(&option, market_price);
Expand Down Expand Up @@ -108,6 +109,22 @@ fn example_monte_carlo() {
);
}

fn example_black_76() {
let instrument = Instrument::new().with_spot(2006.0);
let option = EuropeanOption::new(instrument, 2100.0, 0.08493, Call);
let model = Black76Model::new(0.050067, 0.35);

let call_price = model.price(&option);
println!("Black-76 Call Price: {}", call_price);

let put_price = model.price(&option.flip());
println!("Black-76 Put Price: {}", put_price);

let market_price = 10.0; // Example market price
let implied_volatility = model.implied_volatility(&option, market_price);
println!("Implied Volatility: {}\n", implied_volatility);
}

fn example_greeks() {
let instrument = Instrument::new().with_spot(100.0);
let option = EuropeanOption::new(instrument, 100.0, 1.0, Call);
Expand Down
157 changes: 154 additions & 3 deletions src/options/instrument.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ use rand_distr::{Distribution, Normal};
/// A struct representing an instrument with dividend properties.
#[derive(Debug, Default, Clone)]
pub struct Instrument {
/// Current price of the underlying asset.
/// Current price of the underlying asset or future price at time 0.
pub spot: f64,
/// Maximum spot price of the underlying asset.
pub max_spot: f64,
Expand All @@ -47,7 +47,11 @@ pub struct Instrument {
}

impl Instrument {
/// Create a new `Instrument`.
/// Create a new simple `Instrument` with default values.
///
/// # Returns
///
/// A new `Instrument`.
pub fn new() -> Self {
Self {
spot: 0.0,
Expand All @@ -62,24 +66,56 @@ impl Instrument {
}

/// Set the spot price of the instrument.
///
/// # Arguments
///
/// * `spot` - The spot price of the instrument (i.e., the current price of the underlying asset or future price at time 0).
///
/// # Returns
///
/// The instrument with the spot price set.
pub fn with_spot(mut self, spot: f64) -> Self {
self.spot = spot;
self
}

/// Set 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
}

/// Set the minimum spot price of the instrument.
///
/// # Arguments
///
/// * `min_spot` - 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
}

/// Set the continuous dividend yield of the instrument.
///
/// # Arguments
///
/// * `yield_` - The continuous dividend yield of the instrument.
///
/// # Returns
///
/// The instrument with the continuous dividend yield set.
pub fn with_continuous_dividend_yield(mut self, yield_: f64) -> Self {
self.continuous_dividend_yield = yield_;
self.assets.iter_mut().for_each(|(a, _)| {
Expand All @@ -94,18 +130,42 @@ impl Instrument {
}

/// Set the discrete dividend yield of the instrument.
///
/// # Arguments
///
/// * `yield_` - The discrete dividend yield of the instrument.
///
/// # Returns
///
/// The instrument with the discrete dividend yield set.
pub fn with_discrete_dividend_yield(mut self, yield_: f64) -> Self {
self.discrete_dividend_yield = yield_;
self
}

/// Set the dividend times of the instrument.
///
/// # Arguments
///
/// * `times` - The dividend times of the instrument.
///
/// # Returns
///
/// The instrument with the dividend times set.
pub fn with_dividend_times(mut self, times: Vec<f64>) -> Self {
self.dividend_times = times;
self
}

/// Set the assets of the instrument.
///
/// # Arguments
///
/// * `assets` - The assets of the instrument.
///
/// # Returns
///
/// The instrument with the assets set.
pub fn with_assets(mut self, assets: Vec<Instrument>) -> Self {
if assets.is_empty() {
return self;
Expand All @@ -119,6 +179,14 @@ impl Instrument {
}

/// Set the assets and their weights of the instrument.
///
/// # Arguments
///
/// * `assets` - The assets and their weights of the instrument.
///
/// # Returns
///
/// The instrument with the assets and their weights set.
pub fn with_weighted_assets(mut self, assets: Vec<(Instrument, f64)>) -> Self {
if assets.is_empty() {
return self;
Expand All @@ -138,6 +206,10 @@ impl Instrument {
}

/// Get best performing asset.
///
/// # Returns
///
/// The best performing asset.
pub fn best_performer(&self) -> &Instrument {
if self.assets.is_empty() {
return self;
Expand All @@ -149,6 +221,10 @@ impl Instrument {
}

/// Get worst performing asset.
///
/// # Returns
///
/// The worst performing asset.
pub fn worst_performer(&self) -> &Instrument {
if self.assets.is_empty() {
return self;
Expand All @@ -160,7 +236,32 @@ impl Instrument {
&self.assets.last().unwrap().0
}

/// Calculate the adjusted spot price.
///
/// # Arguments
///
/// * `instrument` - The instrument to calculate the adjusted spot price for.
/// * `ttm` - Time to maturity of the option.
///
/// # Returns
///
/// The adjusted spot price.
pub fn calculate_adjusted_spot(&self, ttm: f64) -> f64 {
let n_dividends = self.dividend_times.iter().filter(|&&t| t <= ttm).count() as f64;
self.spot * (1.0 - self.discrete_dividend_yield).powf(n_dividends)
}

/// Simulate random asset prices (Euler method)
///
/// # Arguments
///
/// * `rng` - Random number generator.
/// * `risk_free_rate` - Risk-free rate.
/// * `volatility` - Volatility.
///
/// # Returns
///
/// A vector of simulated asset prices.
pub fn euler_simulation(
&self,
rng: &mut ThreadRng,
Expand All @@ -181,6 +282,18 @@ impl Instrument {
}

/// Simulate random asset prices' logarithms
///
/// # Arguments
///
/// * `rng` - Random number generator.
/// * `volatility` - Volatility.
/// * `time_to_maturity` - Time to maturity.
/// * `risk_free_rate` - Risk-free rate.
/// * `steps` - Number of steps.
///
/// # Returns
///
/// A vector of simulated asset prices' logarithms.
pub fn log_simulation(
&self,
rng: &mut ThreadRng,
Expand All @@ -202,6 +315,19 @@ impl Instrument {
}

/// Average asset prices
///
/// # Arguments
///
/// * `rng` - Random number generator.
/// * `method` - Simulation method.
/// * `volatility` - Volatility.
/// * `time_to_maturity` - Time to maturity.
/// * `risk_free_rate` - Risk-free rate.
/// * `steps` - Number of steps.
///
/// # Returns
///
/// The average asset price.
pub fn simulate_arithmetic_average(
&self,
rng: &mut ThreadRng,
Expand All @@ -227,6 +353,19 @@ impl Instrument {
}

/// Geometric average asset prices
///
/// # Arguments
///
/// * `rng` - Random number generator.
/// * `method` - Simulation method.
/// * `volatility` - Volatility.
/// * `time_to_maturity` - Time to maturity.
/// * `risk_free_rate` - Risk-free rate.
/// * `steps` - Number of steps.
///
/// # Returns
///
/// The geometric average asset price.
pub fn simulate_geometric_average(
&self,
rng: &mut ThreadRng,
Expand All @@ -250,7 +389,19 @@ impl Instrument {
}
}

// Directly simulate the asset price using the geometric Brownian motion formula
/// Directly simulate the asset price using the geometric Brownian motion formula
///
/// # Arguments
///
/// * `rng` - Random number generator.
/// * `volatility` - Volatility.
/// * `time_to_maturity` - Time to maturity.
/// * `risk_free_rate` - Risk-free rate.
/// * `steps` - Number of steps.
///
/// # Returns
///
/// The simulated asset price.
pub fn simulate_geometric_brownian_motion(
&self,
rng: &mut ThreadRng,
Expand Down
10 changes: 10 additions & 0 deletions src/options/models/binomial_tree.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,16 @@ pub struct BinomialTreeModel {

impl BinomialTreeModel {
/// Create a new `BinomialTreeModel`.
///
/// # Arguments
///
/// * `risk_free_rate` - Risk-free interest rate (e.g., 0.05 for 5%).
/// * `volatility` - Annualized standard deviation of an asset's continuous returns (e.g., 0.2 for 20%).
/// * `steps` - The number of steps in the binomial tree.
///
/// # Returns
///
/// A new `BinomialTreeModel`.
pub fn new(risk_free_rate: f64, volatility: f64, steps: usize) -> Self {
Self {
risk_free_rate,
Expand Down
Loading