Rust library for quantitative finance tools.
๐ฏ I want to hit a stable and legitimate v1.0.0
by the end of 2023, so any and all feedback, suggestions, or contributions are strongly welcomed!
Contact: [email protected]
Discord: https://discord.gg/tQcM77h8vr
Disclaimer: This is currently a free-time project and not a professional financial software library. Nothing in this library should be taken as financial advice, and I do not recommend you to use it for trading or making financial decisions.
See CHANGELOG.md for a full list of changes.
๐ Automatic Differentiation
Reverse (Adjoint) Mode Automatic Differentiation.
Currently only gradients can be computed. Suggestions on how to extend the functionality to Hessian matrices are definitely welcome.
Additionally, only functions
- Reverse (Adjoint) Mode
- Implementation via Operator and Function Overloading.
- Useful when number of outputs is smaller than number of inputs.
- i.e for functions
$f:\mathbb{R}^n \rightarrow \mathbb{R}^m$ , where$m \ll n$
- i.e for functions
- Forward (Tangent) Mode
- Implementation via Dual Numbers.
- Useful when number of outputs is larger than number of inputs.
- i.e. for functions
$f:\mathbb{R}^n \rightarrow \mathbb{R}^m$ , where$m \gg n$
- i.e. for functions
use RustQuant::autodiff::*;
fn main() {
// Create a new Graph to store the computations.
let g = Graph::new();
// Assign variables.
let x = g.var(69.);
let y = g.var(420.);
// Define a function.
let f = {
let a = x.powi(2);
let b = y.powi(2);
a + b + (x * y).exp()
};
// Accumulate the gradient.
let gradient = f.accumulate();
println!("Function = {}", f);
println!("Gradient = {:?}", gradient.wrt([x, y]));
}
You can also generate Graphviz (dot) code to visualize the computation graphs:
println!("{}", graphviz(&graph, &variables));
The computation graph from computing Black-Scholes Greeks is:
It is clearly a work in progress, but gives a general idea of how the computation graph is structured.
If you want to improve the visualization, please feel free to submit a PR!
Methods for reading and writing data from/to various sources (CSV, JSON, Parquet). Can also download data from Yahoo! Finance.
You can:
- Download data from Yahoo! Finance into a Polars
DataFrame
. - Compute returns on the
DataFrame
you just downloaded.
use RustQuant::data::*;
use time::macros::date;
fn main() {
// New YahooFinanceData instance.
// By default, date range is: 1970-01-01 to present.
let mut yfd = YahooFinanceData::new("AAPL".to_string());
// Can specify custom dates (optional).
yfd.set_start_date(time::macros::datetime!(2019 - 01 - 01 0:00 UTC));
yfd.set_end_date(time::macros::datetime!(2020 - 01 - 01 0:00 UTC));
// Download the historical data.
yfd.get_price_history();
// Compute the returns.
// Specify the type of returns to compute (Simple, Logarithmic, Absolute)
// You don't need to run .get_price_history() first, .compute_returns()
// will do it for you if necessary.
yfd.compute_returns(ReturnsType::Logarithmic);
println!("Apple's quotes: {:?}", yfd.price_history);
println!("Apple's returns: {:?}", yfd.returns);
}
Apple's quotes: Some(shape: (252, 7)
โโโโโโโโโโโโโโฌโโโโโโโโโโโโฌโโโโโโโโโโโโฌโโโโโโโโโโโโฌโโโโโโโโโโโโฌโโโโโโโโโโโโโฌโโโโโโโโโโโโ
โ date โ open โ high โ low โ close โ volume โ adjusted โ
โ --- โ --- โ --- โ --- โ --- โ --- โ --- โ
โ date โ f64 โ f64 โ f64 โ f64 โ f64 โ f64 โ
โโโโโโโโโโโโโโชโโโโโโโโโโโโชโโโโโโโโโโโโชโโโโโโโโโโโโชโโโโโโโโโโโโชโโโโโโโโโโโโโชโโโโโโโโโโโโก
โ 2019-01-02 โ 38.7225 โ 39.712502 โ 38.557499 โ 39.48 โ 1.481588e8 โ 37.994499 โ
โ 2019-01-03 โ 35.994999 โ 36.43 โ 35.5 โ 35.547501 โ 3.652488e8 โ 34.209969 โ
โ 2019-01-04 โ 36.1325 โ 37.137501 โ 35.950001 โ 37.064999 โ 2.344284e8 โ 35.670372 โ
โ 2019-01-07 โ 37.174999 โ 37.2075 โ 36.474998 โ 36.982498 โ 2.191112e8 โ 35.590965 โ
โ โฆ โ โฆ โ โฆ โ โฆ โ โฆ โ โฆ โ โฆ โ
โ 2019-12-26 โ 71.205002 โ 72.495003 โ 71.175003 โ 72.477501 โ 9.31212e7 โ 70.798401 โ
โ 2019-12-27 โ 72.779999 โ 73.4925 โ 72.029999 โ 72.449997 โ 1.46266e8 โ 70.771545 โ
โ 2019-12-30 โ 72.364998 โ 73.172501 โ 71.305 โ 72.879997 โ 1.441144e8 โ 71.191582 โ
โ 2019-12-31 โ 72.482498 โ 73.419998 โ 72.379997 โ 73.412498 โ 1.008056e8 โ 71.711739 โ
โโโโโโโโโโโโโโดโโโโโโโโโโโโดโโโโโโโโโโโโดโโโโโโโโโโโโดโโโโโโโโโโโโดโโโโโโโโโโโโโดโโโโโโโโโโโโ)
Apple's returns: Some(shape: (252, 7)
โโโโโโโโโโโโโโฌโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโ
โ date โ volume โ open_logarith โ high_logarith โ low_logarithm โ close_logari โ adjusted_log โ
โ --- โ --- โ mic โ mic โ ic โ thmic โ arithmic โ
โ date โ f64 โ --- โ --- โ --- โ --- โ --- โ
โ โ โ f64 โ f64 โ f64 โ f64 โ f64 โ
โโโโโโโโโโโโโโชโโโโโโโโโโโโโชโโโโโโโโโโโโโโโโชโโโโโโโโโโโโโโโโชโโโโโโโโโโโโโโโโชโโโโโโโโโโโโโโโชโโโโโโโโโโโโโโโก
โ 2019-01-02 โ 1.481588e8 โ null โ null โ null โ null โ null โ
โ 2019-01-03 โ 3.652488e8 โ -0.073041 โ -0.086273 โ -0.082618 โ -0.104924 โ -0.104925 โ
โ 2019-01-04 โ 2.344284e8 โ 0.003813 โ 0.019235 โ 0.012596 โ 0.041803 โ 0.041803 โ
โ 2019-01-07 โ 2.191112e8 โ 0.028444 โ 0.001883 โ 0.014498 โ -0.002228 โ -0.002229 โ
โ โฆ โ โฆ โ โฆ โ โฆ โ โฆ โ โฆ โ โฆ โ
โ 2019-12-26 โ 9.31212e7 โ 0.000457 โ 0.017709 โ 0.006272 โ 0.019646 โ 0.019646 โ
โ 2019-12-27 โ 1.46266e8 โ 0.021878 โ 0.013666 โ 0.011941 โ -0.00038 โ -0.00038 โ
โ 2019-12-30 โ 1.441144e8 โ -0.005718 โ -0.004364 โ -0.010116 โ 0.005918 โ 0.005918 โ
โ 2019-12-31 โ 1.008056e8 โ 0.001622 โ 0.003377 โ 0.014964 โ 0.00728 โ 0.00728 โ
โโโโโโโโโโโโโโดโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโ)
use RustQuant::data::*;
fn main() {
// New `Data` instance.
let mut data = Data::new(
format: DataFormat::CSV, // Can also be JSON or PARQUET.
path: String::from("./file/path/read.csv")
)
// Read from the given file.
data.read().unwrap();
// New path to write the data to.
data.path = String::from("./file/path/write.csv")
data.write().unwrap();
println!("{:?}", data.data)
}
PDFs, CDFs, MGFs, CFs, and other ditrubution related functions for common distributions.
Probability density/mass functions, distribution functions, characteristic functions, etc.
- Gaussian
- Bernoulli
- Binomial
- Poisson
- Uniform (discrete & continuous)
- Chi-Squared
- Gamma
- Exponential
Various implementations for instruments like `Bonds` and `Options`, and the pricing of them. Others coming in the future (swaps, futures, CDSs, etc).
- Prices:
- The Vasicek Model
- The Cox, Ingersoll, and Ross Model
- The HullโWhite (One-Factor) Model
- The Rendleman and Bartter Model
- The HoโLee Model
- The BlackโDermanโToy Model
- The BlackโKarasinski Model
- Duration
- Convexity
-
Closed-form price solutions:
- Heston Model
- Barrier
- European
- Greeks/Sensitivities
- Lookback
- Asian: Continuous Geometric Average
- Forward Start
- Basket
- Rainbow
- American
-
Lattice models:
- Binomial Tree (Cox-Ross-Rubinstein)
The stochastic process generators can be used to price path-dependent options via Monte-Carlo.
- Monte Carlo pricing:
- Lookback
- Asian
- Chooser
- Barrier
use RustQuant::options::*;
fn main() {
let VanillaOption = EuropeanOption {
initial_price: 100.0,
strike_price: 110.0,
risk_free_rate: 0.05,
volatility: 0.2,
dividend_rate: 0.02,
time_to_maturity: 0.5,
};
let prices = VanillaOption.price();
println!("Call price = {}", prices.0);
println!("Put price = {}", prices.1);
}
Fast Fourier Transform (FFT), numerical integration (double-exponential quadrature), optimisation/root-finding (gradient descent, Newton-Raphson), and risk-reward metrics.
- Gradient Descent
- Newton-Raphson
Note: the reason you need to specify the lifetimes and use the type Variable
is because the gradient descent optimiser uses the RustQuant::autodiff
module to compute the gradients. This is a slight inconvenience, but the speed-up is enormous when working with functions with many inputs (when compared with using finite-difference quotients).
use RustQuant::optimisation::GradientDescent;
// Define the objective function.
fn himmelblau<'v>(variables: &[Variable<'v>]) -> Variable<'v> {
let x = variables[0];
let y = variables[1];
((x.powf(2.0) + y - 11.0).powf(2.0) + (x + y.powf(2.0) - 7.0).powf(2.0))
}
fn main() {
// Create a new GradientDescent object with:
// - Step size: 0.005
// - Iterations: 10000
// - Tolerance: sqrt(machine epsilon)
let gd = GradientDescent::new(0.005, 10000, std::f64::EPSILON.sqrt() );
// Perform the optimisation with:
// - Initial guess (10.0, 10.0),
// - Verbose output.
let result = gd.optimize(&himmelblau, &vec![10.0, 10.0], true);
// Print the result.
println!("{:?}", result.minimizer);
}
- Numerical Integration (needed for Heston model, for example):
- Tanh-Sinh (double exponential) quadrature
- Composite Midpoint Rule
- Composite Trapezoidal Rule
- Composite Simpson's 3/8 Rule
use RustQuant::math::*;
fn main() {
// Define a function to integrate: e^(sin(x))
fn f(x: f64) -> f64 {
(x.sin()).exp()
}
// Integrate from 0 to 5.
let integral = integrate(f, 0.0, 5.0);
// ~ 7.18911925
println!("Integral = {}", integral);
}
- Risk-Reward Measures (Sharpe, Treynor, Sortino, etc)
Currently only linear regression is implemented (and working on logistic regression). More to come in the future.
- Linear (using QR or SVD decomposition)
- Logistic (via IRLS, adding MLE in the future).
Implementations for `Cashflows`, `Currencies`, and `Quotes`, and similar objects.
Cashflow
Currency
Money
Quote
Leg
๐ Stochastic Processes and Short Rate Models
Can generate Brownian Motion (standard, arithmetric and geometric) and various short-rate models (CIR, OU, Vasicek, Hull-White, etc).
The following is a list of stochastic processes that can be generated.
- Brownian Motions:
- Standard Brownian Motion
$dX(t) = dW(t)$
- Arithmetic Brownian Motion
$dX(t) = \mu dt + \sigma dW(t)$
- Geometric Brownian Motion
$dX(t) = \mu X(t) dt + \sigma X(t) dW(t)$
- Fractional Brownian Motion
- Standard Brownian Motion
- Cox-Ingersoll-Ross (1985)
$dX(t) = \left[ \theta - \alpha X(t) \right] dt + \sigma \sqrt{r_t} dW(t)$
- Ornstein-Uhlenbeck process
$dX(t) = \theta \left[ \mu - X(t) \right] dt + \sigma dW(t)$
- Ho-Lee (1986)
$dX(t) = \theta(t) dt + \sigma dW(t)$
- Hull-White (1990)
$dX(t) = \left[ \theta(t) - \alpha X(t) \right]dt + \sigma dW(t)$
- Extended Vasicek (1990)
$dX(t) = \left[ \theta(t) - \alpha(t) X(t) \right] dt + \sigma dW(t)$
- Black-Derman-Toy (1990)
$d\ln[X(t)] = \left[ \theta(t) + \frac{\sigma'(t)}{\sigma(t)}\ln[X(t)] \right]dt + \sigma_t dW(t)$
use RustQuant::stochastics::*;
fn main() {
// Create new GBM with mu and sigma.
let gbm = GeometricBrownianMotion::new(0.05, 0.9);
// Generate path using Euler-Maruyama scheme.
// Parameters: x_0, t_0, t_n, n, sims, parallel.
let output = (&gbm).euler_maruyama(10.0, 0.0, 0.5, 10, 1, false);
println!("GBM = {:?}", output.paths);
}
Time and date functionality. Mostly the `DayCounter` for pricing options and bonds.
DayCounter
Various helper functions and macros.
A collection of utility functions and macros.
- Plot a vector.
- Write vector to file.
- Cumulative sum of vector.
- Linearly spaced sequence.
-
assert_approx_equal!
Guides for using RustQuant.
See /examples for more details. Run them with:
cargo run --example automatic_differentiation
I would not recommend using RustQuant within any other libraries for some time, as it will most likely go through many breaking changes as I learn more Rust and settle on a decent structure for the library.
๐ I would greatly appreciate contributions so it can get to the v1.0.0
mark ASAP.
References and resources used for this project.
- John C. Hull - Options, Futures, and Other Derivatives
- Damiano Brigo & Fabio Mercurio - Interest Rate Models - Theory and Practice (With Smile, Inflation and Credit)
- Paul Glasserman - Monte Carlo Methods in Financial Engineering
- Andreas Griewank & Andrea Walther - Evaluating Derivatives - Principles and Techniques of Algorithmic Differentiation
- Steven E. Shreve - Stochastic Calculus for Finance II: Continuous-Time Models
- Espen Gaarder Haug - Option Pricing Formulas
- Antoine Savine - Modern Computational Finance: AAD and Parallel Simulations