Skip to content

Commit 2c7007f

Browse files
Add Black-76 pricing model (#50)
* Add basic structure for black 76 model * Add disclaimer * Update method documentation for option pricing models and traits * Implement black76 for simple european options * Add black76 greeks
1 parent 63e0d83 commit 2c7007f

File tree

9 files changed

+821
-113
lines changed

9 files changed

+821
-113
lines changed

README.md

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ Quantrs supports options pricing with various models for both vanilla and exotic
3636

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

5555
> ¹ _"Exotic" options with standard exercise style; only differ in their payoff value_\
5656
> ² _Non-vanilla path-dependent "exotic" options_\
5757
> ³ _MC simulates underlying price paths based on geometric Brownian motion for Black-Scholes models and geometric average price paths for Asian and Lookback options_\
5858
> ✅ = Supported, ⏳ = Planned / In progress, ❌ = Not supported / Not applicable
5959
60+
<!--Bachelier and Modified Bachelier-->
61+
6062
</details>
6163

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

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

216+
## Disclaimer
217+
218+
This library is not intended for professional use. It is a hobby project and should be treated as such.
219+
214220
## License
215221

216222
This project is licensed under either of:

examples/options_pricing.rs

Lines changed: 28 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
// Run: cargo run --release --example options_pricing
22

33
use quantrs::options::{
4-
AsianOption, BinaryOption, BinomialTreeModel, BlackScholesModel, EuropeanOption, Greeks,
5-
Instrument, MonteCarloModel, Option, OptionGreeks, OptionPricing, OptionStrategy,
4+
AsianOption, BinaryOption, BinomialTreeModel, Black76Model, BlackScholesModel, EuropeanOption,
5+
Greeks, Instrument, MonteCarloModel, Option, OptionGreeks, OptionPricing, OptionStrategy,
66
OptionType::*, RainbowOption,
77
};
88

@@ -11,6 +11,7 @@ fn main() {
1111
example_black_scholes();
1212
example_binomial_tree();
1313
example_monte_carlo();
14+
example_black_76();
1415
example_greeks();
1516
example_asian();
1617
example_rainbow();
@@ -58,15 +59,15 @@ fn example_black_scholes() {
5859
}
5960

6061
fn example_binomial_tree() {
61-
//let instrument = Instrument::new().with_spot(100.0);
62-
//let option = EuropeanOption::new(instrument, 100.0, 1.0, Call);
63-
//let model = BinomialTreeModel::new(0.05, 0.2, 100);
64-
//
65-
//let call_price = model.price(&option);
66-
//println!("Binomial Tree Call Price: {}", call_price);
67-
//
68-
//let put_price = model.price(&option.flip());
69-
//println!("Binomial Tree Put Price: {}", put_price);
62+
let instrument = Instrument::new().with_spot(100.0);
63+
let option = EuropeanOption::new(instrument, 100.0, 1.0, Call);
64+
let model = BinomialTreeModel::new(0.05, 0.2, 100);
65+
66+
let call_price = model.price(&option);
67+
println!("Binomial Tree Call Price: {}", call_price);
68+
69+
let put_price = model.price(&option.flip());
70+
println!("Binomial Tree Put Price: {}", put_price);
7071
//
7172
//let market_price = 10.0; // Example market price
7273
//let implied_volatility = model.implied_volatility(&option, market_price);
@@ -108,6 +109,22 @@ fn example_monte_carlo() {
108109
);
109110
}
110111

112+
fn example_black_76() {
113+
let instrument = Instrument::new().with_spot(2006.0);
114+
let option = EuropeanOption::new(instrument, 2100.0, 0.08493, Call);
115+
let model = Black76Model::new(0.050067, 0.35);
116+
117+
let call_price = model.price(&option);
118+
println!("Black-76 Call Price: {}", call_price);
119+
120+
let put_price = model.price(&option.flip());
121+
println!("Black-76 Put Price: {}", put_price);
122+
123+
let market_price = 10.0; // Example market price
124+
let implied_volatility = model.implied_volatility(&option, market_price);
125+
println!("Implied Volatility: {}\n", implied_volatility);
126+
}
127+
111128
fn example_greeks() {
112129
let instrument = Instrument::new().with_spot(100.0);
113130
let option = EuropeanOption::new(instrument, 100.0, 1.0, Call);

src/options/instrument.rs

Lines changed: 154 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ use rand_distr::{Distribution, Normal};
2828
/// A struct representing an instrument with dividend properties.
2929
#[derive(Debug, Default, Clone)]
3030
pub struct Instrument {
31-
/// Current price of the underlying asset.
31+
/// Current price of the underlying asset or future price at time 0.
3232
pub spot: f64,
3333
/// Maximum spot price of the underlying asset.
3434
pub max_spot: f64,
@@ -47,7 +47,11 @@ pub struct Instrument {
4747
}
4848

4949
impl Instrument {
50-
/// Create a new `Instrument`.
50+
/// Create a new simple `Instrument` with default values.
51+
///
52+
/// # Returns
53+
///
54+
/// A new `Instrument`.
5155
pub fn new() -> Self {
5256
Self {
5357
spot: 0.0,
@@ -62,24 +66,56 @@ impl Instrument {
6266
}
6367

6468
/// Set the spot price of the instrument.
69+
///
70+
/// # Arguments
71+
///
72+
/// * `spot` - The spot price of the instrument (i.e., the current price of the underlying asset or future price at time 0).
73+
///
74+
/// # Returns
75+
///
76+
/// The instrument with the spot price set.
6577
pub fn with_spot(mut self, spot: f64) -> Self {
6678
self.spot = spot;
6779
self
6880
}
6981

7082
/// Set the maximum spot price of the instrument.
83+
///
84+
/// # Arguments
85+
///
86+
/// * `max_spot` - The maximum spot price of the instrument.
87+
///
88+
/// # Returns
89+
///
90+
/// The instrument with the maximum spot price set.
7191
pub fn with_max_spot(mut self, max_spot: f64) -> Self {
7292
self.max_spot = max_spot;
7393
self
7494
}
7595

7696
/// Set the minimum spot price of the instrument.
97+
///
98+
/// # Arguments
99+
///
100+
/// * `min_spot` - The minimum spot price of the instrument.
101+
///
102+
/// # Returns
103+
///
104+
/// The instrument with the minimum spot price set.
77105
pub fn with_min_spot(mut self, min_spot: f64) -> Self {
78106
self.min_spot = min_spot;
79107
self
80108
}
81109

82110
/// Set the continuous dividend yield of the instrument.
111+
///
112+
/// # Arguments
113+
///
114+
/// * `yield_` - The continuous dividend yield of the instrument.
115+
///
116+
/// # Returns
117+
///
118+
/// The instrument with the continuous dividend yield set.
83119
pub fn with_continuous_dividend_yield(mut self, yield_: f64) -> Self {
84120
self.continuous_dividend_yield = yield_;
85121
self.assets.iter_mut().for_each(|(a, _)| {
@@ -94,18 +130,42 @@ impl Instrument {
94130
}
95131

96132
/// Set the discrete dividend yield of the instrument.
133+
///
134+
/// # Arguments
135+
///
136+
/// * `yield_` - The discrete dividend yield of the instrument.
137+
///
138+
/// # Returns
139+
///
140+
/// The instrument with the discrete dividend yield set.
97141
pub fn with_discrete_dividend_yield(mut self, yield_: f64) -> Self {
98142
self.discrete_dividend_yield = yield_;
99143
self
100144
}
101145

102146
/// Set the dividend times of the instrument.
147+
///
148+
/// # Arguments
149+
///
150+
/// * `times` - The dividend times of the instrument.
151+
///
152+
/// # Returns
153+
///
154+
/// The instrument with the dividend times set.
103155
pub fn with_dividend_times(mut self, times: Vec<f64>) -> Self {
104156
self.dividend_times = times;
105157
self
106158
}
107159

108160
/// Set the assets of the instrument.
161+
///
162+
/// # Arguments
163+
///
164+
/// * `assets` - The assets of the instrument.
165+
///
166+
/// # Returns
167+
///
168+
/// The instrument with the assets set.
109169
pub fn with_assets(mut self, assets: Vec<Instrument>) -> Self {
110170
if assets.is_empty() {
111171
return self;
@@ -119,6 +179,14 @@ impl Instrument {
119179
}
120180

121181
/// Set the assets and their weights of the instrument.
182+
///
183+
/// # Arguments
184+
///
185+
/// * `assets` - The assets and their weights of the instrument.
186+
///
187+
/// # Returns
188+
///
189+
/// The instrument with the assets and their weights set.
122190
pub fn with_weighted_assets(mut self, assets: Vec<(Instrument, f64)>) -> Self {
123191
if assets.is_empty() {
124192
return self;
@@ -138,6 +206,10 @@ impl Instrument {
138206
}
139207

140208
/// Get best performing asset.
209+
///
210+
/// # Returns
211+
///
212+
/// The best performing asset.
141213
pub fn best_performer(&self) -> &Instrument {
142214
if self.assets.is_empty() {
143215
return self;
@@ -149,6 +221,10 @@ impl Instrument {
149221
}
150222

151223
/// Get worst performing asset.
224+
///
225+
/// # Returns
226+
///
227+
/// The worst performing asset.
152228
pub fn worst_performer(&self) -> &Instrument {
153229
if self.assets.is_empty() {
154230
return self;
@@ -160,7 +236,32 @@ impl Instrument {
160236
&self.assets.last().unwrap().0
161237
}
162238

239+
/// Calculate the adjusted spot price.
240+
///
241+
/// # Arguments
242+
///
243+
/// * `instrument` - The instrument to calculate the adjusted spot price for.
244+
/// * `ttm` - Time to maturity of the option.
245+
///
246+
/// # Returns
247+
///
248+
/// The adjusted spot price.
249+
pub fn calculate_adjusted_spot(&self, ttm: f64) -> f64 {
250+
let n_dividends = self.dividend_times.iter().filter(|&&t| t <= ttm).count() as f64;
251+
self.spot * (1.0 - self.discrete_dividend_yield).powf(n_dividends)
252+
}
253+
163254
/// Simulate random asset prices (Euler method)
255+
///
256+
/// # Arguments
257+
///
258+
/// * `rng` - Random number generator.
259+
/// * `risk_free_rate` - Risk-free rate.
260+
/// * `volatility` - Volatility.
261+
///
262+
/// # Returns
263+
///
264+
/// A vector of simulated asset prices.
164265
pub fn euler_simulation(
165266
&self,
166267
rng: &mut ThreadRng,
@@ -181,6 +282,18 @@ impl Instrument {
181282
}
182283

183284
/// Simulate random asset prices' logarithms
285+
///
286+
/// # Arguments
287+
///
288+
/// * `rng` - Random number generator.
289+
/// * `volatility` - Volatility.
290+
/// * `time_to_maturity` - Time to maturity.
291+
/// * `risk_free_rate` - Risk-free rate.
292+
/// * `steps` - Number of steps.
293+
///
294+
/// # Returns
295+
///
296+
/// A vector of simulated asset prices' logarithms.
184297
pub fn log_simulation(
185298
&self,
186299
rng: &mut ThreadRng,
@@ -202,6 +315,19 @@ impl Instrument {
202315
}
203316

204317
/// Average asset prices
318+
///
319+
/// # Arguments
320+
///
321+
/// * `rng` - Random number generator.
322+
/// * `method` - Simulation method.
323+
/// * `volatility` - Volatility.
324+
/// * `time_to_maturity` - Time to maturity.
325+
/// * `risk_free_rate` - Risk-free rate.
326+
/// * `steps` - Number of steps.
327+
///
328+
/// # Returns
329+
///
330+
/// The average asset price.
205331
pub fn simulate_arithmetic_average(
206332
&self,
207333
rng: &mut ThreadRng,
@@ -227,6 +353,19 @@ impl Instrument {
227353
}
228354

229355
/// Geometric average asset prices
356+
///
357+
/// # Arguments
358+
///
359+
/// * `rng` - Random number generator.
360+
/// * `method` - Simulation method.
361+
/// * `volatility` - Volatility.
362+
/// * `time_to_maturity` - Time to maturity.
363+
/// * `risk_free_rate` - Risk-free rate.
364+
/// * `steps` - Number of steps.
365+
///
366+
/// # Returns
367+
///
368+
/// The geometric average asset price.
230369
pub fn simulate_geometric_average(
231370
&self,
232371
rng: &mut ThreadRng,
@@ -250,7 +389,19 @@ impl Instrument {
250389
}
251390
}
252391

253-
// Directly simulate the asset price using the geometric Brownian motion formula
392+
/// Directly simulate the asset price using the geometric Brownian motion formula
393+
///
394+
/// # Arguments
395+
///
396+
/// * `rng` - Random number generator.
397+
/// * `volatility` - Volatility.
398+
/// * `time_to_maturity` - Time to maturity.
399+
/// * `risk_free_rate` - Risk-free rate.
400+
/// * `steps` - Number of steps.
401+
///
402+
/// # Returns
403+
///
404+
/// The simulated asset price.
254405
pub fn simulate_geometric_brownian_motion(
255406
&self,
256407
rng: &mut ThreadRng,

src/options/models/binomial_tree.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,16 @@ pub struct BinomialTreeModel {
8787

8888
impl BinomialTreeModel {
8989
/// Create a new `BinomialTreeModel`.
90+
///
91+
/// # Arguments
92+
///
93+
/// * `risk_free_rate` - Risk-free interest rate (e.g., 0.05 for 5%).
94+
/// * `volatility` - Annualized standard deviation of an asset's continuous returns (e.g., 0.2 for 20%).
95+
/// * `steps` - The number of steps in the binomial tree.
96+
///
97+
/// # Returns
98+
///
99+
/// A new `BinomialTreeModel`.
90100
pub fn new(risk_free_rate: f64, volatility: f64, steps: usize) -> Self {
91101
Self {
92102
risk_free_rate,

0 commit comments

Comments
 (0)