Skip to content

Commit 3fd5ef0

Browse files
Implement lookback options (#52)
* Update readme example * Implement floating strike lookback options * Implement floating strike lookback options * Implement floating strike lookback options for blackscholes
1 parent 1b381c8 commit 3fd5ef0

File tree

9 files changed

+303
-205
lines changed

9 files changed

+303
-205
lines changed

README.md

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,8 @@ Quantrs supports options pricing with various models for both vanilla and exotic
4545
| ²Double Barrier | ❌ (mod. BSM) |||| ❌ (complex) ||
4646
| ²Asian (fixed strike) | ❌ (mod. BSM) ||||||
4747
| ²Asian (floating strike) | ❌ (mod. BSM) ||||||
48-
| ²Lookback (fixed strike) | ||| |||
49-
| ²Lookback (floating strike) | ||| |||
48+
| ²Lookback (fixed strike) | ||| |||
49+
| ²Lookback (floating strike) | ||| |||
5050
| ²Binary Cash-or-Nothing ||||| ❌ (mod. PDE) ||
5151
| ²Binary Asset-or-Nothing ||||| ❌ (mod. PDE) ||
5252
| Greeks (Δ,ν,Θ,ρ,Γ) |||||||
@@ -95,7 +95,7 @@ Add this to your `Cargo.toml`:
9595
quantrs = "0.1.6"
9696
```
9797

98-
Now if you want to e.g., model binary call options using the Black-Scholes model, you can:
98+
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:
9999

100100
```rust
101101
use quantrs::options::*;
@@ -131,7 +131,7 @@ Greeks { delta: 0.013645840354947947, gamma: -0.0008813766475726433, theta: 0.17
131131

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

134-
E.g., Plot the P/L of a slightly skewed Condor spread using the Monte-Carlo model:
134+
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:
135135

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

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

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

160160
// Plot a breakdown of the Condor spread with a spot price range of [80,120]
161161
model.plot_strategy_breakdown(

examples/images/strategy.png

6.99 KB
Loading

examples/options_pricing.rs

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -711,24 +711,24 @@ fn example_strategy() {
711711
}
712712

713713
fn example_plots() {
714-
// Create a new Monte-Carlo model with:
715-
// - Risk-free interest rate (r) = 5%
716-
// - Volatility (σ) = 20%
717-
// - Number of simulations = 10,000
718-
// - Number of time steps = 365
719-
let model = MonteCarloModel::geometric(0.05, 0.2, 10_000, 365);
720-
721714
// Create a new instrument with a spot price of 100 and a dividend yield of 2%
722715
let instrument = Instrument::new().with_spot(100.0).with_cont_yield(0.02);
723716

724-
// Create a vector of European call options with different strike prices
717+
// Create a vector of fixed-strike Asian calls options with different strike prices
725718
let options = vec![
726-
EuropeanOption::new(instrument.clone(), 85.0, 1.0, Call),
727-
EuropeanOption::new(instrument.clone(), 95.0, 1.0, Call),
728-
EuropeanOption::new(instrument.clone(), 102.0, 1.0, Call),
729-
EuropeanOption::new(instrument.clone(), 115.0, 1.0, Call),
719+
AsianOption::fixed(instrument.clone(), 85.0, 1.0, Call),
720+
AsianOption::fixed(instrument.clone(), 95.0, 1.0, Call),
721+
AsianOption::fixed(instrument.clone(), 102.0, 1.0, Call),
722+
AsianOption::fixed(instrument.clone(), 115.0, 1.0, Call),
730723
];
731724

725+
// Create a new Monte-Carlo model with:
726+
// - Risk-free interest rate (r) = 5%
727+
// - Volatility (σ) = 20%
728+
// - Number of simulations = 10,000
729+
// - Number of time steps = 252
730+
let model = MonteCarloModel::geometric(0.05, 0.2, 10_000, 252);
731+
732732
// Plot a breakdown of the Condor spread with a spot price range of [80,120]
733733
let _ = model.plot_strategy_breakdown(
734734
"Condor Example",

src/options/instrument.rs

Lines changed: 8 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,6 @@ use rand_distr::{Distribution, Normal};
3030
pub struct Instrument {
3131
/// Current price of the underlying asset or future price at time 0.
3232
pub spot: Vec<f64>,
33-
/// Maximum spot price of the underlying asset.
34-
pub max_spot: f64,
35-
/// Minimum spot price of the underlying asset.
36-
pub min_spot: f64,
3733
/// Continuous dividend yield where the dividend amount is proportional to the level of the underlying asset (e.g., 0.02 for 2%).
3834
pub continuous_dividend_yield: f64,
3935
/// Discrete proportional dividend yield (e.g., 0.02 for 2%).
@@ -55,8 +51,6 @@ impl Instrument {
5551
pub fn new() -> Self {
5652
Self {
5753
spot: vec![0.0],
58-
max_spot: 0.0,
59-
min_spot: 0.0,
6054
continuous_dividend_yield: 0.0,
6155
discrete_dividend_yield: 0.0,
6256
dividend_times: Vec::new(),
@@ -93,32 +87,22 @@ impl Instrument {
9387
self
9488
}
9589

96-
/// Set the maximum spot price of the instrument.
90+
/// Get the maximum spot price of the instrument.
9791
///
98-
/// # Arguments
99-
///
100-
/// * `max_spot` - The maximum spot price of the instrument.
101-
///
10292
/// # Returns
10393
///
104-
/// The instrument with the maximum spot price set.
105-
pub fn with_max_spot(mut self, max_spot: f64) -> Self {
106-
self.max_spot = max_spot;
107-
self
94+
/// The maximum spot price of the instrument.
95+
pub fn max_spot(&self) -> f64 {
96+
*self.spot.iter().max_by(|x, y| x.total_cmp(y)).unwrap()
10897
}
10998

110-
/// Set the minimum spot price of the instrument.
111-
///
112-
/// # Arguments
113-
///
114-
/// * `min_spot` - The minimum spot price of the instrument.
99+
/// Get the minimum spot price of the instrument.
115100
///
116101
/// # Returns
117102
///
118-
/// The instrument with the minimum spot price set.
119-
pub fn with_min_spot(mut self, min_spot: f64) -> Self {
120-
self.min_spot = min_spot;
121-
self
103+
/// The minimum spot price of the instrument.
104+
pub fn min_spot(&self) -> f64 {
105+
*self.spot.iter().min_by(|x, y| x.total_cmp(y)).unwrap()
122106
}
123107

124108
/// Set the continuous dividend yield of the instrument.

src/options/models/black_scholes.rs

Lines changed: 56 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -15,48 +15,14 @@
1515
//! including the current price of the underlying asset, the strike price of the option, the time to expiration, the risk-free interest rate,
1616
//! and the volatility of the underlying asset.
1717
//!
18-
//! ## Formula
19-
//!
20-
//! The price of an option using the Black-Scholes model is calculated as follows:
21-
//!
22-
//! ```text
23-
//! C = S * N(d1) - X * e^(-rT) * N(d2) for a call option
24-
//! P = X * e^(-rT) * N(-d2) - S * N(-d1) for a put option
25-
//! ```
26-
//!
27-
//! where:
28-
//! - `C` is the price of the call option.
29-
//! - `P` is the price of the put option.
30-
//! - `S` is the current price of the underlying asset.
31-
//! - `X` is the strike price of the option.
32-
//! - `r` is the risk-free interest rate.
33-
//! - `T` is the time to maturity.
34-
//! - `N` is the cumulative distribution function of the standard normal distribution.
35-
//! - `d1` and `d2` are calculated as follows:
36-
//! ```text
37-
//! d1 = (ln(S / X) + (r + 0.5 * σ^2) * T) / (σ * sqrt(T))
38-
//! d2 = d1 - σ * sqrt(T)
39-
//! ```
40-
//! - `σ` is the volatility of the underlying asset.
41-
//!
42-
//! The payoff of the option is calculated as:
43-
//!
44-
//! ```text
45-
//! payoff = max(ST - K, 0) for a call option
46-
//! payoff = max(K - ST, 0) for a put option
47-
//! ```
48-
//!
49-
//! where:
50-
//! - `ST` is the price of the underlying asset at maturity.
51-
//! - `K` is the strike price of the option.
52-
//! - `max` is the maximum function.
53-
//!
5418
//! ## References
5519
//!
5620
//! - [Wikipedia - Black-Scholes model](https://en.wikipedia.org/wiki/Black%E2%80%93Scholes_model)
5721
//! - [Black-Scholes Calculator](https://www.math.drexel.edu/~pg/fin/VanillaCalculator.html)
5822
//! - [Cash or Nothing Options' Greeks](https://quantpie.co.uk/bsm_bin_c_formula/bs_bin_c_summary.php)
5923
//! - [Asset or Nothing Options' Greeks](https://quantpie.co.uk/bsm_bin_a_formula/bs_bin_a_summary.php)
24+
//! - Musiela, M., Rutkowski, M. Martingale Methods in Financial Modelling, 2nd Ed Springer, 2007
25+
//! - Joshi, M. The Concepts and Practice of Mathematical Finance, 2nd Ed Cambridge University Press, 2008
6026
//!
6127
//! ## Example
6228
//!
@@ -74,8 +40,9 @@
7440
use crate::options::{
7541
types::BinaryType::{AssetOrNothing, CashOrNothing},
7642
Instrument, Option, OptionGreeks, OptionPricing, OptionStrategy, OptionStyle, OptionType,
77-
RainbowType,
43+
Permutation, RainbowType,
7844
};
45+
use rand_distr::num_traits::Pow;
7946
use statrs::distribution::{Continuous, ContinuousCDF, Normal};
8047

8148
/// A struct representing a Black-Scholes model.
@@ -297,6 +264,57 @@ impl BlackScholesModel {
297264
)
298265
}
299266

267+
/// Calculate the price of a lookback option using the Black-Scholes formula.
268+
///
269+
/// # Arguments
270+
///
271+
/// * `option` - The option to price.
272+
/// * `normal` - The standard normal distribution.
273+
///
274+
/// # Returns
275+
///
276+
/// The price of the option.
277+
pub fn price_lookback<T: Option>(&self, option: &T, normal: &Normal) -> f64 {
278+
let max = option.instrument().max_spot();
279+
let min = option.instrument().min_spot();
280+
let sqrt_t = option.time_to_maturity().sqrt();
281+
let s = option.instrument().spot();
282+
let r = self.risk_free_rate;
283+
let t = option.time_to_maturity();
284+
let vola = self.volatility;
285+
286+
assert!(s > 0.0 && max > 0.0 && min > 0.0, "Spot prices must be > 0");
287+
288+
println!("max: {}, min: {}", max, min);
289+
290+
let a1 = |s: f64, h: f64| ((s / h).ln() + (r + 0.5 * vola.powi(2)) * t) / (vola * sqrt_t);
291+
let a2 = |s: f64, h: f64| a1(s, h) - vola * sqrt_t;
292+
let a3 = |s: f64, h: f64| a1(s, h) - 2.0 * r * sqrt_t / vola;
293+
294+
let phi = |x: f64| normal.cdf(x);
295+
296+
match option.option_type() {
297+
OptionType::Call => {
298+
s * phi(a1(s, min))
299+
- min * (-r * t).exp() * phi(a2(s, min))
300+
- (0.5 * s * vola.powi(2)) / (r)
301+
* (phi(-a1(s, min))
302+
- (-r * t).exp()
303+
* (min / s).pow((2f64 * r) / (vola.powi(2)))
304+
* phi(-a3(s, min)))
305+
}
306+
OptionType::Put => {
307+
-s * phi(-a1(s, max))
308+
+ max * (-r * t).exp() * phi(-a2(s, max))
309+
+ (0.5 * s * vola.powi(2)) / (r)
310+
* (phi(a1(s, max))
311+
- (-r * t).exp()
312+
* (max / s).pow((2f64 * r) / (vola.powi(2)))
313+
* phi(a3(s, max)))
314+
}
315+
}
316+
}
317+
300318
/// Calculate the option price using the Black-Scholes formula with a given volatility.
301319
///
302320
/// # Arguments
@@ -369,6 +387,7 @@ impl OptionPricing for BlackScholesModel {
369387
(_, OptionStyle::Binary(AssetOrNothing)) => self.price_asset_or_nothing(option, &normal),
370388
(OptionType::Call, OptionStyle::Rainbow(_)) => self.price_rainbow_call(option, &normal),
371389
(OptionType::Put, OptionStyle::Rainbow(_)) => self.price_rainbow_put(option, &normal),
390+
(_, OptionStyle::Lookback(Permutation::Floating)) => self.price_lookback(option, &normal),
372391
_ => panic!("BlackScholesModel does not support this option type or style"),
373392
}
374393
}

0 commit comments

Comments
 (0)