adding par methods, benchmarks, github publishing

This commit is contained in:
Andy Pack 2025-02-18 18:45:00 +00:00
parent 6fbfee180f
commit 2628571717
Signed by: sarsoo
GPG Key ID: A55BA3536A5E0ED7
19 changed files with 593 additions and 264 deletions

@ -97,6 +97,7 @@ jobs:
name: Publish .NET
runs-on: ubuntu-latest
needs: [ buildNET ] # for ignoring bad builds
if: github.event_name == 'push' && github.ref == 'refs/heads/master'
steps:
- name: Checkout
uses: actions/checkout@v4
@ -124,11 +125,11 @@ jobs:
- name: Pack
working-directory: ./FinLib.NET/FinLib
run: dotnet pack
run: dotnet pack -p:PackageVersion=$(pushd ../../finlib > /dev/null && cargo pkgid | awk -F '[,#]' '{print $2}' && popd > /dev/null)
- name: Push
working-directory: ./FinLib.NET/FinLib
run: dotnet nuget push ./bin/Release/FinLib.NET.0.0.5.nupkg --api-key ${{ secrets.DOCKERHUB_TOKEN }} --source https://gitea.sheep-ghoul.ts.net/api/packages/sarsoo/nuget/index.json
run: dotnet nuget push ./bin/Release/FinLib.NET.$(pushd ../../finlib > /dev/null && cargo pkgid | awk -F '[,#]' '{print $2}' && popd > /dev/null).nupkg --api-key ${{ secrets.DOCKERHUB_TOKEN }} --source https://gitea.sheep-ghoul.ts.net/api/packages/sarsoo/nuget/index.json
buildWASM:
name: Build WASM

@ -99,6 +99,42 @@ jobs:
working-directory: ./FinLib.NET
run: dotnet test --no-restore
publishNET:
name: Publish .NET
runs-on: ubuntu-latest
needs: [ buildNET ] # for ignoring bad builds
if: github.event_name == 'push' && github.ref == 'refs/heads/master'
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install Rust
uses: actions-rs/toolchain@v1
with:
toolchain: stable
- name: Cargo Build
uses: actions-rs/cargo@v1
with:
command: build
- name: Setup .NET Core SDK 9.0.x
uses: actions/setup-dotnet@v3.0.3
with:
dotnet-version: 9.0.x
- name: Install Dependencies
working-directory: ./FinLib.NET
run: dotnet restore
- name: Pack
working-directory: ./FinLib.NET/FinLib
run: dotnet pack -p:PackageVersion=$(pushd ../../finlib > /dev/null && cargo pkgid | awk -F '[,#]' '{print $2}' && popd > /dev/null) -P:PackageId="Sarsoo.FinLib.NET"
- name: Push
working-directory: ./FinLib.NET/FinLib
run: dotnet nuget push ./bin/Release/Sarsoo.FinLib.NET.$(pushd ../../finlib > /dev/null && cargo pkgid | awk -F '[,#]' '{print $2}' && popd > /dev/null).nupkg --api-key ${{ secrets.NUGET_TOKEN }} --source https://api.nuget.org/v3/index.json
buildWASM:
name: Build WASM
runs-on: ubuntu-latest
@ -125,7 +161,7 @@ jobs:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-latest
needs: [ build, buildPy, buildWASM ] # for ignoring bad builds
needs: [ build, buildPy, buildWASM, buildNET ] # for ignoring bad builds
if: github.event_name == 'push' && github.ref == 'refs/heads/master'
steps:
- name: Checkout
@ -228,4 +264,33 @@ jobs:
uses: actions-rs/cargo@v1
with:
command: publish
args: --package finlib
args: --package finlib
publishPy:
runs-on: ubuntu-latest
name: Publish Python Library
needs: [ buildPy ] # for ignoring bad builds
if: github.event_name == 'push' && github.ref == 'refs/heads/master'
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install Rust
uses: actions-rs/toolchain@v1
with:
toolchain: stable
- name: Install Python 3
uses: actions/setup-python@v4
with:
python-version: ${{ env.python-version }}
- name: Install Maturin
working-directory: ./pyfinlib
run: python3 -m venv .venv && source .venv/bin/activate && pip3 install -r requirements.txt
- name: Publish
working-directory: ./pyfinlib
run: source .venv/bin/activate && maturin publish
env:
MATURIN_PYPI_TOKEN: ${{ secrets.PYPI_TOKEN }}

