Merge branch 'dev'
This commit is contained in:
commit
53eb0610d8
.gitea/workflows
.github/workflows
Cargo.lockCargo.tomlFinLib.NET/FinLib
finlib-cpp/include
finlib-ffi
finlib/src
pyfinlib/src
@ -56,6 +56,83 @@ jobs:
|
|||||||
working-directory: ./pyfinlib
|
working-directory: ./pyfinlib
|
||||||
run: maturin build
|
run: maturin build
|
||||||
|
|
||||||
|
buildNET:
|
||||||
|
name: Build .NET
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: [ build ] # for ignoring bad builds
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
github-server-url: https://gitea.sheep-ghoul.ts.net
|
||||||
|
|
||||||
|
- 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: Build
|
||||||
|
working-directory: ./FinLib.NET
|
||||||
|
run: dotnet build --configuration Debug --no-restore
|
||||||
|
|
||||||
|
- name: Test
|
||||||
|
working-directory: ./FinLib.NET
|
||||||
|
run: dotnet test --no-restore
|
||||||
|
|
||||||
|
publishNET:
|
||||||
|
name: Build .NET
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: [ buildNET ] # for ignoring bad builds
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
github-server-url: https://gitea.sheep-ghoul.ts.net
|
||||||
|
|
||||||
|
- 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: Add Gitea Repo
|
||||||
|
run: dotnet nuget add source --name gitea --api-key ${{ secrets.DOCKERHUB_TOKEN }} https://gitea.sheep-ghoul.ts.net/api/packages/sarsoo/nuget/index.json
|
||||||
|
|
||||||
|
- name: Install Dependencies
|
||||||
|
working-directory: ./FinLib.NET
|
||||||
|
run: dotnet restore
|
||||||
|
|
||||||
|
- name: Pack
|
||||||
|
working-directory: ./FinLib.NET/FinLib
|
||||||
|
run: dotnet pack
|
||||||
|
|
||||||
|
- name: Push
|
||||||
|
working-directory: ./FinLib.NET/FinLib
|
||||||
|
run: dotnet nuget push --source gitea ./bin/Release/FinLib.NET.0.0.4.nupkg
|
||||||
|
|
||||||
buildWASM:
|
buildWASM:
|
||||||
name: Build WASM
|
name: Build WASM
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
35
.github/workflows/build.yml
vendored
35
.github/workflows/build.yml
vendored
@ -64,6 +64,41 @@ jobs:
|
|||||||
working-directory: ./pyfinlib
|
working-directory: ./pyfinlib
|
||||||
run: maturin build
|
run: maturin build
|
||||||
|
|
||||||
|
buildNET:
|
||||||
|
name: Build .NET
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: [ build ] # for ignoring bad builds
|
||||||
|
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: Build
|
||||||
|
working-directory: ./FinLib.NET
|
||||||
|
run: dotnet build --configuration Debug --no-restore
|
||||||
|
|
||||||
|
- name: Test
|
||||||
|
working-directory: ./FinLib.NET
|
||||||
|
run: dotnet test --no-restore
|
||||||
|
|
||||||
buildWASM:
|
buildWASM:
|
||||||
name: Build WASM
|
name: Build WASM
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
8
Cargo.lock
generated
8
Cargo.lock
generated
@ -259,7 +259,7 @@ checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "finlib"
|
name = "finlib"
|
||||||
version = "0.0.3"
|
version = "0.0.4"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"getrandom 0.2.15",
|
"getrandom 0.2.15",
|
||||||
"log",
|
"log",
|
||||||
@ -274,7 +274,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "finlib-ffi"
|
name = "finlib-ffi"
|
||||||
version = "0.0.3"
|
version = "0.0.4"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cbindgen",
|
"cbindgen",
|
||||||
"csbindgen",
|
"csbindgen",
|
||||||
@ -283,7 +283,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "finlib-wasm"
|
name = "finlib-wasm"
|
||||||
version = "0.0.3"
|
version = "0.0.4"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"console_error_panic_hook",
|
"console_error_panic_hook",
|
||||||
"console_log",
|
"console_log",
|
||||||
@ -606,7 +606,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pyfinlib"
|
name = "pyfinlib"
|
||||||
version = "0.0.3"
|
version = "0.0.4"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"finlib",
|
"finlib",
|
||||||
"log",
|
"log",
|
||||||
|
@ -14,7 +14,7 @@ default-members = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[workspace.package]
|
[workspace.package]
|
||||||
version = "0.0.3"
|
version = "0.0.4"
|
||||||
authors = ["sarsoo <andy@sarsoo.xyz>"]
|
authors = ["sarsoo <andy@sarsoo.xyz>"]
|
||||||
description = "Quant finance functions implemented in Rust"
|
description = "Quant finance functions implemented in Rust"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
@ -7,8 +7,5 @@ namespace FinLib.Interest;
|
|||||||
|
|
||||||
public static class Interest
|
public static class Interest
|
||||||
{
|
{
|
||||||
public static double Compound(double principal, double rate, double time, double n)
|
public static double Compound(double principal, double rate, double time, double n) => NativeMethods.interest_compound(principal, rate, time, n);
|
||||||
{
|
|
||||||
return NativeMethods.interest_compound(principal, rate, time, n);
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -30,4 +30,6 @@ public static class ValueAtRisk
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static double ScaleValueAtRisk(double initialValue, nint timeCycles) => NativeMethods.scale_value_at_risk(initialValue, timeCycles);
|
||||||
}
|
}
|
@ -1,25 +1,25 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<PackageId>FinLib.NET</PackageId>
|
||||||
|
<Version>0.0.4</Version>
|
||||||
|
<Authors>sarsoo</Authors>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFrameworks>netstandard2.0</TargetFrameworks>
|
<TargetFrameworks>netstandard2.0</TargetFrameworks>
|
||||||
<LangVersion>latest</LangVersion>
|
<LangVersion>latest</LangVersion>
|
||||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup>
|
|
||||||
<PackageId>FinLib.NET</PackageId>
|
|
||||||
<Version>0.0.1</Version>
|
|
||||||
<Authors>sarsoo</Authors>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
<ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
|
<ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||||
<Content Include="..\..\target\debug\libfinlib_ffi.dylib">
|
<Content Include="..\..\target\debug\libfinlib_ffi.*">
|
||||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||||
</Content>
|
</Content>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup Condition=" '$(Configuration)' == 'Release' ">
|
<ItemGroup Condition=" '$(Configuration)' == 'Release' ">
|
||||||
<Content Include="..\..\target\release\libfinlib_ffi.dylib">
|
<Content Include="..\..\target\release\libfinlib_ffi.*">
|
||||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||||
</Content>
|
</Content>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
@ -28,9 +28,48 @@ namespace FinLib
|
|||||||
[DllImport(__DllName, EntryPoint = "varcovar_value_at_risk", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
|
[DllImport(__DllName, EntryPoint = "varcovar_value_at_risk", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
|
||||||
internal static extern double* varcovar_value_at_risk(double* arr, nuint len, double confidence);
|
internal static extern double* varcovar_value_at_risk(double* arr, nuint len, double confidence);
|
||||||
|
|
||||||
|
[DllImport(__DllName, EntryPoint = "scale_value_at_risk", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
|
||||||
|
internal static extern double scale_value_at_risk(double initial_value, nint time_cycles);
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
internal unsafe partial struct Portfolio
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
internal unsafe partial struct PortfolioAsset
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
internal unsafe partial struct OptionVariables
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
internal unsafe partial struct CallOption
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
internal unsafe partial struct PutOption
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
internal unsafe partial struct OptionGreeks
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
internal enum ValueType : byte
|
||||||
|
{
|
||||||
|
Absolute,
|
||||||
|
RateOfChange,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -23,6 +23,8 @@ const double *historical_value_at_risk(const double *arr, size_t len, double con
|
|||||||
|
|
||||||
double interest_compound(double principal, double rate, double time, double n);
|
double interest_compound(double principal, double rate, double time, double n);
|
||||||
|
|
||||||
|
double scale_value_at_risk(double initial_value, ptrdiff_t time_cycles);
|
||||||
|
|
||||||
const double *varcovar_value_at_risk(const double *arr, size_t len, double confidence);
|
const double *varcovar_value_at_risk(const double *arr, size_t len, double confidence);
|
||||||
|
|
||||||
} // extern "C"
|
} // extern "C"
|
||||||
|
@ -19,7 +19,18 @@ fn main() {
|
|||||||
csbindgen::Builder::default()
|
csbindgen::Builder::default()
|
||||||
.input_extern_file("src/lib.rs")
|
.input_extern_file("src/lib.rs")
|
||||||
.input_extern_file("../finlib/src/lib.rs")
|
.input_extern_file("../finlib/src/lib.rs")
|
||||||
|
.input_extern_file("../finlib/src/risk/portfolio.rs")
|
||||||
|
.input_extern_file("../finlib/src/options/blackscholes/mod.rs")
|
||||||
.csharp_dll_name("libfinlib_ffi")
|
.csharp_dll_name("libfinlib_ffi")
|
||||||
|
.always_included_types([
|
||||||
|
"Portfolio",
|
||||||
|
"ValueType",
|
||||||
|
"PortfolioAsset",
|
||||||
|
"OptionVariables",
|
||||||
|
"CallOption",
|
||||||
|
"PutOption",
|
||||||
|
"OptionGreeks",
|
||||||
|
])
|
||||||
.csharp_namespace("FinLib")
|
.csharp_namespace("FinLib")
|
||||||
.generate_csharp_file("../FinLib.NET/FinLib/NativeMethods.g.cs")
|
.generate_csharp_file("../FinLib.NET/FinLib/NativeMethods.g.cs")
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
@ -144,11 +144,11 @@ bitflags = false
|
|||||||
############## Options for How Your Rust library Should Be Parsed ##############
|
############## Options for How Your Rust library Should Be Parsed ##############
|
||||||
|
|
||||||
[parse]
|
[parse]
|
||||||
parse_deps = false
|
parse_deps = true
|
||||||
# include = []
|
include = ["finlib"]
|
||||||
exclude = []
|
#exclude = []
|
||||||
clean = false
|
clean = false
|
||||||
extra_bindings = []
|
extra_bindings = ["finlib"]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -44,3 +44,9 @@ pub unsafe extern "C" fn varcovar_value_at_risk(arr: *const f64, len: usize, con
|
|||||||
|
|
||||||
Box::into_raw(Box::new(finlib::risk::var::varcovar::value_at_risk_percent(input_array, confidence)))
|
Box::into_raw(Box::new(finlib::risk::var::varcovar::value_at_risk_percent(input_array, confidence)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn scale_value_at_risk(initial_value: f64, time_cycles: isize) -> f64 {
|
||||||
|
|
||||||
|
finlib::risk::var::varcovar::scale_value_at_risk(initial_value, time_cycles)
|
||||||
|
}
|
||||||
|
@ -1 +1,2 @@
|
|||||||
pub mod portfolio;
|
pub mod portfolio;
|
||||||
|
pub mod options;
|
21
finlib/src/ffi/py/options.rs
Normal file
21
finlib/src/ffi/py/options.rs
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
use pyo3::prelude::*;
|
||||||
|
use crate::options::blackscholes::{OptionVariables, OptionGreeks};
|
||||||
|
use crate::risk::portfolio::{Portfolio, PortfolioAsset};
|
||||||
|
|
||||||
|
#[pymethods]
|
||||||
|
impl OptionVariables {
|
||||||
|
#[new]
|
||||||
|
pub fn init(underlying_price: f64,
|
||||||
|
strike_price: f64,
|
||||||
|
volatility: f64,
|
||||||
|
risk_free_interest_rate: f64,
|
||||||
|
dividend: f64,
|
||||||
|
time_to_expiration: f64) -> Self {
|
||||||
|
OptionVariables::from(underlying_price,
|
||||||
|
strike_price,
|
||||||
|
volatility,
|
||||||
|
risk_free_interest_rate,
|
||||||
|
dividend,
|
||||||
|
time_to_expiration)
|
||||||
|
}
|
||||||
|
}
|
@ -5,6 +5,7 @@ pub mod stats;
|
|||||||
pub mod util;
|
pub mod util;
|
||||||
pub mod risk;
|
pub mod risk;
|
||||||
pub mod ffi;
|
pub mod ffi;
|
||||||
|
pub mod options;
|
||||||
|
|
||||||
#[cfg(feature = "parallel")]
|
#[cfg(feature = "parallel")]
|
||||||
use rayon::prelude::*;
|
use rayon::prelude::*;
|
||||||
|
174
finlib/src/options/blackscholes/OptionSurface.rs
Normal file
174
finlib/src/options/blackscholes/OptionSurface.rs
Normal file
@ -0,0 +1,174 @@
|
|||||||
|
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
|
||||||
|
.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)>>();
|
||||||
|
|
||||||
|
let a1 = a.first();
|
||||||
|
}
|
||||||
|
}
|
372
finlib/src/options/blackscholes/mod.rs
Normal file
372
finlib/src/options/blackscholes/mod.rs
Normal file
@ -0,0 +1,372 @@
|
|||||||
|
mod OptionSurface;
|
||||||
|
|
||||||
|
use statrs::distribution::{Continuous, ContinuousCDF, Normal};
|
||||||
|
#[cfg(feature = "wasm")]
|
||||||
|
use wasm_bindgen::prelude::*;
|
||||||
|
#[cfg(feature = "py")]
|
||||||
|
use pyo3::prelude::*;
|
||||||
|
|
||||||
|
#[cfg_attr(feature = "wasm", wasm_bindgen)]
|
||||||
|
#[cfg_attr(feature = "py", pyclass)]
|
||||||
|
#[cfg_attr(feature = "ffi", repr(C))]
|
||||||
|
#[derive(Debug, Copy, Clone, Default, PartialEq, PartialOrd)]
|
||||||
|
pub struct OptionVariables {
|
||||||
|
underlying_price: f64,
|
||||||
|
strike_price: f64,
|
||||||
|
volatility: f64,
|
||||||
|
risk_free_interest_rate: f64,
|
||||||
|
dividend: f64,
|
||||||
|
time_to_expiration: f64,
|
||||||
|
d1: std::option::Option<f64>,
|
||||||
|
d2: std::option::Option<f64>
|
||||||
|
}
|
||||||
|
|
||||||
|
impl OptionVariables {
|
||||||
|
|
||||||
|
pub fn from(underlying_price: f64,
|
||||||
|
strike_price: f64,
|
||||||
|
volatility: f64,
|
||||||
|
risk_free_interest_rate: f64,
|
||||||
|
dividend: f64,
|
||||||
|
time_to_expiration: f64) -> Self {
|
||||||
|
Self {
|
||||||
|
underlying_price,
|
||||||
|
strike_price,
|
||||||
|
volatility,
|
||||||
|
risk_free_interest_rate,
|
||||||
|
dividend,
|
||||||
|
time_to_expiration,
|
||||||
|
d1: None,
|
||||||
|
d2: None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn call(mut self) -> CallOption {
|
||||||
|
let n = Normal::new(0., 1.0).unwrap();
|
||||||
|
let (d1, d2) = self.d1_d2();
|
||||||
|
self.d1 = Some(d1);
|
||||||
|
self.d2 = Some(d2);
|
||||||
|
|
||||||
|
let first = self.underlying_price * (-self.dividend * self.time_to_expiration).exp() * n.cdf(d1);
|
||||||
|
|
||||||
|
let second = self.strike_price * (-self.risk_free_interest_rate * self.time_to_expiration).exp() * n.cdf(d2);
|
||||||
|
|
||||||
|
CallOption::from(first - second, self)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub fn put(mut self) -> PutOption {
|
||||||
|
let n = Normal::new(0., 1.0).unwrap();
|
||||||
|
let (d1, d2) = self.d1_d2();
|
||||||
|
self.d1 = Some(d1);
|
||||||
|
self.d2 = Some(d2);
|
||||||
|
|
||||||
|
let first = self.strike_price * (-self.risk_free_interest_rate * self.time_to_expiration).exp() * n.cdf(-d2);
|
||||||
|
|
||||||
|
let second = self.underlying_price * (-self.dividend * self.time_to_expiration).exp() * n.cdf(-d1);
|
||||||
|
|
||||||
|
PutOption::from(first - second, self)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn d1_d2(&self) -> (f64, f64) {
|
||||||
|
let d1 = self.d1();
|
||||||
|
|
||||||
|
(d1, self.d2(d1))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn d1(&self) -> f64 {
|
||||||
|
|
||||||
|
let first = (self.underlying_price / self.strike_price).log(std::f64::consts::E);
|
||||||
|
|
||||||
|
let second = self.time_to_expiration * (self.risk_free_interest_rate - self.dividend + (f64::powi(self.volatility, 2) / 2.));
|
||||||
|
|
||||||
|
let denominator = self.volatility * f64::sqrt(self.time_to_expiration);
|
||||||
|
|
||||||
|
(first + second) / denominator
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn d2(&self, d1: f64) -> f64 {
|
||||||
|
d1 - (self.volatility * f64::sqrt(self.time_to_expiration))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait Option {
|
||||||
|
fn delta(&self) -> f64;
|
||||||
|
fn gamma(&self) -> f64;
|
||||||
|
fn vega(&self) -> f64;
|
||||||
|
fn theta(&self) -> f64;
|
||||||
|
fn rho(&self) -> f64;
|
||||||
|
fn calc_greeks(&mut self);
|
||||||
|
fn has_greeks(&self) -> bool;
|
||||||
|
}
|
||||||
|
|
||||||
|
// #[cfg_attr(feature = "wasm", wasm_bindgen)]
|
||||||
|
#[cfg_attr(feature = "py", pyclass)]
|
||||||
|
#[cfg_attr(feature = "ffi", repr(C))]
|
||||||
|
#[derive(Debug, Copy, Clone, Default, PartialEq, PartialOrd)]
|
||||||
|
pub struct CallOption {
|
||||||
|
pub price: f64,
|
||||||
|
pub variables: OptionVariables,
|
||||||
|
pub greeks: std::option::Option<OptionGreeks>
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CallOption {
|
||||||
|
pub fn from(price: f64, variables: OptionVariables) -> Self {
|
||||||
|
Self { price, variables, greeks: None }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Option for CallOption {
|
||||||
|
fn delta(&self) -> f64 {
|
||||||
|
let n = Normal::new(0., 1.0).unwrap();
|
||||||
|
|
||||||
|
(-self.variables.dividend * self.variables.time_to_expiration).exp() * n.cdf(self.variables.d1.unwrap())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn gamma(&self) -> f64 {
|
||||||
|
gamma(&self.variables)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn vega(&self) -> f64 {
|
||||||
|
vega(&self.variables)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn theta(&self) -> f64 {
|
||||||
|
let n = Normal::new(0., 1.0).unwrap();
|
||||||
|
let first = theta_first(&self.variables, &n);
|
||||||
|
|
||||||
|
let second = self.variables.risk_free_interest_rate
|
||||||
|
* self.variables.strike_price
|
||||||
|
* (-self.variables.risk_free_interest_rate * self.variables.time_to_expiration).exp()
|
||||||
|
* n.cdf(self.variables.d2.unwrap());
|
||||||
|
|
||||||
|
let third = self.variables.dividend
|
||||||
|
* self.variables.underlying_price
|
||||||
|
* (-self.variables.dividend * self.variables.time_to_expiration).exp()
|
||||||
|
* n.cdf(self.variables.d1.unwrap());
|
||||||
|
|
||||||
|
first - second + third
|
||||||
|
}
|
||||||
|
|
||||||
|
fn rho(&self) -> f64 {
|
||||||
|
let n = Normal::new(0., 1.0).unwrap();
|
||||||
|
|
||||||
|
self.variables.strike_price * self.variables.time_to_expiration * (-self.variables.risk_free_interest_rate * self.variables.time_to_expiration).exp() * n.cdf(self.variables.d2.unwrap())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn calc_greeks(&mut self) {
|
||||||
|
self.greeks = Some(OptionGreeks::from(self));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn has_greeks(&self) -> bool {
|
||||||
|
self.greeks.is_some()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// #[cfg_attr(feature = "wasm", wasm_bindgen)]
|
||||||
|
#[cfg_attr(feature = "py", pyclass)]
|
||||||
|
#[cfg_attr(feature = "ffi", repr(C))]
|
||||||
|
#[derive(Debug, Copy, Clone, Default, PartialEq, PartialOrd)]
|
||||||
|
pub struct PutOption {
|
||||||
|
pub price: f64,
|
||||||
|
pub variables: OptionVariables,
|
||||||
|
pub greeks: std::option::Option<OptionGreeks>
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PutOption {
|
||||||
|
pub fn from(price: f64, variables: OptionVariables) -> Self {
|
||||||
|
Self { price, variables, greeks: None }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Option for PutOption {
|
||||||
|
fn delta(&self) -> f64 {
|
||||||
|
let n = Normal::new(0., 1.0).unwrap();
|
||||||
|
|
||||||
|
(-self.variables.dividend * self.variables.time_to_expiration).exp() * (n.cdf(self.variables.d1.unwrap()) - 1.)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn gamma(&self) -> f64 {
|
||||||
|
gamma(&self.variables)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn vega(&self) -> f64 {
|
||||||
|
vega(&self.variables)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn theta(&self) -> f64 {
|
||||||
|
let n = Normal::new(0., 1.0).unwrap();
|
||||||
|
let first = theta_first(&self.variables, &n);
|
||||||
|
|
||||||
|
let second = self.variables.risk_free_interest_rate
|
||||||
|
* self.variables.strike_price
|
||||||
|
* (-self.variables.risk_free_interest_rate * self.variables.time_to_expiration).exp()
|
||||||
|
* n.cdf(-self.variables.d2.unwrap());
|
||||||
|
|
||||||
|
let third = self.variables.dividend
|
||||||
|
* self.variables.underlying_price
|
||||||
|
* (-self.variables.dividend * self.variables.time_to_expiration).exp()
|
||||||
|
* n.cdf(-self.variables.d1.unwrap());
|
||||||
|
|
||||||
|
first + second - third
|
||||||
|
}
|
||||||
|
|
||||||
|
fn rho(&self) -> f64 {
|
||||||
|
let n = Normal::new(0., 1.0).unwrap();
|
||||||
|
|
||||||
|
- self.variables.strike_price * self.variables.time_to_expiration * (-self.variables.risk_free_interest_rate * self.variables.time_to_expiration).exp() * n.cdf(-self.variables.d2.unwrap())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn calc_greeks(&mut self) {
|
||||||
|
self.greeks = Some(OptionGreeks::from(self));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn has_greeks(&self) -> bool {
|
||||||
|
self.greeks.is_some()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn theta_first(v: &OptionVariables, n: &Normal) -> f64 {
|
||||||
|
let numerator = v.underlying_price * v.volatility * (-v.dividend * v.time_to_expiration).exp();
|
||||||
|
let denominator = 2. * f64::sqrt(v.time_to_expiration);
|
||||||
|
|
||||||
|
-(numerator / denominator) * n.pdf(v.d1.unwrap())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn gamma(v: &OptionVariables) -> f64 {
|
||||||
|
let n = Normal::new(0., 1.0).unwrap();
|
||||||
|
|
||||||
|
let numerator = (-v.dividend * v.time_to_expiration).exp();
|
||||||
|
let denominator = v.underlying_price * v.volatility * f64::sqrt(v.time_to_expiration);
|
||||||
|
|
||||||
|
(numerator / denominator) * n.pdf(v.d1.unwrap())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn vega(v: &OptionVariables) -> f64 {
|
||||||
|
let n = Normal::new(0., 1.0).unwrap();
|
||||||
|
|
||||||
|
let numerator = (-v.dividend * v.time_to_expiration).exp();
|
||||||
|
|
||||||
|
v.underlying_price * numerator * f64::sqrt(v.time_to_expiration) * n.pdf(v.d1.unwrap())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg_attr(feature = "wasm", wasm_bindgen)]
|
||||||
|
#[cfg_attr(feature = "py", pyclass)]
|
||||||
|
#[cfg_attr(feature = "ffi", repr(C))]
|
||||||
|
#[derive(Debug, Copy, Clone, Default, PartialEq, PartialOrd)]
|
||||||
|
pub struct OptionGreeks {
|
||||||
|
pub delta: f64,
|
||||||
|
pub gamma: f64,
|
||||||
|
pub vega: f64,
|
||||||
|
pub theta: f64,
|
||||||
|
pub rho: f64
|
||||||
|
}
|
||||||
|
|
||||||
|
impl OptionGreeks {
|
||||||
|
pub fn from(option: &impl Option) -> Self {
|
||||||
|
Self {
|
||||||
|
delta: option.delta(),
|
||||||
|
gamma: option.gamma(),
|
||||||
|
vega: option.vega(),
|
||||||
|
theta: option.theta(),
|
||||||
|
rho: option.rho()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
// https://goodcalculators.com/black-scholes-calculator/
|
||||||
|
|
||||||
|
fn get_example_option() -> OptionVariables {
|
||||||
|
OptionVariables::from(100., 100., 0.25, 0.05, 0.01, 30./365.25)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn call_test() {
|
||||||
|
let v = get_example_option();
|
||||||
|
|
||||||
|
let diff = (v.call().price - 3.019).abs();
|
||||||
|
|
||||||
|
assert!(diff < 0.01);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn put_test() {
|
||||||
|
let v = get_example_option();
|
||||||
|
|
||||||
|
let diff = (v.put().price - 2.691).abs();
|
||||||
|
assert!(diff < 0.01);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn call_delta_test() {
|
||||||
|
let v = get_example_option();
|
||||||
|
|
||||||
|
let diff = (v.call().delta() - 0.532).abs();
|
||||||
|
assert!(diff < 0.01);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn put_delta_test() {
|
||||||
|
let v = get_example_option();
|
||||||
|
|
||||||
|
let delta = v.put().delta();
|
||||||
|
let diff = (delta - -0.467).abs();
|
||||||
|
assert!(diff < 0.01);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn gamma_test() {
|
||||||
|
let v = get_example_option();
|
||||||
|
|
||||||
|
let gamma = v.put().gamma();
|
||||||
|
let diff = (gamma - 0.055).abs();
|
||||||
|
assert!(diff < 0.01);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn vega_test() {
|
||||||
|
let v = get_example_option();
|
||||||
|
|
||||||
|
let vega = v.put().vega();
|
||||||
|
let diff = (vega - 11.390).abs();
|
||||||
|
assert!(diff < 0.01);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn call_rho_test() {
|
||||||
|
let v = get_example_option();
|
||||||
|
|
||||||
|
let diff = (v.call().rho() - 4.126).abs();
|
||||||
|
assert!(diff < 0.01);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn put_rho_test() {
|
||||||
|
let v = get_example_option();
|
||||||
|
|
||||||
|
let rho = v.put().rho();
|
||||||
|
let diff = (rho - -4.060).abs();
|
||||||
|
assert!(diff < 0.01);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn call_theta_test() {
|
||||||
|
let v = get_example_option();
|
||||||
|
|
||||||
|
let diff = (v.call().theta() - -19.300).abs();
|
||||||
|
assert!(diff < 0.01);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn put_theta_test() {
|
||||||
|
let v = get_example_option();
|
||||||
|
|
||||||
|
let theta = v.put().theta();
|
||||||
|
let diff = (theta - -15.319).abs();
|
||||||
|
assert!(diff < 0.01);
|
||||||
|
}
|
||||||
|
}
|
1
finlib/src/options/mod.rs
Normal file
1
finlib/src/options/mod.rs
Normal file
@ -0,0 +1 @@
|
|||||||
|
pub mod blackscholes;
|
@ -25,6 +25,20 @@ mod pyfinlib {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[pymodule]
|
||||||
|
mod options {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[pymodule_export]
|
||||||
|
use finlib::options::blackscholes::OptionVariables;
|
||||||
|
#[pymodule_export]
|
||||||
|
use finlib::options::blackscholes::CallOption;
|
||||||
|
#[pymodule_export]
|
||||||
|
use finlib::options::blackscholes::PutOption;
|
||||||
|
#[pymodule_export]
|
||||||
|
use finlib::options::blackscholes::OptionGreeks;
|
||||||
|
}
|
||||||
|
|
||||||
#[pymodule]
|
#[pymodule]
|
||||||
mod risk {
|
mod risk {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user