adding historical var, dotnet benchmarking

This commit is contained in:
Andy Pack 2025-02-14 01:35:29 +00:00
parent a2ce71161e
commit 335f5dbc82
Signed by: sarsoo
GPG Key ID: A55BA3536A5E0ED7
23 changed files with 269 additions and 23 deletions

@ -0,0 +1,34 @@
using System.Security.Cryptography;
using BenchmarkDotNet.Attributes;
using FinLib.Bench.Comparisons;
namespace FinLib.Bench.Benchmarks;
public class HistoricalVar
{
private const int N = 10000;
// [Params(0.05d, 0.1d)]
public double confidence = 0.05;
[Params(10, 1000, 10000)]
public int length;
private double[] data;
[GlobalSetup]
public void Setup()
{
data = new double[length];
var random = new Random(42);
for (int i = 0; i < length; i++)
{
data[i] = random.NextDouble();
}
}
[Benchmark]
public double FinLibDotnet() => HistoricalVARDotnet.ValueAtRisk(data.ToList(), confidence);
[Benchmark]
public double FinLibRust() => FinLib.Risk.ValueAtRisk.Historical(data, confidence);
}

@ -0,0 +1,37 @@
namespace FinLib.Bench.Comparisons;
public static class HistoricalVARDotnet
{
public static IEnumerable<(T, T)> Pairwise<T>(this IEnumerable<T> enumerable)
{
var previous = default(T);
using (var e = enumerable.GetEnumerator())
{
if (e.MoveNext())
previous = e.Current;
while (e.MoveNext())
{
previous = e.Current;
yield return (previous, e.Current);
}
}
}
public static IEnumerable<double> RatesOfChange(this IEnumerable<double> values)
{
foreach (var value in values.Pairwise())
{
yield return (value.Item2 - value.Item1) / value.Item1;
}
}
public static double ValueAtRisk(List<double> values, double confidence)
{
var roc = values.RatesOfChange().Order().ToArray();
var threshold = (int) Math.Floor(confidence * roc.Length);
return roc[threshold];
}
}

@ -0,0 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="BenchmarkDotNet" Version="0.14.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\FinLib\FinLib.csproj" />
</ItemGroup>
</Project>

@ -0,0 +1,13 @@
using BenchmarkDotNet.Running;
using FinLib.Bench.Benchmarks;
namespace FinLib.Bench
{
public class Program
{
public static void Main(string[] args)
{
var summary = BenchmarkRunner.Run<HistoricalVar>();
}
}
}

@ -11,24 +11,24 @@ public class Tests
[Test]
public void TestCompoundInterest()
{
FinLib.CompoundInterest(100, 0.05, 1, 1).Should().Be(105);
Interest.Interest.Compound(100, 0.05, 1, 1).Should().Be(105);
}
[Test]
public void TestCompoundInterestMonthly()
{
Math.Round(FinLib.CompoundInterest(100, 0.05, 1, 12), 2).Should().Be(105.12);
Math.Round(Interest.Interest.Compound(100, 0.05, 1, 12), 2).Should().Be(105.12);
}
[Test]
public void Covariance()
{
FinLib.Covariance([1d, 2d, 3d, 4], [1d, 2, 3, 4]).Should().Be(1.6666666666666667);
Stats.Stats.Covariance([1d, 2d, 3d, 4], [1d, 2, 3, 4]).Should().Be(1.6666666666666667);
}
[Test]
public void CovarianceBreaking()
{
FinLib.Covariance([1d, 2d, 3d, 4], [1d]).Should().BeNull();
Stats.Stats.Covariance([1d, 2d, 3d, 4], [1d]).Should().BeNull();
}
}