173
Cargo.lock generated

@ -11,6 +11,12 @@ dependencies = [
"memchr",
]
[[package]]
name = "anes"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299"
[[package]]
name = "anstream"
version = "0.6.18"
@ -106,6 +112,12 @@ version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]]
name = "cast"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5"
[[package]]
name = "cbindgen"
version = "0.28.0"
@ -140,6 +152,33 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "ciborium"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e"
dependencies = [
"ciborium-io",
"ciborium-ll",
"serde",
]
[[package]]
name = "ciborium-io"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757"
[[package]]
name = "ciborium-ll"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9"
dependencies = [
"ciborium-io",
"half",
]
[[package]]
name = "clap"
version = "4.5.29"
@ -194,6 +233,42 @@ dependencies = [
"web-sys",
]
[[package]]
name = "criterion"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2b12d017a929603d80db1831cd3a24082f8137ce19c69e6447f54f5fc8d692f"
dependencies = [
"anes",
"cast",
"ciborium",
"clap",
"criterion-plot",
"is-terminal",
"itertools 0.10.5",
"num-traits",
"once_cell",
"oorandom",
"plotters",
"rayon",
"regex",
"serde",
"serde_derive",
"serde_json",
"tinytemplate",
"walkdir",
]
[[package]]
name = "criterion-plot"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1"
dependencies = [
"cast",
"itertools 0.10.5",
]
[[package]]
name = "crossbeam-deque"
version = "0.8.6"
@ -219,6 +294,12 @@ version = "0.8.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
[[package]]
name = "crunchy"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43da5946c66ffcc7745f48db692ffbb10a83bfe0afd96235c5c2a4fb23994929"
[[package]]
name = "csbindgen"
version = "1.9.3"
@ -259,14 +340,16 @@ checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
[[package]]
name = "finlib"
version = "0.0.5"
version = "0.0.6"
dependencies = [
"criterion",
"getrandom 0.2.15",
"log",
"nalgebra",
"ndarray",
"ndarray-stats",
"pyo3",
"rand",
"rayon",
"statrs",
"wasm-bindgen",
@ -274,7 +357,7 @@ dependencies = [
[[package]]
name = "finlib-ffi"
version = "0.0.5"
version = "0.0.6"
dependencies = [
"cbindgen",
"csbindgen",
@ -283,7 +366,7 @@ dependencies = [
[[package]]
name = "finlib-wasm"
version = "0.0.5"
version = "0.0.6"
dependencies = [
"console_error_panic_hook",
"console_log",
@ -318,6 +401,16 @@ dependencies = [
"windows-targets",
]
[[package]]
name = "half"
version = "2.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6dd08c532ae367adf81c312a4580bc67f1d0fe8bc9c460520283f4c0ff277888"
dependencies = [
"cfg-if",
"crunchy",
]
[[package]]
name = "hashbrown"
version = "0.15.2"
@ -336,6 +429,12 @@ version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]]
name = "hermit-abi"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc"
[[package]]
name = "indexmap"
version = "2.7.1"
@ -352,12 +451,32 @@ version = "2.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b248f5224d1d606005e02c97f5aa4e88eeb230488bcc03bc9ca4d7991399f2b5"
[[package]]
name = "is-terminal"
version = "0.4.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e19b23d53f35ce9f56aebc7d1bb4e6ac1e9c0db7ac85c8d1760c04379edced37"
dependencies = [
"hermit-abi",
"libc",
"windows-sys",
]
[[package]]
name = "is_terminal_polyfill"
version = "1.70.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
[[package]]
name = "itertools"
version = "0.10.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473"
dependencies = [
"either",
]
[[package]]
name = "itertools"
version = "0.13.0"
@ -493,7 +612,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "17ebbe97acce52d06aebed4cd4a87c0941f4b2519b59b82b4feb5bd0ce003dfd"
dependencies = [
"indexmap",
"itertools",
"itertools 0.13.0",
"ndarray",
"noisy_float",
"num-integer",
@ -565,12 +684,46 @@ version = "1.20.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "945462a4b81e43c4e3ba96bd7b49d834c6f61198356aa858733bc4acf3cbe62e"
[[package]]
name = "oorandom"
version = "11.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b410bbe7e14ab526a0e86877eb47c6996a2bd7746f027ba551028c925390e4e9"
[[package]]
name = "paste"
version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
[[package]]
name = "plotters"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5aeb6f403d7a4911efb1e33402027fc44f29b5bf6def3effcc22d7bb75f2b747"
dependencies = [
"num-traits",
"plotters-backend",
"plotters-svg",
"wasm-bindgen",
"web-sys",
]
[[package]]
name = "plotters-backend"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df42e13c12958a16b3f7f4386b9ab1f3e7933914ecea48da7139435263a4172a"
[[package]]
name = "plotters-svg"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51bae2ac328883f7acdfea3d66a7c35751187f870bc81f94563733a154d7a670"
dependencies = [
"plotters-backend",
]
[[package]]
name = "portable-atomic"
version = "1.10.0"
@ -606,7 +759,7 @@ dependencies = [
[[package]]
name = "pyfinlib"
version = "0.0.5"
version = "0.0.6"
dependencies = [
"finlib",
"log",
@ -944,6 +1097,16 @@ dependencies = [
"windows-sys",
]
[[package]]
name = "tinytemplate"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc"
dependencies = [
"serde",
"serde_json",
]
[[package]]
name = "toml"
version = "0.8.20"

@ -14,7 +14,7 @@ default-members = [
]
[workspace.package]
version = "0.0.5"
version = "0.0.6"
authors = ["sarsoo <andy@sarsoo.xyz>"]
description = "Quant finance functions implemented in Rust"
edition = "2021"

@ -2,7 +2,6 @@
<PropertyGroup>
<PackageId>FinLib.NET</PackageId>
<Version>0.0.5</Version>
<Authors>sarsoo</Authors>
</PropertyGroup>

@ -1,18 +1,54 @@
# finlib
[![Build Binaries](https://github.com/Sarsoo/finlib/actions/workflows/build.yml/badge.svg?branch=master)](https://github.com/Sarsoo/finlib/actions/workflows/build.yml)
Some quantitative finance functionality written in Rust and consumable from many higher-level languages.
## Derivatives Pricing
- Options
- Black-Scholes
- Prices
- Greeks
## Risk
- Value-at-Risk
- Historical
- Variance-Covariance (Parametric)
- Single Asset
- Portfolio
# FFI
## Python
- C++
- FFI header files for C++ are generated automatically during build by [cbindgen](https://github.com/mozilla/cbindgen).
- .NET
- FFI wrapper code for C# tareting .NET Standard 2.0 is generated automatically using [csbindgen](https://github.com/Cysharp/csbindgen/).
- Python
- An adapter library for Python is generated usign [PyO3](https://github.com/PyO3/pyo3)
- WASM (Js)
- A Javascript library is generated using [wasm-bindgen](https://github.com/rustwasm/wasm-bindgen)
## [.NET](./finlib-ffi)
```bash
cargo build
cd FinLib.NET
dotnet build
```
## [Python](./pyfinlib)
```bash
cd pyfinlib
python -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
maturin develop
```
## WASM
## [WASM](finlib-wasm)
```
```bash
cd finlib-wasm
wasm-pack build
```

@ -13,16 +13,27 @@ license.workspace = true
[dependencies]
wasm-bindgen = { workspace = true, optional = true }
pyo3 = { workspace = true, optional = true }
rayon = { workspace = true, optional = true }
rayon = { workspace = true }
ndarray = { workspace = true }
ndarray-stats = { workspace = true }
nalgebra = { workspace = true }
statrs = { workspace = true }
log = { workspace = true }
getrandom = "~0"
rand = "0.8.5"
[dev-dependencies]
criterion = "0.5.1"
[[bench]]
name = "rayon_roc"
harness = false
[[bench]]
name = "rayon_options"
harness = false
[features]
py = ["dep:pyo3"]
wasm = ["getrandom/js", "dep:wasm-bindgen"]
parallel = ["dep:rayon"]
ffi = []

@ -0,0 +1,43 @@
use criterion::{criterion_group, criterion_main, AxisScale, BatchSize, BenchmarkId, Criterion, PlotConfiguration, Throughput};
use finlib::options::blackscholes::option_surface::OptionSurface;
use finlib::options::blackscholes::{generate_options, par_generate_options};
pub fn bench_generate_options(c: &mut Criterion) {
let mut group = c.benchmark_group("Options::generate_options");
let plot_config = PlotConfiguration::default()
.summary_scale(AxisScale::Logarithmic);
group.plot_config(plot_config);
for i in [1, 10, 100, 1000].into_iter() {
let surface = OptionSurface::from(
0 .. 10,
(100., 200.),
0 .. i,
(100., 200.),
0 .. 5,
(0.25, 0.50),
0 .. 10,
(0.05, 0.08),
0 .. 1,
(0.01, 0.02),
0 .. 10,
(30./365.25, 30./365.25),
);
let variables = surface.walk();
group.throughput(Throughput::Elements(variables.len() as u64));
group.bench_function(BenchmarkId::new("Sequential", variables.len()), |b| {
b.iter_batched(|| variables.clone(), |p| { generate_options(&p); }, BatchSize::SmallInput)
});
group.bench_function(BenchmarkId::new("Parallel", variables.len()), |b| {
b.iter_batched(|| variables.clone(), |p| { par_generate_options(&p); }, BatchSize::SmallInput)
});
}
group.finish();
}
criterion_group!(benches, bench_generate_options);
criterion_main!(benches);

@ -0,0 +1,65 @@
use criterion::{criterion_group, criterion_main, AxisScale, BatchSize, BenchmarkId, Criterion, PlotConfiguration, Throughput};
use finlib::risk::portfolio::{Portfolio, PortfolioAsset};
use rand::Rng;
pub fn bench_apply_rates_of_change_values(c: &mut Criterion) {
let mut group = c.benchmark_group("Portfolio::apply_rates_of_change/values");
let mut rng = rand::thread_rng();
let plot_config = PlotConfiguration::default()
.summary_scale(AxisScale::Logarithmic);
group.plot_config(plot_config);
for i in [10, 100, 1000, 10_000, 100_000, 1_000_000].into_iter() {
let portfolio = Portfolio::from(vec![
PortfolioAsset::new(0.1, "a".to_string(), (0 .. i).map(|_| rng.gen::<f64>()).collect()),
PortfolioAsset::new(0.1, "a".to_string(), (0 .. i).map(|_| rng.gen::<f64>()).collect()),
PortfolioAsset::new(0.1, "a".to_string(), (0 .. i).map(|_| rng.gen::<f64>()).collect()),
PortfolioAsset::new(0.1, "a".to_string(), (0 .. i).map(|_| rng.gen::<f64>()).collect()),
PortfolioAsset::new(0.1, "a".to_string(), (0 .. i).map(|_| rng.gen::<f64>()).collect()),
PortfolioAsset::new(0.1, "a".to_string(), (0 .. i).map(|_| rng.gen::<f64>()).collect()),
PortfolioAsset::new(0.1, "a".to_string(), (0 .. i).map(|_| rng.gen::<f64>()).collect()),
PortfolioAsset::new(0.1, "a".to_string(), (0 .. i).map(|_| rng.gen::<f64>()).collect()),
PortfolioAsset::new(0.1, "a".to_string(), (0 .. i).map(|_| rng.gen::<f64>()).collect()),
PortfolioAsset::new(0.1, "a".to_string(), (0 .. i).map(|_| rng.gen::<f64>()).collect())
]);
group.throughput(Throughput::Elements((i * 10) as u64));
group.bench_function(BenchmarkId::new("Sequential", i), |b| {
b.iter_batched(|| portfolio.clone(), |mut p| { p.apply_rates_of_change(); }, BatchSize::SmallInput)
});
group.bench_function(BenchmarkId::new("Parallel", i), |b| {
b.iter_batched(|| portfolio.clone(), |mut p| { p.par_apply_rates_of_change(); }, BatchSize::SmallInput)
});
}
group.finish();
}
pub fn bench_apply_rates_of_change_assets(c: &mut Criterion) {
let mut group = c.benchmark_group("Portfolio::apply_rates_of_change/assets");
let mut rng = rand::thread_rng();
let plot_config = PlotConfiguration::default()
.summary_scale(AxisScale::Logarithmic);
group.plot_config(plot_config);
for i in [10, 100, 1000, 10_000].into_iter() {
let portfolio = Portfolio::from((0 .. i).map(|_| {
PortfolioAsset::new(0.1, "a".to_string(), (0 .. 10000).map(|_| rng.gen::<f64>()).collect())
}).collect());
group.throughput(Throughput::Elements(i as u64));
group.bench_function(BenchmarkId::new("Sequential", i), |b| {
b.iter_batched(|| portfolio.clone(), |mut p| { p.apply_rates_of_change(); }, BatchSize::SmallInput)
});
group.bench_function(BenchmarkId::new("Parallel", i), |b| {
b.iter_batched(|| portfolio.clone(), |mut p| { p.par_apply_rates_of_change(); }, BatchSize::SmallInput)
});
}
group.finish();
}
criterion_group!(benches, bench_apply_rates_of_change_values, bench_apply_rates_of_change_assets);
criterion_main!(benches);

@ -5,30 +5,4 @@ pub mod stats;
pub mod util;
pub mod risk;
pub mod ffi;
pub mod options;
#[cfg(feature = "parallel")]
use rayon::prelude::*;
#[macro_export]
macro_rules! gated_iter {
($x:expr) => {
{
$x.iter()
}
};
}
#[macro_export]
macro_rules! gated_iter_mut {
($x:expr) => {
{
if cfg!(feature = "parallel") {
$x.par_iter_mut()
}
else {
$x.iter_mut()
}
}
};
}
pub mod options;

@ -1,174 +0,0 @@
use core::ops::Range;
use std::sync::{Arc, Mutex};
#[cfg(feature = "parallel")]
use rayon::prelude::*;
use crate::options::blackscholes::OptionVariables;
pub struct OptionSurface {
underlying_price: Range<isize>,
underlying_price_bounds: (f64, f64),
strike_price: Range<isize>,
strike_price_bounds: (f64, f64),
volatility: Range<isize>,
volatility_bounds: (f64, f64),
risk_free_interest_rate: Range<isize>,
risk_free_interest_rate_bounds: (f64, f64),
dividend: Range<isize>,
dividend_bounds: (f64, f64),
time_to_expiration: Range<isize>,
time_to_expiration_bounds: (f64, f64)
}
impl OptionSurface {
pub fn from(underlying_price: Range<isize>,
underlying_price_bounds: (f64, f64),
strike_price: Range<isize>,
strike_price_bounds: (f64, f64),
volatility: Range<isize>,
volatility_bounds: (f64, f64),
risk_free_interest_rate: Range<isize>,
risk_free_interest_rate_bounds: (f64, f64),
dividend: Range<isize>,
dividend_bounds: (f64, f64),
time_to_expiration: Range<isize>,
time_to_expiration_bounds: (f64, f64)) -> Self {
Self {
underlying_price,
underlying_price_bounds,
strike_price,
strike_price_bounds,
volatility,
volatility_bounds,
risk_free_interest_rate,
risk_free_interest_rate_bounds,
dividend,
dividend_bounds,
time_to_expiration,
time_to_expiration_bounds,
}
}
pub fn walk(self) -> Vec<OptionVariables> {
// #[cfg(feature = "parallel")]
// {
// let vec: Arc<Mutex<Vec<OptionVariables>>> = Arc::new(Mutex::new(vec![]));
// self.underlying_price
// .into_par_iter()
// .for_each(|p| {
// self.strike_price
// .clone()
// .into_par_iter()
// .for_each(|s| {
// self.volatility
// .clone()
// .into_par_iter()
// .for_each(|v| {
// self.risk_free_interest_rate
// .clone()
// .into_par_iter()
// .for_each(|i| {
// self.dividend
// .clone()
// .into_par_iter()
// .for_each(|d| {
// self.time_to_expiration
// .clone()
// .into_par_iter()
// .for_each(|t| {
// let mut m = vec.clone();
// let mut guard = m.lock().unwrap();
// guard.push(OptionVariables::from(
// self.underlying_price_bounds.0 + (self.underlying_price_bounds.1 - self.underlying_price_bounds.0) * p as f64,
// self.strike_price_bounds.0 + (self.strike_price_bounds.1 - self.strike_price_bounds.0) * s as f64,
// self.volatility_bounds.0 + (self.volatility_bounds.1 - self.volatility_bounds.0) * v as f64,
// self.risk_free_interest_rate_bounds.0 + (self.risk_free_interest_rate_bounds.1 - self.risk_free_interest_rate_bounds.0) * i as f64,
// self.dividend_bounds.0 + (self.dividend_bounds.1 - self.dividend_bounds.0) * d as f64,
// self.time_to_expiration_bounds.0 + (self.time_to_expiration_bounds.1 - self.time_to_expiration_bounds.0) * t as f64
// ));
// })
// })
// })
// })
// })
// });
//
// Arc::try_unwrap(vec).unwrap().into_inner().unwrap()
// }
// #[cfg(not(feature = "parallel"))]
{
let mut vec: Vec<OptionVariables> = Vec::with_capacity(
self.underlying_price.len()
* self.strike_price.len()
* self.volatility.len()
* self.risk_free_interest_rate.len()
* self.dividend.len()
* self.time_to_expiration.len()
);
for p in self.underlying_price {
for s in self.strike_price.clone() {
for v in self.volatility.clone() {
for i in self.risk_free_interest_rate.clone() {
for d in self.dividend.clone() {
for t in self.time_to_expiration.clone() {
let v = OptionVariables::from(
self.underlying_price_bounds.0 + (self.underlying_price_bounds.1 - self.underlying_price_bounds.0) * p as f64,
self.strike_price_bounds.0 + (self.strike_price_bounds.1 - self.strike_price_bounds.0) * s as f64,
self.volatility_bounds.0 + (self.volatility_bounds.1 - self.volatility_bounds.0) * v as f64,
self.risk_free_interest_rate_bounds.0 + (self.risk_free_interest_rate_bounds.1 - self.risk_free_interest_rate_bounds.0) * i as f64,
self.dividend_bounds.0 + (self.dividend_bounds.1 - self.dividend_bounds.0) * d as f64,
self.time_to_expiration_bounds.0 + (self.time_to_expiration_bounds.1 - self.time_to_expiration_bounds.0) * t as f64
);
vec.push(v);
}
}
}
}
}
}
vec
}
}
}
#[cfg(test)]
mod tests {
use crate::options::blackscholes::{CallOption, Option, PutOption};
use super::*;
#[test]
fn walk_test() {
let w = OptionSurface::from(
(0 .. 50),
(100., 200.),
(0 .. 50),
(100., 200.),
(0 .. 5),
(0.25, 0.50),
(0 .. 10),
(0.05, 0.08),
(0 .. 1),
(0.01, 0.02),
(0 .. 10),
(30./365.25, 30./365.25),
);
let a = w.walk();
let options = a
.iter()
.map(|v| {
let mut call = v.call();
let mut put = v.put();
call.calc_greeks();
put.calc_greeks();
(call, put)
})
.collect::<Vec<(CallOption, PutOption)>>();
let a1 = a.first();
}
}

@ -0,0 +1,32 @@
use rayon::prelude::*;
use crate::options::blackscholes::{CallOption, Option, OptionVariables, PutOption};
pub fn generate_options(option_variables: &Vec<OptionVariables>) -> Vec<(CallOption, PutOption)> {
option_variables
.iter()
.map(|v| {
let mut call = v.call();
let mut put = v.put();
call.calc_greeks();
put.calc_greeks();
(call, put)
})
.collect::<Vec<(CallOption, PutOption)>>()
}
pub fn par_generate_options(option_variables: &Vec<OptionVariables>) -> Vec<(CallOption, PutOption)> {
option_variables
.par_iter()
.map(|v| {
let mut call = v.call();
let mut put = v.put();
call.calc_greeks();
put.calc_greeks();
(call, put)
})
.collect::<Vec<(CallOption, PutOption)>>()
}

@ -1,4 +1,6 @@
mod OptionSurface;
pub mod option_surface;
pub mod generate;
pub use generate::*;
use statrs::distribution::{Continuous, ContinuousCDF, Normal};
#[cfg(feature = "wasm")]

@ -0,0 +1,113 @@
use core::ops::Range;
use std::sync::{Arc, Mutex};
use crate::options::blackscholes::{CallOption, Option, OptionVariables, PutOption};
pub struct OptionSurface {
underlying_price: Range<isize>,
underlying_price_bounds: (f64, f64),
strike_price: Range<isize>,
strike_price_bounds: (f64, f64),
volatility: Range<isize>,
volatility_bounds: (f64, f64),
risk_free_interest_rate: Range<isize>,
risk_free_interest_rate_bounds: (f64, f64),
dividend: Range<isize>,
dividend_bounds: (f64, f64),
time_to_expiration: Range<isize>,
time_to_expiration_bounds: (f64, f64)
}
impl OptionSurface {
pub fn from(underlying_price: Range<isize>,
underlying_price_bounds: (f64, f64),
strike_price: Range<isize>,
strike_price_bounds: (f64, f64),
volatility: Range<isize>,
volatility_bounds: (f64, f64),
risk_free_interest_rate: Range<isize>,
risk_free_interest_rate_bounds: (f64, f64),
dividend: Range<isize>,
dividend_bounds: (f64, f64),
time_to_expiration: Range<isize>,
time_to_expiration_bounds: (f64, f64)) -> Self {
Self {
underlying_price,
underlying_price_bounds,
strike_price,
strike_price_bounds,
volatility,
volatility_bounds,
risk_free_interest_rate,
risk_free_interest_rate_bounds,
dividend,
dividend_bounds,
time_to_expiration,
time_to_expiration_bounds,
}
}
pub fn walk(self) -> Vec<OptionVariables> {
let mut vec: Vec<OptionVariables> = Vec::with_capacity(
self.underlying_price.len()
* self.strike_price.len()
* self.volatility.len()
* self.risk_free_interest_rate.len()
* self.dividend.len()
* self.time_to_expiration.len()
);
for p in self.underlying_price {
for s in self.strike_price.clone() {
for v in self.volatility.clone() {
for i in self.risk_free_interest_rate.clone() {
for d in self.dividend.clone() {
for t in self.time_to_expiration.clone() {
let v = OptionVariables::from(
self.underlying_price_bounds.0 + (self.underlying_price_bounds.1 - self.underlying_price_bounds.0) * p as f64,
self.strike_price_bounds.0 + (self.strike_price_bounds.1 - self.strike_price_bounds.0) * s as f64,
self.volatility_bounds.0 + (self.volatility_bounds.1 - self.volatility_bounds.0) * v as f64,
self.risk_free_interest_rate_bounds.0 + (self.risk_free_interest_rate_bounds.1 - self.risk_free_interest_rate_bounds.0) * i as f64,
self.dividend_bounds.0 + (self.dividend_bounds.1 - self.dividend_bounds.0) * d as f64,
self.time_to_expiration_bounds.0 + (self.time_to_expiration_bounds.1 - self.time_to_expiration_bounds.0) * t as f64
);
vec.push(v);
}
}
}
}
}
}
vec
}
}
#[cfg(test)]
mod tests {
use crate::options::blackscholes::{generate_options, CallOption, Option, PutOption};
use super::*;
#[test]
fn walk_test() {
let w = OptionSurface::from(
(0 .. 50),
(100., 200.),
(0 .. 50),
(100., 200.),
(0 .. 5),
(0.25, 0.50),
(0 .. 10),
(0.05, 0.08),
(0 .. 1),
(0.01, 0.02),
(0 .. 10),
(30./365.25, 30./365.25),
);
let a = w.walk();
let options = generate_options(&a);
let a1 = a.first();
}
}

@ -5,7 +5,6 @@ use ndarray_stats::CorrelationExt;
use wasm_bindgen::prelude::*;
#[cfg(feature = "py")]
use pyo3::prelude::*;
#[cfg(feature = "parallel")]
use rayon::prelude::*;
use statrs::distribution::{ContinuousCDF, Normal};
use crate::risk::forecast::{mean_investment, std_dev_investment};
@ -96,18 +95,16 @@ impl Portfolio {
/// Convert a portfolio of assets with absolute values to the percentage change in values
pub fn apply_rates_of_change(&mut self) {
#[cfg(feature = "parallel")]
{
self.assets.par_iter_mut().for_each(|asset| {
asset.apply_rates_of_change();
});
}
#[cfg(not(feature = "parallel"))]
{
self.assets.iter_mut().for_each(|asset| {
asset.apply_rates_of_change();
});
}
self.assets.iter_mut().for_each(|asset| {
asset.apply_rates_of_change();
});
}
#[deprecated(note = "a lot slower than the sequential method, sans par prefix")]
pub fn par_apply_rates_of_change(&mut self) {
self.assets.par_iter_mut().for_each(|asset| {
asset.apply_rates_of_change();
});
}
/// Do all the assets in the portfolio have the same number of values (required to perform matrix operations)
@ -155,29 +152,33 @@ impl Portfolio {
let column_count = self.assets.len();
let row_count = self.assets[0].values.len();
#[cfg(feature = "parallel")]
{
let matrix = Array2::from_shape_vec((column_count, row_count),
self.assets
.par_iter()
.map(|a| a.values.clone())
.flatten()
.collect::<Vec<f64>>()
).unwrap();
Some(matrix.into_owned())
}
#[cfg(not(feature = "parallel"))]
{
let matrix = Array2::from_shape_vec((column_count, row_count),
self.assets
.iter()
.map(|a| a.values.clone())
.flatten()
.collect::<Vec<f64>>()
).unwrap();
Some(matrix.into_owned())
let matrix = Array2::from_shape_vec((column_count, row_count),
self.assets
.iter()
.map(|a| a.values.clone())
.flatten()
.collect::<Vec<f64>>()
).unwrap();
Some(matrix.into_owned())
}
/// Format the asset values in the portfolio as a matrix such that statistical operations can be applied to it
pub fn par_get_matrix(&self) -> Option<Array2<f64>> {
if self.assets.is_empty() || !self.valid_sizes() {
return None;
}
let column_count = self.assets.len();
let row_count = self.assets[0].values.len();
let matrix = Array2::from_shape_vec((column_count, row_count),
self.assets
.par_iter()
.map(|a| a.values.clone())
.flatten()
.collect::<Vec<f64>>()
).unwrap();
Some(matrix.into_owned())
}
/// Calculate the mean and the standard deviation of a portfolio, taking into account the relative weights and covariance of the portfolio's assets

@ -1,6 +1,4 @@
use crate::util::roc::rates_of_change;
#[cfg(feature = "parallel")]
use rayon::prelude::*;
// https://www.simtrade.fr/blog_simtrade/historical-method-var-calculation/
@ -8,7 +6,6 @@ use rayon::prelude::*;
pub fn value_at_risk(values: &[f64], confidence: f64) -> f64 {
let mut roc = rates_of_change(values).collect::<Vec<_>>();
// roc.par_sort_by(|x, y| x.partial_cmp(y).unwrap());
roc.sort_by(|x, y| x.partial_cmp(y).unwrap());
let threshold = (confidence * roc.len() as f64).floor() as usize;
@ -16,6 +13,16 @@ pub fn value_at_risk(values: &[f64], confidence: f64) -> f64 {
roc[threshold]
}
pub fn par_value_at_risk(values: &[f64], confidence: f64) -> f64 {
let mut roc = rates_of_change(values).collect::<Vec<_>>();
roc.par_sort_by(|x, y| x.partial_cmp(y).unwrap());
let threshold = (confidence * roc.len() as f64).floor() as usize;
roc[threshold]
}
#[cfg(test)]
mod tests {
use super::*;

@ -1,7 +1,6 @@
use crate::stats;
use crate::util::roc::rates_of_change;
#[cfg(feature = "parallel")]
use rayon::prelude::*;
use statrs::distribution::{ContinuousCDF, Normal};
// https://medium.com/@serdarilarslan/value-at-risk-var-and-its-implementation-in-python-5c9150f73b0e

@ -1,5 +1,3 @@
#[cfg(feature = "parallel")]
use rayon::prelude::*;
use super::mean;
pub fn covariance(slice: &[f64], slice_two: &[f64]) -> Option<f64>
@ -10,10 +8,8 @@ pub fn covariance(slice: &[f64], slice_two: &[f64]) -> Option<f64>
let mean_2 = mean(slice_two);
Some(slice
// .par_iter()
.iter()
.zip(slice_two
// .par_iter()
.iter()
)
.map(|(x, y)| (x - mean_1) * (y - mean_2))

@ -1,17 +1,13 @@
#[cfg(feature = "parallel")]
use rayon::prelude::*;
pub fn changes(values: &[f64]) -> impl Iterator<Item = f64> + use<'_> {
values
.windows(2)
// .par_windows(2)
.map(|x| x[1] - x[0])
}
pub fn rates_of_change(values: &[f64]) -> impl Iterator<Item = f64> + use<'_> {
values
.windows(2)
// .par_windows(2)
.map(|x| (x[1] - x[0])/x[0])
}