diff --git a/Cargo.toml b/Cargo.toml
index 0cff3150..8e22e3e6 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -75,6 +75,7 @@ thiserror = "1.0.57" # https://docs.rs/thiserror/latest/thiserror/
yahoo_finance_api = "2.1.0" # https://docs.rs/yahoo-finance-api/latest/yahoo_finance_api/
tokio-test = "0.4.3" # https://docs.rs/tokio-test/latest/tokio_test/
+
# https://docs.rs/num/latest/num/
num = { version = "0.4.1", features = ["rand"] }
@@ -84,6 +85,9 @@ time = { version = "0.3.34", features = ["macros"] }
# https://docs.rs/polars/latest/polars/
polars = { version = "0.41.1", features = ["docs-selection"] }
+# https://docs.rs/uuid/latest/uuid/
+uuid = { version = "1.10.0", features = ["v4", "fast-rng"] }
+
[dev-dependencies]
finitediff = "0.1.4" # https://docs.rs/finitediff/latest/finitediff/
diff --git a/book/book.toml b/book/book.toml
index 1e5df9e8..a86b30b8 100644
--- a/book/book.toml
+++ b/book/book.toml
@@ -15,3 +15,4 @@ title = "The RustQuant Book"
## and you will be able to use the usual $...$ and $$...$$ delimiters.
[output.html]
mathjax-support = true
+fold = { enable = true }
diff --git a/book/src/Introduction.md b/book/src/Introduction.md
index 445388bb..06039d23 100644
--- a/book/src/Introduction.md
+++ b/book/src/Introduction.md
@@ -1,8 +1,27 @@
-# Introduction to RustQuant
+![](./assets/logo.png)
RustQuant is a Rust library (crate) for quantitative finance.
-You can download the library from [Crates.io](https://crates.io/crates/RustQuant), and API documentation can be found at [Docs.rs](https://docs.rs/crate/RustQuant/latest).
+# Code
-> Note: this book is a very early work-in-progress and has almost no content.
+The crate is available for download from [Crates.io](https://crates.io/crates/RustQuant), and the source is available on [GitHub](https://github.com/avhz/RustQuant).
+
+# Installation
+
+RustQuant of course requires [Rust](https://www.rust-lang.org/) to be installed first.
+
+You can easily add RustQuant to your Rust project by running:
+
+```bash
+cargo add RustQuant
+```
+# Documentation
+
+API documentation can be found at [Docs.rs](https://docs.rs/crate/RustQuant/latest).
+
+The documentation contained in this book is more *"cookbook"* style.
+
+Contributions to documentation in any form are highly welcome.
+
+> Note: this book is a very early work-in-progress and has almost no content.
diff --git a/book/src/Modules.md b/book/src/Modules.md
deleted file mode 100644
index a55ecc05..00000000
--- a/book/src/Modules.md
+++ /dev/null
@@ -1 +0,0 @@
-# Modules
diff --git a/book/src/Modules/autodiff.md b/book/src/Modules/autodiff/autodiff.md
similarity index 100%
rename from book/src/Modules/autodiff.md
rename to book/src/Modules/autodiff/autodiff.md
diff --git a/book/src/Modules/cashflows.md b/book/src/Modules/cashflows/cashflows.md
similarity index 100%
rename from book/src/Modules/cashflows.md
rename to book/src/Modules/cashflows/cashflows.md
diff --git a/book/src/Modules/data.md b/book/src/Modules/data.md
deleted file mode 100644
index aa94f50f..00000000
--- a/book/src/Modules/data.md
+++ /dev/null
@@ -1 +0,0 @@
-# `data`
\ No newline at end of file
diff --git a/book/src/Modules/data/curves.md b/book/src/Modules/data/curves.md
new file mode 100644
index 00000000..22c0856a
--- /dev/null
+++ b/book/src/Modules/data/curves.md
@@ -0,0 +1,39 @@
+# Curves
+
+Curves can be fit to market data. Here we include an example of a spot curve being fitted.
+
+![`Spot curve`](../../assets/spotcurve.png)
+
+```rust
+{{#include ../../../../examples/curves_spot.rs}}
+```
+
+
+
+
+ Good |
+ Bad |
+
+
+
+
+```rust
+int foo() {
+ int result = 4;
+ return result;
+}
+```
+
+ |
+
+
+```rust
+int foo() {
+ int x = 4;
+ return x;
+}
+```
+
+ |
+
+
\ No newline at end of file
diff --git a/book/src/Modules/data/data.md b/book/src/Modules/data/data.md
new file mode 100644
index 00000000..740ffd94
--- /dev/null
+++ b/book/src/Modules/data/data.md
@@ -0,0 +1,8 @@
+# `data`
+
+The `data` module encompasses everything data related.
+
+That is, anything that can be observed, either in markets or derived from market observable data, and also facilities to manage that data.
+
+Another form of data is contextual (or reference) data. These are things such as calendars and date conventions. While there are facilities to handle these data inside the `data` module, the underlying implementations are in other modules, such as the `time` module.
+
diff --git a/book/src/Modules/error.md b/book/src/Modules/error.md
index 68d3b6bd..df79877d 100644
--- a/book/src/Modules/error.md
+++ b/book/src/Modules/error.md
@@ -1 +1 @@
-# `error`
\ No newline at end of file
+# error
diff --git a/book/src/Modules/instruments.md b/book/src/Modules/instruments/instruments.md
similarity index 100%
rename from book/src/Modules/instruments.md
rename to book/src/Modules/instruments/instruments.md
diff --git a/book/src/Modules/iso.md b/book/src/Modules/iso/iso.md
similarity index 100%
rename from book/src/Modules/iso.md
rename to book/src/Modules/iso/iso.md
diff --git a/book/src/Modules/macros.md b/book/src/Modules/macros.md
index 416061d3..24dabf6d 100644
--- a/book/src/Modules/macros.md
+++ b/book/src/Modules/macros.md
@@ -1 +1 @@
-# `macros`
\ No newline at end of file
+# macros
diff --git a/book/src/Modules/math.md b/book/src/Modules/math/math.md
similarity index 100%
rename from book/src/Modules/math.md
rename to book/src/Modules/math/math.md
diff --git a/book/src/Modules/ml.md b/book/src/Modules/ml/ml.md
similarity index 100%
rename from book/src/Modules/ml.md
rename to book/src/Modules/ml/ml.md
diff --git a/book/src/Modules/models.md b/book/src/Modules/models/models.md
similarity index 100%
rename from book/src/Modules/models.md
rename to book/src/Modules/models/models.md
diff --git a/book/src/Modules/portfolio.md b/book/src/Modules/portfolio/portfolio.md
similarity index 100%
rename from book/src/Modules/portfolio.md
rename to book/src/Modules/portfolio/portfolio.md
diff --git a/book/src/Modules/stochastics.md b/book/src/Modules/stochastics/stochastics.md
similarity index 100%
rename from book/src/Modules/stochastics.md
rename to book/src/Modules/stochastics/stochastics.md
diff --git a/book/src/Modules/time.md b/book/src/Modules/time/time.md
similarity index 100%
rename from book/src/Modules/time.md
rename to book/src/Modules/time/time.md
diff --git a/book/src/Modules/trading.md b/book/src/Modules/trading/trading.md
similarity index 100%
rename from book/src/Modules/trading.md
rename to book/src/Modules/trading/trading.md
diff --git a/book/src/SUMMARY.md b/book/src/SUMMARY.md
index 889b6875..dc0765da 100644
--- a/book/src/SUMMARY.md
+++ b/book/src/SUMMARY.md
@@ -1,21 +1,35 @@
# Summary
-- [Introduction to RustQuant](./Introduction.md)
-- [Modules](./Modules.md)
- - [`autodiff`](./Modules/autodiff.md)
- - [`cashflows`](./Modules/cashflows.md)
- - [`data`](./Modules/data.md)
- - [`error`](./Modules/error.md)
- - [`instruments`](./Modules/instruments.md)
- - [`iso`](./Modules/iso.md)
- - [`macros`](./Modules/macros.md)
- - [`math`](./Modules/math.md)
- - [`ml`](./Modules/ml.md)
- - [`models`](./Modules/models.md)
- - [`portfolio`](./Modules/portfolio.md)
- - [`stochastics`](./Modules/stochastics.md)
- - [`time`](./Modules/time.md)
- - [`trading`](./Modules/trading.md)
+# Introduction
+
+- [RustQuant](./Introduction.md)
+
+---
+
+# Modules
+
+
+
+- [`data`](./Modules/data/data.md)
+ - [Curves](./Modules/data/curves.md)
+
+
+
+---
+
+# Development
+
- [Contributing](./Contributing.md)
- [File Template](./Template.md)
- [References](./References.md)
diff --git a/book/src/assets/gbm.png b/book/src/assets/gbm.png
new file mode 100644
index 00000000..40f43c79
Binary files /dev/null and b/book/src/assets/gbm.png differ
diff --git a/book/src/assets/logo.png b/book/src/assets/logo.png
new file mode 100644
index 00000000..8a5d7099
Binary files /dev/null and b/book/src/assets/logo.png differ
diff --git a/book/src/assets/logo_banner.png b/book/src/assets/logo_banner.png
new file mode 100644
index 00000000..a469a55c
Binary files /dev/null and b/book/src/assets/logo_banner.png differ
diff --git a/book/src/assets/logo_square.png b/book/src/assets/logo_square.png
new file mode 100644
index 00000000..7dcec701
Binary files /dev/null and b/book/src/assets/logo_square.png differ
diff --git a/book/src/assets/spotcurve.png b/book/src/assets/spotcurve.png
new file mode 100644
index 00000000..b4da4613
Binary files /dev/null and b/book/src/assets/spotcurve.png differ
diff --git a/examples/curve.rs b/examples/curve.rs
index d6749306..0bfc82d8 100644
--- a/examples/curve.rs
+++ b/examples/curve.rs
@@ -1,4 +1,3 @@
-use plotly::{Plot, Scatter};
use time::macros::date;
use time::{Date, Duration};
use RustQuant::data::Curve;
diff --git a/examples/curves_discount.rs b/examples/curves_discount.rs
index e3d98638..c0a1d10c 100644
--- a/examples/curves_discount.rs
+++ b/examples/curves_discount.rs
@@ -1,10 +1,9 @@
-use plotly::{Plot, Scatter};
use polars::prelude::*;
use time::macros::date;
-use time::{Date, Duration};
-use RustQuant::data::{discount_curve, Curve, DiscountCurve};
+use time::Date;
+use RustQuant::data::Curves;
+use RustQuant::data::{Curve, DiscountCurve};
use RustQuant::time::oceania::australia::AustraliaCalendar;
-use RustQuant::time::Calendar;
fn main() {
let cal = AustraliaCalendar;
diff --git a/examples/curves_spot.rs b/examples/curves_spot.rs
index f2486e5a..1f6a6e2e 100644
--- a/examples/curves_spot.rs
+++ b/examples/curves_spot.rs
@@ -1,46 +1,13 @@
-use plotly::{Plot, Scatter};
-// use polars::prelude::*;
-use time::macros::date;
-use time::{Date, Duration};
-use RustQuant::data::CurveModel;
-use RustQuant::data::Curves;
-use RustQuant::data::{Curve, DiscountCurve, SpotCurve};
-use RustQuant::models::NelsonSiegelSvensson;
+use time::{macros::date, Date};
+use RustQuant::data::{Curves, SpotCurve};
use RustQuant::time::oceania::australia::AustraliaCalendar;
-// use RustQuant::time::Calendar;
fn main() {
- // let cal = AustraliaCalendar;
- // let curve = Curve::::new_from_slice(&DATES, &RATES);
+ let mut spot_curve = SpotCurve::::new(&DATES, &RATES);
- let mut discount_curve = SpotCurve::::new(&DATES, &RATES);
+ spot_curve.get_rates(&NEW_DATES);
- let new_dates = [
- date!(2025 - 01 - 01),
- date!(2026 - 01 - 01),
- date!(2027 - 01 - 01),
- date!(2028 - 01 - 01),
- date!(2029 - 01 - 01),
- date!(2030 - 01 - 01),
- date!(2033 - 01 - 01),
- date!(2036 - 01 - 01),
- date!(2040 - 01 - 01),
- date!(2044 - 01 - 01),
- date!(2046 - 01 - 01),
- date!(2048 - 01 - 01),
- date!(2050 - 01 - 01),
- date!(2053 - 01 - 01),
- ];
-
- discount_curve.get_rates(&new_dates);
-
- discount_curve.plot();
-
- // let nss = NelsonSiegelSvensson::new(0.0806, -0.0031, -0.0625, -0.0198, 1.58, 0.15);
-
- // let date = date!(2027 - 01 - 01);
- // println!("Forward rate: {:?}", nss.forward_rate(date));
- // println!("Spot rate: {:?}", nss.spot_rate(date));
+ spot_curve.plot();
}
const DATES: [Date; 33] = [
@@ -79,10 +46,56 @@ const DATES: [Date; 33] = [
date!(2054 - 07 - 28),
];
+#[rustfmt::skip]
const RATES: [f64; 33] = [
- 0.03400521, 0.03259227, 0.0313705, 0.03031886, 0.02746567, 0.02614014, 0.02574612, 0.02590431,
- 0.02637474, 0.02700684, 0.02770726, 0.02841916, 0.02910886, 0.02975736, 0.03035484, 0.03089715,
- 0.0313836, 0.03181554, 0.03219547, 0.03252652, 0.03281203, 0.03305541, 0.03326001, 0.033429,
- 0.03356541, 0.03367205, 0.03375153, 0.03380629, 0.03383858, 0.03385046, 0.03384384, 0.03382048,
+ 0.03400521,
+ 0.03259227,
+ 0.0313705,
+ 0.03031886,
+ 0.02746567,
+ 0.02614014,
+ 0.02574612,
+ 0.02590431,
+ 0.02637474,
+ 0.02700684,
+ 0.02770726,
+ 0.02841916,
+ 0.02910886,
+ 0.02975736,
+ 0.03035484,
+ 0.03089715,
+ 0.0313836,
+ 0.03181554,
+ 0.03219547,
+ 0.03252652,
+ 0.03281203,
+ 0.03305541,
+ 0.03326001,
+ 0.033429,
+ 0.03356541,
+ 0.03367205,
+ 0.03375153,
+ 0.03380629,
+ 0.03383858,
+ 0.03385046,
+ 0.03384384,
+ 0.03382048,
0.033782,
];
+
+const NEW_DATES: [Date; 14] = [
+ date!(2025 - 01 - 01),
+ date!(2026 - 01 - 01),
+ date!(2027 - 01 - 01),
+ date!(2028 - 01 - 01),
+ date!(2029 - 01 - 01),
+ date!(2030 - 01 - 01),
+ date!(2033 - 01 - 01),
+ date!(2036 - 01 - 01),
+ date!(2040 - 01 - 01),
+ date!(2044 - 01 - 01),
+ date!(2046 - 01 - 01),
+ date!(2048 - 01 - 01),
+ date!(2050 - 01 - 01),
+ date!(2053 - 01 - 01),
+];
diff --git a/examples/custom_payoffs.rs b/examples/custom_payoffs.rs
index 18e033d7..f12d6c0a 100644
--- a/examples/custom_payoffs.rs
+++ b/examples/custom_payoffs.rs
@@ -63,7 +63,8 @@ fn main() {
// Generate path using Euler-Maruyama scheme.
// Parameters: x_0, t_0, t_n, n, sims, parallel.
- let gbm_out = gbm.euler_maruyama(INITIAL_VALUE, START_TIME, END_TIME, NUM_STEPS, NUM_SIMS, PARALLEL);
+ let config = StochasticProcessConfig::new(INITIAL_VALUE, START_TIME, END_TIME, NUM_STEPS, NUM_SIMS, PARALLEL);
+ let gbm_out = gbm.euler_maruyama(&config);
// Price the options.
println!("Up-and-out call: {}", barrier_option_payoff(&gbm_out.paths[0], 10.0, 12.0, OptionType::Call, BarrierType::UpAndOut));
diff --git a/examples/custom_process.rs b/examples/custom_process.rs
index b1b2f4b7..3ae55a8f 100644
--- a/examples/custom_process.rs
+++ b/examples/custom_process.rs
@@ -21,7 +21,11 @@
// """
use std::f64::consts::PI;
-use RustQuant::{math::Sequence, plot_vector, stochastics::process::StochasticProcess};
+use RustQuant::{
+ math::Sequence,
+ plot_vector,
+ stochastics::{process::StochasticProcess, StochasticProcessConfig},
+};
fn main() {
// Create an x-axis.
@@ -40,7 +44,8 @@ fn main() {
};
// Generate a path and plot it.
- let output = custom_process.euler_maruyama(0.01, 0.0, 10.0, 500, 1, false);
+ let config = StochasticProcessConfig::new(0.01, 0.0, 10.0, 500, 1, false);
+ let output = custom_process.euler_maruyama(&config);
plot_vector!(output.paths[0], "./images/ricker_wavelet_process.png");
}
diff --git a/examples/market_data.rs b/examples/market_data.rs
new file mode 100644
index 00000000..3c6d4243
--- /dev/null
+++ b/examples/market_data.rs
@@ -0,0 +1,14 @@
+use RustQuant::pricer::MarketData;
+use RustQuant::pricer::MarketDataBuilder;
+use RustQuant::time::oceania::australia::AustraliaCalendar;
+
+fn main() {
+ let market_data: MarketData = MarketDataBuilder::default()
+ .underlying_price(Some(100.0))
+ .volatility(Some(0.2))
+ .dividend_yield(Some(0.0))
+ .build()
+ .unwrap();
+
+ println!("{:?}", market_data);
+}
diff --git a/examples/stochastic_processes.rs b/examples/stochastic_processes.rs
index c6849111..d9942828 100644
--- a/examples/stochastic_processes.rs
+++ b/examples/stochastic_processes.rs
@@ -37,19 +37,21 @@ fn main() {
// Generate path using Euler-Maruyama scheme.
// Parameters: x_0, t_0, t_n, n, sims, parallel.
- let abm_out = abm.euler_maruyama(INITIAL_VALUE, START_TIME, END_TIME, NUM_STEPS, NUM_SIMS, PARALLEL);
- let bdt_out = bdt.euler_maruyama(INITIAL_VALUE, START_TIME, END_TIME, NUM_STEPS, NUM_SIMS, PARALLEL);
- let bm_out = bm.euler_maruyama(INITIAL_VALUE, START_TIME, END_TIME, NUM_STEPS, NUM_SIMS, PARALLEL);
- let cir_out = cir.euler_maruyama(INITIAL_VALUE, START_TIME, END_TIME, NUM_STEPS, NUM_SIMS, PARALLEL);
- let ev_out = ev.euler_maruyama(INITIAL_VALUE, START_TIME, END_TIME, NUM_STEPS, NUM_SIMS, PARALLEL);
- let gbm_out = gbm.euler_maruyama(INITIAL_VALUE, START_TIME, END_TIME, NUM_STEPS, NUM_SIMS, PARALLEL);
- let hl_out = hl.euler_maruyama(INITIAL_VALUE, START_TIME, END_TIME, NUM_STEPS, NUM_SIMS, PARALLEL);
- let hw_out = hw.euler_maruyama(INITIAL_VALUE, START_TIME, END_TIME, NUM_STEPS, NUM_SIMS, PARALLEL);
- let ou_out = ou.euler_maruyama(INITIAL_VALUE, START_TIME, END_TIME, NUM_STEPS, NUM_SIMS, PARALLEL);
- let fbm_out = fbm.euler_maruyama(INITIAL_VALUE, START_TIME, END_TIME, NUM_STEPS, NUM_SIMS, PARALLEL);
- let mjd_out = mjd.euler_maruyama(INITIAL_VALUE, START_TIME, END_TIME, NUM_STEPS, NUM_SIMS, PARALLEL);
- let gbb_out = gbb.euler_maruyama(INITIAL_VALUE, START_TIME, END_TIME, NUM_STEPS, NUM_SIMS, PARALLEL);
- let cev_out = cev.euler_maruyama(INITIAL_VALUE, START_TIME, END_TIME, NUM_STEPS, NUM_SIMS, PARALLEL);
+ let config = StochasticProcessConfig::new(INITIAL_VALUE, START_TIME, END_TIME, NUM_STEPS, NUM_SIMS, PARALLEL);
+
+ let abm_out = abm.euler_maruyama(&config);
+ let bdt_out = bdt.euler_maruyama(&config);
+ let bm_out = bm.euler_maruyama(&config);
+ let cir_out = cir.euler_maruyama(&config);
+ let ev_out = ev.euler_maruyama(&config);
+ let gbm_out = gbm.euler_maruyama(&config);
+ let hl_out = hl.euler_maruyama(&config);
+ let hw_out = hw.euler_maruyama(&config);
+ let ou_out = ou.euler_maruyama(&config);
+ let fbm_out = fbm.euler_maruyama(&config);
+ let mjd_out = mjd.euler_maruyama(&config);
+ let gbb_out = gbb.euler_maruyama(&config);
+ let cev_out = cev.euler_maruyama(&config);
// Plot the paths.
plot_vector!(abm_out.paths[0].clone(), "./images/arithmetic_brownian_motion.png");
@@ -65,4 +67,34 @@ fn main() {
plot_vector!(mjd_out.paths[0].clone(), "./images/merton_jump_diffusion.png");
plot_vector!(gbb_out.paths[0].clone(), "./images/geometric_brownian_bridge.png");
plot_vector!(cev_out.paths[0].clone(), "./images/constant_elasticity_of_variance.png");
+
+ plot_trajectories(&gbm_out, true);
+}
+
+use plotly::{common::Mode, Plot, Scatter};
+
+fn plot_trajectories(paths: &Trajectories, show: bool) -> Plot {
+ let mut plot = Plot::new();
+
+ let xs = paths
+ .times
+ .iter()
+ .map(|x| x.to_string())
+ .collect::>();
+
+ for (i, path) in paths.paths.iter().enumerate() {
+ let ys = path.iter().cloned().collect::>();
+
+ let trace = Scatter::new(xs.clone(), ys)
+ .mode(Mode::Lines)
+ .name(format!("Path {}", i + 1));
+
+ plot.add_trace(trace);
+ }
+
+ if show {
+ plot.show();
+ }
+
+ plot
}
diff --git a/examples/yield_curve_interpolation.rs b/examples/yield_curve_interpolation.rs
deleted file mode 100644
index 599be4d2..00000000
--- a/examples/yield_curve_interpolation.rs
+++ /dev/null
@@ -1,38 +0,0 @@
-use time::{Date, Duration};
-use RustQuant::{
- data::{Curve, YieldCurve},
- plot_vector,
- time::today,
-};
-
-fn main() {
- // Initial date of the curve (today).
- let t0 = today();
-
- // Create a treasury yield curve with 8 points (3m, 6m, 1y, 2y, 5y, 10y, 30y).
- // Values from Bloomberg:
- let rate_vec = vec![0.0544, 0.0556, 0.0546, 0.0514, 0.0481, 0.0481, 0.0494];
- let date_vec = vec![
- t0 + Duration::days(90),
- t0 + Duration::days(180),
- t0 + Duration::days(365),
- t0 + Duration::days(2 * 365),
- t0 + Duration::days(5 * 365),
- t0 + Duration::days(10 * 365),
- t0 + Duration::days(30 * 365),
- ];
-
- let yield_curve = YieldCurve::from_dates_and_rates(&date_vec, &rate_vec);
-
- // Create a vector of dates to interpolate the yield curve at.
- let dates_to_plot = (91..(30 * 365))
- .step_by(10)
- .map(|i| t0 + Duration::days(i))
- .collect::>();
-
- // Compute the discount factors.
- let discount_factors = yield_curve.discount_factors(&dates_to_plot);
-
- // Plot the interpolated yield curve.
- plot_vector!(discount_factors, "./images/interpolated_yield_curve.png");
-}
diff --git a/images/arithmetic_brownian_motion.png b/images/arithmetic_brownian_motion.png
index 5d1c9d6d..2172f206 100644
Binary files a/images/arithmetic_brownian_motion.png and b/images/arithmetic_brownian_motion.png differ
diff --git a/images/black_derman_toy.png b/images/black_derman_toy.png
index db0a383f..5dcb0f64 100644
Binary files a/images/black_derman_toy.png and b/images/black_derman_toy.png differ
diff --git a/images/brownian_motion.png b/images/brownian_motion.png
index cca81907..fd5abd2e 100644
Binary files a/images/brownian_motion.png and b/images/brownian_motion.png differ
diff --git a/images/constant_elasticity_of_variance.png b/images/constant_elasticity_of_variance.png
index 8187d3d8..ac3ae178 100644
Binary files a/images/constant_elasticity_of_variance.png and b/images/constant_elasticity_of_variance.png differ
diff --git a/images/cox_ingersoll_ross.png b/images/cox_ingersoll_ross.png
index f9f738f8..cf94efd0 100644
Binary files a/images/cox_ingersoll_ross.png and b/images/cox_ingersoll_ross.png differ
diff --git a/images/extended_vasicek.png b/images/extended_vasicek.png
index 08340190..92befd57 100644
Binary files a/images/extended_vasicek.png and b/images/extended_vasicek.png differ
diff --git a/images/fractional_brownian_motion.png b/images/fractional_brownian_motion.png
index f32db9f0..e94ad1fa 100644
Binary files a/images/fractional_brownian_motion.png and b/images/fractional_brownian_motion.png differ
diff --git a/images/geometric_brownian_bridge.png b/images/geometric_brownian_bridge.png
index c5ba2724..17835c9b 100644
Binary files a/images/geometric_brownian_bridge.png and b/images/geometric_brownian_bridge.png differ
diff --git a/images/geometric_brownian_motion.png b/images/geometric_brownian_motion.png
index 13fd4074..853a5a7f 100644
Binary files a/images/geometric_brownian_motion.png and b/images/geometric_brownian_motion.png differ
diff --git a/images/ho_lee.png b/images/ho_lee.png
index 4f1b0674..02002d08 100644
Binary files a/images/ho_lee.png and b/images/ho_lee.png differ
diff --git a/images/hull_white.png b/images/hull_white.png
index 15fe08d4..1641e2c6 100644
Binary files a/images/hull_white.png and b/images/hull_white.png differ
diff --git a/images/merton_jump_diffusion.png b/images/merton_jump_diffusion.png
index 5a207b9a..aa4f942e 100644
Binary files a/images/merton_jump_diffusion.png and b/images/merton_jump_diffusion.png differ
diff --git a/images/ornstein_uhlenbeck.png b/images/ornstein_uhlenbeck.png
index bc3e4936..aa618578 100644
Binary files a/images/ornstein_uhlenbeck.png and b/images/ornstein_uhlenbeck.png differ
diff --git a/src/cashflows/cashflow.rs b/src/cashflows/cashflow.rs
index 3f0cfde3..88a05ad2 100644
--- a/src/cashflows/cashflow.rs
+++ b/src/cashflows/cashflow.rs
@@ -174,20 +174,20 @@ mod test_cashflows {
use super::*;
use time::Duration;
- use crate::assert_approx_equal;
+ use crate::{assert_approx_equal, time::today};
use std::f64::EPSILON as EPS;
// Test to verify the `amount` method.
#[test]
fn test_amount() {
- let cf = Cashflow::new(100.0, Date::now_utc());
+ let cf = Cashflow::new(100.0, today());
assert_approx_equal!(cf.amount(), 100.0, EPS);
}
// Test to verify the `date` method.
#[test]
fn test_date() {
- let now = Date::now_utc();
+ let now = today();
let cf = Cashflow::new(100.0, now);
assert_eq!(cf.date(), now);
}
@@ -195,41 +195,26 @@ mod test_cashflows {
// Test to verify the `npv` method.
#[test]
fn test_npv() {
- let now = Date::now_utc();
- let cf = Cashflow::new(100.0, now);
+ let cf = Cashflow::new(100.0, today());
- // Discount function that reduces value by 10%.
- let df = |date: Date| if date == now { 0.9 } else { 1.0 };
- assert_approx_equal!(cf.npv(df), 90.0, EPS);
+ assert_approx_equal!(cf.npv(0.9), 90.0, EPS);
}
// Test to verify the `npv` method with a zero discount rate.
#[test]
fn test_npv_zero_discount() {
- let now = Date::now_utc();
+ let now = today();
let cf = Cashflow::new(100.0, now);
// Discount function that keeps value the same.
- let df = |_: Date| 1.0;
+ let df = 1.0;
assert_approx_equal!(cf.npv(df), 100.0, EPS);
}
- // Test to verify the `npv` method with future date
- #[test]
- fn test_npv_future_date() {
- let now = Date::now_utc();
- let future_date = now + Duration::days(30);
- let cf = Cashflow::new(100.0, future_date);
-
- // Discount function that reduces value by 10% for future_date.
- let df = |date: Date| if date == future_date { 0.9 } else { 1.0 };
- assert_approx_equal!(cf.npv(df), 90.0, EPS);
- }
-
// Test to verify addition of cashflows with the same date.
#[test]
fn test_add_cashflows() {
- let date = Date::now_utc();
+ let date = today();
let cf1 = Cashflow::new(100.0, date);
let cf2 = Cashflow::new(50.0, date);
let result = cf1 + cf2;
@@ -240,7 +225,7 @@ mod test_cashflows {
// Test to verify subtraction of cashflows with the same date.
#[test]
fn test_sub_cashflows() {
- let date = Date::now_utc();
+ let date = today();
let cf1 = Cashflow::new(100.0, date);
let cf2 = Cashflow::new(50.0, date);
let result = cf1 - cf2;
@@ -251,7 +236,7 @@ mod test_cashflows {
// Test for negative cashflows.
#[test]
fn test_negative_cashflow() {
- let date = Date::now_utc();
+ let date = today();
let cf = Cashflow::new(-100.0, date);
assert_approx_equal!(cf.amount(), -100.0, EPS);
}
@@ -259,7 +244,7 @@ mod test_cashflows {
// Test for zero cashflows.
#[test]
fn test_zero_cashflow() {
- let date = Date::now_utc();
+ let date = today();
let cf = Cashflow::new(0.0, date);
assert_approx_equal!(cf.amount(), 0.0, EPS);
}
@@ -268,7 +253,7 @@ mod test_cashflows {
#[test]
#[should_panic(expected = "Dates must match.")]
fn test_non_matching_dates_add() {
- let date1 = Date::now_utc();
+ let date1 = today();
let date2 = date1 + Duration::days(1);
let cf1 = Cashflow::new(100.0, date1);
let cf2 = Cashflow::new(50.0, date2);
diff --git a/src/cashflows/legs.rs b/src/cashflows/legs.rs
index a1b8e55d..6cd2bd53 100644
--- a/src/cashflows/legs.rs
+++ b/src/cashflows/legs.rs
@@ -78,18 +78,20 @@ impl Leg {
#[cfg(test)]
mod tests_legs {
- use super::super::SimpleCashflow;
+ // use super::super::SimpleCashflow;
use super::*;
use crate::assert_approx_equal;
+ use crate::time::today;
use std::f64::EPSILON as EPS;
use time::Duration;
+ use time::OffsetDateTime;
// Utility function to generate a simple leg for testing.
- fn generate_simple_leg(now: OffsetDateTime) -> Leg {
+ fn generate_simple_leg(now: Date) -> Leg {
let cashflows = vec![
- SimpleCashflow::new(100.0, now),
- SimpleCashflow::new(200.0, now + Duration::days(30)),
- SimpleCashflow::new(300.0, now + Duration::days(60)),
+ Cashflow::new(100.0, now),
+ Cashflow::new(200.0, now + Duration::days(30)),
+ Cashflow::new(300.0, now + Duration::days(60)),
];
Leg::new(cashflows)
}
@@ -97,7 +99,7 @@ mod tests_legs {
// Test to verify the `size` method.
#[test]
fn test_size() {
- let now = OffsetDateTime::now_utc();
+ let now = today();
let leg = generate_simple_leg(now);
assert_eq!(leg.size(), 3);
}
@@ -105,20 +107,20 @@ mod tests_legs {
// Test to verify the `npv` method.
#[test]
fn test_npv() {
- let now = OffsetDateTime::now_utc();
+ let now = today();
let leg = generate_simple_leg(now);
// Discount function that reduces value by 10%.
- let df = |_| 0.9;
+ let df = 0.9;
assert_approx_equal!(leg.npv(df), 540.0, EPS);
}
// Test to verify the `add_cashflow` method.
#[test]
fn test_add_cashflow() {
- let now = OffsetDateTime::now_utc();
+ let now = today();
let mut leg = generate_simple_leg(now);
- let new_cashflow = SimpleCashflow::new(400.0, now + Duration::days(90));
+ let new_cashflow = Cashflow::new(400.0, now + Duration::days(90));
leg.add_cashflow(new_cashflow.clone());
assert_eq!(leg.size(), 4);
assert_approx_equal!(
@@ -131,7 +133,7 @@ mod tests_legs {
// Test to verify the `start_date` and `end_date` methods.
#[test]
fn test_start_end_date() {
- let now = OffsetDateTime::now_utc();
+ let now = today();
let leg = generate_simple_leg(now);
let start = leg.start_date().unwrap();
let end = leg.end_date().unwrap();
@@ -142,7 +144,7 @@ mod tests_legs {
// Test to verify the `is_active` method.
#[test]
fn test_is_active() {
- let now = OffsetDateTime::now_utc();
+ let now = today();
let leg = generate_simple_leg(now);
assert!(leg.is_active(now));
assert!(leg.is_active(now + Duration::days(30)));
diff --git a/src/data/curves.rs b/src/data/curves.rs
index f9732e2c..febfe41e 100644
--- a/src/data/curves.rs
+++ b/src/data/curves.rs
@@ -316,15 +316,6 @@ pub trait Curves {
/// Create a new curve from a set of `Date`s and rates (`f64`s).
fn new(dates: &[Date], rates: &[f64]) -> Self;
- /// Set the calendar for the curve.
- fn with_calendar(&mut self, calendar: C);
-
- /// Set the day count convention for the curve.
- fn with_day_count_convention(&mut self, day_count_convention: DayCountConvention);
-
- /// Set the date rolling convention for the curve.
- fn with_date_rolling_convention(&mut self, date_rolling_convention: DateRollingConvention);
-
/// Get the initial date of the curve.
fn initial_date(&self) -> Date;
@@ -347,7 +338,7 @@ pub trait Curves {
fn plot(&self);
}
-macro_rules! impl_specific_curve {
+macro_rules! impl_specific_curve_cost_function {
($curve:ident, $curve_function:ident) => {
impl CostFunction for &$curve
where
@@ -377,11 +368,11 @@ macro_rules! impl_specific_curve {
Ok(log_cosh_loss)
}
}
+ };
+}
- // impl $curve
- // where
- // C: Calendar + Clone,
- // {
+macro_rules! impl_specific_curve {
+ ($curve:ident, $curve_function:ident) => {
impl Curves for $curve
where
C: Calendar + Clone,
@@ -439,24 +430,6 @@ macro_rules! impl_specific_curve {
}
}
- #[doc = concat!("Set the calendar for the ", stringify!($curve))]
- fn with_calendar(&mut self, calendar: C) {
- self.calendar = Some(calendar);
- }
-
- #[doc = concat!("Set the day count convention for the ", stringify!($curve))]
- fn with_day_count_convention(&mut self, day_count_convention: DayCountConvention) {
- self.day_count_convention = Some(day_count_convention);
- }
-
- #[doc = concat!("Set the date rolling convention for the ", stringify!($curve))]
- fn with_date_rolling_convention(
- &mut self,
- date_rolling_convention: DateRollingConvention,
- ) {
- self.date_rolling_convention = Some(date_rolling_convention);
- }
-
#[doc = concat!("Get the initial date of the ", stringify!($curve))]
fn initial_date(&self) -> Date {
*self.curve.first_key().unwrap()
@@ -569,7 +542,7 @@ macro_rules! impl_specific_curve {
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/// Discount curve data structure.
-#[derive(Builder, Clone)]
+#[derive(Builder, Clone, Debug)]
pub struct DiscountCurve
where
I: CurveIndex,
@@ -601,6 +574,7 @@ where
pub fitted_curve: Option>,
}
+impl_specific_curve_cost_function!(DiscountCurve, discount_factor);
impl_specific_curve!(DiscountCurve, discount_factor);
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -608,7 +582,7 @@ impl_specific_curve!(DiscountCurve, discount_factor);
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/// Spot curve data structure.
-#[derive(Builder, Clone)]
+#[derive(Builder, Clone, Debug)]
pub struct SpotCurve
where
I: CurveIndex,
@@ -640,6 +614,7 @@ where
pub fitted_curve: Option>,
}
+impl_specific_curve_cost_function!(SpotCurve, spot_rate);
impl_specific_curve!(SpotCurve, spot_rate);
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -647,7 +622,7 @@ impl_specific_curve!(SpotCurve, spot_rate);
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/// Forward curve data structure.
-#[derive(Builder, Clone)]
+#[derive(Builder, Clone, Debug)]
pub struct ForwardCurve
where
I: CurveIndex,
@@ -679,8 +654,100 @@ where
pub fitted_curve: Option>,
}
+impl_specific_curve_cost_function!(ForwardCurve, forward_rate);
impl_specific_curve!(ForwardCurve, forward_rate);
+// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+// FLAT CURVE
+// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+/// Flat curve data structure.
+#[derive(Builder, Clone, Debug)]
+pub struct FlatCurve
+where
+ C: Calendar,
+{
+ /// Rate of the curve.
+ pub rate: f64,
+
+ /// Calendar.
+ pub calendar: Option,
+
+ /// Day count convention.
+ pub day_count_convention: Option,
+
+ /// Date rolling convention.
+ pub date_rolling_convention: Option,
+}
+
+impl FlatCurve
+where
+ C: Calendar,
+{
+ /// Create a new flat curve.
+ pub fn new_flat_curve(rate: f64) -> Self {
+ Self {
+ rate,
+ calendar: None,
+ day_count_convention: None,
+ date_rolling_convention: None,
+ }
+ }
+
+ /// Get the rate of the curve.
+ pub fn get_rate(&self) -> f64 {
+ self.rate
+ }
+
+ /// Get rate for a specific date.
+ pub fn get_rate_for_date(&self, _date: Date) -> f64 {
+ self.rate
+ }
+
+ /// Get rates for multiple dates.
+ pub fn get_rates_for_dates(&self, dates: &[Date]) -> Vec {
+ vec![self.rate; dates.len()]
+ }
+}
+
+// impl Curves for FlatCurve
+// where
+// C: Calendar,
+// {
+// /// NOT TO BE USED. Prefer the `new_flat_curve()` method.
+// fn new(dates: &[Date], rates: &[f64]) -> Self {
+// unimplemented!("FlatCurve does not support this method. Use `new_flat_curve()` instead.")
+// }
+
+// fn initial_date(&self) -> Date {
+// Date::MIN
+// }
+
+// fn terminal_date(&self) -> Date {
+// Date::MAX
+// }
+
+// fn get_rate(&mut self, date: Date) -> f64 {
+// self.rate
+// }
+
+// fn get_rates(&mut self, dates: &[Date]) -> Vec {
+// vec![self.rate; dates.len()]
+// }
+
+// fn insert_rate(&mut self, date: Date, rate: f64) {
+// todo!()
+// }
+
+// fn fit(&mut self) -> Result<(), argmin::core::Error> {
+// unimplemented!()
+// }
+
+// fn plot(&self) {
+// unimplemented!()
+// }
+// }
+
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Base Curve Trait
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -774,342 +841,341 @@ impl_specific_curve!(ForwardCurve, forward_rate);
#[cfg(test)]
mod tests_curves {
- use super::*;
- use crate::time::today;
- use std::collections::BTreeMap;
- use time::Duration;
- use time::OffsetDateTime;
-
- #[test]
- fn test_discount_curve_creation() {
- let dates = [today() + Duration::days(30), today() + Duration::days(60)];
- let rates = [0.025, 0.03];
-
- let discount_curve = DiscountCurve::new(&dates, &rates);
-
- assert_eq!(discount_curve.rates, rates);
- }
-
- #[test]
- fn test_discount_curve_initial_date() {
- let dates = [
- OffsetDateTime::UNIX_EPOCH.date() + Duration::days(30),
- OffsetDateTime::UNIX_EPOCH.date() + Duration::days(60),
- ];
-
- let rates = [0.025, 0.03];
-
- let discount_curve = DiscountCurve::new(&dates, &rates);
- let initial_date = discount_curve.initial_date();
-
- assert_eq!(
- initial_date,
- OffsetDateTime::UNIX_EPOCH.date() + Duration::days(30)
- );
- }
-
- #[test]
- fn test_discount_curve_final_date() {
- let dates = [
- OffsetDateTime::UNIX_EPOCH.date() + Duration::days(30),
- OffsetDateTime::UNIX_EPOCH.date() + Duration::days(60),
- ];
-
- let rates = [0.025, 0.03];
-
- let discount_curve = DiscountCurve::new(&dates, &rates);
- let final_date = discount_curve.terminal_date();
-
- assert_eq!(
- final_date,
- OffsetDateTime::UNIX_EPOCH.date() + Duration::days(60)
- );
- }
-
- #[test]
- fn test_find_date_interval() {
- let dates = [
- OffsetDateTime::UNIX_EPOCH.date() + Duration::days(30),
- OffsetDateTime::UNIX_EPOCH.date() + Duration::days(60),
- ];
-
- let rates = [0.025, 0.03];
-
- let discount_curve = DiscountCurve::new(&dates, &rates);
-
- let date1 = OffsetDateTime::UNIX_EPOCH.date() + Duration::days(30);
- let date2 = OffsetDateTime::UNIX_EPOCH.date() + Duration::days(45);
- let date3 = OffsetDateTime::UNIX_EPOCH.date() + Duration::days(60);
-
- let interval1 = discount_curve.find_date_interval(date1);
- let interval2 = discount_curve.find_date_interval(date2);
- let interval3 = discount_curve.find_date_interval(date3);
-
- assert_eq!(interval1, (date1, date1));
- assert_eq!(interval2, (date1, date3));
- assert_eq!(interval3, (date3, date3));
- }
-
- #[allow(clippy::similar_names)]
- #[test]
- fn test_discount_curve_discount_factor() {
- // Initial date of the curve.
- let t0 = OffsetDateTime::UNIX_EPOCH.date();
-
- // Create a discount curve with 8 points.
- let rate_vec = vec![0.025, 0.03, 0.035, 0.04, 0.045, 0.05, 0.055, 0.06];
- let date_vec = vec![
- t0 + Duration::days(30),
- t0 + Duration::days(60),
- t0 + Duration::days(90),
- t0 + Duration::days(120),
- t0 + Duration::days(150),
- t0 + Duration::days(180),
- t0 + Duration::days(210),
- t0 + Duration::days(360),
- ];
-
- let discount_curve = DiscountCurve::from_dates_and_rates(&date_vec, &rate_vec);
-
- println!("Curve: {:?}", discount_curve.rates);
-
- // Test the discount factor for a dates inside the curve's range.
- let date1 = OffsetDateTime::UNIX_EPOCH.date() + Duration::days(45);
- let date2 = OffsetDateTime::UNIX_EPOCH.date() + Duration::days(80);
- let date3 = OffsetDateTime::UNIX_EPOCH.date() + Duration::days(250);
-
- let df1 = discount_curve.discount_factor(date1);
- let df2 = discount_curve.discount_factor(date2);
- let df3 = discount_curve.discount_factor(date3);
-
- println!("df1: {:?}", df1);
- println!("df2: {:?}", df2);
- println!("df3: {:?}", df3);
-
- assert!(df1 > 0.0 && df1 < 1.0 && df2 > 0.0 && df2 < 1.0 && df3 > 0.0 && df3 < 1.0);
-
- assert!(df1 > df2 && df2 > df3);
- }
-
- #[test]
- fn test_discount_curve_creation() {
- let dates = [today() + Duration::days(30), today() + Duration::days(60)];
- let rates = [0.025, 0.03];
-
- let discount_curve = ForwardCurve::new(&dates, &rates);
-
- assert_eq!(discount_curve.rates, rates);
- }
-
- #[test]
- fn test_discount_curve_initial_date() {
- let dates = [
- OffsetDateTime::UNIX_EPOCH.date() + Duration::days(30),
- OffsetDateTime::UNIX_EPOCH.date() + Duration::days(60),
- ];
-
- let rates = [0.025, 0.03];
-
- let discount_curve = ForwardCurve::new(&dates, &rates);
- let initial_date = discount_curve.initial_date();
-
- assert_eq!(
- initial_date,
- OffsetDateTime::UNIX_EPOCH.date() + Duration::days(30)
- );
- }
-
- #[test]
- fn test_discount_curve_final_date() {
- let dates = [
- OffsetDateTime::UNIX_EPOCH.date() + Duration::days(30),
- OffsetDateTime::UNIX_EPOCH.date() + Duration::days(60),
- ];
-
- let rates = [0.025, 0.03];
-
- let discount_curve = ForwardCurve::new(&dates, &rates);
- let final_date = discount_curve.terminal_date();
+ // use super::*;
+ // use crate::time::today;
+ // use time::Duration;
+ // use time::OffsetDateTime;
+
+ // #[test]
+ // fn test_discount_curve_creation() {
+ // let dates = [today() + Duration::days(30), today() + Duration::days(60)];
+ // let rates = [0.025, 0.03];
+
+ // let discount_curve = DiscountCurve::new(&dates, &rates);
+
+ // assert_eq!(discount_curve.rates, rates);
+ // }
+
+ // #[test]
+ // fn test_discount_curve_initial_date() {
+ // let dates = [
+ // OffsetDateTime::UNIX_EPOCH.date() + Duration::days(30),
+ // OffsetDateTime::UNIX_EPOCH.date() + Duration::days(60),
+ // ];
+
+ // let rates = [0.025, 0.03];
+
+ // let discount_curve = DiscountCurve::new(&dates, &rates);
+ // let initial_date = discount_curve.initial_date();
+
+ // assert_eq!(
+ // initial_date,
+ // OffsetDateTime::UNIX_EPOCH.date() + Duration::days(30)
+ // );
+ // }
+
+ // #[test]
+ // fn test_discount_curve_final_date() {
+ // let dates = [
+ // OffsetDateTime::UNIX_EPOCH.date() + Duration::days(30),
+ // OffsetDateTime::UNIX_EPOCH.date() + Duration::days(60),
+ // ];
+
+ // let rates = [0.025, 0.03];
+
+ // let discount_curve = DiscountCurve::new(&dates, &rates);
+ // let final_date = discount_curve.terminal_date();
+
+ // assert_eq!(
+ // final_date,
+ // OffsetDateTime::UNIX_EPOCH.date() + Duration::days(60)
+ // );
+ // }
+
+ // #[test]
+ // fn test_find_date_interval() {
+ // let dates = [
+ // OffsetDateTime::UNIX_EPOCH.date() + Duration::days(30),
+ // OffsetDateTime::UNIX_EPOCH.date() + Duration::days(60),
+ // ];
+
+ // let rates = [0.025, 0.03];
+
+ // let discount_curve = DiscountCurve::new(&dates, &rates);
+
+ // let date1 = OffsetDateTime::UNIX_EPOCH.date() + Duration::days(30);
+ // let date2 = OffsetDateTime::UNIX_EPOCH.date() + Duration::days(45);
+ // let date3 = OffsetDateTime::UNIX_EPOCH.date() + Duration::days(60);
+
+ // let interval1 = discount_curve.find_date_interval(date1);
+ // let interval2 = discount_curve.find_date_interval(date2);
+ // let interval3 = discount_curve.find_date_interval(date3);
+
+ // assert_eq!(interval1, (date1, date1));
+ // assert_eq!(interval2, (date1, date3));
+ // assert_eq!(interval3, (date3, date3));
+ // }
+
+ // #[allow(clippy::similar_names)]
+ // #[test]
+ // fn test_discount_curve_discount_factor() {
+ // // Initial date of the curve.
+ // let t0 = OffsetDateTime::UNIX_EPOCH.date();
+
+ // // Create a discount curve with 8 points.
+ // let rate_vec = vec![0.025, 0.03, 0.035, 0.04, 0.045, 0.05, 0.055, 0.06];
+ // let date_vec = vec![
+ // t0 + Duration::days(30),
+ // t0 + Duration::days(60),
+ // t0 + Duration::days(90),
+ // t0 + Duration::days(120),
+ // t0 + Duration::days(150),
+ // t0 + Duration::days(180),
+ // t0 + Duration::days(210),
+ // t0 + Duration::days(360),
+ // ];
+
+ // let discount_curve = DiscountCurve::from_dates_and_rates(&date_vec, &rate_vec);
+
+ // println!("Curve: {:?}", discount_curve.rates);
+
+ // // Test the discount factor for a dates inside the curve's range.
+ // let date1 = OffsetDateTime::UNIX_EPOCH.date() + Duration::days(45);
+ // let date2 = OffsetDateTime::UNIX_EPOCH.date() + Duration::days(80);
+ // let date3 = OffsetDateTime::UNIX_EPOCH.date() + Duration::days(250);
+
+ // let df1 = discount_curve.discount_factor(date1);
+ // let df2 = discount_curve.discount_factor(date2);
+ // let df3 = discount_curve.discount_factor(date3);
+
+ // println!("df1: {:?}", df1);
+ // println!("df2: {:?}", df2);
+ // println!("df3: {:?}", df3);
+
+ // assert!(df1 > 0.0 && df1 < 1.0 && df2 > 0.0 && df2 < 1.0 && df3 > 0.0 && df3 < 1.0);
+
+ // assert!(df1 > df2 && df2 > df3);
+ // }
+
+ // #[test]
+ // fn test_discount_curve_creation() {
+ // let dates = [today() + Duration::days(30), today() + Duration::days(60)];
+ // let rates = [0.025, 0.03];
+
+ // let discount_curve = ForwardCurve::new(&dates, &rates);
+
+ // assert_eq!(discount_curve.rates, rates);
+ // }
+
+ // #[test]
+ // fn test_discount_curve_initial_date() {
+ // let dates = [
+ // OffsetDateTime::UNIX_EPOCH.date() + Duration::days(30),
+ // OffsetDateTime::UNIX_EPOCH.date() + Duration::days(60),
+ // ];
+
+ // let rates = [0.025, 0.03];
+
+ // let discount_curve = ForwardCurve::new(&dates, &rates);
+ // let initial_date = discount_curve.initial_date();
+
+ // assert_eq!(
+ // initial_date,
+ // OffsetDateTime::UNIX_EPOCH.date() + Duration::days(30)
+ // );
+ // }
+
+ // #[test]
+ // fn test_discount_curve_final_date() {
+ // let dates = [
+ // OffsetDateTime::UNIX_EPOCH.date() + Duration::days(30),
+ // OffsetDateTime::UNIX_EPOCH.date() + Duration::days(60),
+ // ];
+
+ // let rates = [0.025, 0.03];
+
+ // let discount_curve = ForwardCurve::new(&dates, &rates);
+ // let final_date = discount_curve.terminal_date();
+
+ // assert_eq!(
+ // final_date,
+ // OffsetDateTime::UNIX_EPOCH.date() + Duration::days(60)
+ // );
+ // }
+
+ // #[test]
+ // fn test_find_date_interval() {
+ // let dates = [
+ // OffsetDateTime::UNIX_EPOCH.date() + Duration::days(30),
+ // OffsetDateTime::UNIX_EPOCH.date() + Duration::days(60),
+ // ];
+
+ // let rates = [0.025, 0.03];
+
+ // let discount_curve = ForwardCurve::new(&dates, &rates);
+
+ // let date1 = OffsetDateTime::UNIX_EPOCH.date() + Duration::days(30);
+ // let date2 = OffsetDateTime::UNIX_EPOCH.date() + Duration::days(45);
+ // let date3 = OffsetDateTime::UNIX_EPOCH.date() + Duration::days(60);
+
+ // let interval1 = discount_curve.find_date_interval(date1);
+ // let interval2 = discount_curve.find_date_interval(date2);
+ // let interval3 = discount_curve.find_date_interval(date3);
+
+ // assert_eq!(interval1, (date1, date1));
+ // assert_eq!(interval2, (date1, date3));
+ // assert_eq!(interval3, (date3, date3));
+ // }
+
+ // #[allow(clippy::similar_names)]
+ // #[test]
+ // fn test_discount_curve_discount_factor() {
+ // // Initial date of the curve.
+ // let t0 = OffsetDateTime::UNIX_EPOCH.date();
- assert_eq!(
- final_date,
- OffsetDateTime::UNIX_EPOCH.date() + Duration::days(60)
- );
- }
-
- #[test]
- fn test_find_date_interval() {
- let dates = [
- OffsetDateTime::UNIX_EPOCH.date() + Duration::days(30),
- OffsetDateTime::UNIX_EPOCH.date() + Duration::days(60),
- ];
-
- let rates = [0.025, 0.03];
-
- let discount_curve = ForwardCurve::new(&dates, &rates);
-
- let date1 = OffsetDateTime::UNIX_EPOCH.date() + Duration::days(30);
- let date2 = OffsetDateTime::UNIX_EPOCH.date() + Duration::days(45);
- let date3 = OffsetDateTime::UNIX_EPOCH.date() + Duration::days(60);
-
- let interval1 = discount_curve.find_date_interval(date1);
- let interval2 = discount_curve.find_date_interval(date2);
- let interval3 = discount_curve.find_date_interval(date3);
-
- assert_eq!(interval1, (date1, date1));
- assert_eq!(interval2, (date1, date3));
- assert_eq!(interval3, (date3, date3));
- }
-
- #[allow(clippy::similar_names)]
- #[test]
- fn test_discount_curve_discount_factor() {
- // Initial date of the curve.
- let t0 = OffsetDateTime::UNIX_EPOCH.date();
-
- // Create a discount curve with 8 points.
- let rate_vec = vec![0.025, 0.03, 0.035, 0.04, 0.045, 0.05, 0.055, 0.06];
- let date_vec = vec![
- t0 + Duration::days(30),
- t0 + Duration::days(60),
- t0 + Duration::days(90),
- t0 + Duration::days(120),
- t0 + Duration::days(150),
- t0 + Duration::days(180),
- t0 + Duration::days(210),
- t0 + Duration::days(360),
- ];
-
- let discount_curve = ForwardCurve::from_dates_and_rates(&date_vec, &rate_vec);
-
- println!("Curve: {:?}", discount_curve.rates);
-
- // Test the discount factor for a dates inside the curve's range.
- let date1 = OffsetDateTime::UNIX_EPOCH.date() + Duration::days(45);
- let date2 = OffsetDateTime::UNIX_EPOCH.date() + Duration::days(80);
- let date3 = OffsetDateTime::UNIX_EPOCH.date() + Duration::days(250);
-
- let df1 = discount_curve.discount_factor(date1);
- let df2 = discount_curve.discount_factor(date2);
- let df3 = discount_curve.discount_factor(date3);
-
- println!("df1: {:?}", df1);
- println!("df2: {:?}", df2);
- println!("df3: {:?}", df3);
-
- assert!(df1 > 0.0 && df1 < 1.0 && df2 > 0.0 && df2 < 1.0 && df3 > 0.0 && df3 < 1.0);
-
- assert!(df1 > df2 && df2 > df3);
- }
-
- #[test]
- fn test_discount_curve_creation() {
- let dates = [today() + Duration::days(30), today() + Duration::days(60)];
- let rates = [0.025, 0.03];
-
- let discount_curve = SpotCurve::new(&dates, &rates);
-
- assert_eq!(discount_curve.rates, rates);
- }
-
- #[test]
- fn test_discount_curve_initial_date() {
- let dates = [
- OffsetDateTime::UNIX_EPOCH.date() + Duration::days(30),
- OffsetDateTime::UNIX_EPOCH.date() + Duration::days(60),
- ];
-
- let rates = [0.025, 0.03];
+ // // Create a discount curve with 8 points.
+ // let rate_vec = vec![0.025, 0.03, 0.035, 0.04, 0.045, 0.05, 0.055, 0.06];
+ // let date_vec = vec![
+ // t0 + Duration::days(30),
+ // t0 + Duration::days(60),
+ // t0 + Duration::days(90),
+ // t0 + Duration::days(120),
+ // t0 + Duration::days(150),
+ // t0 + Duration::days(180),
+ // t0 + Duration::days(210),
+ // t0 + Duration::days(360),
+ // ];
+
+ // let discount_curve = ForwardCurve::from_dates_and_rates(&date_vec, &rate_vec);
+
+ // println!("Curve: {:?}", discount_curve.rates);
+
+ // // Test the discount factor for a dates inside the curve's range.
+ // let date1 = OffsetDateTime::UNIX_EPOCH.date() + Duration::days(45);
+ // let date2 = OffsetDateTime::UNIX_EPOCH.date() + Duration::days(80);
+ // let date3 = OffsetDateTime::UNIX_EPOCH.date() + Duration::days(250);
+
+ // let df1 = discount_curve.discount_factor(date1);
+ // let df2 = discount_curve.discount_factor(date2);
+ // let df3 = discount_curve.discount_factor(date3);
+
+ // println!("df1: {:?}", df1);
+ // println!("df2: {:?}", df2);
+ // println!("df3: {:?}", df3);
+
+ // assert!(df1 > 0.0 && df1 < 1.0 && df2 > 0.0 && df2 < 1.0 && df3 > 0.0 && df3 < 1.0);
+
+ // assert!(df1 > df2 && df2 > df3);
+ // }
+
+ // #[test]
+ // fn test_discount_curve_creation() {
+ // let dates = [today() + Duration::days(30), today() + Duration::days(60)];
+ // let rates = [0.025, 0.03];
+
+ // let discount_curve = SpotCurve::new(&dates, &rates);
+
+ // assert_eq!(discount_curve.rates, rates);
+ // }
+
+ // #[test]
+ // fn test_discount_curve_initial_date() {
+ // let dates = [
+ // OffsetDateTime::UNIX_EPOCH.date() + Duration::days(30),
+ // OffsetDateTime::UNIX_EPOCH.date() + Duration::days(60),
+ // ];
+
+ // let rates = [0.025, 0.03];
+
+ // let discount_curve = SpotCurve::new(&dates, &rates);
+ // let initial_date = discount_curve.initial_date();
+
+ // assert_eq!(
+ // initial_date,
+ // OffsetDateTime::UNIX_EPOCH.date() + Duration::days(30)
+ // );
+ // }
+
+ // #[test]
+ // fn test_discount_curve_final_date() {
+ // let dates = [
+ // OffsetDateTime::UNIX_EPOCH.date() + Duration::days(30),
+ // OffsetDateTime::UNIX_EPOCH.date() + Duration::days(60),
+ // ];
+
+ // let rates = [0.025, 0.03];
+
+ // let discount_curve = SpotCurve::new(&dates, &rates);
+ // let final_date = discount_curve.terminal_date();
+
+ // assert_eq!(
+ // final_date,
+ // OffsetDateTime::UNIX_EPOCH.date() + Duration::days(60)
+ // );
+ // }
+
+ // #[test]
+ // fn test_find_date_interval() {
+ // let dates = [
+ // OffsetDateTime::UNIX_EPOCH.date() + Duration::days(30),
+ // OffsetDateTime::UNIX_EPOCH.date() + Duration::days(60),
+ // ];
+
+ // let rates = [0.025, 0.03];
+
+ // let discount_curve = SpotCurve::new(&dates, &rates);
+
+ // let date1 = OffsetDateTime::UNIX_EPOCH.date() + Duration::days(30);
+ // let date2 = OffsetDateTime::UNIX_EPOCH.date() + Duration::days(45);
+ // let date3 = OffsetDateTime::UNIX_EPOCH.date() + Duration::days(60);
+
+ // let interval1 = discount_curve.find_date_interval(date1);
+ // let interval2 = discount_curve.find_date_interval(date2);
+ // let interval3 = discount_curve.find_date_interval(date3);
+
+ // assert_eq!(interval1, (date1, date1));
+ // assert_eq!(interval2, (date1, date3));
+ // assert_eq!(interval3, (date3, date3));
+ // }
+
+ // #[allow(clippy::similar_names)]
+ // #[test]
+ // fn test_discount_curve_discount_factor() {
+ // // Initial date of the curve.
+ // let t0 = OffsetDateTime::UNIX_EPOCH.date();
- let discount_curve = SpotCurve::new(&dates, &rates);
- let initial_date = discount_curve.initial_date();
-
- assert_eq!(
- initial_date,
- OffsetDateTime::UNIX_EPOCH.date() + Duration::days(30)
- );
- }
-
- #[test]
- fn test_discount_curve_final_date() {
- let dates = [
- OffsetDateTime::UNIX_EPOCH.date() + Duration::days(30),
- OffsetDateTime::UNIX_EPOCH.date() + Duration::days(60),
- ];
-
- let rates = [0.025, 0.03];
-
- let discount_curve = SpotCurve::new(&dates, &rates);
- let final_date = discount_curve.terminal_date();
-
- assert_eq!(
- final_date,
- OffsetDateTime::UNIX_EPOCH.date() + Duration::days(60)
- );
- }
-
- #[test]
- fn test_find_date_interval() {
- let dates = [
- OffsetDateTime::UNIX_EPOCH.date() + Duration::days(30),
- OffsetDateTime::UNIX_EPOCH.date() + Duration::days(60),
- ];
-
- let rates = [0.025, 0.03];
-
- let discount_curve = SpotCurve::new(&dates, &rates);
-
- let date1 = OffsetDateTime::UNIX_EPOCH.date() + Duration::days(30);
- let date2 = OffsetDateTime::UNIX_EPOCH.date() + Duration::days(45);
- let date3 = OffsetDateTime::UNIX_EPOCH.date() + Duration::days(60);
-
- let interval1 = discount_curve.find_date_interval(date1);
- let interval2 = discount_curve.find_date_interval(date2);
- let interval3 = discount_curve.find_date_interval(date3);
-
- assert_eq!(interval1, (date1, date1));
- assert_eq!(interval2, (date1, date3));
- assert_eq!(interval3, (date3, date3));
- }
-
- #[allow(clippy::similar_names)]
- #[test]
- fn test_discount_curve_discount_factor() {
- // Initial date of the curve.
- let t0 = OffsetDateTime::UNIX_EPOCH.date();
-
- // Create a discount curve with 8 points.
- let rate_vec = vec![0.025, 0.03, 0.035, 0.04, 0.045, 0.05, 0.055, 0.06];
- let date_vec = vec![
- t0 + Duration::days(30),
- t0 + Duration::days(60),
- t0 + Duration::days(90),
- t0 + Duration::days(120),
- t0 + Duration::days(150),
- t0 + Duration::days(180),
- t0 + Duration::days(210),
- t0 + Duration::days(360),
- ];
-
- let discount_curve = SpotCurve::from_dates_and_rates(&date_vec, &rate_vec);
-
- println!("Curve: {:?}", discount_curve.rates);
-
- // Test the discount factor for a dates inside the curve's range.
- let date1 = OffsetDateTime::UNIX_EPOCH.date() + Duration::days(45);
- let date2 = OffsetDateTime::UNIX_EPOCH.date() + Duration::days(80);
- let date3 = OffsetDateTime::UNIX_EPOCH.date() + Duration::days(250);
-
- let df1 = discount_curve.discount_factor(date1);
- let df2 = discount_curve.discount_factor(date2);
- let df3 = discount_curve.discount_factor(date3);
-
- println!("df1: {:?}", df1);
- println!("df2: {:?}", df2);
- println!("df3: {:?}", df3);
-
- assert!(df1 > 0.0 && df1 < 1.0 && df2 > 0.0 && df2 < 1.0 && df3 > 0.0 && df3 < 1.0);
-
- assert!(df1 > df2 && df2 > df3);
- }
+ // // Create a discount curve with 8 points.
+ // let rate_vec = vec![0.025, 0.03, 0.035, 0.04, 0.045, 0.05, 0.055, 0.06];
+ // let date_vec = vec![
+ // t0 + Duration::days(30),
+ // t0 + Duration::days(60),
+ // t0 + Duration::days(90),
+ // t0 + Duration::days(120),
+ // t0 + Duration::days(150),
+ // t0 + Duration::days(180),
+ // t0 + Duration::days(210),
+ // t0 + Duration::days(360),
+ // ];
+
+ // let discount_curve = SpotCurve::new(&date_vec, &rate_vec);
+
+ // println!("Curve: {:?}", discount_curve.rates);
+
+ // // Test the discount factor for a dates inside the curve's range.
+ // let date1 = OffsetDateTime::UNIX_EPOCH.date() + Duration::days(45);
+ // let date2 = OffsetDateTime::UNIX_EPOCH.date() + Duration::days(80);
+ // let date3 = OffsetDateTime::UNIX_EPOCH.date() + Duration::days(250);
+
+ // let df1 = discount_curve.discount_factor(date1);
+ // let df2 = discount_curve.discount_factor(date2);
+ // let df3 = discount_curve.discount_factor(date3);
+
+ // println!("df1: {:?}", df1);
+ // println!("df2: {:?}", df2);
+ // println!("df3: {:?}", df3);
+
+ // assert!(df1 > 0.0 && df1 < 1.0 && df2 > 0.0 && df2 < 1.0 && df3 > 0.0 && df3 < 1.0);
+
+ // assert!(df1 > df2 && df2 > df3);
+ // }
}
diff --git a/src/instruments/fx/currency.rs b/src/instruments/fx/currency.rs
index ca09b08b..3f71ccf3 100644
--- a/src/instruments/fx/currency.rs
+++ b/src/instruments/fx/currency.rs
@@ -22,7 +22,7 @@ use std::fmt::{self, Formatter};
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/// Currency data struct.
-#[derive(Debug, Clone, Copy)]
+#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Currency {
/// Currency name. e.g. United States Dollar
pub name: &'static str,
@@ -36,6 +36,16 @@ pub struct Currency {
pub fractions: usize,
}
+/// Currency pair.
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
+pub struct CurrencyPair {
+ /// Base currency.
+ pub base: Currency,
+
+ /// Quote currency.
+ pub quote: Currency,
+}
+
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// IMPLEMENTATIONS
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -108,14 +118,21 @@ impl Instrument for Currency {
}
}
-impl Eq for Currency {}
-
-impl PartialEq for Currency {
- fn eq(&self, other: &Self) -> bool {
- self.code == other.code
+impl CurrencyPair {
+ /// Create a new currency pair.
+ pub fn new(base: Currency, quote: Currency) -> Self {
+ Self { base, quote }
}
}
+// impl Eq for Currency {}
+
+// impl PartialEq for Currency {
+// fn eq(&self, other: &Self) -> bool {
+// self.code == other.code
+// }
+// }
+
impl fmt::Display for Currency {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "Currency:\t{}\nISO Code:\t{:?}", self.name, self.code)
diff --git a/src/instruments/fx/exchange.rs b/src/instruments/fx/exchange.rs
index 95fe7fa3..05a8691b 100644
--- a/src/instruments/fx/exchange.rs
+++ b/src/instruments/fx/exchange.rs
@@ -9,6 +9,7 @@
//! FX exchange module.
+use super::CurrencyPair;
use crate::instruments::fx::currency::Currency;
use crate::instruments::fx::money::Money;
use std::collections::HashMap;
@@ -24,7 +25,7 @@ pub struct Exchange {
/// The key is a string of the form e.g. "USD_EUR",
/// and the value is an ExchangeRate struct.
/// The key is generated from the from_currency and to_currency of the ExchangeRate.
- pub rates: HashMap,
+ pub rates: HashMap,
}
/// `ExchangeRate` struct to hold exchange rate information.
@@ -79,10 +80,11 @@ impl Exchange {
/// ```
///
pub fn add_rate(&mut self, rate: ExchangeRate) {
- let key = format!(
- "{}/{}",
- rate.from_currency.code.alphabetic, rate.to_currency.code.alphabetic
- );
+ // let key = format!(
+ // "{}/{}",
+ // rate.from_currency.code.alphabetic, rate.to_currency.code.alphabetic
+ // );
+ let key = CurrencyPair::new(rate.from_currency, rate.to_currency);
self.rates.insert(key, rate);
}
@@ -115,10 +117,11 @@ impl Exchange {
from_currency: &Currency,
to_currency: &Currency,
) -> Option<&ExchangeRate> {
- let key = format!(
- "{}/{}",
- from_currency.code.alphabetic, to_currency.code.alphabetic
- );
+ // let key = format!(
+ // "{}/{}",
+ // from_currency.code.alphabetic, to_currency.code.alphabetic
+ // );
+ let key = CurrencyPair::new(*from_currency, *to_currency);
self.rates.get(&key)
}
diff --git a/src/instruments/instrument.rs b/src/instruments/instrument.rs
index 380e359a..748e6d19 100644
--- a/src/instruments/instrument.rs
+++ b/src/instruments/instrument.rs
@@ -7,8 +7,6 @@
// - LICENSE-MIT.md
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-use time::Date;
-
/// Instrument trait
/// The trait provides a common interface for all instruments.
/// All instruments can be queried for their net present value (NPV) and
@@ -25,65 +23,8 @@ pub trait Instrument {
fn error(&self) -> Option;
/// Returns the date at which the NPV is calculated.
- fn valuation_date(&self) -> Date;
+ fn valuation_date(&self) -> time::Date;
/// Instrument type.
fn instrument_type(&self) -> &'static str;
}
-
-/// Price structure.
-pub struct Price {
- /// Price of the instrument.
- pub price: f64,
-
- /// Error on the price of the instrument.
- pub error: Option,
-}
-
-/// Pricing engine for instruments.
-pub enum PricingEngine {
- /// Analytic pricing method (e.g. closed-form solution).
- Analytic,
-
- /// Simulation pricing method (e.g. Monte Carlo).
- Simulation,
-
- /// Numerical method (e.g. PDE, lattice, finite differences).
- Numerical,
-}
-
-/// Path independent payoff trait.
-pub trait PathIndependentPayoff {
- /// Base method for path independent option payoffs.
- fn payoff(&self, underlying: f64) -> f64;
-}
-
-/// Path dependent payoff trait.
-pub trait PathDependentPayoff {
- /// Base method for path dependent option payoffs.
- fn payoff(&self, path: &[f64]) -> f64;
-}
-
-// trait Payoff {
-// fn path_dependent(&self, path: &[f64]) -> f64;
-// fn path_independent(&self, path: &[f64]) -> f64;
-// }
-
-// struct MonteCarloPricer
-// where
-// PAYOFF: crate::instruments::PathDependentPayoff,
-// MODEL: crate::stochastics::StochasticProcess,
-// {
-// payoff: PAYOFF,
-// model: MODEL,
-// }
-
-// impl PathDependentPayoff for EuropeanOption {
-// fn payoff(&self, path: &[f64]) -> f64 {
-// let spot = path.last().unwrap();
-// match self.option_type {
-// OptionType::Call => (spot - self.strike_price).max(0.0),
-// OptionType::Put => (self.strike_price - spot).max(0.0),
-// }
-// }
-// }
diff --git a/src/instruments/mod.rs b/src/instruments/mod.rs
index 844622cb..48cd03c8 100644
--- a/src/instruments/mod.rs
+++ b/src/instruments/mod.rs
@@ -75,7 +75,7 @@ pub use instrument::*;
/// Bond pricing models.
pub mod bonds;
-pub use bonds::*;
+// pub use bonds::*;
/// Option pricers and sensitivity functions.
pub mod options;
@@ -92,3 +92,7 @@ pub use equities::*;
/// Ticker symbol.
pub mod ticker;
pub use ticker::*;
+
+/// Generic derivative payoff trait.
+pub mod payoff;
+pub use payoff::*;
diff --git a/src/instruments/options/american/mod.rs b/src/instruments/options/american/mod.rs
deleted file mode 100644
index fe604d51..00000000
--- a/src/instruments/options/american/mod.rs
+++ /dev/null
@@ -1,25 +0,0 @@
-// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-// RustQuant: A Rust library for quantitative finance tools.
-// Copyright (C) 2023-24 https://github.com/avhz
-// Dual licensed under Apache 2.0 and MIT.
-// See:
-// - LICENSE-APACHE.md
-// - LICENSE-MIT.md
-// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-struct AmericanOption {
- /// The underlying asset price.
- underlying: f64,
-
- /// The strike price.
- strike: f64,
-
- /// The risk-free interest rate.
- rate: f64,
-
- /// The volatility of the underlying asset.
- volatility: f64,
-
- /// The time to expiry.
- time_to_expiry: f64,
-}
diff --git a/src/instruments/options/asian.rs b/src/instruments/options/asian.rs
index 4d5e8daf..65eb5909 100644
--- a/src/instruments/options/asian.rs
+++ b/src/instruments/options/asian.rs
@@ -7,155 +7,45 @@
// - LICENSE-MIT.md
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-use crate::{
- math::distributions::{gaussian::Gaussian, Distribution},
- time::{today, DayCountConvention},
-};
-use time::Date;
-
-// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-// STRUCTS
-// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-/// Type of Asian option (fixed or floating strike).
-#[allow(clippy::module_name_repetitions)]
-#[derive(Debug, Clone, Copy)]
-pub enum AsianStrike {
- /// Floating strike Asian option.
- /// Payoffs:
- /// - Call: `max(S_T - A, 0)`
- /// - Put: `max(A - S_T, 0)`
- Floating,
- /// Fixed strike Asian option.
- /// Payoffs:
- /// - Call: `max(A - K, 0)`
- /// - Put: `max(K - A, 0)`
- Fixed,
-}
-
-/// Method of averaging (arithmetic or geometric, and continuous or discrete).
-#[derive(Debug, Clone, Copy)]
-pub enum AveragingMethod {
- /// Arithmetic Asian option with discrete averaging.
- ArithmeticDiscrete,
- /// Arithmetic Asian option with continuous averaging.
- ArithmeticContinuous,
- /// Geometric Asian option with discrete averaging.
- GeometricDiscrete,
- /// Geometric Asian option with continuous averaging.
- GeometricContinuous,
-}
-
-/// Asian Option struct.
-#[allow(clippy::module_name_repetitions)]
-#[derive(derive_builder::Builder, Debug, Clone, Copy)]
+/// Asian option.
+#[derive(Debug, Clone)]
pub struct AsianOption {
- /// `S` - Initial price of the underlying.
- pub initial_price: f64,
- /// `K` - Strike price.
- pub strike_price: f64,
- /// `r` - Risk-free rate parameter.
- pub risk_free_rate: f64,
- /// `v` - Volatility parameter.
- pub volatility: f64,
- /// `q` - Dividend rate.
- pub dividend_rate: f64,
-
- /// `evaluation_date` - Valuation date.
- #[builder(default = "None")]
- pub evaluation_date: Option,
-
- /// `expiry_date` - Expiry date.
- pub expiration_date: Date,
-}
-
-// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-// IMPLEMENTATIONS
-// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ /// The option contract.
+ pub contract: OptionContract,
-impl AsianOption {
- /// New Asian Option
- #[must_use]
- pub const fn new(
- initial_price: f64,
- strike_price: f64,
- risk_free_rate: f64,
- volatility: f64,
- dividend_rate: f64,
- evaluation_date: Option,
- expiration_date: Date,
- ) -> Self {
- Self {
- initial_price,
- strike_price,
- risk_free_rate,
- volatility,
- dividend_rate,
- evaluation_date,
- expiration_date,
- }
- }
-
- /// Geometric Continuous Average-Rate Price
- #[must_use]
- pub fn price_geometric_average(&self) -> (f64, f64) {
- let S = self.initial_price;
- let K = self.strike_price;
- // let T = self.time_to_maturity;
- let r = self.risk_free_rate;
- let v = self.volatility;
- let q = self.dividend_rate;
-
- // Compute time to maturity.
- let T = DayCountConvention::default().day_count_factor(
- self.evaluation_date.unwrap_or(today()),
- self.expiration_date,
- );
-
- let v_a = v / 3_f64.sqrt();
- let b = r - q;
- let b_a = 0.5 * (b - v * v / 6.0);
-
- let d1 = ((S / K).ln() + (b_a + 0.5 * v_a * v_a) * T) / (v_a * (T).sqrt());
- let d2 = d1 - v_a * (T).sqrt();
-
- let N = Gaussian::default();
+ /// Averging method (arithmetic or geometric).
+ pub averaging_method: AveragingMethod,
- let c = S * ((b_a - r) * T).exp() * N.cdf(d1) - K * (-r * T).exp() * N.cdf(d2);
- let p = -S * ((b_a - r) * T).exp() * N.cdf(-d1) + K * (-r * T).exp() * N.cdf(-d2);
-
- (c, p)
- }
+ /// Strike price of the option.
+ /// Required for fixed strike Asian options.
+ pub strike: Option,
}
-// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-// TESTS
-// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+impl Payoff for AsianOption {
+ type Underlying = Vec;
-#[cfg(test)]
-mod tests {
- use time::Duration;
+ fn payoff(&self, underlying: Self::Underlying) -> f64 {
+ let n = underlying.len();
+ let path = underlying.iter();
+ let terminal = underlying[n - 1];
- use super::*;
- use crate::assert_approx_equal;
+ let average = match self.averaging_method {
+ AveragingMethod::ArithmeticDiscrete => path.sum::() / n as f64,
+ AveragingMethod::GeometricDiscrete => path.product::().powf(1.0 / n as f64),
- #[test]
- fn test_asian_geometric() {
- let expiry_date = today() + Duration::days(92);
-
- let AsianOption = AsianOption {
- initial_price: 80.0,
- strike_price: 85.0,
- risk_free_rate: 0.05,
- volatility: 0.2,
- evaluation_date: None,
- expiration_date: expiry_date,
- dividend_rate: -0.03,
+ // Continuous averaging (i.e. integral of the path).
+ _ => panic!("Continuous averaging not implemented."),
};
- let prices = AsianOption.price_geometric_average();
-
- // Value from Haug's book.
- assert_approx_equal!(prices.1, 4.6922, 0.0001);
+ match self.contract.strike_flag {
+ StrikeFlag::Fixed => match self.contract.type_flag {
+ TypeFlag::Call => (average - self.strike.unwrap_or_default()).max(0.0),
+ TypeFlag::Put => (self.strike.unwrap_or_default() - average).max(0.0),
+ },
+ StrikeFlag::Floating => match self.contract.type_flag {
+ TypeFlag::Call => (terminal - average).max(0.0),
+ TypeFlag::Put => (average - terminal).max(0.0),
+ },
+ }
}
}
diff --git a/src/instruments/options/barrier.rs b/src/instruments/options/barrier.rs
index 08855545..89e90f6b 100644
--- a/src/instruments/options/barrier.rs
+++ b/src/instruments/options/barrier.rs
@@ -7,314 +7,21 @@
// - LICENSE-MIT.md
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-use crate::math::distributions::{gaussian::Gaussian, Distribution};
-
-// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-// BARRIER OPTION STRUCT
-// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-/// Barrier Option struct for parameters and pricing methods.
-#[derive(Debug, Clone, Copy)]
-#[allow(clippy::module_name_repetitions)]
+/// Barrier option.
+#[derive(Debug, Clone)]
pub struct BarrierOption {
- /// * `S` - Initial underlying price.
- pub initial_price: f64,
- /// * `X` - Strike price.
- pub strike_price: f64,
- /// * `H` - Barrier.
- pub barrier: f64,
- /// * `t` - Time to expiry.
- pub time_to_expiry: f64,
- /// * `r` - Risk-free rate.
- pub risk_free_rate: f64,
- /// * `v` - Volatility.
- pub volatility: f64,
- /// * `K` - Rebate (paid if the option is not able to be exercised).
- pub rebate: f64,
- /// * `q` - Dividend yield.
- pub dividend_yield: f64,
-}
-
-/// Barrier option type enum.
-#[derive(Debug, Clone, Copy)]
-#[allow(clippy::module_name_repetitions)]
-pub enum BarrierType {
- /// Call (up-and-in)
- /// Payoff: `max(S_T - X, 0) * I(max(S_t) > H)`
- CUI,
- /// Call (down-and-in)
- /// Payoff: `max(S_T - X, 0) * I(min(S_t) < H)`
- CDI,
- /// Call (up-and-out)
- /// Payoff: `max(S_T - X, 0) * I(max(S_t) < H)`
- CUO,
- /// Call (down-and-out)
- /// Payoff: `max(S_T - X, 0) * I(min(S_t) > H)`
- CDO,
- /// Put (up-and-in)
- /// Payoff: `max(X - S_T, 0) * I(max(S_t) > H)`
- PUI,
- /// Put (down-and-in)
- /// Payoff: `max(X - S_T, 0) * I(min(S_t) < H)`
- PDI,
- /// Put (up-and-out)
- /// Payoff: `max(X - S_T, 0) * I(max(S_t) < H)`
- PUO,
- /// Put (down-and-out)
- /// Payoff: `max(X - S_T, 0) * I(min(S_t) > H)`
- PDO,
-}
-
-// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-// BARRIER OPTION IMPLEMENTATION
-// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-impl BarrierOption {
- /// Closed-form solution for path-dependent barrier options.
- ///
- /// Adapted from Haug's *Complete Guide to Option Pricing Formulas*.
- ///
- /// # Arguments:
- ///
- /// * `type_flag` - One of: `cui`, `cuo`, `pui`, `puo`, `cdi`, `cdo`, `pdi`, `pdo`.
- ///
- /// # Note:
- /// * `b = r - q` - The cost of carry.
- #[must_use]
- pub fn price(&self, type_flag: BarrierType) -> f64 {
- let S = self.initial_price;
- let X = self.strike_price;
- let H = self.barrier;
- let t = self.time_to_expiry;
- let r = self.risk_free_rate;
- let v = self.volatility;
- let K = self.rebate;
- let q = self.dividend_yield;
-
- let b: f64 = r - q;
-
- // Common terms:
- let mu: f64 = (b - v * v / 2.) / (v * v);
- let lambda: f64 = (mu * mu + 2. * r / (v * v)).sqrt();
- let z: f64 = (H / S).ln() / (v * t.sqrt()) + lambda * v * t.sqrt();
-
- let x1: f64 = (S / X).ln() / (v * t.sqrt()) + (1. + mu) * v * t.sqrt();
- let x2: f64 = (S / H).ln() / (v * t.sqrt()) + (1. + mu) * v * t.sqrt();
-
- let y1: f64 = (H * H / (S * X)).ln() / (v * t.sqrt()) + (1. + mu) * v * t.sqrt();
- let y2: f64 = (H / S).ln() / (v * t.sqrt()) + (1. + mu) * v * t.sqrt();
-
- let norm = Gaussian::default();
-
- // Common functions:
- let A = |phi: f64| -> f64 {
- let term1: f64 = phi * S * ((b - r) * t).exp() * norm.cdf(phi * x1);
- let term2: f64 = phi * X * (-r * t).exp() * norm.cdf(phi * x1 - phi * v * (t).sqrt());
- term1 - term2
- };
-
- let B = |phi: f64| -> f64 {
- let term1: f64 = phi * S * ((b - r) * t).exp() * norm.cdf(phi * x2);
- let term2: f64 = phi * X * (-r * t).exp() * norm.cdf(phi * x2 - phi * v * (t).sqrt());
- term1 - term2
- };
-
- let C = |phi: f64, eta: f64| -> f64 {
- let term1: f64 =
- phi * S * ((b - r) * t).exp() * (H / S).powf(2. * (mu + 1.)) * norm.cdf(eta * y1);
- let term2: f64 = phi
- * X
- * (-r * t).exp()
- * (H / S).powf(2. * mu)
- * norm.cdf(eta * y1 - eta * v * t.sqrt());
- term1 - term2
- };
-
- let D = |phi: f64, eta: f64| -> f64 {
- let term1: f64 =
- phi * S * ((b - r) * t).exp() * (H / S).powf(2. * (mu + 1.)) * norm.cdf(eta * y2);
- let term2: f64 = phi
- * X
- * (-r * t).exp()
- * (H / S).powf(2. * mu)
- * norm.cdf(eta * y2 - eta * v * (t).sqrt());
+ /// The option contract.
+ pub contract: OptionContract,
- term1 - term2
- };
+ /// Barrier type (up-and-out, down-and-out, up-and-in, down-and-in).
+ pub barrier_type: BarrierType,
- let E = |eta: f64| -> f64 {
- let term1: f64 = norm.cdf(eta * x2 - eta * v * (t).sqrt());
- let term2: f64 = (H / S).powf(2. * mu) * norm.cdf(eta * y2 - eta * v * t.sqrt());
-
- K * (-r * t).exp() * (term1 - term2)
- };
-
- let F = |eta: f64| -> f64 {
- let term1: f64 = (H / S).powf(mu + lambda) * norm.cdf(eta * z);
- let term2: f64 =
- (H / S).powf(mu - lambda) * norm.cdf(eta * z - 2. * eta * lambda * v * t.sqrt());
-
- K * (term1 + term2)
- };
-
- // Strike above barrier (X >= H):
- if X >= H {
- match type_flag {
- // Knock-In calls:
- BarrierType::CDI if S >= H => C(1., 1.) + E(1.),
- BarrierType::CUI if S <= H => A(1.) + E(-1.),
- // Knock-In puts:
- BarrierType::PDI if S >= H => B(-1.) - C(-1., 1.) + D(-1., 1.) + E(1.),
- BarrierType::PUI if S <= H => A(-1.) - B(-1.) + D(-1., -1.) + E(-1.),
- // Knock-Out calls:
- BarrierType::CDO if S >= H => A(1.) - C(1., 1.) + F(1.),
- BarrierType::CUO if S <= H => F(-1.),
- // Knock-Out puts:
- BarrierType::PDO if S >= H => A(-1.) - B(-1.) + C(-1., 1.) - D(-1., 1.) + F(1.),
- BarrierType::PUO if S <= H => B(-1.) - D(-1., -1.) + F(-1.),
-
- _ => panic!("Barrier touched - check barrier and type flag."),
- }
- }
- // Strike below barrier (X < H):
- else {
- match type_flag {
- // Knock-In calls:
- BarrierType::CDI if S >= H => A(1.) - B(1.) + D(1., 1.) + E(1.),
- BarrierType::CUI if S <= H => B(1.) - C(1., -1.) + D(1., -1.) + E(-1.),
- // Knock-In puts:
- BarrierType::PDI if S >= H => A(-1.) + E(1.),
- BarrierType::PUI if S <= H => C(-1., -1.) + E(-1.),
- // Knock-Out calls:
- BarrierType::CDO if S >= H => B(1.) - D(1., 1.) + F(1.),
- BarrierType::CUO if S <= H => A(1.) - B(1.) + C(1., -1.) - D(1., -1.) + F(-1.),
- // Knock-Out puts:
- BarrierType::PDO if S >= H => F(1.),
- BarrierType::PUO if S <= H => A(-1.) - C(-1., -1.) + F(-1.),
-
- _ => panic!("Barrier touched - check barrier and type flag."),
- }
- }
- }
-}
-
-// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-// TESTS
-// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-#[cfg(test)]
-mod tests {
- use super::*;
- use crate::assert_approx_equal;
- use crate::RUSTQUANT_EPSILON;
-
- // use std::f64::EPSILON as EPS;
-
- // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- // Initial underlying price ABOVE the barrier.
- //
- // If S > H, then:
- // - "down-in" and "down-out" options have a defined price.
- // - "up-in" and "up-out" options make no sense.
- // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
- static S_ABOVE_H: BarrierOption = BarrierOption {
- initial_price: 110.0,
- strike_price: 100.0,
- barrier: 105.0,
- time_to_expiry: 1.0,
- risk_free_rate: 0.05,
- volatility: 0.2,
- rebate: 0.0,
- dividend_yield: 0.01,
- };
-
- #[allow(clippy::similar_names)]
- #[test]
- fn test_S_above_H() {
- let cdi = S_ABOVE_H.price(BarrierType::CDI);
- let cdo = S_ABOVE_H.price(BarrierType::CDO);
- let pdi = S_ABOVE_H.price(BarrierType::PDI);
- let pdo = S_ABOVE_H.price(BarrierType::PDO);
-
- assert_approx_equal!(cdi, 9.504_815_211_050_698, RUSTQUANT_EPSILON);
- assert_approx_equal!(cdo, 7.295_021_649_666_765, RUSTQUANT_EPSILON);
- assert_approx_equal!(pdi, 3.017_297_598_380_377_4, RUSTQUANT_EPSILON);
- assert_approx_equal!(pdo, 0.000_000, RUSTQUANT_EPSILON);
- }
-
- #[test]
- #[should_panic(expected = "Barrier touched - check barrier and type flag.")]
- fn cui_panic() {
- let _ = S_ABOVE_H.price(BarrierType::CUI);
- }
- #[test]
- #[should_panic(expected = "Barrier touched - check barrier and type flag.")]
- fn cuo_panic() {
- let _ = S_ABOVE_H.price(BarrierType::CUO);
- }
- #[test]
- #[should_panic(expected = "Barrier touched - check barrier and type flag.")]
- fn pui_panic() {
- let _ = S_ABOVE_H.price(BarrierType::PUI);
- }
- #[test]
- #[should_panic(expected = "Barrier touched - check barrier and type flag.")]
- fn puo_panic() {
- let _ = S_ABOVE_H.price(BarrierType::PUO);
- }
-
- // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- // Initial underlying price BELOW the barrier.
- //
- // If S < H, then:
- // - "down-in" and "down-out" options make no sense.
- // - "up-in" and "up-out" options have a defined price.
- // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
- static S_BELOW_H: BarrierOption = BarrierOption {
- initial_price: 90.0,
- strike_price: 100.0,
- barrier: 105.0,
- time_to_expiry: 1.0,
- risk_free_rate: 0.05,
- volatility: 0.2,
- rebate: 0.0,
- dividend_yield: 0.01,
- };
-
- #[allow(clippy::similar_names)]
- #[test]
- fn test_S_below_H() {
- let cui = S_BELOW_H.price(BarrierType::CUI);
- let cuo = S_BELOW_H.price(BarrierType::CUO);
- let pui = S_BELOW_H.price(BarrierType::PUI);
- let puo = S_BELOW_H.price(BarrierType::PUO);
+ /// Barrier level.
+ pub barrier: f64,
- assert_approx_equal!(cui, 4.692_603_355_387_815, RUSTQUANT_EPSILON);
- assert_approx_equal!(cuo, 0.022_448_676_101_445_74, RUSTQUANT_EPSILON);
- assert_approx_equal!(pui, 1.359_553_168_024_573_8, RUSTQUANT_EPSILON);
- assert_approx_equal!(puo, 9.373_956_276_110_954, RUSTQUANT_EPSILON);
- }
+ /// Strike price of the option.
+ pub strike: f64,
- #[test]
- #[should_panic(expected = "Barrier touched - check barrier and type flag.")]
- fn cdi_panic() {
- let _ = S_BELOW_H.price(BarrierType::CDI);
- }
- #[test]
- #[should_panic(expected = "Barrier touched - check barrier and type flag.")]
- fn cdo_panic() {
- let _ = S_BELOW_H.price(BarrierType::CDO);
- }
- #[test]
- #[should_panic(expected = "Barrier touched - check barrier and type flag.")]
- fn pdi_panic() {
- let _ = S_BELOW_H.price(BarrierType::PDI);
- }
- #[test]
- #[should_panic(expected = "Barrier touched - check barrier and type flag.")]
- fn pdo_panic() {
- let _ = S_BELOW_H.price(BarrierType::PDO);
- }
+ /// Rebate amount.
+ pub rebate: Option,
}
diff --git a/src/instruments/options/binary.rs b/src/instruments/options/binary.rs
index cee2e2d4..9bceed18 100644
--- a/src/instruments/options/binary.rs
+++ b/src/instruments/options/binary.rs
@@ -7,154 +7,44 @@
// - LICENSE-MIT.md
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-//! This module contains various 'binary', or 'digital', option types.
+/// Binary option.
+#[derive(Debug, Clone)]
+pub struct BinaryOption {
+ /// The option contract.
+ pub contract: OptionContract,
-use crate::math::distributions::{gaussian::Gaussian, Distribution};
+ /// Strike price of the option.
+ pub strike: f64,
-// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-// STRUCTS
-// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-/// Gap option parameters.
-#[derive(Debug, Clone, Copy)]
-pub struct GapOption {
- /// `S` - Initial price of the underlying.
- pub initial_price: f64,
- /// `K_1` - First strike price (barrier strike).
- pub strike_1: f64,
- /// `K_2` - Second strike price (payoff strike).
- pub strike_2: f64,
- /// `r` - Risk-free rate parameter.
- pub risk_free_rate: f64,
- /// `v` - Volatility parameter.
- pub volatility: f64,
- /// `b` - Cost-of-carry.
- pub cost_of_carry: f64,
- /// `T` - Time to expiry/maturity.
- pub time_to_maturity: f64,
-}
-
-/// Cash-or-Nothing option parameters.
-#[derive(Debug, Clone, Copy)]
-pub struct CashOrNothingOption {
- /// `S` - Initial price of the underlying.
- pub initial_price: f64,
- /// `X` - Strike price.
- pub strike_price: f64,
- /// `K` - Cash payout amount.
- pub payout_value: f64,
- /// `r` - Risk-free rate parameter.
- pub risk_free_rate: f64,
- /// `v` - Volatility parameter.
- pub volatility: f64,
- /// `b` - Cost-of-carry.
- pub cost_of_carry: f64,
- /// `T` - Time to expiry/maturity.
- pub time_to_maturity: f64,
+ /// Type of binary option.
+ pub binary_type: BinaryType,
}
-// pub struct AssetOrNothingOption {}
-// pub struct SupershareOption {}
-// pub struct BinaryBarrierOption {}
-
-// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-// IMPLEMENTATIONS
-// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-impl GapOption {
- /// Gap option pricer.
- /// The payoff from a call is $0$ if $S < K_1$ and $S — K_2$ if $S > K_1$.
- /// Similarly, the payoff from a put is $0$ if $S > K_1$ and $K_2 — S$ if $S < K_1$.
- #[must_use]
- pub fn price(&self) -> (f64, f64) {
- let S = self.initial_price;
- let K_1 = self.strike_1;
- let K_2 = self.strike_2;
- let T = self.time_to_maturity;
- let r = self.risk_free_rate;
- let v = self.volatility;
- let b = self.cost_of_carry;
-
- let d1 = ((S / K_1).ln() + (b + 0.5 * v * v) * T) / (v * (T).sqrt());
- let d2 = d1 - v * (T).sqrt();
-
- let N = Gaussian::default();
-
- let c = S * ((b - r) * T).exp() * N.cdf(d1) - K_2 * (-r * T).exp() * N.cdf(d2);
- let p = -S * ((b - r) * T).exp() * N.cdf(-d1) + K_2 * (-r * T).exp() * N.cdf(-d2);
-
- (c, p)
- }
-}
-
-impl CashOrNothingOption {
- /// Cah-or-Nothing option pricer.
- /// The payoff from a call is 0 if S < X and K if S > X.
- /// The payoff from a put is 0 if S > X and K if S < X.
- #[must_use]
- pub fn price(&self) -> (f64, f64) {
- let S = self.initial_price;
- let X = self.strike_price;
- let K = self.payout_value;
- let T = self.time_to_maturity;
- let r = self.risk_free_rate;
- let v = self.volatility;
- let b = self.cost_of_carry;
-
- let d = ((S / X).ln() + (b - 0.5 * v * v) * T) / (v * (T).sqrt());
-
- let N = Gaussian::default();
-
- let c = K * (-r * T).exp() * N.cdf(d);
- let p = K * (-r * T).exp() * N.cdf(-d);
-
- (c, p)
- }
-}
-
-// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-// TESTS
-// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-#[cfg(test)]
-mod tests {
- use super::*;
- use crate::assert_approx_equal;
- use crate::RUSTQUANT_EPSILON;
-
- #[test]
- fn test_gap_option() {
- let gap = GapOption {
- initial_price: 50.0,
- strike_1: 50.0,
- strike_2: 57.0,
- risk_free_rate: 0.09,
- volatility: 0.2,
- time_to_maturity: 0.5,
- cost_of_carry: 0.09,
- };
-
- let prices = gap.price();
-
- // Value from Haug's book (note: gap option payoffs can be negative).
- assert_approx_equal!(prices.0, -0.005_252_489_258_779_747, RUSTQUANT_EPSILON);
- }
-
- #[test]
- fn test_cash_or_nothing_option() {
- let CON = CashOrNothingOption {
- initial_price: 100.0,
- payout_value: 10.0,
- strike_price: 80.0,
- risk_free_rate: 0.06,
- volatility: 0.35,
- time_to_maturity: 0.75,
- cost_of_carry: 0.0,
- };
-
- let prices = CON.price();
-
- // Value from Haug's book.
- assert_approx_equal!(prices.1, 2.671_045_684_461_347, RUSTQUANT_EPSILON);
+impl Payoff for BinaryOption {
+ type Underlying = f64;
+
+ fn payoff(&self, underlying: Self::Underlying) -> f64 {
+ match self.binary_type {
+ BinaryType::CashOrNothing => match self.contract.type_flag {
+ TypeFlag::Call => match underlying > self.strike {
+ true => self.strike,
+ false => 0.0,
+ },
+ TypeFlag::Put => match underlying < self.strike {
+ true => self.strike,
+ false => 0.0,
+ },
+ },
+ BinaryType::AssetOrNothing => match self.contract.type_flag {
+ TypeFlag::Call => match underlying > self.strike {
+ true => underlying,
+ false => 0.0,
+ },
+ TypeFlag::Put => match underlying < self.strike {
+ true => underlying,
+ false => 0.0,
+ },
+ },
+ }
}
}
diff --git a/src/instruments/options/finite_difference_pricer.rs b/src/instruments/options/finite_difference_pricer.rs
index fa83fee0..c2a8e5c0 100644
--- a/src/instruments/options/finite_difference_pricer.rs
+++ b/src/instruments/options/finite_difference_pricer.rs
@@ -7,7 +7,7 @@
// - LICENSE-MIT.md
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-use crate::instruments::options::option::{ExerciseFlag, TypeFlag};
+use super::option_flags::*;
use crate::time::{today, DayCountConvention};
use std::cmp::Ordering;
use time::Date;
@@ -84,11 +84,11 @@ impl FiniteDifferencePricer {
}
fn tridiagonal_matrix_multiply_vector(
- &self,
- sub_diagonal: f64,
- diagonal: f64,
- super_diagonal: f64,
- v: Vec
+ &self,
+ sub_diagonal: f64,
+ diagonal: f64,
+ super_diagonal: f64,
+ v: Vec,
) -> Vec {
let mut Av: Vec = Vec::new();
@@ -97,7 +97,7 @@ impl FiniteDifferencePricer {
for i in 1..(v.len() - 1) {
Av.push(sub_diagonal * v[i - 1] + diagonal * v[i] + super_diagonal * v[i + 1])
}
-
+
Av.push(sub_diagonal * v[v.len() - 2] + diagonal * v[v.len() - 1]);
Av
@@ -118,22 +118,21 @@ impl FiniteDifferencePricer {
Av
}
- fn invert_tridiagonal_matrix(&self, sub_diagonal: f64, diagonal: f64, super_diagonal: f64) -> Vec> {
+ fn invert_tridiagonal_matrix(
+ &self,
+ sub_diagonal: f64,
+ diagonal: f64,
+ super_diagonal: f64,
+ ) -> Vec> {
let mut theta: Vec = Vec::new();
let system_size: usize = (self.price_steps - 1) as usize;
theta.push(1.0);
theta.push(diagonal);
- theta.push(
- diagonal * diagonal
- - super_diagonal * sub_diagonal,
- );
+ theta.push(diagonal * diagonal - super_diagonal * sub_diagonal);
for i in 2..system_size {
- theta.push(
- diagonal * theta[i]
- - super_diagonal * sub_diagonal * theta[i - 1]
- )
+ theta.push(diagonal * theta[i] - super_diagonal * sub_diagonal * theta[i - 1])
}
let mut phi: Vec = Vec::new();
@@ -141,10 +140,7 @@ impl FiniteDifferencePricer {
phi.push(diagonal);
for i in 1..(system_size) {
- phi.push(
- diagonal * phi[i]
- - super_diagonal * sub_diagonal * phi[i - 1]
- )
+ phi.push(diagonal * phi[i] - super_diagonal * sub_diagonal * phi[i - 1])
}
let theta_n = theta.pop().unwrap();
@@ -157,14 +153,14 @@ impl FiniteDifferencePricer {
for i in 0..system_size {
for j in 0..system_size {
- value = (- 1.0_f64).powi((i + j) as i32);
+ value = (-1.0_f64).powi((i + j) as i32);
match i.cmp(&j) {
Ordering::Less => {
for k in i..j {
value *= match k {
- k if k == system_size - 1 => {diagonal},
- _ => super_diagonal
+ k if k == system_size - 1 => diagonal,
+ _ => super_diagonal,
}
}
value *= theta[i] * phi[j] / theta_n;
@@ -197,30 +193,25 @@ impl FiniteDifferencePricer {
(1..self.price_steps)
.map(|i: u32| {
v[(i - 1) as usize].max(
- f64::exp(self.risk_free_rate * tau) * self.payoff(
- f64::exp(
- x_min + (i as f64) * delta_x
- )
- )
+ f64::exp(self.risk_free_rate * tau)
+ * self.payoff(f64::exp(x_min + (i as f64) * delta_x)),
)
})
- .collect()
+ .collect()
}
fn initial_condition(&self, x_min: f64, delta_x: f64) -> Vec {
(1..self.price_steps)
- .map(|i: u32| self.payoff(
- f64::exp(
- x_min + (i as f64) * delta_x)))
+ .map(|i: u32| self.payoff(f64::exp(x_min + (i as f64) * delta_x)))
.collect()
}
fn call_boundary(&self, tau: f64, x_max: f64) -> f64 {
- f64::exp(x_max) - self.strike_price * f64::exp(- self.risk_free_rate * tau)
+ f64::exp(x_max) - self.strike_price * f64::exp(-self.risk_free_rate * tau)
}
fn put_boundary(&self, tau: f64, x_min: f64) -> f64 {
- self.strike_price * f64::exp(- self.risk_free_rate * tau) - f64::exp(x_min)
+ self.strike_price * f64::exp(-self.risk_free_rate * tau) - f64::exp(x_min)
}
fn year_fraction(&self) -> f64 {
@@ -245,13 +236,17 @@ impl FiniteDifferencePricer {
let T: f64 = self.year_fraction();
let delta_t: f64 = T / (self.time_steps as f64);
let x_min: f64 = self.initial_price.ln() - 5.0 * self.volatility * T.sqrt();
- let delta_x: f64 = (self.initial_price.ln() + 5.0 * self.volatility * T.sqrt() - x_min) / self.price_steps as f64;
-
+ let delta_x: f64 = (self.initial_price.ln() + 5.0 * self.volatility * T.sqrt() - x_min)
+ / self.price_steps as f64;
+
(T, delta_t, delta_x, x_min)
}
fn coefficients(&self, delta_t: f64, delta_x: f64) -> (f64, f64) {
- (0.5 * delta_t * self.volatility.powi(2) / delta_x.powi(2), delta_t * (self.risk_free_rate - 0.5 * self.volatility.powi(2)) / (2.0 * delta_x))
+ (
+ 0.5 * delta_t * self.volatility.powi(2) / delta_x.powi(2),
+ delta_t * (self.risk_free_rate - 0.5 * self.volatility.powi(2)) / (2.0 * delta_x),
+ )
}
/// Explicit method
@@ -269,22 +264,27 @@ impl FiniteDifferencePricer {
match self.type_flag {
TypeFlag::Call => {
- v[(self.price_steps - 2) as usize] += super_diagonal * self.call_boundary(
- (t as f64) * delta_t,
- self.initial_price.ln() + 5.0 * self.volatility * T.sqrt()
- );
+ v[(self.price_steps - 2) as usize] += super_diagonal
+ * self.call_boundary(
+ (t as f64) * delta_t,
+ self.initial_price.ln() + 5.0 * self.volatility * T.sqrt(),
+ );
}
TypeFlag::Put => {
v[0] += sub_diagonal * self.put_boundary((t as f64) * delta_t, x_min);
}
}
- if let ExerciseFlag::American = self.exercise_flag {
+ if let ExerciseFlag::American {
+ start: Date::MIN,
+ end: Date::MAX,
+ } = self.exercise_flag
+ {
v = self.american_time_stop_step(v, (t as f64) * delta_t, x_min, delta_x);
}
}
- f64::exp(- self.risk_free_rate * T) * self.return_price(v)
+ f64::exp(-self.risk_free_rate * T) * self.return_price(v)
}
///Implicit method
@@ -292,35 +292,37 @@ impl FiniteDifferencePricer {
let (T, delta_t, delta_x, x_min) = self.grid();
let (x, y) = self.coefficients(delta_t, delta_x);
- let inverse_matrix: Vec> = self.invert_tridiagonal_matrix(
- - x + y,
- 1.0 + 2.0 * x,
- - x - y
- );
+ let inverse_matrix: Vec> =
+ self.invert_tridiagonal_matrix(-x + y, 1.0 + 2.0 * x, -x - y);
let mut v: Vec = self.initial_condition(x_min, delta_x);
for t in 1..(self.time_steps + 1) {
match self.type_flag {
TypeFlag::Call => {
- v[(self.price_steps - 2) as usize] -= (- x - y) * self.call_boundary(
- (t as f64) * delta_t,
- self.initial_price.ln() + 5.0 * self.volatility * T.sqrt()
- );
+ v[(self.price_steps - 2) as usize] -= (-x - y)
+ * self.call_boundary(
+ (t as f64) * delta_t,
+ self.initial_price.ln() + 5.0 * self.volatility * T.sqrt(),
+ );
}
TypeFlag::Put => {
- v[0] -= (- x + y) * self.put_boundary((t as f64) * delta_t, x_min);
+ v[0] -= (-x + y) * self.put_boundary((t as f64) * delta_t, x_min);
}
}
v = self.general_matrix_multiply_vector(&inverse_matrix, v);
- if let ExerciseFlag::American = self.exercise_flag {
+ if let ExerciseFlag::American {
+ start: Date::MIN,
+ end: Date::MAX,
+ } = self.exercise_flag
+ {
v = self.american_time_stop_step(v, (t as f64) * delta_t, x_min, delta_x);
}
}
- f64::exp(- self.risk_free_rate * T) * self.return_price(v)
+ f64::exp(-self.risk_free_rate * T) * self.return_price(v)
}
/// Crank-Nicolson method
@@ -331,29 +333,22 @@ impl FiniteDifferencePricer {
let diagonal: f64 = 1.0 - x;
let super_diagonal: f64 = 0.5 * (x + y);
- let inverse_future_matrix = self.invert_tridiagonal_matrix(
- - sub_diagonal,
- 1.0 + x,
- - super_diagonal
- );
+ let inverse_future_matrix =
+ self.invert_tridiagonal_matrix(-sub_diagonal, 1.0 + x, -super_diagonal);
let mut v: Vec = self.initial_condition(x_min, delta_x);
for t in 1..(self.time_steps + 1) {
- v = self.tridiagonal_matrix_multiply_vector(
- sub_diagonal,
- diagonal,
- super_diagonal,
- v
- );
+ v = self.tridiagonal_matrix_multiply_vector(sub_diagonal, diagonal, super_diagonal, v);
match self.type_flag {
TypeFlag::Call => {
- v[(self.price_steps - 2) as usize] +=
- 2.0 * super_diagonal * self.call_boundary(
- (t as f64) * delta_t,
- self.initial_price.ln() + 5.0 * self.volatility * T.sqrt()
- );
+ v[(self.price_steps - 2) as usize] += 2.0
+ * super_diagonal
+ * self.call_boundary(
+ (t as f64) * delta_t,
+ self.initial_price.ln() + 5.0 * self.volatility * T.sqrt(),
+ );
}
TypeFlag::Put => {
v[0] += 2.0 * sub_diagonal * self.put_boundary((t as f64) * delta_t, x_min);
@@ -362,12 +357,16 @@ impl FiniteDifferencePricer {
v = self.general_matrix_multiply_vector(&inverse_future_matrix, v);
- if let ExerciseFlag::American = self.exercise_flag {
+ if let ExerciseFlag::American {
+ start: Date::MIN,
+ end: Date::MAX,
+ } = self.exercise_flag
+ {
v = self.american_time_stop_step(v, (t as f64) * delta_t, x_min, delta_x);
}
}
- f64::exp(- self.risk_free_rate * T) * self.return_price(v)
+ f64::exp(-self.risk_free_rate * T) * self.return_price(v)
}
}
@@ -392,7 +391,9 @@ mod tests_finite_difference_pricer_at_the_money {
time_steps: 10000,
price_steps: 250,
type_flag: TypeFlag::Call,
- exercise_flag: ExerciseFlag::European,
+ exercise_flag: ExerciseFlag::European {
+ expiry: date!(2025 - 01 - 01),
+ },
};
const EUROPEAN_PUT: FiniteDifferencePricer = FiniteDifferencePricer {
@@ -405,7 +406,9 @@ mod tests_finite_difference_pricer_at_the_money {
time_steps: 10000,
price_steps: 250,
type_flag: TypeFlag::Put,
- exercise_flag: ExerciseFlag::European,
+ exercise_flag: ExerciseFlag::European {
+ expiry: date!(2025 - 01 - 01),
+ },
};
const AMERICAN_CALL: FiniteDifferencePricer = FiniteDifferencePricer {
@@ -418,7 +421,10 @@ mod tests_finite_difference_pricer_at_the_money {
time_steps: 10000,
price_steps: 250,
type_flag: TypeFlag::Call,
- exercise_flag: ExerciseFlag::American,
+ exercise_flag: ExerciseFlag::American {
+ start: date!(2024 - 01 - 01),
+ end: date!(2025 - 01 - 01),
+ },
};
const AMERICAN_PUT: FiniteDifferencePricer = FiniteDifferencePricer {
@@ -431,7 +437,10 @@ mod tests_finite_difference_pricer_at_the_money {
time_steps: 10000,
price_steps: 250,
type_flag: TypeFlag::Put,
- exercise_flag: ExerciseFlag::American,
+ exercise_flag: ExerciseFlag::American {
+ start: date!(2024 - 01 - 01),
+ end: date!(2025 - 01 - 01),
+ },
};
const EXPECT_A_CALL: f64 = 0.680_478_009_892_241;
@@ -521,7 +530,9 @@ mod tests_finite_difference_pricer_in_the_money {
time_steps: 10000,
price_steps: 200,
type_flag: TypeFlag::Call,
- exercise_flag: ExerciseFlag::European,
+ exercise_flag: ExerciseFlag::European {
+ expiry: date!(2025 - 01 - 01),
+ },
};
const EUROPEAN_PUT: FiniteDifferencePricer = FiniteDifferencePricer {
@@ -534,7 +545,9 @@ mod tests_finite_difference_pricer_in_the_money {
time_steps: 10000,
price_steps: 200,
type_flag: TypeFlag::Put,
- exercise_flag: ExerciseFlag::European,
+ exercise_flag: ExerciseFlag::European {
+ expiry: date!(2025 - 01 - 01),
+ },
};
const AMERICAN_CALL: FiniteDifferencePricer = FiniteDifferencePricer {
@@ -547,7 +560,10 @@ mod tests_finite_difference_pricer_in_the_money {
time_steps: 10000,
price_steps: 200,
type_flag: TypeFlag::Call,
- exercise_flag: ExerciseFlag::American,
+ exercise_flag: ExerciseFlag::American {
+ start: date!(2024 - 01 - 01),
+ end: date!(2025 - 01 - 01),
+ },
};
const AMERICAN_PUT: FiniteDifferencePricer = FiniteDifferencePricer {
@@ -560,7 +576,10 @@ mod tests_finite_difference_pricer_in_the_money {
time_steps: 10000,
price_steps: 200,
type_flag: TypeFlag::Put,
- exercise_flag: ExerciseFlag::American,
+ exercise_flag: ExerciseFlag::American {
+ start: date!(2024 - 01 - 01),
+ end: date!(2025 - 01 - 01),
+ },
};
const EXPECT_A_CALL: f64 = 5.487_706_388_002_172;
@@ -650,7 +669,9 @@ mod tests_finite_difference_pricer_out_of_the_money {
time_steps: 10000,
price_steps: 200,
type_flag: TypeFlag::Call,
- exercise_flag: ExerciseFlag::European,
+ exercise_flag: ExerciseFlag::European {
+ expiry: date!(2025 - 01 - 01),
+ },
};
const EUROPEAN_PUT: FiniteDifferencePricer = FiniteDifferencePricer {
@@ -663,7 +684,9 @@ mod tests_finite_difference_pricer_out_of_the_money {
time_steps: 10000,
price_steps: 200,
type_flag: TypeFlag::Put,
- exercise_flag: ExerciseFlag::European,
+ exercise_flag: ExerciseFlag::European {
+ expiry: date!(2025 - 01 - 01),
+ },
};
const AMERICAN_CALL: FiniteDifferencePricer = FiniteDifferencePricer {
@@ -676,7 +699,10 @@ mod tests_finite_difference_pricer_out_of_the_money {
time_steps: 10000,
price_steps: 200,
type_flag: TypeFlag::Call,
- exercise_flag: ExerciseFlag::American,
+ exercise_flag: ExerciseFlag::American {
+ start: date!(2024 - 01 - 01),
+ end: date!(2025 - 01 - 01),
+ },
};
const AMERICAN_PUT: FiniteDifferencePricer = FiniteDifferencePricer {
@@ -689,7 +715,10 @@ mod tests_finite_difference_pricer_out_of_the_money {
time_steps: 10000,
price_steps: 200,
type_flag: TypeFlag::Put,
- exercise_flag: ExerciseFlag::American,
+ exercise_flag: ExerciseFlag::American {
+ start: date!(2024 - 01 - 01),
+ end: date!(2025 - 01 - 01),
+ },
};
const EXPECT_A_CALL: f64 = 0.000_059_393_327_777_911;
diff --git a/src/instruments/options/forward_start.rs b/src/instruments/options/forward_start.rs
index 34654568..8de2d1f1 100644
--- a/src/instruments/options/forward_start.rs
+++ b/src/instruments/options/forward_start.rs
@@ -7,122 +7,15 @@
// - LICENSE-MIT.md
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-// FORWARD START OPTION STRUCT
-// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-use time::Date;
-
-use crate::{
- math::distributions::{Distribution, Gaussian},
- time::{today, DayCountConvention},
-};
-
-/// Forward Start Option parameters struct
-#[allow(clippy::module_name_repetitions)]
-#[derive(derive_builder::Builder, Debug)]
+/// Forward start option.
+#[derive(Debug, Clone)]
pub struct ForwardStartOption {
- /// `S` - Initial price of the underlying.
- pub initial_price: f64,
- /// `alpha` - The proportion of S to set the strike price.
- /// Three possibilities:
- /// - alpha < 1: call (put) will start (1 - alpha)% in-the-money (out-of-the-money).
- /// - alpha = 1: the option starts at-the-money.
- /// - alpha > 1: call (put) will start (alpha - 1)% out-of-the-money (in-the-money).
- pub alpha: f64,
- /// `r` - Risk-free rate parameter.
- pub risk_free_rate: f64,
- /// `v` - Volatility parameter.
- pub volatility: f64,
- /// `q` - Dividend rate.
- pub dividend_rate: f64,
-
- /// `valuation_date` - Valuation date.
- #[builder(default = "None")]
- pub valuation_date: Option,
-
- /// `start` - Time until the start of the option (`T` in most literature).
- pub start: Date,
- /// `end` - Time until the end of the option (`t` in most literature).
- pub end: Date,
-}
-
-// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-// FORWARD START OPTION IMPLEMENTATION
-// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-impl ForwardStartOption {
- /// Rubinstein (1990) Forward Start Option Price formula.
- /// Returns a tuple: `(call_price, put_price)`
- /// # Note:
- /// * `b = r - q` - The cost of carry.
- #[must_use]
- pub fn price(&self) -> (f64, f64) {
- let S = self.initial_price;
- let a = self.alpha;
-
- let r = self.risk_free_rate;
- let v = self.volatility;
- let q = self.dividend_rate;
-
- let T = DayCountConvention::default()
- .day_count_factor(self.valuation_date.unwrap_or(today()), self.end);
-
- let t = DayCountConvention::default()
- .day_count_factor(self.valuation_date.unwrap_or(today()), self.start);
-
- let b = r - q;
-
- let d1 = ((1. / a).ln() + (b + v * v / 2.) * (T - t)) / (v * (T - t).sqrt());
- let d2 = d1 - v * (T - t).sqrt();
-
- let norm = Gaussian::default();
-
- let Nd1: f64 = norm.cdf(d1);
- let Nd2: f64 = norm.cdf(d2);
-
- let Nd1_: f64 = norm.cdf(-d1);
- let Nd2_: f64 = norm.cdf(-d2);
-
- let c: f64 = S
- * ((b - r) * t).exp()
- * (((b - r) * (T - t)).exp() * Nd1 - a * (-r * (T - t)).exp() * Nd2);
- let p: f64 = S
- * ((b - r) * t).exp()
- * (-((b - r) * (T - t)).exp() * Nd1_ + a * (-r * (T - t)).exp() * Nd2_);
-
- (c, p)
- }
-}
-
-// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-// TESTS
-// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-#[cfg(test)]
-mod tests_forward_start {
- use super::*;
- use crate::assert_approx_equal;
-
- #[test]
- fn TEST_forward_start_option() {
- let start = today() + time::Duration::days(91);
- let end = today() + time::Duration::days(365);
-
- let ForwardStart = ForwardStartOption {
- initial_price: 60.0,
- alpha: 1.1,
- risk_free_rate: 0.08,
- volatility: 0.3,
- dividend_rate: 0.04,
- valuation_date: None,
- start,
- end,
- };
+ /// The option contract.
+ pub contract: OptionContract,
- let prices = ForwardStart.price();
+ /// Strike price of the option.
+ pub strike: f64,
- // Call price example from Haug's book.
- assert_approx_equal!(prices.0, 4.402888269001168, 1e-2);
- }
+ /// Forward start date.
+ pub start_date: Date,
}
diff --git a/src/instruments/options/gap.rs b/src/instruments/options/gap.rs
new file mode 100644
index 00000000..3b917757
--- /dev/null
+++ b/src/instruments/options/gap.rs
@@ -0,0 +1,21 @@
+// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+// RustQuant: A Rust library for quantitative finance tools.
+// Copyright (C) 2023 https://github.com/avhz
+// Dual licensed under Apache 2.0 and MIT.
+// See:
+// - LICENSE-APACHE.md
+// - LICENSE-MIT.md
+// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+/// Gap option.
+#[derive(Debug, Clone)]
+pub struct GapOption {
+ /// The option contract.
+ pub contract: OptionContract,
+
+ /// First strike price (barrier strike).
+ pub strike_1: f64,
+
+ /// Second strike price (payoff strike).
+ pub strike_2: f64,
+}
diff --git a/src/instruments/options/lookback.rs b/src/instruments/options/lookback.rs
index 14731c6a..d6de49b7 100644
--- a/src/instruments/options/lookback.rs
+++ b/src/instruments/options/lookback.rs
@@ -40,29 +40,25 @@ pub enum LookbackStrike {
#[allow(clippy::module_name_repetitions)]
#[derive(Debug, Clone, Copy)]
pub struct LookbackOption {
- /// `S` - Initial price of the underlying.
- pub initial_price: f64,
- /// `r` - Risk-free rate parameter.
- pub risk_free_rate: f64,
+ /// The option contract.
+ pub contract: OptionContract,
+
/// `K` - Strike price (only needed for fixed strike lookbacks).
/// If the strike is floating, then this is `None`.
pub strike_price: Option,
- /// `v` - Volatility parameter.
- pub volatility: f64,
- /// `T` - Time to expiry/maturity.
- pub time_to_maturity: f64,
- /// `q` - dividend yield.
- pub dividend_yield: f64,
+
/// Minimum value of the underlying price observed **so far**.
/// If the contract starts at t=0, then `S_min = S_0`.
/// Used for the closed-form put price.
pub s_min: f64,
+
/// Maximum value of the underlying price observed **so far**.
/// If the contract starts at t=0, then `S_max = S_0`.
/// Used for the closed-form call price.
pub s_max: f64,
- /// Strike type.
- pub strike_type: LookbackStrike,
+
+ /// Start date of the contract.
+ pub start_date: Date,
}
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
diff --git a/src/instruments/options/mod.rs b/src/instruments/options/mod.rs
index 9d753aae..58d70b98 100644
--- a/src/instruments/options/mod.rs
+++ b/src/instruments/options/mod.rs
@@ -8,49 +8,56 @@
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
pub use crate::instruments::options::{
- asian::*, bachelier::*, barrier::*, binary::*, binomial::*, black_scholes_merton::*,
- forward_start::*, heston::*, implied_volatility::*, lookback::*, merton_jump_diffusion::*,
- option::*, power::*,
+ black_scholes_merton::*, implied_volatility::*, merton_jump_diffusion::*, option_contract::*,
};
-/// Asian option pricers.
-pub mod asian;
+// /// Asian option pricers.
+// pub mod asian;
-/// Bachelier option pricer.
-pub mod bachelier;
+// /// Bachelier option pricer.
+// pub mod bachelier;
-/// Barrier option pricers.
-pub mod barrier;
+// /// Barrier option pricers.
+// pub mod barrier;
-/// Binary option pricers.
-pub mod binary;
+// /// Binary option pricers.
+// pub mod binary;
-/// Binomial option pricers.
-pub mod binomial;
+// /// Binomial option pricers.
+// pub mod binomial;
/// Generalised Black-Scholes-Merton option pricer.
pub mod black_scholes_merton;
-/// Forward start options pricers.
-pub mod forward_start;
+// /// Forward start options pricers.
+// pub mod forward_start;
-/// Heston model option pricer.
-pub mod heston;
+// /// Heston model option pricer.
+// pub mod heston;
/// Implied volatility functions.
pub mod implied_volatility;
-/// Lookback option pricers.
-pub mod lookback;
+// /// Lookback option pricers.
+// pub mod lookback;
/// Merton (1976) jump diffusion model.
pub mod merton_jump_diffusion;
/// Base option traits.
-pub mod option;
+pub mod option_contract;
+// pub use option_contract::*;
-/// Power option pricers.
-pub mod power;
+// /// Power option pricers.
+// pub mod power;
/// Finite Difference Pricer
pub mod finite_difference_pricer;
+
+/// Option flags.
+pub mod option_flags;
+pub use option_flags::*;
+
+/// Vanilla option pricers.
+pub mod vanilla;
+pub use vanilla::*;
diff --git a/src/instruments/options/option.rs b/src/instruments/options/option.rs
deleted file mode 100644
index 658582d8..00000000
--- a/src/instruments/options/option.rs
+++ /dev/null
@@ -1,279 +0,0 @@
-// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-// RustQuant: A Rust library for quantitative finance tools.
-// Copyright (C) 2023 https://github.com/avhz
-// Dual licensed under Apache 2.0 and MIT.
-// See:
-// - LICENSE-APACHE.md
-// - LICENSE-MIT.md
-// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-/// Option contract data.
-pub struct OptionContract {
- /// The option's type flag (call or put).
- pub type_flag: TypeFlag,
-
- /// The option's strike type (fixed or floating).
- pub strike_flag: StrikeFlag,
-
- /// The option's exercise type (European, American, Bermudan).
- pub exercise_flag: ExerciseFlag,
-
- /// The option's settlement type (cash or physical).
- pub settlement_flag: SettlementFlag,
-}
-
-/// Option type enum.
-#[derive(Debug, Clone, Copy)]
-pub enum TypeFlag {
- /// Call option (right to BUY the underlying asset).
- Call = 1,
-
- /// Put option (right to SELL the underlying asset).
- Put = -1,
-}
-
-/// American/European option type enum.
-#[derive(Debug, Clone, Copy)]
-pub enum ExerciseFlag {
- /// European option (can only be exercised at expiry).
- European,
-
- /// American option (can be exercised at any time before expiry).
- American,
-
- /// Bermudan option (can be exercised at specific dates before expiry).
- Bermudan,
-}
-
-/// Option strike type enum.
-#[derive(Debug, Clone, Copy)]
-pub enum StrikeFlag {
- /// Strike is fixed.
- Fixed,
-
- /// Strike is floating (e.g. strike = S_max).
- Floating,
-}
-
-/// Instrument settlement flag.
-#[derive(Debug, Clone, Copy)]
-pub enum SettlementFlag {
- /// Cash settlement.
- Cash,
-
- /// Physical settlement.
- Physical,
-}
-
-/// Generic option parameters struct.
-/// Contains the common parameters (as in Black-Scholes).
-/// Other option types may have additional parameters,
-/// such as lookback options (S_min, S_max).
-#[allow(clippy::module_name_repetitions)]
-#[derive(Debug, Clone)]
-pub struct OptionParameters {
- /// `S` - Initial price of the underlying.
- pub S: Vec,
- /// `K` - Strike price.
- pub K: Vec,
- /// `T` - Time to expiry/maturity.
- pub T: Vec,
- /// `r` - Risk-free rate parameter.
- pub r: Vec,
- /// `v` - Volatility parameter.
- pub v: Vec,
- /// `q` - Dividend rate.
- pub q: Vec,
-}
-
-impl OptionParameters {
- /// New option parameters struct initialiser.
- #[must_use]
- pub const fn new(
- initial_price: Vec,
- strike_price: Vec,
- risk_free_rate: Vec,
- volatility: Vec,
- dividend_rate: Vec,
- time_to_maturity: Vec,
- ) -> Self {
- Self {
- S: initial_price,
- K: strike_price,
- T: time_to_maturity,
- r: risk_free_rate,
- v: volatility,
- q: dividend_rate,
- }
- }
-}
-
-#[allow(dead_code)]
-trait Payoff {
- fn payoff(&self, underlying: U, strike: S) -> f64;
-}
-
-impl Payoff for OptionContract {
- fn payoff(&self, underlying: f64, strike: f64) -> f64 {
- match self.type_flag {
- TypeFlag::Call => (underlying - strike).max(0.0),
- TypeFlag::Put => (strike - underlying).max(0.0),
- }
- }
-}
-
-impl Payoff, f64> for OptionContract {
- fn payoff(&self, underlying: Vec, strike: f64) -> f64 {
- let mut payoff = 0.0;
-
- for &spot in underlying.iter() {
- payoff += match self.type_flag {
- TypeFlag::Call => (spot - strike).max(0.0),
- TypeFlag::Put => (strike - spot).max(0.0),
- };
- }
-
- payoff / underlying.len() as f64
- }
-}
-
-// trait Payoff {
-// type Underlying;
-// type Strike;
-
-// fn call_payoff(&self, underlying: Self::Underlying, strike: Self::Strike) -> f64;
-// fn put_payoff(&self, underlying: Self::Underlying, strike: Self::Strike) -> f64;
-// }
-
-// impl Payoff for OptionContract {
-// type Underlying = f64;
-// type Strike = f64;
-
-// #[inline]
-// fn call_payoff(&self, underlying: f64, strike: f64) -> f64 {
-// f64::max(underlying - strike, 0.0)
-// }
-
-// #[inline]
-// fn put_payoff(&self, underlying: f64, strike: f64) -> f64 {
-// f64::max(strike - underlying, 0.0)
-// }
-// }
-
-// impl Payoff for OptionContract {
-// type Underlying = Vec;
-// type Strike = f64;
-
-// #[inline]
-// fn call_payoff(&self, underlying: Vec, strike: f64) -> f64 {
-// let mut payoff = 0.0;
-// for &spot in underlying.iter() {
-// payoff += f64::max(spot - strike, 0.0);
-// }
-// payoff / underlying.len() as f64
-// }
-
-// #[inline]
-// fn put_payoff(&self, underlying: Vec, strike: f64) -> f64 {
-// let mut payoff = 0.0;
-// for &spot in underlying.iter() {
-// payoff += f64::max(strike - spot, 0.0);
-// }
-// payoff / underlying.len() as f64
-// }
-// }
-
-// trait Payoff {
-// fn call_payoff(&self, underlying: UNDERLYING, strike: STRIKE) -> f64;
-// fn put_payoff(&self, underlying: UNDERLYING, strike: STRIKE) -> f64;
-// }
-
-// impl Payoff for f64 {
-// fn call_payoff(&self, underlying: f64, strike: f64) -> f64 {
-// (underlying - strike).max(0.0)
-// }
-
-// fn put_payoff(&self, underlying: f64, strike: f64) -> f64 {
-// (strike - underlying).max(0.0)
-// }
-// }
-
-// impl Payoff, f64> for Vec {
-// fn call_payoff(&self, underlying: Vec, strike: f64) -> f64 {
-// let mut payoff = 0.0;
-// for (i, &spot) in underlying.iter().enumerate() {
-// payoff += (spot - strike).max(0.0);
-// }
-// payoff / underlying.len() as f64
-// }
-
-// fn put_payoff(&self, underlying: Vec, strike: f64) -> f64 {
-// let mut payoff = 0.0;
-// for (i, &spot) in underlying.iter().enumerate() {
-// payoff += (strike - spot).max(0.0);
-// }
-// payoff / underlying.len() as f64
-// }
-// }
-
-// impl Payoff for f64 {
-// fn payoff(&self, spot: f64) -> f64 {
-// self.max(spot)
-// }
-// }
-
-// impl Payoff> for Vec< {
-// fn payoff(&self, spot: Decimal) -> Decimal {
-// self.max(spot)
-// }
-// }
-
-// pub trait PathIndependentOption {
-// fn price(&self) -> f64;
-// }
-
-// /// Path-dependent option trait.
-// pub trait PathDependentOption {
-// /// Base method for path-dependent call option payoff.
-// fn call_payoff(&self, path: &[f64]) -> f64;
-
-// /// Base method for path-dependent put option payoff.
-// fn put_payoff(&self, path: &[f64]) -> f64;
-
-// /// Base method for path-dependent option prices using closed-form solution (call and put).
-// fn closed_form_prices(&self) -> (f64, f64);
-
-// /// Base method for path-dependent option prices using Monte Carlo (call and put).
-// fn monte_carlo_prices(&self, n_steps: usize, n_sims: usize, parallel: bool) -> (f64, f64);
-// }
-
-// /// General option trait.
-// /// All option types must implement this trait.
-// /// All option contracts have:
-// /// - `Prices` struct to store the option prices (call, put).
-// /// - `Parameters` struct to store the option parameters (S, K, T, etc...).
-// /// - `TypeFlag` enum to store the option type (call, put).
-// /// - `Greeks` struct to store the option Greeks (sensitivities).
-// /// - `prices` method to compute the option prices (call, put).
-// /// - `set_parameters` method to set the option parameters.
-// /// - `option_type` method to set the option type.
-// /// - `greeks` method to compute the option Greeks (sensitivities).
-// pub trait OptionContract {
-// /// Option prices struct.
-// type Prices;
-// /// Option parameters struct.
-// type Parameters;
-// /// Option type enum (call or put).
-// type Type;
-// /// Option Greeks struct.
-// type Greeks;
-
-// /// Base method for computing the options prices (call and put).
-// fn prices(&self) -> Self::Prices;
-// /// Base method for setting the option parameters.
-// fn set_parameters(&self) -> Self::Parameters;
-// /// Base method for setting the option type.
-// fn option_type(&self) -> Self::Type;
-// /// Base method for computing the Greeks (sensitivities).
-// fn greeks(&self) -> Self::Greeks;
-// }
diff --git a/src/instruments/options/option_contract.rs b/src/instruments/options/option_contract.rs
new file mode 100644
index 00000000..1f19a006
--- /dev/null
+++ b/src/instruments/options/option_contract.rs
@@ -0,0 +1,29 @@
+// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+// RustQuant: A Rust library for quantitative finance tools.
+// Copyright (C) 2023 https://github.com/avhz
+// Dual licensed under Apache 2.0 and MIT.
+// See:
+// - LICENSE-APACHE.md
+// - LICENSE-MIT.md
+// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+use super::option_flags::*;
+use derive_builder::Builder;
+
+/// Option contract data.
+#[derive(Debug, Clone, Builder)]
+pub struct OptionContract {
+ /// Mandatory: Option type (call or put).
+ pub type_flag: TypeFlag,
+
+ /// Mandatory: Exercise type (European, American, Bermudan).
+ pub exercise_flag: ExerciseFlag,
+
+ /// Optional: Strike type (fixed or floating).
+ #[builder(default)]
+ pub strike_flag: Option,
+
+ /// Optional: Settlement type (cash or physical).
+ #[builder(default)]
+ pub settlement_flag: Option,
+}
diff --git a/src/instruments/options/option_exercise.rs b/src/instruments/options/option_exercise.rs
new file mode 100644
index 00000000..a2bca2fc
--- /dev/null
+++ b/src/instruments/options/option_exercise.rs
@@ -0,0 +1,29 @@
+// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+// RustQuant: A Rust library for quantitative finance tools.
+// Copyright (C) 2023 https://github.com/avhz
+// Dual licensed under Apache 2.0 and MIT.
+// See:
+// - LICENSE-APACHE.md
+// - LICENSE-MIT.md
+// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+/// European exercise type.
+pub struct EuropeanExercise {
+ /// The expiry date of the option.
+ pub expiry: Date,
+}
+
+/// American exercise type.
+pub struct AmericanExercise {
+ /// Initial date of the option.
+ pub start: Date,
+
+ /// The terminal date of the option.
+ pub end: Date,
+}
+
+/// Bermudan exercise type.
+pub struct BermudanExercise {
+ /// The exercise dates of the option.
+ pub exercise_dates: Vec,
+}
diff --git a/src/instruments/options/option_flags.rs b/src/instruments/options/option_flags.rs
new file mode 100644
index 00000000..7b0fb6c7
--- /dev/null
+++ b/src/instruments/options/option_flags.rs
@@ -0,0 +1,114 @@
+// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+// RustQuant: A Rust library for quantitative finance tools.
+// Copyright (C) 2023 https://github.com/avhz
+// Dual licensed under Apache 2.0 and MIT.
+// See:
+// - LICENSE-APACHE.md
+// - LICENSE-MIT.md
+// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+use time::Date;
+
+/// Option type enum.
+#[derive(Debug, Clone, Copy)]
+pub enum TypeFlag {
+ /// Call option (right to BUY the underlying asset).
+ Call = 1,
+
+ /// Put option (right to SELL the underlying asset).
+ Put = -1,
+}
+
+/// American/European option type enum.
+#[derive(Debug, Clone)]
+pub enum ExerciseFlag {
+ /// European option (can only be exercised at expiry).
+ /// Most index options are European.
+ European {
+ /// The expiry date of the option.
+ expiry: Date,
+ },
+
+ /// American option (can be exercised at any time before expiry).
+ /// Most stock options are American.
+ American {
+ /// Initial date of the option.
+ start: Date,
+ /// The terminal date of the option.
+ end: Date,
+ },
+
+ /// Bermudan option (can be exercised at specific dates before expiry).
+ /// Bermudan options are a hybrid of American and European options,
+ /// hence the name. These are relatively rare and typically used
+ /// in OTC markets.
+ Bermudan {
+ /// The exercise dates of the option.
+ exercise_dates: Vec,
+ },
+}
+
+/// Option strike type enum.
+///
+/// These are used for options such as
+/// Asian options (average) or Lookback options (extreme).
+#[derive(Debug, Clone, Copy)]
+pub enum StrikeFlag {
+ /// Strike is fixed.
+ Fixed,
+
+ /// Strike is floating (e.g. strike = S_max).
+ Floating,
+}
+
+/// Instrument settlement flag.
+#[derive(Debug, Clone, Copy)]
+pub enum SettlementFlag {
+ /// Cash settlement.
+ Cash,
+
+ /// Physical settlement.
+ Physical,
+}
+
+/// Method of averaging (arithmetic or geometric, and continuous or discrete).
+#[derive(Debug, Clone, Copy)]
+pub enum AveragingMethod {
+ /// Arithmetic Asian option with discrete averaging.
+ ArithmeticDiscrete,
+
+ /// Arithmetic Asian option with continuous averaging.
+ ArithmeticContinuous,
+
+ /// Geometric Asian option with discrete averaging.
+ GeometricDiscrete,
+
+ /// Geometric Asian option with continuous averaging.
+ GeometricContinuous,
+}
+
+/// Barrier type flag.
+#[derive(Clone, Copy, Debug)]
+pub enum BarrierType {
+ /// Up-and-out barrier option.
+ UpAndOut,
+
+ /// Down-and-out barrier option.
+ DownAndOut,
+
+ /// Up-and-in barrier option.
+ UpAndIn,
+
+ /// Down-and-in barrier option.
+ DownAndIn,
+}
+
+/// Binary type enum.
+#[derive(Debug, Clone, Copy)]
+pub enum BinaryType {
+ /// Asset-or-nothing binary option.
+ AssetOrNothing,
+
+ /// Cash-or-nothing binary option.
+ CashOrNothing,
+}
diff --git a/src/instruments/options/power.rs b/src/instruments/options/power.rs
index 9d2fd372..11a62c17 100644
--- a/src/instruments/options/power.rs
+++ b/src/instruments/options/power.rs
@@ -7,115 +7,44 @@
// - LICENSE-MIT.md
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-//! # Power Contracts
-//!
-//! Power contracts are options with the payoff: (S/K)^i
-//! where i is the (fixed) power of the contract.
-
-use crate::time::{today, DayCountConvention};
-use time::Date;
-
-// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-// STRUCTS, ENUMS, AND TRAITS
-// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-/// Power Option contract.
-#[allow(clippy::module_name_repetitions)]
+/// Power Option.
#[derive(Debug, Clone, Copy)]
pub struct PowerOption {
- /// `S` - Initial price of the underlying.
- pub initial_price: f64,
- /// `K` - Strike price.
- pub strike_price: f64,
- /// `i` - Power of the contract.
- pub power: f64,
+ /// The option contract.
+ pub contract: OptionContract,
- /// `r` - Risk-free rate parameter.
- pub risk_free_rate: f64,
- /// `b` - Cost of carry.
- pub cost_of_carry: f64,
- /// `v` - Volatility parameter.
- pub volatility: f64,
+ /// Strike price of the option.
+ pub strike_price: f64,
- /// `valuation_date` - Valuation date.
- pub evaluation_date: Option,
- /// `expiry_date` - Expiry date.
- pub expiration_date: Date,
+ /// Power parameter.
+ pub power: f64,
}
-// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-// IMPLEMENTATIONS
-// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-impl PowerOption {
- /// New Power Option contract.
- #[allow(clippy::too_many_arguments)]
- #[must_use]
- pub fn new(
- initial_price: f64,
- strike_price: f64,
- power: f64,
- risk_free_rate: f64,
- cost_of_carry: f64,
- volatility: f64,
- evaluation_date: Option,
- expiration_date: Date,
- ) -> Self {
- Self {
- initial_price,
- strike_price,
- power,
- risk_free_rate,
- cost_of_carry,
- volatility,
- evaluation_date,
- expiration_date,
- }
- }
+/// Power Option.
+#[derive(Debug, Clone, Copy)]
+pub struct PowerContract {
+ /// Strike price of the option.
+ pub strike_price: f64,
- /// Power Option price.
- #[must_use]
- pub fn price(&self) -> f64 {
- let S = self.initial_price;
- let K = self.strike_price;
- let r = self.risk_free_rate;
- let v = self.volatility;
- let b = self.cost_of_carry;
- let i = self.power;
+ /// Power parameter.
+ pub power: f64,
+}
- // Compute time to maturity.
- let T = DayCountConvention::default().day_count_factor(
- self.evaluation_date.unwrap_or(today()),
- self.expiration_date,
- );
+impl Payoff for PowerContract {
+ type Underlying = f64;
- (S / K).powf(i) * (((b - 0.5 * v.powi(2)) * i - r + 0.5 * (i * v).powi(2)) * T).exp()
+ fn payoff(&self, underlying: Self::Underlying) -> f64 {
+ (underlying / self.strike_price).powf(self.power)
}
}
-// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-// TESTS
-// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-#[cfg(test)]
-mod tests_power_contract {
- use super::*;
- use crate::{assert_approx_equal, RUSTQUANT_EPSILON};
- use time::Duration;
+impl Payoff for PowerOption {
+ type Underlying = f64;
- #[test]
- fn test_power() {
- let power_option = PowerOption {
- initial_price: 400.,
- strike_price: 450.,
- power: 2.,
- risk_free_rate: 0.08,
- cost_of_carry: 0.06,
- volatility: 0.25,
- evaluation_date: None,
- expiration_date: today() + Duration::days(182),
- };
-
- assert_approx_equal!(power_option.price(), 0.83144001309052, RUSTQUANT_EPSILON);
+ fn payoff(&self, underlying: Self::Underlying) -> f64 {
+ match self.contract.type_flag {
+ TypeFlag::Call => (underlying.powf(self.power) - self.strike).max(0.0),
+ TypeFlag::Put => (self.strike - underlying.powf(self.power)).max(0.0),
+ }
}
}
diff --git a/src/instruments/options/supershare.rs b/src/instruments/options/supershare.rs
new file mode 100644
index 00000000..72a8efae
--- /dev/null
+++ b/src/instruments/options/supershare.rs
@@ -0,0 +1,32 @@
+// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+// RustQuant: A Rust library for quantitative finance tools.
+// Copyright (C) 2023 https://github.com/avhz
+// Dual licensed under Apache 2.0 and MIT.
+// See:
+// - LICENSE-APACHE.md
+// - LICENSE-MIT.md
+// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+/// Supershare option.
+#[derive(Debug, Clone)]
+pub struct SupershareOption {
+ /// The option contract.
+ pub contract: OptionContract,
+
+ /// Lower strike price.
+ pub strike_1: f64,
+
+ /// Upper strike price.
+ pub strike_2: f64,
+}
+
+impl Payoff for SupershareOption {
+ type Underlying = f64;
+
+ fn payoff(&self, underlying: Self::Underlying) -> f64 {
+ match (strike_1..=strike_2).contains(&underlying) {
+ true => underlying / strike_1,
+ false => 0.0,
+ }
+ }
+}
diff --git a/src/instruments/options/todo/asian.rs b/src/instruments/options/todo/asian.rs
new file mode 100644
index 00000000..adb2b080
--- /dev/null
+++ b/src/instruments/options/todo/asian.rs
@@ -0,0 +1,150 @@
+// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+// RustQuant: A Rust library for quantitative finance tools.
+// Copyright (C) 2023 https://github.com/avhz
+// Dual licensed under Apache 2.0 and MIT.
+// See:
+// - LICENSE-APACHE.md
+// - LICENSE-MIT.md
+// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+use crate::{
+ math::distributions::{gaussian::Gaussian, Distribution},
+ time::{today, DayCountConvention},
+};
+use time::Date;
+
+use super::OptionContract;
+
+// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+// STRUCTS
+// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+/// Type of Asian option (fixed or floating strike).
+#[allow(clippy::module_name_repetitions)]
+#[derive(Debug, Clone, Copy)]
+pub enum AsianStrike {
+ /// Floating strike Asian option.
+ /// Payoffs:
+ /// - Call: `max(S_T - A, 0)`
+ /// - Put: `max(A - S_T, 0)`
+ Floating,
+ /// Fixed strike Asian option.
+ /// Payoffs:
+ /// - Call: `max(A - K, 0)`
+ /// - Put: `max(K - A, 0)`
+ Fixed,
+}
+
+/// Asian Option struct.
+#[allow(clippy::module_name_repetitions)]
+#[derive(derive_builder::Builder, Debug, Clone, Copy)]
+pub struct AsianOption {
+ /// `S` - Initial price of the underlying.
+ pub initial_price: f64,
+ /// `K` - Strike price.
+ pub strike_price: f64,
+ /// `r` - Risk-free rate parameter.
+ pub risk_free_rate: f64,
+ /// `v` - Volatility parameter.
+ pub volatility: f64,
+ /// `q` - Dividend rate.
+ pub dividend_rate: f64,
+
+ /// `evaluation_date` - Valuation date.
+ #[builder(default = "None")]
+ pub evaluation_date: Option,
+
+ /// `expiry_date` - Expiry date.
+ pub expiration_date: Date,
+}
+
+// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+// IMPLEMENTATIONS
+// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+impl AsianOption {
+ /// New Asian Option
+ #[must_use]
+ pub const fn new(
+ initial_price: f64,
+ strike_price: f64,
+ risk_free_rate: f64,
+ volatility: f64,
+ dividend_rate: f64,
+ evaluation_date: Option,
+ expiration_date: Date,
+ ) -> Self {
+ Self {
+ initial_price,
+ strike_price,
+ risk_free_rate,
+ volatility,
+ dividend_rate,
+ evaluation_date,
+ expiration_date,
+ }
+ }
+
+ /// Geometric Continuous Average-Rate Price
+ #[must_use]
+ pub fn price_geometric_average(&self) -> (f64, f64) {
+ let S = self.initial_price;
+ let K = self.strike_price;
+ // let T = self.time_to_maturity;
+ let r = self.risk_free_rate;
+ let v = self.volatility;
+ let q = self.dividend_rate;
+
+ // Compute time to maturity.
+ let T = DayCountConvention::default().day_count_factor(
+ self.evaluation_date.unwrap_or(today()),
+ self.expiration_date,
+ );
+
+ let v_a = v / 3_f64.sqrt();
+ let b = r - q;
+ let b_a = 0.5 * (b - v * v / 6.0);
+
+ let d1 = ((S / K).ln() + (b_a + 0.5 * v_a * v_a) * T) / (v_a * (T).sqrt());
+ let d2 = d1 - v_a * (T).sqrt();
+
+ let N = Gaussian::default();
+
+ let c = S * ((b_a - r) * T).exp() * N.cdf(d1) - K * (-r * T).exp() * N.cdf(d2);
+ let p = -S * ((b_a - r) * T).exp() * N.cdf(-d1) + K * (-r * T).exp() * N.cdf(-d2);
+
+ (c, p)
+ }
+}
+
+// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+// TESTS
+// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+#[cfg(test)]
+mod tests {
+ use time::Duration;
+
+ use super::*;
+ use crate::assert_approx_equal;
+
+ #[test]
+ fn test_asian_geometric() {
+ let expiry_date = today() + Duration::days(92);
+
+ let AsianOption = AsianOption {
+ initial_price: 80.0,
+ strike_price: 85.0,
+ risk_free_rate: 0.05,
+ volatility: 0.2,
+ evaluation_date: None,
+ expiration_date: expiry_date,
+ dividend_rate: -0.03,
+ };
+
+ let prices = AsianOption.price_geometric_average();
+
+ // Value from Haug's book.
+ assert_approx_equal!(prices.1, 4.6922, 0.0001);
+ }
+}
diff --git a/src/instruments/options/bachelier.rs b/src/instruments/options/todo/bachelier.rs
similarity index 100%
rename from src/instruments/options/bachelier.rs
rename to src/instruments/options/todo/bachelier.rs
diff --git a/src/instruments/options/todo/barrier.rs b/src/instruments/options/todo/barrier.rs
new file mode 100644
index 00000000..08855545
--- /dev/null
+++ b/src/instruments/options/todo/barrier.rs
@@ -0,0 +1,320 @@
+// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+// RustQuant: A Rust library for quantitative finance tools.
+// Copyright (C) 2023 https://github.com/avhz
+// Dual licensed under Apache 2.0 and MIT.
+// See:
+// - LICENSE-APACHE.md
+// - LICENSE-MIT.md
+// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+use crate::math::distributions::{gaussian::Gaussian, Distribution};
+
+// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+// BARRIER OPTION STRUCT
+// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+/// Barrier Option struct for parameters and pricing methods.
+#[derive(Debug, Clone, Copy)]
+#[allow(clippy::module_name_repetitions)]
+pub struct BarrierOption {
+ /// * `S` - Initial underlying price.
+ pub initial_price: f64,
+ /// * `X` - Strike price.
+ pub strike_price: f64,
+ /// * `H` - Barrier.
+ pub barrier: f64,
+ /// * `t` - Time to expiry.
+ pub time_to_expiry: f64,
+ /// * `r` - Risk-free rate.
+ pub risk_free_rate: f64,
+ /// * `v` - Volatility.
+ pub volatility: f64,
+ /// * `K` - Rebate (paid if the option is not able to be exercised).
+ pub rebate: f64,
+ /// * `q` - Dividend yield.
+ pub dividend_yield: f64,
+}
+
+/// Barrier option type enum.
+#[derive(Debug, Clone, Copy)]
+#[allow(clippy::module_name_repetitions)]
+pub enum BarrierType {
+ /// Call (up-and-in)
+ /// Payoff: `max(S_T - X, 0) * I(max(S_t) > H)`
+ CUI,
+ /// Call (down-and-in)
+ /// Payoff: `max(S_T - X, 0) * I(min(S_t) < H)`
+ CDI,
+ /// Call (up-and-out)
+ /// Payoff: `max(S_T - X, 0) * I(max(S_t) < H)`
+ CUO,
+ /// Call (down-and-out)
+ /// Payoff: `max(S_T - X, 0) * I(min(S_t) > H)`
+ CDO,
+ /// Put (up-and-in)
+ /// Payoff: `max(X - S_T, 0) * I(max(S_t) > H)`
+ PUI,
+ /// Put (down-and-in)
+ /// Payoff: `max(X - S_T, 0) * I(min(S_t) < H)`
+ PDI,
+ /// Put (up-and-out)
+ /// Payoff: `max(X - S_T, 0) * I(max(S_t) < H)`
+ PUO,
+ /// Put (down-and-out)
+ /// Payoff: `max(X - S_T, 0) * I(min(S_t) > H)`
+ PDO,
+}
+
+// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+// BARRIER OPTION IMPLEMENTATION
+// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+impl BarrierOption {
+ /// Closed-form solution for path-dependent barrier options.
+ ///
+ /// Adapted from Haug's *Complete Guide to Option Pricing Formulas*.
+ ///
+ /// # Arguments:
+ ///
+ /// * `type_flag` - One of: `cui`, `cuo`, `pui`, `puo`, `cdi`, `cdo`, `pdi`, `pdo`.
+ ///
+ /// # Note:
+ /// * `b = r - q` - The cost of carry.
+ #[must_use]
+ pub fn price(&self, type_flag: BarrierType) -> f64 {
+ let S = self.initial_price;
+ let X = self.strike_price;
+ let H = self.barrier;
+ let t = self.time_to_expiry;
+ let r = self.risk_free_rate;
+ let v = self.volatility;
+ let K = self.rebate;
+ let q = self.dividend_yield;
+
+ let b: f64 = r - q;
+
+ // Common terms:
+ let mu: f64 = (b - v * v / 2.) / (v * v);
+ let lambda: f64 = (mu * mu + 2. * r / (v * v)).sqrt();
+ let z: f64 = (H / S).ln() / (v * t.sqrt()) + lambda * v * t.sqrt();
+
+ let x1: f64 = (S / X).ln() / (v * t.sqrt()) + (1. + mu) * v * t.sqrt();
+ let x2: f64 = (S / H).ln() / (v * t.sqrt()) + (1. + mu) * v * t.sqrt();
+
+ let y1: f64 = (H * H / (S * X)).ln() / (v * t.sqrt()) + (1. + mu) * v * t.sqrt();
+ let y2: f64 = (H / S).ln() / (v * t.sqrt()) + (1. + mu) * v * t.sqrt();
+
+ let norm = Gaussian::default();
+
+ // Common functions:
+ let A = |phi: f64| -> f64 {
+ let term1: f64 = phi * S * ((b - r) * t).exp() * norm.cdf(phi * x1);
+ let term2: f64 = phi * X * (-r * t).exp() * norm.cdf(phi * x1 - phi * v * (t).sqrt());
+ term1 - term2
+ };
+
+ let B = |phi: f64| -> f64 {
+ let term1: f64 = phi * S * ((b - r) * t).exp() * norm.cdf(phi * x2);
+ let term2: f64 = phi * X * (-r * t).exp() * norm.cdf(phi * x2 - phi * v * (t).sqrt());
+ term1 - term2
+ };
+
+ let C = |phi: f64, eta: f64| -> f64 {
+ let term1: f64 =
+ phi * S * ((b - r) * t).exp() * (H / S).powf(2. * (mu + 1.)) * norm.cdf(eta * y1);
+ let term2: f64 = phi
+ * X
+ * (-r * t).exp()
+ * (H / S).powf(2. * mu)
+ * norm.cdf(eta * y1 - eta * v * t.sqrt());
+ term1 - term2
+ };
+
+ let D = |phi: f64, eta: f64| -> f64 {
+ let term1: f64 =
+ phi * S * ((b - r) * t).exp() * (H / S).powf(2. * (mu + 1.)) * norm.cdf(eta * y2);
+ let term2: f64 = phi
+ * X
+ * (-r * t).exp()
+ * (H / S).powf(2. * mu)
+ * norm.cdf(eta * y2 - eta * v * (t).sqrt());
+
+ term1 - term2
+ };
+
+ let E = |eta: f64| -> f64 {
+ let term1: f64 = norm.cdf(eta * x2 - eta * v * (t).sqrt());
+ let term2: f64 = (H / S).powf(2. * mu) * norm.cdf(eta * y2 - eta * v * t.sqrt());
+
+ K * (-r * t).exp() * (term1 - term2)
+ };
+
+ let F = |eta: f64| -> f64 {
+ let term1: f64 = (H / S).powf(mu + lambda) * norm.cdf(eta * z);
+ let term2: f64 =
+ (H / S).powf(mu - lambda) * norm.cdf(eta * z - 2. * eta * lambda * v * t.sqrt());
+
+ K * (term1 + term2)
+ };
+
+ // Strike above barrier (X >= H):
+ if X >= H {
+ match type_flag {
+ // Knock-In calls:
+ BarrierType::CDI if S >= H => C(1., 1.) + E(1.),
+ BarrierType::CUI if S <= H => A(1.) + E(-1.),
+ // Knock-In puts:
+ BarrierType::PDI if S >= H => B(-1.) - C(-1., 1.) + D(-1., 1.) + E(1.),
+ BarrierType::PUI if S <= H => A(-1.) - B(-1.) + D(-1., -1.) + E(-1.),
+ // Knock-Out calls:
+ BarrierType::CDO if S >= H => A(1.) - C(1., 1.) + F(1.),
+ BarrierType::CUO if S <= H => F(-1.),
+ // Knock-Out puts:
+ BarrierType::PDO if S >= H => A(-1.) - B(-1.) + C(-1., 1.) - D(-1., 1.) + F(1.),
+ BarrierType::PUO if S <= H => B(-1.) - D(-1., -1.) + F(-1.),
+
+ _ => panic!("Barrier touched - check barrier and type flag."),
+ }
+ }
+ // Strike below barrier (X < H):
+ else {
+ match type_flag {
+ // Knock-In calls:
+ BarrierType::CDI if S >= H => A(1.) - B(1.) + D(1., 1.) + E(1.),
+ BarrierType::CUI if S <= H => B(1.) - C(1., -1.) + D(1., -1.) + E(-1.),
+ // Knock-In puts:
+ BarrierType::PDI if S >= H => A(-1.) + E(1.),
+ BarrierType::PUI if S <= H => C(-1., -1.) + E(-1.),
+ // Knock-Out calls:
+ BarrierType::CDO if S >= H => B(1.) - D(1., 1.) + F(1.),
+ BarrierType::CUO if S <= H => A(1.) - B(1.) + C(1., -1.) - D(1., -1.) + F(-1.),
+ // Knock-Out puts:
+ BarrierType::PDO if S >= H => F(1.),
+ BarrierType::PUO if S <= H => A(-1.) - C(-1., -1.) + F(-1.),
+
+ _ => panic!("Barrier touched - check barrier and type flag."),
+ }
+ }
+ }
+}
+
+// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+// TESTS
+// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::assert_approx_equal;
+ use crate::RUSTQUANT_EPSILON;
+
+ // use std::f64::EPSILON as EPS;
+
+ // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ // Initial underlying price ABOVE the barrier.
+ //
+ // If S > H, then:
+ // - "down-in" and "down-out" options have a defined price.
+ // - "up-in" and "up-out" options make no sense.
+ // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ static S_ABOVE_H: BarrierOption = BarrierOption {
+ initial_price: 110.0,
+ strike_price: 100.0,
+ barrier: 105.0,
+ time_to_expiry: 1.0,
+ risk_free_rate: 0.05,
+ volatility: 0.2,
+ rebate: 0.0,
+ dividend_yield: 0.01,
+ };
+
+ #[allow(clippy::similar_names)]
+ #[test]
+ fn test_S_above_H() {
+ let cdi = S_ABOVE_H.price(BarrierType::CDI);
+ let cdo = S_ABOVE_H.price(BarrierType::CDO);
+ let pdi = S_ABOVE_H.price(BarrierType::PDI);
+ let pdo = S_ABOVE_H.price(BarrierType::PDO);
+
+ assert_approx_equal!(cdi, 9.504_815_211_050_698, RUSTQUANT_EPSILON);
+ assert_approx_equal!(cdo, 7.295_021_649_666_765, RUSTQUANT_EPSILON);
+ assert_approx_equal!(pdi, 3.017_297_598_380_377_4, RUSTQUANT_EPSILON);
+ assert_approx_equal!(pdo, 0.000_000, RUSTQUANT_EPSILON);
+ }
+
+ #[test]
+ #[should_panic(expected = "Barrier touched - check barrier and type flag.")]
+ fn cui_panic() {
+ let _ = S_ABOVE_H.price(BarrierType::CUI);
+ }
+ #[test]
+ #[should_panic(expected = "Barrier touched - check barrier and type flag.")]
+ fn cuo_panic() {
+ let _ = S_ABOVE_H.price(BarrierType::CUO);
+ }
+ #[test]
+ #[should_panic(expected = "Barrier touched - check barrier and type flag.")]
+ fn pui_panic() {
+ let _ = S_ABOVE_H.price(BarrierType::PUI);
+ }
+ #[test]
+ #[should_panic(expected = "Barrier touched - check barrier and type flag.")]
+ fn puo_panic() {
+ let _ = S_ABOVE_H.price(BarrierType::PUO);
+ }
+
+ // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ // Initial underlying price BELOW the barrier.
+ //
+ // If S < H, then:
+ // - "down-in" and "down-out" options make no sense.
+ // - "up-in" and "up-out" options have a defined price.
+ // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ static S_BELOW_H: BarrierOption = BarrierOption {
+ initial_price: 90.0,
+ strike_price: 100.0,
+ barrier: 105.0,
+ time_to_expiry: 1.0,
+ risk_free_rate: 0.05,
+ volatility: 0.2,
+ rebate: 0.0,
+ dividend_yield: 0.01,
+ };
+
+ #[allow(clippy::similar_names)]
+ #[test]
+ fn test_S_below_H() {
+ let cui = S_BELOW_H.price(BarrierType::CUI);
+ let cuo = S_BELOW_H.price(BarrierType::CUO);
+ let pui = S_BELOW_H.price(BarrierType::PUI);
+ let puo = S_BELOW_H.price(BarrierType::PUO);
+
+ assert_approx_equal!(cui, 4.692_603_355_387_815, RUSTQUANT_EPSILON);
+ assert_approx_equal!(cuo, 0.022_448_676_101_445_74, RUSTQUANT_EPSILON);
+ assert_approx_equal!(pui, 1.359_553_168_024_573_8, RUSTQUANT_EPSILON);
+ assert_approx_equal!(puo, 9.373_956_276_110_954, RUSTQUANT_EPSILON);
+ }
+
+ #[test]
+ #[should_panic(expected = "Barrier touched - check barrier and type flag.")]
+ fn cdi_panic() {
+ let _ = S_BELOW_H.price(BarrierType::CDI);
+ }
+ #[test]
+ #[should_panic(expected = "Barrier touched - check barrier and type flag.")]
+ fn cdo_panic() {
+ let _ = S_BELOW_H.price(BarrierType::CDO);
+ }
+ #[test]
+ #[should_panic(expected = "Barrier touched - check barrier and type flag.")]
+ fn pdi_panic() {
+ let _ = S_BELOW_H.price(BarrierType::PDI);
+ }
+ #[test]
+ #[should_panic(expected = "Barrier touched - check barrier and type flag.")]
+ fn pdo_panic() {
+ let _ = S_BELOW_H.price(BarrierType::PDO);
+ }
+}
diff --git a/src/instruments/options/todo/binary.rs b/src/instruments/options/todo/binary.rs
new file mode 100644
index 00000000..cee2e2d4
--- /dev/null
+++ b/src/instruments/options/todo/binary.rs
@@ -0,0 +1,160 @@
+// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+// RustQuant: A Rust library for quantitative finance tools.
+// Copyright (C) 2023 https://github.com/avhz
+// Dual licensed under Apache 2.0 and MIT.
+// See:
+// - LICENSE-APACHE.md
+// - LICENSE-MIT.md
+// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+//! This module contains various 'binary', or 'digital', option types.
+
+use crate::math::distributions::{gaussian::Gaussian, Distribution};
+
+// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+// STRUCTS
+// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+/// Gap option parameters.
+#[derive(Debug, Clone, Copy)]
+pub struct GapOption {
+ /// `S` - Initial price of the underlying.
+ pub initial_price: f64,
+ /// `K_1` - First strike price (barrier strike).
+ pub strike_1: f64,
+ /// `K_2` - Second strike price (payoff strike).
+ pub strike_2: f64,
+ /// `r` - Risk-free rate parameter.
+ pub risk_free_rate: f64,
+ /// `v` - Volatility parameter.
+ pub volatility: f64,
+ /// `b` - Cost-of-carry.
+ pub cost_of_carry: f64,
+ /// `T` - Time to expiry/maturity.
+ pub time_to_maturity: f64,
+}
+
+/// Cash-or-Nothing option parameters.
+#[derive(Debug, Clone, Copy)]
+pub struct CashOrNothingOption {
+ /// `S` - Initial price of the underlying.
+ pub initial_price: f64,
+ /// `X` - Strike price.
+ pub strike_price: f64,
+ /// `K` - Cash payout amount.
+ pub payout_value: f64,
+ /// `r` - Risk-free rate parameter.
+ pub risk_free_rate: f64,
+ /// `v` - Volatility parameter.
+ pub volatility: f64,
+ /// `b` - Cost-of-carry.
+ pub cost_of_carry: f64,
+ /// `T` - Time to expiry/maturity.
+ pub time_to_maturity: f64,
+}
+
+// pub struct AssetOrNothingOption {}
+// pub struct SupershareOption {}
+// pub struct BinaryBarrierOption {}
+
+// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+// IMPLEMENTATIONS
+// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+impl GapOption {
+ /// Gap option pricer.
+ /// The payoff from a call is $0$ if $S < K_1$ and $S — K_2$ if $S > K_1$.
+ /// Similarly, the payoff from a put is $0$ if $S > K_1$ and $K_2 — S$ if $S < K_1$.
+ #[must_use]
+ pub fn price(&self) -> (f64, f64) {
+ let S = self.initial_price;
+ let K_1 = self.strike_1;
+ let K_2 = self.strike_2;
+ let T = self.time_to_maturity;
+ let r = self.risk_free_rate;
+ let v = self.volatility;
+ let b = self.cost_of_carry;
+
+ let d1 = ((S / K_1).ln() + (b + 0.5 * v * v) * T) / (v * (T).sqrt());
+ let d2 = d1 - v * (T).sqrt();
+
+ let N = Gaussian::default();
+
+ let c = S * ((b - r) * T).exp() * N.cdf(d1) - K_2 * (-r * T).exp() * N.cdf(d2);
+ let p = -S * ((b - r) * T).exp() * N.cdf(-d1) + K_2 * (-r * T).exp() * N.cdf(-d2);
+
+ (c, p)
+ }
+}
+
+impl CashOrNothingOption {
+ /// Cah-or-Nothing option pricer.
+ /// The payoff from a call is 0 if S < X and K if S > X.
+ /// The payoff from a put is 0 if S > X and K if S < X.
+ #[must_use]
+ pub fn price(&self) -> (f64, f64) {
+ let S = self.initial_price;
+ let X = self.strike_price;
+ let K = self.payout_value;
+ let T = self.time_to_maturity;
+ let r = self.risk_free_rate;
+ let v = self.volatility;
+ let b = self.cost_of_carry;
+
+ let d = ((S / X).ln() + (b - 0.5 * v * v) * T) / (v * (T).sqrt());
+
+ let N = Gaussian::default();
+
+ let c = K * (-r * T).exp() * N.cdf(d);
+ let p = K * (-r * T).exp() * N.cdf(-d);
+
+ (c, p)
+ }
+}
+
+// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+// TESTS
+// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::assert_approx_equal;
+ use crate::RUSTQUANT_EPSILON;
+
+ #[test]
+ fn test_gap_option() {
+ let gap = GapOption {
+ initial_price: 50.0,
+ strike_1: 50.0,
+ strike_2: 57.0,
+ risk_free_rate: 0.09,
+ volatility: 0.2,
+ time_to_maturity: 0.5,
+ cost_of_carry: 0.09,
+ };
+
+ let prices = gap.price();
+
+ // Value from Haug's book (note: gap option payoffs can be negative).
+ assert_approx_equal!(prices.0, -0.005_252_489_258_779_747, RUSTQUANT_EPSILON);
+ }
+
+ #[test]
+ fn test_cash_or_nothing_option() {
+ let CON = CashOrNothingOption {
+ initial_price: 100.0,
+ payout_value: 10.0,
+ strike_price: 80.0,
+ risk_free_rate: 0.06,
+ volatility: 0.35,
+ time_to_maturity: 0.75,
+ cost_of_carry: 0.0,
+ };
+
+ let prices = CON.price();
+
+ // Value from Haug's book.
+ assert_approx_equal!(prices.1, 2.671_045_684_461_347, RUSTQUANT_EPSILON);
+ }
+}
diff --git a/src/instruments/options/binomial.rs b/src/instruments/options/todo/binomial.rs
similarity index 97%
rename from src/instruments/options/binomial.rs
rename to src/instruments/options/todo/binomial.rs
index 41848c3e..ad37e817 100644
--- a/src/instruments/options/binomial.rs
+++ b/src/instruments/options/todo/binomial.rs
@@ -86,16 +86,16 @@ impl BinomialOption {
for j in (0..n).rev() {
for i in 0..=j {
match ame_eur_flag {
- ExerciseFlag::American => {
+ ExerciseFlag::American { .. } => {
option_value[i] = (f64::from(z)
* (S * u.powi(i as i32) * d.powi(j as i32 - i as i32) - K))
.max(Df * (p * (option_value[i + 1]) + (1.0 - p) * option_value[i]));
}
- ExerciseFlag::European => {
+ ExerciseFlag::European { .. } => {
option_value[i] =
Df * (p * (option_value[i + 1]) + (1.0 - p) * option_value[i]);
}
- ExerciseFlag::Bermudan => {
+ ExerciseFlag::Bermudan { .. } => {
panic!("Bermudan option pricing not implemented yet.");
}
}
diff --git a/src/instruments/options/todo/forward_start.rs b/src/instruments/options/todo/forward_start.rs
new file mode 100644
index 00000000..34654568
--- /dev/null
+++ b/src/instruments/options/todo/forward_start.rs
@@ -0,0 +1,128 @@
+// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+// RustQuant: A Rust library for quantitative finance tools.
+// Copyright (C) 2023 https://github.com/avhz
+// Dual licensed under Apache 2.0 and MIT.
+// See:
+// - LICENSE-APACHE.md
+// - LICENSE-MIT.md
+// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+// FORWARD START OPTION STRUCT
+// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+use time::Date;
+
+use crate::{
+ math::distributions::{Distribution, Gaussian},
+ time::{today, DayCountConvention},
+};
+
+/// Forward Start Option parameters struct
+#[allow(clippy::module_name_repetitions)]
+#[derive(derive_builder::Builder, Debug)]
+pub struct ForwardStartOption {
+ /// `S` - Initial price of the underlying.
+ pub initial_price: f64,
+ /// `alpha` - The proportion of S to set the strike price.
+ /// Three possibilities:
+ /// - alpha < 1: call (put) will start (1 - alpha)% in-the-money (out-of-the-money).
+ /// - alpha = 1: the option starts at-the-money.
+ /// - alpha > 1: call (put) will start (alpha - 1)% out-of-the-money (in-the-money).
+ pub alpha: f64,
+ /// `r` - Risk-free rate parameter.
+ pub risk_free_rate: f64,
+ /// `v` - Volatility parameter.
+ pub volatility: f64,
+ /// `q` - Dividend rate.
+ pub dividend_rate: f64,
+
+ /// `valuation_date` - Valuation date.
+ #[builder(default = "None")]
+ pub valuation_date: Option,
+
+ /// `start` - Time until the start of the option (`T` in most literature).
+ pub start: Date,
+ /// `end` - Time until the end of the option (`t` in most literature).
+ pub end: Date,
+}
+
+// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+// FORWARD START OPTION IMPLEMENTATION
+// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+impl ForwardStartOption {
+ /// Rubinstein (1990) Forward Start Option Price formula.
+ /// Returns a tuple: `(call_price, put_price)`
+ /// # Note:
+ /// * `b = r - q` - The cost of carry.
+ #[must_use]
+ pub fn price(&self) -> (f64, f64) {
+ let S = self.initial_price;
+ let a = self.alpha;
+
+ let r = self.risk_free_rate;
+ let v = self.volatility;
+ let q = self.dividend_rate;
+
+ let T = DayCountConvention::default()
+ .day_count_factor(self.valuation_date.unwrap_or(today()), self.end);
+
+ let t = DayCountConvention::default()
+ .day_count_factor(self.valuation_date.unwrap_or(today()), self.start);
+
+ let b = r - q;
+
+ let d1 = ((1. / a).ln() + (b + v * v / 2.) * (T - t)) / (v * (T - t).sqrt());
+ let d2 = d1 - v * (T - t).sqrt();
+
+ let norm = Gaussian::default();
+
+ let Nd1: f64 = norm.cdf(d1);
+ let Nd2: f64 = norm.cdf(d2);
+
+ let Nd1_: f64 = norm.cdf(-d1);
+ let Nd2_: f64 = norm.cdf(-d2);
+
+ let c: f64 = S
+ * ((b - r) * t).exp()
+ * (((b - r) * (T - t)).exp() * Nd1 - a * (-r * (T - t)).exp() * Nd2);
+ let p: f64 = S
+ * ((b - r) * t).exp()
+ * (-((b - r) * (T - t)).exp() * Nd1_ + a * (-r * (T - t)).exp() * Nd2_);
+
+ (c, p)
+ }
+}
+
+// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+// TESTS
+// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+#[cfg(test)]
+mod tests_forward_start {
+ use super::*;
+ use crate::assert_approx_equal;
+
+ #[test]
+ fn TEST_forward_start_option() {
+ let start = today() + time::Duration::days(91);
+ let end = today() + time::Duration::days(365);
+
+ let ForwardStart = ForwardStartOption {
+ initial_price: 60.0,
+ alpha: 1.1,
+ risk_free_rate: 0.08,
+ volatility: 0.3,
+ dividend_rate: 0.04,
+ valuation_date: None,
+ start,
+ end,
+ };
+
+ let prices = ForwardStart.price();
+
+ // Call price example from Haug's book.
+ assert_approx_equal!(prices.0, 4.402888269001168, 1e-2);
+ }
+}
diff --git a/src/instruments/options/heston.rs b/src/instruments/options/todo/heston.rs
similarity index 100%
rename from src/instruments/options/heston.rs
rename to src/instruments/options/todo/heston.rs
diff --git a/src/instruments/options/todo/lookback.rs b/src/instruments/options/todo/lookback.rs
new file mode 100644
index 00000000..14731c6a
--- /dev/null
+++ b/src/instruments/options/todo/lookback.rs
@@ -0,0 +1,402 @@
+// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+// RustQuant: A Rust library for quantitative finance tools.
+// Copyright (C) 2023 https://github.com/avhz
+// Dual licensed under Apache 2.0 and MIT.
+// See:
+// - LICENSE-APACHE.md
+// - LICENSE-MIT.md
+// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+use crate::{
+ instruments::options::TypeFlag,
+ math::distributions::{Distribution, Gaussian},
+ math::Statistic,
+ models::geometric_brownian_motion::GeometricBrownianMotion,
+ stochastics::process::StochasticProcess,
+};
+
+// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+// LOOKBACK OPTION STRUCTS
+// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+/// Lookback option strike type enum.
+/// The strike can be either fixed or floating.
+#[allow(clippy::module_name_repetitions)]
+#[derive(Debug, Clone, Copy)]
+pub enum LookbackStrike {
+ /// Floating strike lookback option.
+ /// Payoffs:
+ /// - Call: `max(S_T - S_min, 0)`
+ /// - Put: `max(S_max - S_T, 0)`
+ Floating,
+ /// Fixed strike lookback option.
+ /// Payoffs:
+ /// - Call: `max(S_max - K, 0)`
+ /// - Put: `max(K - S_min, 0)`
+ Fixed,
+}
+
+/// Struct containing Lookback Option parameters.
+#[allow(clippy::module_name_repetitions)]
+#[derive(Debug, Clone, Copy)]
+pub struct LookbackOption {
+ /// `S` - Initial price of the underlying.
+ pub initial_price: f64,
+ /// `r` - Risk-free rate parameter.
+ pub risk_free_rate: f64,
+ /// `K` - Strike price (only needed for fixed strike lookbacks).
+ /// If the strike is floating, then this is `None`.
+ pub strike_price: Option,
+ /// `v` - Volatility parameter.
+ pub volatility: f64,
+ /// `T` - Time to expiry/maturity.
+ pub time_to_maturity: f64,
+ /// `q` - dividend yield.
+ pub dividend_yield: f64,
+ /// Minimum value of the underlying price observed **so far**.
+ /// If the contract starts at t=0, then `S_min = S_0`.
+ /// Used for the closed-form put price.
+ pub s_min: f64,
+ /// Maximum value of the underlying price observed **so far**.
+ /// If the contract starts at t=0, then `S_max = S_0`.
+ /// Used for the closed-form call price.
+ pub s_max: f64,
+ /// Strike type.
+ pub strike_type: LookbackStrike,
+}
+
+// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+// LOOKBACK OPTION IMPLEMENTATIONS
+// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+impl LookbackOption {
+ /// Closed-form lookback option price.
+ #[must_use]
+ pub fn price_analytic(&self) -> (f64, f64) {
+ let s = self.initial_price;
+ let r = self.risk_free_rate;
+ let t = self.time_to_maturity;
+ let v = self.volatility;
+ let q = self.dividend_yield;
+ let s_min = self.s_min;
+ let s_max = self.s_max;
+
+ let b = r - q; // Cost of carry
+
+ let norm = Gaussian::default();
+
+ let call: f64;
+ let put: f64;
+
+ match self.strike_type {
+ LookbackStrike::Floating => {
+ let a1 = ((s / s_min).ln() + (b + v * v / 2.0) * t) / (v * t.sqrt());
+ let a2 = a1 - v * t.sqrt();
+ let b1 = ((s / s_max).ln() + (b + v * v / 2.0) * t) / (v * t.sqrt());
+ let b2 = b1 - v * t.sqrt();
+
+ if b == 0.0 {
+ call = s * (-r * t).exp() * norm.cdf(a1)
+ - s_min * (-r * t).exp() * norm.cdf(a2)
+ + s * (-r * t).exp()
+ * v
+ * t.sqrt()
+ * (norm.pdf(a1) + a1 * (norm.cdf(a1) - 1.0));
+
+ put = -s * ((b - r) * t).exp() * norm.cdf(-b1)
+ + s_max * (-r * t).exp() * norm.cdf(-b2)
+ + s * (-r * t).exp() * v * t.sqrt() * (norm.pdf(b1) + b1 * norm.cdf(b1));
+ } else {
+ call = s * ((b - r) * t).exp() * norm.cdf(a1)
+ - s_min * (-r * t).exp() * norm.cdf(a2)
+ + s * (-r * t).exp()
+ * (v * v / (2.0 * b))
+ * ((s / s_min).powf(-2.0 * b / (v * v))
+ * norm.cdf(-a1 + 2.0 * b * t.sqrt() / v)
+ - (b * t).exp() * norm.cdf(-a1));
+
+ put = -s * ((b - r) * t).exp() * norm.cdf(-b1)
+ + s_max * (-r * t).exp() * norm.cdf(-b2)
+ + s * (-r * t).exp()
+ * (v * v / (2.0 * b))
+ * (-(s / s_max).powf(-2.0 * b / (v * v))
+ * norm.cdf(b1 - 2.0 * b * t.sqrt() / v)
+ + (b * t).exp() * norm.cdf(b1));
+ }
+
+ (call, put)
+ }
+ LookbackStrike::Fixed => {
+ let x = self.strike_price.unwrap();
+
+ let d1 = ((s / x).ln() + (b + v * v / 2.0) * t) / (v * t.sqrt());
+ let d2 = d1 - v * t.sqrt();
+ let e1 = ((s / s_max).ln() + (b + v * v / 2.0) * t) / (v * t.sqrt());
+ let e2 = e1 - v * t.sqrt();
+ let f1 = ((s / s_min).ln() + (b + v * v / 2.0) * t) / (v * t.sqrt());
+ let f2 = f1 - v * t.sqrt();
+
+ let call = if x > s_max {
+ s * ((b - r) * t).exp() * norm.cdf(d1) - x * (-r * t).exp() * norm.cdf(d2)
+ + s * (-r * t).exp()
+ * (v * v / (2.0 * b))
+ * (-(s / x).powf(-2.0 * b / (v * v))
+ * norm.cdf(d1 - 2.0 * b * t.sqrt() / v)
+ + (b * t).exp() * norm.cdf(d1))
+ } else {
+ (-r * t).exp() * (s_max - x) + s * ((b - r) * t).exp() * norm.cdf(e1)
+ - s_max * (-r * t).exp() * norm.cdf(e2)
+ + s * (-r * t).exp()
+ * (v * v / (2.0 * b))
+ * (-(s / s_max).powf(-2.0 * b / (v * v))
+ * norm.cdf(e1 - 2.0 * b * t.sqrt() / v)
+ + (b * t).exp() * norm.cdf(e1))
+ };
+
+ let put = if x < s_min {
+ -s * ((b - r) * t).exp() * norm.cdf(-d1)
+ + x * (-r * t).exp() * norm.cdf(-d2)
+ + s * (-r * t).exp()
+ * (v * v / (2.0 * b))
+ * ((s / x).powf(-2.0 * b / (v * v))
+ * norm.cdf(-d1 + 2.0 * b * t.sqrt() / v)
+ - (b * t).exp() * norm.cdf(-d1))
+ } else {
+ (-r * t).exp() * (x - s_min) - s * ((b - r) * t).exp() * norm.cdf(-f1)
+ + s_min * (-r * t).exp() * norm.cdf(-f2)
+ + s * (-r * t).exp()
+ * (v * v / (2.0 * b))
+ * ((s / s_min).powf(-2.0 * b / (v * v))
+ * norm.cdf(-f1 + 2.0 * b * t.sqrt() / v)
+ - (b * t).exp() * norm.cdf(-f1))
+ };
+
+ (call, put)
+ }
+ }
+ }
+
+ fn payoff(&self, option_type: TypeFlag, strike_type: LookbackStrike, path: &[f64]) -> f64 {
+ // let S_min = path.iter().copied().fold(path[0] /*f64::NAN*/, f64::min);
+ // let S_max = path.iter().copied().fold(path[0] /*f64::NAN*/, f64::max);
+
+ let S_min = *path.iter().min_by(|a, b| a.total_cmp(b)).unwrap();
+ let S_max = *path.iter().max_by(|a, b| a.total_cmp(b)).unwrap();
+
+ let S_T = path.last().unwrap();
+
+ match option_type {
+ TypeFlag::Call => match strike_type {
+ LookbackStrike::Fixed => f64::max(S_max - self.strike_price.unwrap(), 0.0),
+ LookbackStrike::Floating => f64::max(S_T - S_min, 0.0),
+ },
+ TypeFlag::Put => match strike_type {
+ LookbackStrike::Fixed => f64::max(self.strike_price.unwrap() - S_min, 0.0),
+ LookbackStrike::Floating => f64::max(S_max - S_T, 0.0),
+ },
+ }
+ }
+
+ /// Monte Carlo simulation of the lookback option price.
+ #[must_use]
+ pub fn price_simulated(&self, n_steps: usize, n_sims: usize, parallel: bool) -> (f64, f64) {
+ let x_0 = self.initial_price;
+ let r = self.risk_free_rate;
+ let q = self.dividend_yield;
+ let sigma = self.volatility;
+ let t_n = self.time_to_maturity;
+
+ // Adjust the drift term to account for the cost of carry.
+ let cost_of_carry = r - q;
+ let gbm = GeometricBrownianMotion::new(cost_of_carry, sigma);
+
+ let paths = gbm.euler_maruyama(x_0, 0.0, t_n, n_steps, n_sims, parallel);
+
+ let mut call_payoffs = Vec::with_capacity(n_sims);
+ let mut put_payoffs = Vec::with_capacity(n_sims);
+
+ match self.strike_type {
+ LookbackStrike::Fixed => {
+ for path in &paths.paths {
+ call_payoffs.push(Self::payoff(
+ self,
+ TypeFlag::Call,
+ LookbackStrike::Fixed,
+ path,
+ ));
+ put_payoffs.push(Self::payoff(
+ self,
+ TypeFlag::Put,
+ LookbackStrike::Fixed,
+ path,
+ ));
+ }
+ }
+ LookbackStrike::Floating => {
+ for path in &paths.paths {
+ call_payoffs.push(Self::payoff(
+ self,
+ TypeFlag::Call,
+ LookbackStrike::Floating,
+ path,
+ ));
+ put_payoffs.push(Self::payoff(
+ self,
+ TypeFlag::Put,
+ LookbackStrike::Floating,
+ path,
+ ));
+ }
+ }
+ }
+
+ (
+ // Discounted mean of the call and put payoffs.
+ (-r * t_n).exp() * call_payoffs.mean(),
+ (-r * t_n).exp() * put_payoffs.mean(),
+ )
+ }
+}
+
+// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+// LOOKBACK OPTION TESTS
+// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+#[cfg(test)]
+mod tests_lookback {
+ use super::*;
+ use crate::assert_approx_equal;
+
+ #[test]
+ fn test_lookback_floating() {
+ let lbo_floating = LookbackOption {
+ initial_price: 50.0,
+ s_max: 50.0,
+ s_min: 50.0,
+ time_to_maturity: 0.25,
+ risk_free_rate: 0.1,
+ dividend_yield: 0.0,
+ volatility: 0.4,
+ strike_price: None, // Floating strike has no strike price input.
+ strike_type: LookbackStrike::Floating,
+ };
+
+ let prices_mc = lbo_floating.price_simulated(500, 10000, true);
+ let prices_cf = lbo_floating.price_analytic();
+
+ // Analytic and closed form should match.
+ assert_approx_equal!(prices_mc.0, prices_cf.0, 0.5);
+ assert_approx_equal!(prices_mc.1, prices_cf.1, 0.5);
+
+ // Hull p.630: Floating-Strike Lookback Option Values
+ // Monte Carlo prices.
+ assert_approx_equal!(prices_mc.0, 8.04, 0.5);
+ assert_approx_equal!(prices_mc.1, 7.79, 0.5);
+ // Closed-form prices.
+ assert_approx_equal!(prices_cf.0, 8.04, 0.2);
+ assert_approx_equal!(prices_cf.1, 7.79, 0.2);
+
+ // The following example (which includes q = 0.06) from Haug's book
+ // does not work.
+ // The cost of carry seems to be the problem.
+ // Even though it is accounted for in the drift term of the GBM.
+ // let lbo_floating = LookbackOption {
+ // initial_price: 120.0,
+ // s_max: 100.0,
+ // s_min: 100.0,
+ // time_to_maturity: 0.5,
+ // risk_free_rate: 0.1,
+ // dividend_yield: 0.06,
+ // volatility: 0.3,
+ // strike_price: None, // Floating strike has no strike price input.
+ // strike_type: LookbackStrike::Floating,
+ // };
+ // Haug p.145: Floating-Strike Lookback Option Values
+ // Monte Carlo prices.
+ // assert_approx_equal!(prices_mc.0, 25.3533, 0.5);
+ // assert_approx_equal!(prices_mc.1, 1.0534, 0.5);
+ // Analytic prices.
+ // assert_approx_equal!(prices_cf.0, 25.3533, 0.4);
+ // assert_approx_equal!(prices_cf.1, 1.0534, 0.4);
+ }
+
+ #[test]
+ fn test_lookback_fixed() {
+ let lbo_fixed = LookbackOption {
+ initial_price: 100.0,
+ s_max: 100.0,
+ s_min: 100.0,
+ time_to_maturity: 1.0,
+ risk_free_rate: 0.1,
+ volatility: 0.1,
+ strike_price: Some(95.0),
+ dividend_yield: 0.0,
+ strike_type: LookbackStrike::Fixed,
+ };
+
+ let prices_mc = lbo_fixed.price_simulated(1000, 10000, true);
+ let prices_cf = lbo_fixed.price_analytic();
+
+ // Analytic and closed form should match.
+ assert_approx_equal!(prices_mc.0, prices_cf.0, 0.5);
+ assert_approx_equal!(prices_mc.1, prices_cf.1, 0.5);
+
+ // Haug p.145: Fixed-Strike Lookback Option Values
+ // Monte Carlo prices.
+ assert_approx_equal!(prices_mc.0, 18.3241, 0.5);
+ assert_approx_equal!(prices_mc.1, 1.0534, 0.5);
+
+ // Haug p.145: Fixed-Strike Lookback Option Values
+ // Analytic prices.
+ assert_approx_equal!(prices_cf.0, 18.3241, 0.0001);
+ assert_approx_equal!(prices_cf.1, 1.0534, 0.0001);
+ }
+
+ #[test]
+ fn test_lookback_payoff_fixed() {
+ let lbo_fixed = LookbackOption {
+ initial_price: 50.0,
+ s_max: 50.0,
+ s_min: 50.0,
+ time_to_maturity: 0.25,
+ risk_free_rate: 0.1,
+ dividend_yield: 0.0,
+ volatility: 0.4,
+ strike_price: Some(60.0), // Fixed strike has a strike price input.
+ strike_type: LookbackStrike::Fixed,
+ };
+
+ let path = vec![50.0, 55.0, 52.0, 58.0, 54.0];
+
+ let call_payoff = lbo_fixed.payoff(TypeFlag::Call, LookbackStrike::Fixed, &path);
+ let put_payoff = lbo_fixed.payoff(TypeFlag::Put, LookbackStrike::Fixed, &path);
+
+ // Payoff values
+ assert_approx_equal!(call_payoff, 0.0, 0.1); // call payoff = max(S_max - K, 0) = max(58 - 60, 0) = 0
+ assert_approx_equal!(put_payoff, 10.0, 0.1); // put payoff = max(K - S_min, 0) = max(60 - 50, 0) = 10
+ }
+
+ #[test]
+ fn test_lookback_payoff_floating() {
+ let lbo_floating = LookbackOption {
+ initial_price: 50.0,
+ s_max: 50.0,
+ s_min: 50.0,
+ time_to_maturity: 0.25,
+ risk_free_rate: 0.1,
+ dividend_yield: 0.0,
+ volatility: 0.4,
+ strike_price: None, // Floating strike has no strike price input.
+ strike_type: LookbackStrike::Floating,
+ };
+
+ let path = vec![50.0, 55.0, 52.0, 58.0, 54.0];
+
+ let call_payoff = lbo_floating.payoff(TypeFlag::Call, LookbackStrike::Floating, &path);
+ let put_payoff = lbo_floating.payoff(TypeFlag::Put, LookbackStrike::Floating, &path);
+
+ // Payoff values
+ assert_approx_equal!(call_payoff, 4.0, 0.1); // call payoff = max(S_T - S_min, 0) = max(54 - 50, 0) = 4
+ assert_approx_equal!(put_payoff, 4.0, 0.1); // put payoff = max(S_max - S_T, 0) = max(58 - 54, 0) = 4
+ }
+}
diff --git a/src/instruments/options/todo/power.rs b/src/instruments/options/todo/power.rs
new file mode 100644
index 00000000..9d2fd372
--- /dev/null
+++ b/src/instruments/options/todo/power.rs
@@ -0,0 +1,121 @@
+// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+// RustQuant: A Rust library for quantitative finance tools.
+// Copyright (C) 2023 https://github.com/avhz
+// Dual licensed under Apache 2.0 and MIT.
+// See:
+// - LICENSE-APACHE.md
+// - LICENSE-MIT.md
+// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+//! # Power Contracts
+//!
+//! Power contracts are options with the payoff: (S/K)^i
+//! where i is the (fixed) power of the contract.
+
+use crate::time::{today, DayCountConvention};
+use time::Date;
+
+// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+// STRUCTS, ENUMS, AND TRAITS
+// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+/// Power Option contract.
+#[allow(clippy::module_name_repetitions)]
+#[derive(Debug, Clone, Copy)]
+pub struct PowerOption {
+ /// `S` - Initial price of the underlying.
+ pub initial_price: f64,
+ /// `K` - Strike price.
+ pub strike_price: f64,
+ /// `i` - Power of the contract.
+ pub power: f64,
+
+ /// `r` - Risk-free rate parameter.
+ pub risk_free_rate: f64,
+ /// `b` - Cost of carry.
+ pub cost_of_carry: f64,
+ /// `v` - Volatility parameter.
+ pub volatility: f64,
+
+ /// `valuation_date` - Valuation date.
+ pub evaluation_date: Option,
+ /// `expiry_date` - Expiry date.
+ pub expiration_date: Date,
+}
+
+// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+// IMPLEMENTATIONS
+// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+impl PowerOption {
+ /// New Power Option contract.
+ #[allow(clippy::too_many_arguments)]
+ #[must_use]
+ pub fn new(
+ initial_price: f64,
+ strike_price: f64,
+ power: f64,
+ risk_free_rate: f64,
+ cost_of_carry: f64,
+ volatility: f64,
+ evaluation_date: Option,
+ expiration_date: Date,
+ ) -> Self {
+ Self {
+ initial_price,
+ strike_price,
+ power,
+ risk_free_rate,
+ cost_of_carry,
+ volatility,
+ evaluation_date,
+ expiration_date,
+ }
+ }
+
+ /// Power Option price.
+ #[must_use]
+ pub fn price(&self) -> f64 {
+ let S = self.initial_price;
+ let K = self.strike_price;
+ let r = self.risk_free_rate;
+ let v = self.volatility;
+ let b = self.cost_of_carry;
+ let i = self.power;
+
+ // Compute time to maturity.
+ let T = DayCountConvention::default().day_count_factor(
+ self.evaluation_date.unwrap_or(today()),
+ self.expiration_date,
+ );
+
+ (S / K).powf(i) * (((b - 0.5 * v.powi(2)) * i - r + 0.5 * (i * v).powi(2)) * T).exp()
+ }
+}
+
+// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+// TESTS
+// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+#[cfg(test)]
+mod tests_power_contract {
+ use super::*;
+ use crate::{assert_approx_equal, RUSTQUANT_EPSILON};
+ use time::Duration;
+
+ #[test]
+ fn test_power() {
+ let power_option = PowerOption {
+ initial_price: 400.,
+ strike_price: 450.,
+ power: 2.,
+ risk_free_rate: 0.08,
+ cost_of_carry: 0.06,
+ volatility: 0.25,
+ evaluation_date: None,
+ expiration_date: today() + Duration::days(182),
+ };
+
+ assert_approx_equal!(power_option.price(), 0.83144001309052, RUSTQUANT_EPSILON);
+ }
+}
diff --git a/src/instruments/options/vanilla.rs b/src/instruments/options/vanilla.rs
new file mode 100644
index 00000000..d773be7d
--- /dev/null
+++ b/src/instruments/options/vanilla.rs
@@ -0,0 +1,172 @@
+// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+// RustQuant: A Rust library for quantitative finance tools.
+// Copyright (C) 2023 https://github.com/avhz
+// Dual licensed under Apache 2.0 and MIT.
+// See:
+// - LICENSE-APACHE.md
+// - LICENSE-MIT.md
+// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+use super::{OptionContract, TypeFlag};
+use crate::{
+ instruments::Payoff,
+ pricer::MonteCarloPricer,
+ stochastics::{StochasticProcess, StochasticProcessConfig},
+};
+
+/// Vanilla option.
+#[derive(Debug, Clone)]
+pub struct VanillaOption {
+ /// The option contract.
+ pub contract: OptionContract,
+
+ /// Strike price of the option.
+ pub strike: f64,
+}
+
+impl Payoff for VanillaOption {
+ type Underlying = f64;
+
+ fn payoff(&self, underlying: Self::Underlying) -> f64 {
+ match self.contract.type_flag {
+ TypeFlag::Call => (underlying - self.strike).max(0.0),
+ TypeFlag::Put => (self.strike - underlying).max(0.0),
+ }
+ }
+}
+
+impl VanillaOption {
+ /// Create a new vanilla option.
+ pub fn new(contract: OptionContract, strike: f64) -> Self {
+ Self { contract, strike }
+ }
+}
+
+impl MonteCarloPricer for VanillaOption
+where
+ S: StochasticProcess,
+{
+ fn price_monte_carlo(&self, process: S, config: StochasticProcessConfig, rate: f64) -> f64 {
+ let out = process.euler_maruyama(&config);
+
+ let n = out.paths.len();
+
+ let df = (-rate * (config.t_n - config.t_0)).exp();
+
+ out.paths.iter().fold(0.0, |acc, path| {
+ let payoff = self.payoff(path.last().unwrap().clone());
+ acc + df * payoff
+ }) / n as f64
+ }
+}
+
+#[cfg(test)]
+mod test_vanilla_option_monte_carlo {
+ use time::macros::date;
+
+ use crate::{
+ instruments::{ExerciseFlag, OptionContractBuilder},
+ models::GeometricBrownianMotion,
+ };
+
+ use super::*;
+
+ #[test]
+ fn test_vanilla_option_monte_carlo() {
+ let underlying = 100.0;
+ let strike = 100.0;
+ let interest_rate = 0.05;
+ let time_to_maturity = 1.0;
+ let volatility = 0.2;
+
+ let contract = OptionContractBuilder::default()
+ .type_flag(TypeFlag::Call)
+ .exercise_flag(ExerciseFlag::European {
+ expiry: date!(2025 - 01 - 01),
+ })
+ .build()
+ .unwrap();
+
+ let option = VanillaOption::new(contract, strike);
+ let process = GeometricBrownianMotion::new(interest_rate, volatility);
+
+ let config =
+ StochasticProcessConfig::new(underlying, 0.0, time_to_maturity, 1000, 1000, false);
+
+ let price = option.price_monte_carlo(process, config, interest_rate);
+
+ println!("Price: {}", price);
+ }
+}
+
+// impl Instrument for VanillaOption {
+// fn price(&self) -> f64 {
+// 1.
+// }
+
+// fn error(&self) -> Option {
+// None
+// }
+
+// fn valuation_date(&self) -> Date {
+// todo!()
+// }
+
+// fn instrument_type(&self) -> &'static str {
+// todo!()
+// }
+// }
+
+// impl Priceable for VanillaOption
+// where
+// C: Calendar + Clone,
+// {
+// /// VanillaOption pricer implementation.
+// ///
+// /// This aksjdfoasj ofdjsod
+// fn pricer_impl(
+// &self,
+// context_data: &Option>,
+// market_data: &mut Option>,
+// // model: &Option,
+// // engine: &Option,
+// ) -> f64 {
+// // let cal = context_data.as_ref().unwrap().calendar.as_ref().unwrap();
+// let eval = context_data.as_ref().unwrap().evaluation_date.unwrap();
+
+// let s = market_data.as_ref().unwrap().underlying_price.unwrap();
+// let k = self.strike;
+// let t = match self.contract.exercise_flag {
+// ExerciseFlag::European { expiry } => expiry,
+// ExerciseFlag::American { .. } => todo!(),
+// ExerciseFlag::Bermudan { .. } => todo!(),
+// };
+// // let tau = DayCounter::day_count_factor(
+// // cal,
+// // eval,
+// // t,
+// // &context_data.as_ref().unwrap().day_count_convention.unwrap(),
+// // );
+// let r = market_data
+// .as_mut()
+// .unwrap()
+// .spot_curve
+// .as_mut()
+// .unwrap()
+// .get_rate(t);
+// let v = market_data.as_ref().unwrap().volatility.unwrap();
+
+// let bsm = BlackScholesMerton {
+// cost_of_carry: r,
+// underlying_price: s,
+// strike_price: k,
+// volatility: v,
+// risk_free_rate: r,
+// evaluation_date: Some(eval),
+// expiration_date: t,
+// option_type: self.contract.type_flag,
+// };
+
+// bsm.price()
+// }
+// }
diff --git a/src/instruments/payoff.rs b/src/instruments/payoff.rs
new file mode 100644
index 00000000..ca894d23
--- /dev/null
+++ b/src/instruments/payoff.rs
@@ -0,0 +1,17 @@
+// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+// RustQuant: A Rust library for quantitative finance tools.
+// Copyright (C) 2023 https://github.com/avhz
+// Dual licensed under Apache 2.0 and MIT.
+// See:
+// - LICENSE-APACHE.md
+// - LICENSE-MIT.md
+// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+/// Generic payoff trait for derivatives.
+pub trait Payoff {
+ /// Underlying input type for the payoff function.
+ type Underlying;
+
+ /// Payoff function for the derivative.
+ fn payoff(&self, underlying: Self::Underlying) -> f64;
+}
diff --git a/src/iso/iso_4217.rs b/src/iso/iso_4217.rs
index 1b1dbeb4..923372b7 100644
--- a/src/iso/iso_4217.rs
+++ b/src/iso/iso_4217.rs
@@ -22,7 +22,7 @@ use std::fmt::Formatter;
/// - First two letters are the ISO 3166-1 alpha-2 country code. e.g. US = United States
/// - Third letter is the first letter of the currency name. e.g. USD = United States Dollar
/// - The number is the ISO numeric code. e.g. 840 = USD
-#[derive(Debug, Clone, Copy, Eq)]
+#[derive(Debug, Clone, Copy, Eq, PartialEq, PartialOrd, Ord, Hash)]
#[allow(non_camel_case_types)]
pub struct ISO_4217 {
/// The ISO 4217 alphabetic code.
@@ -55,11 +55,11 @@ impl ISO_4217 {
}
}
-impl PartialEq for ISO_4217 {
- fn eq(&self, other: &Self) -> bool {
- self.alphabetic == other.alphabetic && self.numeric == other.numeric
- }
-}
+// impl PartialEq for ISO_4217 {
+// fn eq(&self, other: &Self) -> bool {
+// self.alphabetic == other.alphabetic && self.numeric == other.numeric
+// }
+// }
impl fmt::Display for ISO_4217 {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
diff --git a/src/lib.rs b/src/lib.rs
index 2f824f5f..e25c600e 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -71,6 +71,7 @@ pub mod math;
pub mod ml;
pub mod models;
pub mod portfolio;
+pub mod pricer;
pub mod stochastics;
pub mod time;
pub mod trading;
diff --git a/src/pricer/context_data.rs b/src/pricer/context_data.rs
new file mode 100644
index 00000000..815d0fd5
--- /dev/null
+++ b/src/pricer/context_data.rs
@@ -0,0 +1,57 @@
+// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+// RustQuant: A Rust library for quantitative finance tools.
+// Copyright (C) 2023 https://github.com/avhz
+// Dual licensed under Apache 2.0 and MIT.
+// See:
+// - LICENSE-APACHE.md
+// - LICENSE-MIT.md
+// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+//! Contextual (reference) data container.
+
+use crate::instruments::Currency;
+use crate::time::{
+ Calendar, DateGenerationConvention, DateRollingConvention, DayCountConvention, Frequency,
+ Schedule,
+};
+use ::time::Date;
+use derive_builder::Builder;
+
+/// Contextual (reference) data.
+#[derive(Builder, Clone)]
+pub struct ContextData
+where
+ C: Calendar,
+{
+ /// Calendar object.
+ #[builder(default)]
+ pub calendar: Option,
+
+ /// Evaluation date.
+ #[builder(default)]
+ pub evaluation_date: Option,
+
+ /// Currency.
+ #[builder(default)]
+ pub currency: Option,
+
+ /// Frequency.
+ #[builder(default)]
+ pub frequency: Option,
+
+ /// Schedule.
+ #[builder(default)]
+ pub schedule: Option,
+
+ /// Day count convention.
+ #[builder(default)]
+ pub day_count_convention: Option,
+
+ /// Date rolling convention.
+ #[builder(default)]
+ pub date_rolling_convention: Option,
+
+ /// Date generation convention.
+ #[builder(default)]
+ pub date_generation_convention: Option,
+}
diff --git a/src/pricer/market_data.rs b/src/pricer/market_data.rs
new file mode 100644
index 00000000..66987eed
--- /dev/null
+++ b/src/pricer/market_data.rs
@@ -0,0 +1,55 @@
+// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+// RustQuant: A Rust library for quantitative finance tools.
+// Copyright (C) 2023 https://github.com/avhz
+// Dual licensed under Apache 2.0 and MIT.
+// See:
+// - LICENSE-APACHE.md
+// - LICENSE-MIT.md
+// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+//! Market data container.
+
+use crate::data::{DiscountCurve, FlatCurve, ForwardCurve, SpotCurve};
+use crate::instruments::ExchangeRate;
+use crate::time::Calendar;
+use derive_builder::Builder;
+use time::Date;
+
+/// Market data.
+#[derive(Builder, Clone, Debug)]
+pub struct MarketData
+where
+ C: Calendar,
+{
+ /// Underlying price.
+ #[builder(default)]
+ pub underlying_price: Option,
+
+ /// Exchange rate.
+ #[builder(default)]
+ pub exchange_rate: Option,
+
+ /// Dividend yield.
+ #[builder(default)]
+ pub dividend_yield: Option,
+
+ /// Volatility (implied).
+ #[builder(default)]
+ pub volatility: Option,
+
+ /// Spot curve.
+ #[builder(default)]
+ pub spot_curve: Option>,
+
+ /// Discount curve.
+ #[builder(default)]
+ pub discount_curve: Option>,
+
+ /// Forward curve.
+ #[builder(default)]
+ pub forward_curve: Option>,
+
+ /// Flat curve.
+ #[builder(default)]
+ pub flat_curve: Option>,
+}
diff --git a/src/pricer/mod.rs b/src/pricer/mod.rs
new file mode 100644
index 00000000..9815cc72
--- /dev/null
+++ b/src/pricer/mod.rs
@@ -0,0 +1,93 @@
+// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+// RustQuant: A Rust library for quantitative finance tools.
+// Copyright (C) 2023 https://github.com/avhz
+// Dual licensed under Apache 2.0 and MIT.
+// See:
+// - LICENSE-APACHE.md
+// - LICENSE-MIT.md
+// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+//! Pricer module.
+
+// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+// MODULES
+// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+pub mod context_data;
+pub use context_data::*;
+
+pub mod market_data;
+pub use market_data::*;
+
+pub mod priceable;
+pub use priceable::*;
+
+pub mod monte_carlo_pricer;
+pub use monte_carlo_pricer::*;
+
+// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+// PRICER STRUCT
+// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+/// Pricing engine for instruments.
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+pub enum PricingMethod {
+ /// Analytic pricing method (e.g. closed-form solution).
+ Analytic,
+
+ /// Simulation pricing method (e.g. Monte Carlo).
+ Simulation,
+
+ /// Numerical method (e.g. PDE, lattice, finite differences).
+ Numerical,
+}
+
+// /// Pricer type.
+// /// This is the main struct for pricing financial instruments.
+// #[derive(Builder)]
+// pub struct Pricer
+// where
+// C: Calendar,
+// P: Priceable,
+// {
+// /// The instrument to be priced.
+// pub instrument: P,
+
+// /// Contextual (reference) data for the pricing.
+// pub context_data: Option>,
+
+// /// Market data for the pricing.
+// pub market_data: Option>,
+
+// /// Pricing engine.
+// pub method: Option,
+// // /// The model to be used to price the instrument.
+// // pub model: Option,
+// }
+
+// impl Pricer
+// where
+// C: Calendar,
+// P: Priceable,
+// {
+// /// Compute the Net Present Value (NPV) of the instrument.
+// pub fn npv(&mut self) -> f64 {
+// match self.method {
+// Some(PricingMethod::Analytic) => self.instrument.price_analytic_impl(),
+// Some(PricingMethod::Simulation) => self.instrument.price_simulation_impl(),
+// Some(PricingMethod::Numerical) => self.instrument.price_numerical_impl(),
+// None => {
+// // Default to analytic pricing.
+// self.instrument.price_analytic_impl()
+// }
+// }
+// }
+
+// // self.instrument.pricer_impl(
+// // &self.context_data,
+// // &mut self.market_data,
+// // // &self.model,
+// // // &self.engine,
+// // )
+// // }
+// }
diff --git a/src/pricer/monte_carlo_pricer.rs b/src/pricer/monte_carlo_pricer.rs
new file mode 100644
index 00000000..ba059845
--- /dev/null
+++ b/src/pricer/monte_carlo_pricer.rs
@@ -0,0 +1,21 @@
+// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+// RustQuant: A Rust library for quantitative finance tools.
+// Copyright (C) 2023 https://github.com/avhz
+// Dual licensed under Apache 2.0 and MIT.
+// See:
+// - LICENSE-APACHE.md
+// - LICENSE-MIT.md
+// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+//! Monte-Carlo pricer trait.
+
+use crate::stochastics::{StochasticProcess, StochasticProcessConfig};
+
+/// Monte-Carlo pricer trait.
+pub trait MonteCarloPricer
+where
+ S: StochasticProcess,
+{
+ /// Price the instrument using a Monte-Carlo method.
+ fn price_monte_carlo(&self, process: S, config: StochasticProcessConfig, rate: f64) -> f64;
+}
diff --git a/src/pricer/priceable.rs b/src/pricer/priceable.rs
new file mode 100644
index 00000000..4b829b76
--- /dev/null
+++ b/src/pricer/priceable.rs
@@ -0,0 +1,55 @@
+// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+// RustQuant: A Rust library for quantitative finance tools.
+// Copyright (C) 2023 https://github.com/avhz
+// Dual licensed under Apache 2.0 and MIT.
+// See:
+// - LICENSE-APACHE.md
+// - LICENSE-MIT.md
+// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+//! Priceable trait.
+
+use super::{ContextData, MarketData};
+use crate::{
+ instruments::{Instrument, Payoff},
+ stochastics::process::StochasticProcess,
+ time::Calendar,
+};
+
+/// Priceable trait.
+pub trait Priceable: Payoff + Instrument
+where
+ C: Calendar,
+ S: StochasticProcess,
+ P: Payoff,
+{
+ /// Function to prepare the data for the specific instrument.
+ fn prepare_data(&self) -> ();
+
+ /// Analytic pricer implementation.
+ fn price_analytic_impl(
+ &self,
+ context_data: &Option>,
+ market_data: &mut Option>,
+ model: &Option,
+ // engine: &Option,
+ ) -> f64;
+
+ /// Simulation pricer implementation.
+ fn price_simulation_impl(
+ &self,
+ context_data: &Option>,
+ market_data: &mut Option>,
+ model: &Option,
+ // engine: &Option,
+ ) -> f64;
+
+ /// Numerical pricer implementation.
+ fn price_numerical_impl(
+ &self,
+ context_data: &Option>,
+ market_data: &mut Option>,
+ model: &Option,
+ // engine: &Option,
+ ) -> f64;
+}
diff --git a/src/stochastics/arithmetic_brownian_motion.rs b/src/stochastics/arithmetic_brownian_motion.rs
index f837d855..1f7a1848 100644
--- a/src/stochastics/arithmetic_brownian_motion.rs
+++ b/src/stochastics/arithmetic_brownian_motion.rs
@@ -34,13 +34,13 @@ impl StochasticProcess for ArithmeticBrownianMotion {
#[cfg(test)]
mod tests_abm {
use super::*;
- use crate::{assert_approx_equal, math::*};
+ use crate::{assert_approx_equal, math::*, stochastics::StochasticProcessConfig};
#[test]
fn test_arithmetic_brownian_motion() {
let abm = ArithmeticBrownianMotion::new(0.05, 0.9);
-
- let output = abm.euler_maruyama(10.0, 0.0, 0.5, 125, 1000, false);
+ let config = StochasticProcessConfig::new(10.0, 0.0, 0.5, 125, 1000, false);
+ let output = abm.euler_maruyama(&config);
// let file1 = "./images/ABM1.png";
// plot_vector((&output.trajectories[0]).clone(), file1).unwrap();
diff --git a/src/stochastics/black_derman_toy.rs b/src/stochastics/black_derman_toy.rs
index 39c34c43..33b92323 100644
--- a/src/stochastics/black_derman_toy.rs
+++ b/src/stochastics/black_derman_toy.rs
@@ -42,7 +42,7 @@ pub(crate) fn diff(f: &(dyn Fn(f64) -> f64 + Send + Sync), t: f64) -> f64 {
#[cfg(test)]
mod tests_black_derman_toy {
use super::*;
- use crate::math::*;
+ use crate::{math::*, stochastics::StochasticProcessConfig};
// fn theta_t(_t: f64) -> f64 {
// 1.5
@@ -58,7 +58,8 @@ mod tests_black_derman_toy {
let hw = BlackDermanToy::new(sigma, theta);
- let output = hw.euler_maruyama(0.13, 0.0, 1.0, 100, 100, false);
+ let config = StochasticProcessConfig::new(0.13, 0.0, 1.0, 100, 1000, false);
+ let output = hw.euler_maruyama(&config);
// Test the distribution of the final values.
let X_T: Vec = output
@@ -78,8 +79,8 @@ mod tests_black_derman_toy {
let theta = 1.5;
let hw = BlackDermanToy::new(sigma, theta);
-
- let output = hw.euler_maruyama(0.13, 0.0, 1.0, 100, 1000, false);
+ let config = StochasticProcessConfig::new(0.13, 0.0, 1.0, 100, 1000, false);
+ let output = hw.euler_maruyama(&config);
// Test the distribution of the final values.
let X_T: Vec = output
diff --git a/src/stochastics/brownian_motion.rs b/src/stochastics/brownian_motion.rs
index 24664225..a0d10698 100644
--- a/src/stochastics/brownian_motion.rs
+++ b/src/stochastics/brownian_motion.rs
@@ -33,6 +33,7 @@ mod sde_tests {
// use std::time::Instant;
use super::*;
+ use crate::stochastics::StochasticProcessConfig;
use crate::{assert_approx_equal, math::*};
#[test]
@@ -61,7 +62,8 @@ mod sde_tests {
// }
// assert!(1 == 2);
- let output_serial = bm.euler_maruyama(0.0, 0.0, 0.5, 100, 1000, false);
+ let config = StochasticProcessConfig::new(0.0, 0.0, 0.5, 100, 1000, false);
+ let output_serial = bm.euler_maruyama(&config);
// let output_parallel = (&bm).euler_maruyama(10.0, 0.0, 0.5, 100, 10, true);
// let file1 = "./images/BM1.png";
diff --git a/src/stochastics/constant_elasticity_of_variance.rs b/src/stochastics/constant_elasticity_of_variance.rs
index 7afbc09f..3832e8c3 100644
--- a/src/stochastics/constant_elasticity_of_variance.rs
+++ b/src/stochastics/constant_elasticity_of_variance.rs
@@ -34,13 +34,13 @@ impl StochasticProcess for ConstantElasticityOfVariance {
#[cfg(test)]
mod tests_cev {
use super::*;
- use crate::math::*;
+ use crate::{math::*, stochastics::StochasticProcessConfig};
#[test]
fn test_cev_process() {
let cev = ConstantElasticityOfVariance::new(0.05, 0.9, 0.45);
-
- let output = cev.euler_maruyama(10.0, 0.0, 0.5, 100, 100, false);
+ let config = StochasticProcessConfig::new(10.0, 0.0, 0.5, 100, 100, false);
+ let output = cev.euler_maruyama(&config);
// Test the distribution of the final values.
let X_T: Vec = output
diff --git a/src/stochastics/cox_ingersoll_ross.rs b/src/stochastics/cox_ingersoll_ross.rs
index 43324909..275c35b8 100644
--- a/src/stochastics/cox_ingersoll_ross.rs
+++ b/src/stochastics/cox_ingersoll_ross.rs
@@ -32,13 +32,16 @@ impl StochasticProcess for CoxIngersollRoss {
#[cfg(test)]
mod tests_cir {
use super::*;
+ use crate::stochastics::StochasticProcessConfig;
use crate::{assert_approx_equal, math::*};
#[test]
fn test_cox_ingersoll_ross() {
let cir = CoxIngersollRoss::new(0.15, 0.45, 0.01);
- let output = cir.euler_maruyama(10.0, 0.0, 0.5, 100, 100, false);
+ let config = StochasticProcessConfig::new(10.0, 0.0, 0.5, 100, 100, false);
+
+ let output = cir.euler_maruyama(&config);
// Test the distribution of the final values.
let X_T: Vec = output
diff --git a/src/stochastics/extended_vasicek.rs b/src/stochastics/extended_vasicek.rs
index 7a0172af..d61821b7 100644
--- a/src/stochastics/extended_vasicek.rs
+++ b/src/stochastics/extended_vasicek.rs
@@ -31,6 +31,7 @@ impl StochasticProcess for ExtendedVasicek {
#[cfg(test)]
mod tests_extended_vasicek {
use super::*;
+ use crate::stochastics::StochasticProcessConfig;
use crate::{assert_approx_equal, math::*};
// fn alpha_t(_t: f64) -> f64 {
@@ -48,7 +49,9 @@ mod tests_extended_vasicek {
let ev = ExtendedVasicek::new(alpha, sigma, theta);
- let output = ev.euler_maruyama(10.0, 0.0, 1.0, 150, 1000, false);
+ let config = StochasticProcessConfig::new(10.0, 0.0, 1.0, 150, 1000, false);
+
+ let output = ev.euler_maruyama(&config);
// Test the distribution of the final values.
let X_T: Vec = output
diff --git a/src/stochastics/fractional_brownian_motion.rs b/src/stochastics/fractional_brownian_motion.rs
index a5d3a346..806e54da 100644
--- a/src/stochastics/fractional_brownian_motion.rs
+++ b/src/stochastics/fractional_brownian_motion.rs
@@ -21,6 +21,8 @@ use rand::{rngs::StdRng, SeedableRng};
use rand_distr::StandardNormal;
use rayon::prelude::*;
+use super::StochasticProcessConfig;
+
/// Method used to generate the Fractional Brownian Motion.
#[derive(Debug)]
pub enum FractionalProcessGeneratorMethod {
@@ -149,15 +151,9 @@ impl StochasticProcess for FractionalBrownianMotion {
None
}
- fn euler_maruyama(
- &self,
- x_0: f64,
- t_0: f64,
- t_n: f64,
- n_steps: usize,
- m_paths: usize,
- parallel: bool,
- ) -> Trajectories {
+ fn euler_maruyama(&self, config: &StochasticProcessConfig) -> Trajectories {
+ let (x_0, t_0, t_n, n_steps, m_paths, parallel) = config.unpack();
+
assert!(t_0 < t_n);
let dt: f64 = (t_n - t_0) / (n_steps as f64);
@@ -293,7 +289,8 @@ mod test_fractional_brownian_motion {
#[test]
fn test_brownian_motion() {
let fbm = FractionalBrownianMotion::new(0.7, FractionalProcessGeneratorMethod::FFT);
- let output_serial = fbm.euler_maruyama(0.0, 0.0, 0.5, 100, 1000, false);
+ let config = StochasticProcessConfig::new(0.0, 0.0, 0.5, 100, 1000, false);
+ let output_serial = fbm.euler_maruyama(&config);
// let output_parallel = (&bm).euler_maruyama(10.0, 0.0, 0.5, 100, 10, true);
// Test the distribution of the final values.
diff --git a/src/stochastics/fractional_cox_ingersoll_ross.rs b/src/stochastics/fractional_cox_ingersoll_ross.rs
index 64b519e5..98cf9429 100644
--- a/src/stochastics/fractional_cox_ingersoll_ross.rs
+++ b/src/stochastics/fractional_cox_ingersoll_ross.rs
@@ -10,6 +10,7 @@
use super::{
fractional_brownian_motion::FractionalProcessGeneratorMethod,
process::{StochasticProcess, Trajectories},
+ StochasticProcessConfig,
};
use crate::models::{
fractional_brownian_motion::FractionalBrownianMotion,
@@ -30,15 +31,9 @@ impl StochasticProcess for FractionalCoxIngersollRoss {
Some(0.0)
}
- fn euler_maruyama(
- &self,
- x_0: f64,
- t_0: f64,
- t_n: f64,
- n_steps: usize,
- m_paths: usize,
- parallel: bool,
- ) -> Trajectories {
+ fn euler_maruyama(&self, config: &StochasticProcessConfig) -> Trajectories {
+ let (t_0, x_0, t_n, n_steps, m_paths, parallel) = config.unpack();
+
let fgn = match self.method {
FractionalProcessGeneratorMethod::CHOLESKY => {
let fbm = FractionalBrownianMotion::new(
@@ -100,8 +95,10 @@ mod test_fractional_cir {
FractionalProcessGeneratorMethod::FFT,
);
+ let config = StochasticProcessConfig::new(10.0, 0.0, 0.5, 100, 100, false);
+
#[allow(dead_code)]
- let _output = fou.euler_maruyama(10.0, 0.0, 0.5, 100, 100, false);
+ let _output = fou.euler_maruyama(&config);
std::result::Result::Ok(())
}
diff --git a/src/stochastics/fractional_ornstein_uhlenbeck.rs b/src/stochastics/fractional_ornstein_uhlenbeck.rs
index 28194af6..49841bc2 100644
--- a/src/stochastics/fractional_ornstein_uhlenbeck.rs
+++ b/src/stochastics/fractional_ornstein_uhlenbeck.rs
@@ -17,6 +17,8 @@ use crate::{
};
use rayon::prelude::*;
+use super::StochasticProcessConfig;
+
impl StochasticProcess for FractionalOrnsteinUhlenbeck {
fn drift(&self, x: f64, t: f64) -> f64 {
self.theta.0(t) * (self.mu.0(t) - x)
@@ -31,15 +33,9 @@ impl StochasticProcess for FractionalOrnsteinUhlenbeck {
None
}
- fn euler_maruyama(
- &self,
- x_0: f64,
- t_0: f64,
- t_n: f64,
- n_steps: usize,
- m_paths: usize,
- parallel: bool,
- ) -> Trajectories {
+ fn euler_maruyama(&self, config: &StochasticProcessConfig) -> Trajectories {
+ let (x_0, t_0, t_n, n_steps, m_paths, parallel) = config.unpack();
+
let fgn = match self.method {
FractionalProcessGeneratorMethod::CHOLESKY => {
let fbm = FractionalBrownianMotion::new(
@@ -100,7 +96,8 @@ mod tests_fractional_ornstein_uhlenbeck {
FractionalProcessGeneratorMethod::FFT,
);
+ let config = StochasticProcessConfig::new(10.0, 0.0, 0.5, 100, 100, false);
#[allow(dead_code)]
- let _output = fou.euler_maruyama(10.0, 0.0, 0.5, 100, 100, false);
+ let _output = fou.euler_maruyama(&config);
}
}
diff --git a/src/stochastics/geometric_brownian_bridge.rs b/src/stochastics/geometric_brownian_bridge.rs
index 5fc8c6ce..b0cfbd55 100644
--- a/src/stochastics/geometric_brownian_bridge.rs
+++ b/src/stochastics/geometric_brownian_bridge.rs
@@ -34,6 +34,7 @@ impl StochasticProcess for GeometricBrownianBridge {
#[cfg(test)]
mod tests_gbm_bridge {
use super::*;
+ use crate::stochastics::StochasticProcessConfig;
use crate::{assert_approx_equal, math::*};
/// Test the Geometric Brownian Bridge process.
@@ -41,7 +42,9 @@ mod tests_gbm_bridge {
fn test_geometric_brownian_motion_bridge() {
let gbm = GeometricBrownianBridge::new(0.05, 0.9, 10.0, 0.5);
- let output = gbm.euler_maruyama(10.0, 0.0, 0.5, 125, 10000, false);
+ let config = StochasticProcessConfig::new(10.0, 0.0, 0.5, 125, 10000, false);
+
+ let output = gbm.euler_maruyama(&config);
// Test the distribution of the final values.
let X_T: Vec = output
diff --git a/src/stochastics/geometric_brownian_motion.rs b/src/stochastics/geometric_brownian_motion.rs
index e4f64f43..c7fea84e 100644
--- a/src/stochastics/geometric_brownian_motion.rs
+++ b/src/stochastics/geometric_brownian_motion.rs
@@ -36,13 +36,15 @@ impl StochasticProcess for GeometricBrownianMotion {
#[cfg(test)]
mod tests_gbm {
use super::*;
+ use crate::stochastics::StochasticProcessConfig;
use crate::{assert_approx_equal, math::*};
#[test]
fn test_geometric_brownian_motion() {
let gbm = GeometricBrownianMotion::new(0.05, 0.9);
- let output = gbm.euler_maruyama(10.0, 0.0, 0.5, 125, 10000, false);
+ let config = StochasticProcessConfig::new(10.0, 0.0, 0.5, 125, 10000, false);
+ let output = gbm.euler_maruyama(&config);
// Test the distribution of the final values.
let X_T: Vec = output
diff --git a/src/stochastics/ho_lee.rs b/src/stochastics/ho_lee.rs
index 55cb87dc..f05976b5 100644
--- a/src/stochastics/ho_lee.rs
+++ b/src/stochastics/ho_lee.rs
@@ -32,8 +32,8 @@ impl StochasticProcess for HoLee {
#[cfg(test)]
mod tests_ho_lee {
use super::*;
+ use crate::stochastics::StochasticProcessConfig;
use crate::{assert_approx_equal, math::*};
-
// Test a simple case where theta_t is constant
// Should add tests of simple analytically tractable case
// fn theta_t(_t: f64) -> f64 {
@@ -46,7 +46,8 @@ mod tests_ho_lee {
// X_0 = 10.0
// T = 1.0
- let output = hl.euler_maruyama(10.0, 0.0, 1.0, 125, 1000, false);
+ let config = StochasticProcessConfig::new(10.0, 0.0, 1.0, 125, 1000, false);
+ let output = hl.euler_maruyama(&config);
// Test the distribution of the final values.
let X_T: Vec = output
diff --git a/src/stochastics/hull_white.rs b/src/stochastics/hull_white.rs
index cd91bb1a..193a383d 100644
--- a/src/stochastics/hull_white.rs
+++ b/src/stochastics/hull_white.rs
@@ -30,8 +30,8 @@ impl StochasticProcess for HullWhite {
#[cfg(test)]
mod tests_hull_white {
use super::*;
+ use crate::stochastics::StochasticProcessConfig;
use crate::{assert_approx_equal, math::*};
-
// fn theta_t(_t: f64) -> f64 {
// 0.5
// }
@@ -43,8 +43,9 @@ mod tests_hull_white {
let sigma = 2.0;
let hw = HullWhite::new(alpha, sigma, theta);
+ let config = StochasticProcessConfig::new(10.0, 0.0, 1.0, 150, 1000, false);
- let output = hw.euler_maruyama(10.0, 0.0, 1.0, 150, 1000, false);
+ let output = hw.euler_maruyama(&config);
// Test the distribution of the final values.
let X_T: Vec = output
diff --git a/src/stochastics/merton_jump_diffusion.rs b/src/stochastics/merton_jump_diffusion.rs
index c1a0a01b..ef30369a 100644
--- a/src/stochastics/merton_jump_diffusion.rs
+++ b/src/stochastics/merton_jump_diffusion.rs
@@ -12,6 +12,8 @@ use crate::models::merton_jump_diffusion::MertonJumpDiffusion;
use crate::stochastics::process::{StochasticProcess, Trajectories};
use rand_distr::Distribution;
use rayon::prelude::*;
+
+use super::StochasticProcessConfig;
// use statrs::distribution::Normal;
impl StochasticProcess for MertonJumpDiffusion {
@@ -28,15 +30,9 @@ impl StochasticProcess for MertonJumpDiffusion {
self.gaussian.sample(1).unwrap().first().copied()
}
- fn euler_maruyama(
- &self,
- x_0: f64,
- t_0: f64,
- t_n: f64,
- n_steps: usize,
- m_paths: usize,
- parallel: bool,
- ) -> Trajectories {
+ fn euler_maruyama(&self, config: &StochasticProcessConfig) -> Trajectories {
+ let (x_0, t_0, t_n, n_steps, m_paths, parallel) = config.unpack();
+
assert!(t_0 < t_n);
let dt: f64 = (t_n - t_0) / (n_steps as f64);
@@ -92,8 +88,8 @@ mod tests_gbm_bridge {
#[test]
fn test_geometric_brownian_motion_bridge() {
let mjd = MertonJumpDiffusion::new(0.05, 0.9, 1.0, 0.0, 0.3);
-
- let output = mjd.euler_maruyama(10.0, 0.0, 0.5, 125, 10000, false);
+ let config = StochasticProcessConfig::new(10.0, 0.0, 0.5, 125, 10000, false);
+ let output = mjd.euler_maruyama(&config);
// Test the distribution of the final values.
let X_T: Vec = output
diff --git a/src/stochastics/ornstein_uhlenbeck.rs b/src/stochastics/ornstein_uhlenbeck.rs
index 6e403b1f..7004eaec 100644
--- a/src/stochastics/ornstein_uhlenbeck.rs
+++ b/src/stochastics/ornstein_uhlenbeck.rs
@@ -33,13 +33,14 @@ impl StochasticProcess for OrnsteinUhlenbeck {
#[cfg(test)]
mod tests_ornstein_uhlenbeck {
use super::*;
- use crate::{assert_approx_equal, math::*};
+ use crate::{assert_approx_equal, math::*, stochastics::StochasticProcessConfig};
#[test]
fn test_ornstein_uhlenbeck() {
let ou = OrnsteinUhlenbeck::new(0.15, 0.45, 0.01);
- let output = ou.euler_maruyama(10.0, 0.0, 0.5, 100, 100, false);
+ let config = StochasticProcessConfig::new(10.0, 0.0, 0.5, 100, 100, false);
+ let output = ou.euler_maruyama(&config);
// Test the distribution of the final values.
let X_T: Vec = output
diff --git a/src/stochastics/process.rs b/src/stochastics/process.rs
index d92eeca2..4a771f98 100644
--- a/src/stochastics/process.rs
+++ b/src/stochastics/process.rs
@@ -101,6 +101,59 @@ pub trait StochasticVolatilityProcess: Sync {
}
}
+/// Configuration parameters for simulating a stochastic process.
+pub struct StochasticProcessConfig {
+ /// Initial value of the process.
+ pub x_0: f64,
+
+ /// Initial time point.
+ pub t_0: f64,
+
+ /// Terminal time point.
+ pub t_n: f64,
+
+ /// Number of time steps between `t_0` and `t_n`.
+ pub n_steps: usize,
+
+ /// How many process trajectories to simulate.
+ pub m_paths: usize,
+
+ /// Run in parallel or not (recommended for > 1000 paths).
+ pub parallel: bool,
+}
+
+impl StochasticProcessConfig {
+ /// Create a new configuration for a stochastic process.
+ pub fn new(
+ x_0: f64,
+ t_0: f64,
+ t_n: f64,
+ n_steps: usize,
+ m_paths: usize,
+ parallel: bool,
+ ) -> Self {
+ Self {
+ x_0,
+ t_0,
+ t_n,
+ n_steps,
+ m_paths,
+ parallel,
+ }
+ }
+
+ pub(crate) fn unpack(&self) -> (f64, f64, f64, usize, usize, bool) {
+ (
+ self.x_0,
+ self.t_0,
+ self.t_n,
+ self.n_steps,
+ self.m_paths,
+ self.parallel,
+ )
+ }
+}
+
/// Trait to implement stochastic processes.
#[allow(clippy::module_name_repetitions)]
pub trait StochasticProcess: Sync {
@@ -122,15 +175,8 @@ pub trait StochasticProcess: Sync {
/// * `n_steps` - The number of time steps between `t_0` and `t_n`.
/// * `m_paths` - How many process trajectories to simulate.
/// * `parallel` - Run in parallel or not (recommended for > 1000 paths).
- fn euler_maruyama(
- &self,
- x_0: f64,
- t_0: f64,
- t_n: f64,
- n_steps: usize,
- m_paths: usize,
- parallel: bool,
- ) -> Trajectories {
+ fn euler_maruyama(&self, config: &StochasticProcessConfig) -> Trajectories {
+ let (x_0, t_0, t_n, n_steps, m_paths, parallel) = config.unpack();
assert!(t_0 < t_n);
let dt: f64 = (t_n - t_0) / (n_steps as f64);
@@ -224,20 +270,22 @@ pub trait StochasticProcess: Sync {
mod test_process {
use crate::models::geometric_brownian_motion::GeometricBrownianMotion;
use crate::stochastics::process::StochasticProcess;
+ use crate::stochastics::StochasticProcessConfig;
use std::time::Instant;
#[test]
fn test_euler_maruyama() {
let gbm = GeometricBrownianMotion::new(0.05, 0.9);
+ let config = StochasticProcessConfig::new(10.0, 0.0, 1.0, 125, 10000, false);
let start = Instant::now();
- gbm.euler_maruyama(10.0, 0.0, 1.0, 125, 10000, false);
+ gbm.euler_maruyama(&config);
let serial = start.elapsed();
println!("Serial: \t {:?}", serial);
let start = Instant::now();
- gbm.euler_maruyama(10.0, 0.0, 1.0, 125, 10000, true);
+ gbm.euler_maruyama(&config);
let parallel = start.elapsed();
println!("Parallel: \t {:?}", parallel);
diff --git a/src/time/date_generation.rs b/src/time/date_generation.rs
index 3d21b6c6..37605826 100644
--- a/src/time/date_generation.rs
+++ b/src/time/date_generation.rs
@@ -8,6 +8,7 @@
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/// Date generation conventions.
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum DateGenerationConvention {
/// Forward from the issue date.
Forward,
diff --git a/src/time/date_rolling.rs b/src/time/date_rolling.rs
index 91feeb04..fbdcaea4 100644
--- a/src/time/date_rolling.rs
+++ b/src/time/date_rolling.rs
@@ -22,7 +22,7 @@ use time::Date;
/// time such that it falls in a business day, according with the
/// same business calendar.
/// """
-#[derive(Clone, Copy)]
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum DateRollingConvention {
/// Actual: paid on the actual day, even if it is a non-business day.
Actual,
diff --git a/src/time/day_counting.rs b/src/time/day_counting.rs
index 6c606c8d..9c467723 100644
--- a/src/time/day_counting.rs
+++ b/src/time/day_counting.rs
@@ -34,7 +34,7 @@ use time::{util::is_leap_year, Date, Duration, Month};
/// payment dates, the seller is eligible to some fraction of the coupon amount.
/// """
#[allow(non_camel_case_types)]
-#[derive(Clone, Copy)]
+#[derive(Clone, Copy, Debug)]
pub enum DayCountConvention {
/// The '1/1' day count, which always returns a day count of 1.
One_One,
diff --git a/src/time/mod.rs b/src/time/mod.rs
index fb47c653..5e9379f1 100644
--- a/src/time/mod.rs
+++ b/src/time/mod.rs
@@ -50,3 +50,7 @@ pub use schedule::*;
/// Date generation rules.
pub mod date_generation;
pub use date_generation::*;
+
+/// Stub generation rules.
+pub mod stub_generation;
+pub use stub_generation::*;
diff --git a/src/time/schedule.rs b/src/time/schedule.rs
index 98fc0efb..c94d98c4 100644
--- a/src/time/schedule.rs
+++ b/src/time/schedule.rs
@@ -26,6 +26,7 @@ use time::Date;
///
/// The Schedule struct is used to represent these schedules,
/// and pricing methods should be implemented using date/time functionality.
+#[derive(Clone, Debug)]
pub struct Schedule {
/// The dates of the schedule.
pub dates: Vec,
diff --git a/src/time/stub_generation.rs b/src/time/stub_generation.rs
new file mode 100644
index 00000000..ba346048
--- /dev/null
+++ b/src/time/stub_generation.rs
@@ -0,0 +1,29 @@
+// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+// RustQuant: A Rust library for quantitative finance tools.
+// Copyright (C) 2022-2024 https://github.com/avhz
+// Dual licensed under Apache 2.0 and MIT.
+// See:
+// - LICENSE-APACHE.md
+// - LICENSE-MIT.md
+// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+/// Stub generation rules.
+pub enum StubGeneration {
+ /// No stubs.
+ None,
+
+ /// Short stub at the beginning.
+ ShortFront,
+
+ /// Short stub at the end.
+ ShortBack,
+
+ /// Long stub at the beginning.
+ LongFront,
+
+ /// Long stub at the end.
+ LongBack,
+
+ /// Front and back stubs.
+ Both,
+}
diff --git a/tests/stochastics.rs b/tests/stochastics.rs
new file mode 100644
index 00000000..e69de29b