@ -4,6 +4,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FinLib", "FinLib\FinLib.csp
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FinLib.Test", "FinLib.Test\FinLib.Test.csproj", "{148E024C-07D0-4DC0-A3E2-A146E709897B}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FinLib.Bench", "FinLib.Bench\FinLib.Bench.csproj", "{F4171146-0A63-4611-972B-BAFB9A090D2B}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -18,5 +20,9 @@ Global
{148E024C-07D0-4DC0-A3E2-A146E709897B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{148E024C-07D0-4DC0-A3E2-A146E709897B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{148E024C-07D0-4DC0-A3E2-A146E709897B}.Release|Any CPU.Build.0 = Release|Any CPU
{F4171146-0A63-4611-972B-BAFB9A090D2B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F4171146-0A63-4611-972B-BAFB9A090D2B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F4171146-0A63-4611-972B-BAFB9A090D2B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F4171146-0A63-4611-972B-BAFB9A090D2B}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal

@ -0,0 +1,14 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
namespace FinLib.Interest;
public static class Interest
{
public static double Compound(double principal, double rate, double time, double n)
{
return NativeMethods.interest_compound(principal, rate, time, n);
}
}

@ -0,0 +1,21 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
namespace FinLib.Risk;
public static class ValueAtRisk
{
public static double Historical(IEnumerable<double> values, double confidence)
{
unsafe {
var valueArr = values.ToArray();
fixed (double* ptrOne = valueArr) {
var ret = NativeMethods.value_at_risk(ptrOne, (UIntPtr)valueArr.Length, confidence);
return *ret;
}
}
}
}

@ -3,15 +3,10 @@ using System.Collections;
using System.Collections.Generic;
using System.Linq;
namespace FinLib;
namespace FinLib.Stats;
public static class FinLib
public static class Stats
{
public static double CompoundInterest(double principal, double rate, double time, double n)
{
return NativeMethods.interest_compound(principal, rate, time, n);
}
public static double? Covariance(IEnumerable<double> valuesOne, IEnumerable<double> valuesTwo)
{
unsafe {

@ -22,6 +22,9 @@ namespace FinLib
[DllImport(__DllName, EntryPoint = "covariance", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern double* covariance(double* arr, nuint len, double* arr_two, nuint len_two);
[DllImport(__DllName, EntryPoint = "value_at_risk", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern double* value_at_risk(double* arr, nuint len, double confidence);
}

@ -21,6 +21,8 @@ const double *covariance(const double *arr, size_t len, const double *arr_two, s
double interest_compound(double principal, double rate, double time, double n);
const double *value_at_risk(const double *arr, size_t len, double confidence);
} // extern "C"
} // namespace finlib

@ -14,7 +14,7 @@ fn main() {
.with_config(config)
.generate()
.expect("Unable to generate bindings")
.write_to_file("../finlib-cpp/finlib-native.h");
.write_to_file("../finlib-cpp/include/finlib-native.h");
csbindgen::Builder::default()
.input_extern_file("src/lib.rs")

@ -23,4 +23,14 @@ pub unsafe extern "C" fn covariance(arr: *const f64, len: usize, arr_two: *const
Some(v) => Box::into_raw(Box::new(v))
}
}
#[no_mangle]
pub unsafe extern "C" fn value_at_risk(arr: *const f64, len: usize, confidence: f64) -> *const f64 {
let input_array = unsafe {
assert!(!arr.is_null());
slice::from_raw_parts(arr, len)
};
Box::into_raw(Box::new(finlib::risk::var::historical::value_at_risk(input_array, confidence)))
}

@ -6,7 +6,8 @@ edition.workspace = true
[dependencies]
pyo3 = { workspace = true, optional = true }
rayon = { workspace = true }
rayon = { workspace = true, optional = true }
[features]
py = ["dep:pyo3"]
py = ["dep:pyo3"]
parallel = ["dep::rayon"]

@ -1,2 +1,4 @@
pub mod interest;
pub mod stats;
pub mod stats;
pub mod util;
pub mod risk;

1
finlib/src/risk/mod.rs Normal file

@ -0,0 +1 @@
pub mod var;

@ -0,0 +1,27 @@
use crate::util::roc::rates_of_change;
#[cfg(feature = "parallel")]
use rayon::prelude::*;
// https://www.simtrade.fr/blog_simtrade/historical-method-var-calculation/
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;
roc[threshold]
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn var_test() {
let result = value_at_risk(&[1f64, 2f64, 4f64, 5f64], 0.01f64);
assert_eq!(result, 0.25f64);
}
}

@ -0,0 +1 @@
pub mod historical;

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

@ -1,29 +1,37 @@
mod covariance;
pub use covariance::*;
#[cfg(feature = "parallel")]
use rayon::prelude::*;
pub fn mean(slice: &[f64]) -> f64
{
slice.par_iter().sum::<f64>() / slice.len() as f64
slice
// .par_iter()
.iter()
.sum::<f64>() / slice.len() as f64
}
pub fn population_variance(slice: &[f64]) -> f64
{
let mean = mean(slice);
slice.par_iter()
slice
// .par_iter()
.iter()
.map(|x| f64::powi(x - mean, 2))
.sum::<f64>()
/ slice.len() as f64
/ slice.len() as f64
}
pub fn sample_variance(slice: &[f64]) -> f64
{
let mean = mean(slice);
slice.par_iter()
slice
// .par_iter()
.iter()
.map(|x| f64::powi(x - mean, 2))
.sum::<f64>()
/ ((slice.len() - 1) as f64)
/ ((slice.len() - 1) as f64)
}
pub fn population_std_dev(slice: &[f64]) -> f64

1
finlib/src/util/mod.rs Normal file

@ -0,0 +1 @@
pub mod roc;

33
finlib/src/util/roc.rs Normal file

@ -0,0 +1,33 @@
#[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])
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn change_test() {
let result = changes(&[1f64, 2f64, 4f64, 5f64]).collect::<Vec<_>>();
assert_eq!(result, vec![1f64, 2f64, 1f64]);
}
#[test]
fn roc_test() {
let result = rates_of_change(&[1f64, 2f64, 4f64, 5f64]).collect::<Vec<_>>();
assert_eq!(result, vec![1f64, 1f64, 0.25f64]);
}
}

@ -12,7 +12,19 @@ pub fn covariance(slice: Vec<f64>, slice_two: Vec<f64>) -> PyResult<Option<f64>>
#[pymodule]
fn pyfinlib(m: &Bound<'_, PyModule>) -> PyResult<()> {
m.add_function(wrap_pyfunction!(compound, m)?)?;
m.add_function(wrap_pyfunction!(covariance, m)?)?;
register_interest_module(m);
register_stats_module(m);
Ok(())
}
fn register_interest_module(parent_module: &Bound<'_, PyModule>) -> PyResult<()> {
let child_module = PyModule::new(parent_module.py(), "interest")?;
child_module.add_function(wrap_pyfunction!(compound, &child_module)?)?;
parent_module.add_submodule(&child_module)
}
fn register_stats_module(parent_module: &Bound<'_, PyModule>) -> PyResult<()> {
let child_module = PyModule::new(parent_module.py(), "stats")?;
child_module.add_function(wrap_pyfunction!(covariance, &child_module)?)?;
parent_module.add_submodule(&child_module)
}