diff --git a/.gitignore b/.gitignore index a592abc..002cfdd 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ /target bin obj +cmake-build-debug .venv .idea /finlib-wasm/pkg diff --git a/Cargo.lock b/Cargo.lock index bb049d9..9916d20 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -340,7 +340,7 @@ checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" [[package]] name = "finlib" -version = "0.0.6" +version = "0.0.7" dependencies = [ "criterion", "getrandom 0.2.15", @@ -357,7 +357,7 @@ dependencies = [ [[package]] name = "finlib-ffi" -version = "0.0.6" +version = "0.0.7" dependencies = [ "cbindgen", "csbindgen", @@ -366,7 +366,7 @@ dependencies = [ [[package]] name = "finlib-wasm" -version = "0.0.6" +version = "0.0.7" dependencies = [ "console_error_panic_hook", "console_log", @@ -759,7 +759,7 @@ dependencies = [ [[package]] name = "pyfinlib" -version = "0.0.6" +version = "0.0.7" dependencies = [ "finlib", "log", diff --git a/Cargo.toml b/Cargo.toml index 80c86e4..7c66c5c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,7 +14,7 @@ default-members = [ ] [workspace.package] -version = "0.0.6" +version = "0.0.7" authors = ["sarsoo <andy@sarsoo.xyz>"] description = "Quant finance functions implemented in Rust" edition = "2021" diff --git a/FinLib.NET/FinLib.Bench/Benchmarks/HistoricalVAR.cs b/FinLib.NET/FinLib.Bench/Benchmarks/HistoricalVAR.cs index 186943f..0fe1ac2 100644 --- a/FinLib.NET/FinLib.Bench/Benchmarks/HistoricalVAR.cs +++ b/FinLib.NET/FinLib.Bench/Benchmarks/HistoricalVAR.cs @@ -30,5 +30,5 @@ public class HistoricalVar public double FinLibDotnet() => HistoricalVARDotnet.ValueAtRisk(data.ToList(), confidence); [Benchmark] - public double FinLibRust() => FinLib.Risk.ValueAtRisk.Historical(data, confidence); + public double FinLibRust() => FinLib.Risk.ValueAtRisk.Historical(data, confidence)!.Value; } \ No newline at end of file diff --git a/FinLib.NET/FinLib.Test/Portfolio.cs b/FinLib.NET/FinLib.Test/Portfolio.cs new file mode 100644 index 0000000..a2183da --- /dev/null +++ b/FinLib.NET/FinLib.Test/Portfolio.cs @@ -0,0 +1,34 @@ +using FluentAssertions; + +namespace FinLib.Test; + +public class PortfolioTest +{ + [Test] + public void TestPortfolioCreation() + { + using var portfolio = new Portfolio(); + portfolio.AddAsset(0.5, "first", [0.5, 0.5, 0.5, 0.5]); + portfolio.AddAsset(0.5, "second", [0.5, 0.5, 0.5, 0.5]); + + var (mean, std) = portfolio.GetMeanAndStdDev()!.Value; + mean.Should().Be(0); + std.Should().Be(0); + } + + [Test] + public void TestPortfolioValid() + { + var portfolio = new Portfolio(); + portfolio.AddAsset(0.5, "first", [0.5, 0.5, 0.5, 0.5]); + portfolio.AddAsset(0.5, "second", [0.5, 0.5, 0.5]); + + portfolio.IsValid().Should().BeFalse(); + + var portfolio2 = new Portfolio(); + portfolio2.AddAsset(0.5, "first", [0.5, 0.5, 0.5, 0.5]); + portfolio2.AddAsset(0.5, "second", [0.5, 0.5, 0.5, 0.5]); + + portfolio2.IsValid().Should().BeTrue(); + } +} \ No newline at end of file diff --git a/FinLib.NET/FinLib/FinLib.Risk.cs b/FinLib.NET/FinLib/FinLib.Risk.cs index bba9bee..41b2c1a 100644 --- a/FinLib.NET/FinLib/FinLib.Risk.cs +++ b/FinLib.NET/FinLib/FinLib.Risk.cs @@ -7,26 +7,36 @@ namespace FinLib.Risk; public static class ValueAtRisk { - public static double Historical(IEnumerable<double> values, double confidence) + public static double? Historical(IEnumerable<double> values, double confidence) { unsafe { var valueArr = values.ToArray(); fixed (double* ptrOne = valueArr) { var ret = NativeMethods.historical_value_at_risk(ptrOne, (UIntPtr)valueArr.Length, confidence); - return *ret; + if (ret.is_valid) + { + return ret.val; + } + + return null; } } } - public static double VarCovar(IEnumerable<double> values, double confidence) + public static double? VarCovar(IEnumerable<double> values, double confidence) { unsafe { var valueArr = values.ToArray(); fixed (double* ptrOne = valueArr) { var ret = NativeMethods.varcovar_value_at_risk(ptrOne, (UIntPtr)valueArr.Length, confidence); - return *ret; + if (ret.is_valid) + { + return ret.val; + } + + return null; } } } diff --git a/FinLib.NET/FinLib/FinLib.Stats.cs b/FinLib.NET/FinLib/FinLib.Stats.cs index 1483ee4..b9bd2ce 100644 --- a/FinLib.NET/FinLib/FinLib.Stats.cs +++ b/FinLib.NET/FinLib/FinLib.Stats.cs @@ -16,14 +16,12 @@ public static class Stats fixed (double* ptrTwo = valuesTwoArr) { var ret = NativeMethods.covariance(ptrOne, (UIntPtr)valuesOneArr.Length, ptrTwo, (UIntPtr) valuesTwoArr.Length); - if (ret == null) + if (ret.is_valid) { - return null; - } - else - { - return *ret; + return ret.val; } + + return null; } } } diff --git a/FinLib.NET/FinLib/NativeMethods.cs b/FinLib.NET/FinLib/NativeMethods.cs index 038515a..a0feb89 100644 --- a/FinLib.NET/FinLib/NativeMethods.cs +++ b/FinLib.NET/FinLib/NativeMethods.cs @@ -1,9 +1,7 @@ -using GroupedNativeMethodsGenerator; namespace FinLib; -[GroupedNativeMethods] -internal static unsafe partial class NativeMethods +internal static partial class NativeMethods { } \ No newline at end of file diff --git a/FinLib.NET/FinLib/NativeMethods.g.cs b/FinLib.NET/FinLib/NativeMethods.g.cs index fcbb16b..07f8c5b 100644 --- a/FinLib.NET/FinLib/NativeMethods.g.cs +++ b/FinLib.NET/FinLib/NativeMethods.g.cs @@ -20,56 +20,90 @@ namespace FinLib internal static extern double interest_compound(double principal, double rate, double time, double n); [DllImport(__DllName, EntryPoint = "covariance", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] - internal static extern double* covariance(double* arr, nuint len, double* arr_two, nuint len_two); + internal static extern NullableFloat covariance(double* arr, nuint len, double* arr_two, nuint len_two); [DllImport(__DllName, EntryPoint = "historical_value_at_risk", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] - internal static extern double* historical_value_at_risk(double* arr, nuint len, double confidence); + internal static extern NullableFloat historical_value_at_risk(double* arr, nuint len, double confidence); [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 NullableFloat 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); + [DllImport(__DllName, EntryPoint = "portfolio_asset_new", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + internal static extern PortfolioAsset_native* portfolio_asset_new(double portfolio_weight, byte* name, int name_len, double* values, nuint len); + + [DllImport(__DllName, EntryPoint = "portfolio_asset_destroy", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + internal static extern void portfolio_asset_destroy(PortfolioAsset_native* asset); + + [DllImport(__DllName, EntryPoint = "portfolio_asset_apply_rates_of_change", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + internal static extern void portfolio_asset_apply_rates_of_change(PortfolioAsset_native* asset); + + [DllImport(__DllName, EntryPoint = "portfolio_asset_get_mean_and_std", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + internal static extern Tuple portfolio_asset_get_mean_and_std(PortfolioAsset_native* asset); + + [DllImport(__DllName, EntryPoint = "portfolio_new", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + internal static extern Portfolio_native* portfolio_new(); + + [DllImport(__DllName, EntryPoint = "portfolio_destroy", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + internal static extern void portfolio_destroy(Portfolio_native* portfolio); + + [DllImport(__DllName, EntryPoint = "portfolio_add_asset", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + internal static extern void portfolio_add_asset(Portfolio_native* portfolio, PortfolioAsset_native* asset); + + [DllImport(__DllName, EntryPoint = "portfolio_apply_rates_of_change", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + internal static extern void portfolio_apply_rates_of_change(Portfolio_native* portfolio); + + [DllImport(__DllName, EntryPoint = "portfolio_valid_sizes", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + [return: MarshalAs(UnmanagedType.U1)] + internal static extern bool portfolio_valid_sizes(Portfolio_native* portfolio); + + [DllImport(__DllName, EntryPoint = "portfolio_valid_weights", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + [return: MarshalAs(UnmanagedType.U1)] + internal static extern bool portfolio_valid_weights(Portfolio_native* portfolio); + + [DllImport(__DllName, EntryPoint = "portfolio_is_valid", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + [return: MarshalAs(UnmanagedType.U1)] + internal static extern bool portfolio_is_valid(Portfolio_native* portfolio); + + [DllImport(__DllName, EntryPoint = "portfolio_get_mean_and_std", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + internal static extern Tuple portfolio_get_mean_and_std(Portfolio_native* portfolio); + + [DllImport(__DllName, EntryPoint = "portfolio_value_at_risk", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + internal static extern NullableFloat portfolio_value_at_risk(Portfolio_native* portfolio, double confidence, double initial_investment); + + [DllImport(__DllName, EntryPoint = "portfolio_value_at_risk_percent", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + internal static extern NullableFloat portfolio_value_at_risk_percent(Portfolio_native* portfolio, double confidence); + } [StructLayout(LayoutKind.Sequential)] - internal unsafe partial struct Portfolio + internal unsafe partial struct Tuple + { + public double one; + public double two; + [MarshalAs(UnmanagedType.U1)] public bool is_valid; + } + + [StructLayout(LayoutKind.Sequential)] + internal unsafe partial struct NullableFloat + { + public double val; + [MarshalAs(UnmanagedType.U1)] public bool is_valid; + } + + [StructLayout(LayoutKind.Sequential)] + internal unsafe partial struct Portfolio_native { } [StructLayout(LayoutKind.Sequential)] - internal unsafe partial struct PortfolioAsset + internal unsafe partial struct PortfolioAsset_native { } - [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, - } } diff --git a/FinLib.NET/FinLib/Portfolio.cs b/FinLib.NET/FinLib/Portfolio.cs new file mode 100644 index 0000000..aa11210 --- /dev/null +++ b/FinLib.NET/FinLib/Portfolio.cs @@ -0,0 +1,126 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace FinLib; + +public class Portfolio: IDisposable +{ + private readonly unsafe Portfolio_native* _portfolio; + internal unsafe Portfolio_native* GetPtr() => _portfolio; + + public Portfolio() + { + unsafe + { + _portfolio = NativeMethods.portfolio_new(); + } + } + + public void AddAsset(double portfolioWeight, string assetName, IEnumerable<double> values) + { + unsafe + { + var n = Encoding.UTF8.GetBytes(assetName); + var v = values.ToArray(); + fixed (byte* namePtr = n) + fixed (double* valuesPtr = v){ + NativeMethods.portfolio_add_asset(_portfolio, NativeMethods.portfolio_asset_new(portfolioWeight, namePtr, assetName.Length, valuesPtr, (UIntPtr)v.Length)); + } + } + } + + public bool ValidSize() + { + unsafe + { + return NativeMethods.portfolio_valid_sizes(_portfolio); + } + } + + public bool ValidWeights() + { + unsafe + { + return NativeMethods.portfolio_valid_weights(_portfolio); + } + } + + public bool IsValid() + { + unsafe + { + return NativeMethods.portfolio_is_valid(_portfolio); + } + } + + public void ApplyRatesOfChange() + { + unsafe + { + NativeMethods.portfolio_apply_rates_of_change(_portfolio); + } + } + + public (double, double)? GetMeanAndStdDev() + { + unsafe + { + var ret = NativeMethods.portfolio_get_mean_and_std(_portfolio); + if (ret.is_valid) + { + return (ret.one, ret.two); + } + + return null; + } + } + + public double? ValueAtRisk(double confidence, double initialInvestment) + { + unsafe + { + var ret = NativeMethods.portfolio_value_at_risk(_portfolio, confidence, initialInvestment); + if (ret.is_valid) + { + return ret.val; + } + + return null; + } + } + + public double? ValueAtRiskPercent(double confidence) + { + unsafe + { + var ret = NativeMethods.portfolio_value_at_risk_percent(_portfolio, confidence); + if (ret.is_valid) + { + return ret.val; + } + + return null; + } + } + + private void ReleaseUnmanagedResources() + { + unsafe + { + NativeMethods.portfolio_destroy(_portfolio); + } + } + + public void Dispose() + { + ReleaseUnmanagedResources(); + GC.SuppressFinalize(this); + } + + ~Portfolio() + { + ReleaseUnmanagedResources(); + } +} \ No newline at end of file diff --git a/finlib-cpp/CMakeLists.txt b/finlib-cpp/CMakeLists.txt new file mode 100644 index 0000000..e013ffc --- /dev/null +++ b/finlib-cpp/CMakeLists.txt @@ -0,0 +1,26 @@ +cmake_minimum_required(VERSION 3.19) + + +if(${CMAKE_VERSION} VERSION_LESS 3.19) + cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}) +endif() + +include(FetchContent) + +project(finlib-cpp) + +set(CMAKE_CXX_STANDARD 23) + +set(CMAKE_INSTALL_PREFIX ${PROJECT_SOURCE_DIR}) + +set(FINLIB_INSTALL_INCLUDE_DIR ${PROJECT_SOURCE_DIR}/include) +set(FINLIB_INSTALL_BIN_DIR ${PROJECT_SOURCE_DIR}/bin) +set(FINLIB_INSTALL_LIB_DIR ${PROJECT_SOURCE_DIR}/lib) + +set(FINLIB_HEADERS_DIR ${PROJECT_SOURCE_DIR}/src/finlib) + +include_directories(${FINLIB_INSTALL_INCLUDE_DIR}) +include_directories(${FINLIB_HEADERS_DIR}) + +add_subdirectory(src) +add_subdirectory(test) diff --git a/finlib-cpp/README.md b/finlib-cpp/README.md new file mode 100644 index 0000000..9191209 --- /dev/null +++ b/finlib-cpp/README.md @@ -0,0 +1,3 @@ +# finlib-cpp + +[StackOverflow - How can I build Rust code with a C++/Qt/CMake project?](https://stackoverflow.com/questions/31162438/how-can-i-build-rust-code-with-a-c-qt-cmake-project) \ No newline at end of file diff --git a/finlib-cpp/include/finlib-native.h b/finlib-cpp/include/finlib-native.h deleted file mode 100644 index dea90d7..0000000 --- a/finlib-cpp/include/finlib-native.h +++ /dev/null @@ -1,32 +0,0 @@ -#pragma once - -/* Generated with cbindgen:0.28.0 */ - -/* Warning, this file is autogenerated by cbindgen. Don't modify this manually. */ - -#include <cstdarg> -#include <cstddef> -#include <cstdint> -#include <cstdlib> -#include <ostream> -#include <new> - - -namespace finlib { - - -extern "C" { - -const double *covariance(const double *arr, size_t len, const double *arr_two, size_t len_two); - -const double *historical_value_at_risk(const double *arr, size_t len, double confidence); - -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); - -} // extern "C" - -} // namespace finlib diff --git a/finlib-cpp/include/finlib/finlib-native.h b/finlib-cpp/include/finlib/finlib-native.h new file mode 100644 index 0000000..4358da3 --- /dev/null +++ b/finlib-cpp/include/finlib/finlib-native.h @@ -0,0 +1,85 @@ +#pragma once + +/* Generated with cbindgen:0.28.0 */ + +/* Warning, this file is autogenerated by cbindgen. Don't modify this manually. */ + +#include <cstdarg> +#include <cstddef> +#include <cstdint> +#include <cstdlib> +#include <ostream> +#include <new> + + +namespace finlib { + +/// Describes a Portfolio as a collection of [`PortfolioAsset`]s +struct Portfolio; + +/// Describes a single instrument as a list of previous values with an associated portfolio proportion +struct PortfolioAsset; + +struct NullableFloat +{ + double Val; + bool IsValid; +}; + +struct Tuple +{ + double One; + double Two; + bool IsValid; +}; + + +extern "C" { + +NullableFloat covariance(const double *arr, size_t len, const double *arr_two, size_t len_two); + +NullableFloat historical_value_at_risk(const double *arr, size_t len, double confidence); + +double interest_compound(double principal, double rate, double time, double n); + +void portfolio_add_asset(Portfolio *portfolio, PortfolioAsset *asset); + +void portfolio_apply_rates_of_change(Portfolio *portfolio); + +void portfolio_asset_apply_rates_of_change(PortfolioAsset *asset); + +void portfolio_asset_destroy(PortfolioAsset *asset); + +Tuple portfolio_asset_get_mean_and_std(PortfolioAsset *asset); + +PortfolioAsset *portfolio_asset_new(double portfolio_weight, + const uint8_t *name, + int32_t name_len, + const double *values, + size_t len); + +void portfolio_destroy(Portfolio *portfolio); + +Tuple portfolio_get_mean_and_std(Portfolio *portfolio); + +bool portfolio_is_valid(Portfolio *portfolio); + +Portfolio *portfolio_new(); + +bool portfolio_valid_sizes(Portfolio *portfolio); + +bool portfolio_valid_weights(Portfolio *portfolio); + +NullableFloat portfolio_value_at_risk(Portfolio *portfolio, + double confidence, + double initial_investment); + +NullableFloat portfolio_value_at_risk_percent(Portfolio *portfolio, double confidence); + +double scale_value_at_risk(double initial_value, ptrdiff_t time_cycles); + +NullableFloat varcovar_value_at_risk(const double *arr, size_t len, double confidence); + +} // extern "C" + +} // namespace finlib diff --git a/finlib-cpp/src/CMakeLists.txt b/finlib-cpp/src/CMakeLists.txt new file mode 100644 index 0000000..cbfe99f --- /dev/null +++ b/finlib-cpp/src/CMakeLists.txt @@ -0,0 +1,10 @@ +cmake_minimum_required(VERSION 3.19) +project(finexe) + +add_subdirectory(finlib) +set(SOURCE_FILES main.cpp +) + +add_executable(finexe ${SOURCE_FILES}) +target_link_libraries(finexe finlib) +install(TARGETS finexe DESTINATION ${FINLIB_INSTALL_BIN_DIR}) \ No newline at end of file diff --git a/finlib-cpp/src/finlib/CMakeLists.txt b/finlib-cpp/src/finlib/CMakeLists.txt new file mode 100644 index 0000000..cbf731b --- /dev/null +++ b/finlib-cpp/src/finlib/CMakeLists.txt @@ -0,0 +1,28 @@ +include(ExternalProject) + +project(finlib C CXX) + +set(SOURCE_FILES + finlib.cpp +) + +add_library(finlib SHARED STATIC ${SOURCE_FILES}) + +ExternalProject_Add( + finlibrs + DOWNLOAD_COMMAND "" + CONFIGURE_COMMAND "" + BUILD_COMMAND cargo build + COMMAND cargo build --release + BINARY_DIR "${CMAKE_SOURCE_DIR}/../" + INSTALL_COMMAND "" + LOG_BUILD ON) + +add_dependencies(finlib finlibrs) +target_link_libraries(finlib + debug "${CMAKE_SOURCE_DIR}/../target/debug/libfinlib_ffi.a" + optimized "${CMAKE_SOURCE_DIR}/../target/release/libfinlib_ffi.a" +) + +install(TARGETS finlib DESTINATION ${FINLIB_INSTALL_LIB_DIR}) +install(FILES finlib.h DESTINATION ${FINLIB_INSTALL_INCLUDE_DIR}) \ No newline at end of file diff --git a/finlib-cpp/src/finlib/finlib.cpp b/finlib-cpp/src/finlib/finlib.cpp new file mode 100644 index 0000000..a92e363 --- /dev/null +++ b/finlib-cpp/src/finlib/finlib.cpp @@ -0,0 +1,17 @@ +// +// Created by Andy Pack on 21/02/2025. +// + +#include "finlib.hpp" + +#include <iostream> + +namespace finlib { + void init() { + std::cout << "Initializing..." << std::endl; + + auto ret = interest_compound(100, .05, 1, 1.0); + + std::cout << "Answer: " << ret << std::endl; + } +} diff --git a/finlib-cpp/src/finlib/finlib.hpp b/finlib-cpp/src/finlib/finlib.hpp new file mode 100644 index 0000000..71bd78b --- /dev/null +++ b/finlib-cpp/src/finlib/finlib.hpp @@ -0,0 +1,7 @@ +#pragma once + +#include <finlib/finlib-native.h> + +namespace finlib { + void init(); +} \ No newline at end of file diff --git a/finlib-cpp/src/main.cpp b/finlib-cpp/src/main.cpp new file mode 100644 index 0000000..b20cd1b --- /dev/null +++ b/finlib-cpp/src/main.cpp @@ -0,0 +1,11 @@ +#include <iostream> +#include <finlib.hpp> + +using namespace std; + +int main(int argc, const char *argv[]) { + + finlib::init(); + + return 0; +} \ No newline at end of file diff --git a/finlib-cpp/test/CMakeLists.txt b/finlib-cpp/test/CMakeLists.txt new file mode 100644 index 0000000..f46d352 --- /dev/null +++ b/finlib-cpp/test/CMakeLists.txt @@ -0,0 +1,28 @@ +cmake_minimum_required(VERSION 3.19) +set(CMAKE_CXX_STANDARD 23) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +project(finlib_tests) + +include_directories(${FINLIB_HEADERS_DIR}) + +set(SOURCE_FILES + src/basic_test.cpp +) + +include(FetchContent) +FetchContent_Declare( + googletest + GIT_REPOSITORY https://github.com/google/googletest + GIT_TAG v1.16.0 +) +# For Windows: Prevent overriding the parent project's compiler/linker settings +set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) +FetchContent_MakeAvailable(googletest) + +add_executable(finlib_tests ${SOURCE_FILES}) +target_link_libraries(finlib_tests finlib GTest::gtest_main) +install(TARGETS finlib_tests DESTINATION bin) + +include(GoogleTest) +gtest_discover_tests(finlib_tests) \ No newline at end of file diff --git a/finlib-cpp/test/src/basic_test.cpp b/finlib-cpp/test/src/basic_test.cpp new file mode 100644 index 0000000..5e50c4e --- /dev/null +++ b/finlib-cpp/test/src/basic_test.cpp @@ -0,0 +1,28 @@ +#include <gtest/gtest.h> + +using namespace std; + + +// Demonstrate some basic assertions. +TEST(HelloTest, BasicAssertions) { + // Expect two strings not to be equal. + EXPECT_STRNE("hello", "world"); + // Expect equality. + EXPECT_EQ(7 * 6, 42); +} + +// Demonstrate some basic assertions. +TEST(HelloTest, BasicAssertions2) { + // Expect two strings not to be equal. + EXPECT_STRNE("hello", "world"); + // Expect equality. + EXPECT_EQ(7 * 6, 42); +} + +// Demonstrate some basic assertions. +TEST(HelloTest, BasicAssertions3) { + // Expect two strings not to be equal. + EXPECT_STRNE("hello", "world"); + // Expect equality. + EXPECT_EQ(7 * 6, 42); +} \ No newline at end of file diff --git a/finlib-ffi/Cargo.toml b/finlib-ffi/Cargo.toml index 4056b84..cac44b1 100644 --- a/finlib-ffi/Cargo.toml +++ b/finlib-ffi/Cargo.toml @@ -11,7 +11,7 @@ readme.workspace = true license.workspace = true [lib] -crate-type = ["cdylib"] +crate-type = ["cdylib", "staticlib"] [dependencies] finlib = { path = "../finlib", features = ["ffi"] } diff --git a/finlib-ffi/build.rs b/finlib-ffi/build.rs index 08e65f4..3eff927 100644 --- a/finlib-ffi/build.rs +++ b/finlib-ffi/build.rs @@ -14,24 +14,19 @@ fn main() { .with_config(config) .generate() .expect("Unable to generate bindings") - .write_to_file("../finlib-cpp/include/finlib-native.h"); + .write_to_file("../finlib-cpp/include/finlib/finlib-native.h"); csbindgen::Builder::default() .input_extern_file("src/lib.rs") - .input_extern_file("../finlib/src/lib.rs") + .input_extern_file("src/portfolio.rs") .input_extern_file("../finlib/src/risk/portfolio.rs") - .input_extern_file("../finlib/src/options/blackscholes/mod.rs") .csharp_dll_name("libfinlib_ffi") - .always_included_types([ - "Portfolio", - "ValueType", - "PortfolioAsset", - "OptionVariables", - "CallOption", - "PutOption", - "OptionGreeks", - ]) .csharp_namespace("FinLib") + .csharp_type_rename(|rust_type_name| match rust_type_name.as_str() { // optional, default: `|x| x` + "Portfolio" => "Portfolio_native".into(), + "PortfolioAsset" => "PortfolioAsset_native".into(), + _ => rust_type_name, + }) .generate_csharp_file("../FinLib.NET/FinLib/NativeMethods.g.cs") .unwrap(); } \ No newline at end of file diff --git a/finlib-ffi/src/lib.rs b/finlib-ffi/src/lib.rs index a52cbcf..fb4f934 100644 --- a/finlib-ffi/src/lib.rs +++ b/finlib-ffi/src/lib.rs @@ -1,13 +1,29 @@ -use std::ptr::null; +mod portfolio; + use std::slice; +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct Tuple { + one: f64, + two: f64, + is_valid: bool +} + +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct NullableFloat { + val: f64, + is_valid: bool +} + #[no_mangle] pub extern "C" fn interest_compound(principal: f64, rate: f64, time: f64, n: f64) -> f64 { finlib::interest::compound(principal, rate, time, n) } #[no_mangle] -pub unsafe extern "C" fn covariance(arr: *const f64, len: usize, arr_two: *const f64, len_two: usize) -> *const f64 { +pub unsafe extern "C" fn covariance(arr: *const f64, len: usize, arr_two: *const f64, len_two: usize) -> NullableFloat { let input_array = unsafe { assert!(!arr.is_null()); slice::from_raw_parts(arr, len) @@ -19,30 +35,36 @@ pub unsafe extern "C" fn covariance(arr: *const f64, len: usize, arr_two: *const }; match finlib::stats::covariance(input_array, input_array_two) { - None => null(), - Some(v) => Box::into_raw(Box::new(v)) + None => NullableFloat { val: 0.0, is_valid: false }, + Some(v) => NullableFloat { val: v, is_valid: true } } } #[no_mangle] -pub unsafe extern "C" fn historical_value_at_risk(arr: *const f64, len: usize, confidence: f64) -> *const f64 { +pub unsafe extern "C" fn historical_value_at_risk(arr: *const f64, len: usize, confidence: f64) -> NullableFloat { 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))) + NullableFloat { + val: finlib::risk::var::historical::value_at_risk(input_array, confidence), + is_valid: true + } } #[no_mangle] -pub unsafe extern "C" fn varcovar_value_at_risk(arr: *const f64, len: usize, confidence: f64) -> *const f64 { +pub unsafe extern "C" fn varcovar_value_at_risk(arr: *const f64, len: usize, confidence: f64) -> NullableFloat { let input_array = unsafe { assert!(!arr.is_null()); slice::from_raw_parts(arr, len) }; - Box::into_raw(Box::new(finlib::risk::var::varcovar::value_at_risk_percent(input_array, confidence))) + NullableFloat { + val: finlib::risk::var::varcovar::value_at_risk_percent(input_array, confidence), + is_valid: true + } } #[no_mangle] diff --git a/finlib-ffi/src/portfolio.rs b/finlib-ffi/src/portfolio.rs new file mode 100644 index 0000000..9aee046 --- /dev/null +++ b/finlib-ffi/src/portfolio.rs @@ -0,0 +1,112 @@ +use std::{ptr, slice}; +use finlib::risk::portfolio::{Portfolio, PortfolioAsset}; +use crate::{NullableFloat, Tuple}; + +#[no_mangle] +pub unsafe extern "C" fn portfolio_asset_new(portfolio_weight: f64, name: *const u8, name_len: i32, values: *const f64, len: usize) -> *mut PortfolioAsset { + + if name.is_null() { + return ptr::null_mut(); + } + + let slice = slice::from_raw_parts(name, name_len as usize); + let name = String::from_utf8_unchecked(slice.to_vec()); + + let input_array = unsafe { + assert!(!values.is_null()); + slice::from_raw_parts(values, len) + }; + + + Box::into_raw(Box::new(PortfolioAsset::new(portfolio_weight, name, input_array.to_vec()))) +} + +#[no_mangle] +pub unsafe extern "C" fn portfolio_asset_destroy(asset: *mut PortfolioAsset) { + if !asset.is_null() { + drop(Box::from_raw(asset)); + } +} + +#[no_mangle] +pub unsafe extern "C" fn portfolio_asset_apply_rates_of_change(asset: *mut PortfolioAsset) { + (&mut *asset).apply_rates_of_change(); +} + +#[no_mangle] +pub unsafe extern "C" fn portfolio_asset_get_mean_and_std(asset: *mut PortfolioAsset) -> Tuple { + match (&mut *asset).get_mean_and_std() { + None => Tuple { one: 0.0, two: 0.0, is_valid: false }, + Some((one, two)) => { + Tuple { + one, two, is_valid: true + } + } + } +} + + +#[no_mangle] +pub unsafe extern "C" fn portfolio_new() -> *mut Portfolio { + Box::into_raw(Box::new(Portfolio::from(vec![]))) +} + +#[no_mangle] +pub unsafe extern "C" fn portfolio_destroy(portfolio: *mut Portfolio) { + if !portfolio.is_null() { + drop(Box::from_raw(portfolio)); + } +} + +#[no_mangle] +pub unsafe extern "C" fn portfolio_add_asset(portfolio: *mut Portfolio, asset: *mut PortfolioAsset) { + (&mut *portfolio).add_asset((*asset).clone()); +} + +#[no_mangle] +pub unsafe extern "C" fn portfolio_apply_rates_of_change(portfolio: *mut Portfolio) { + (&mut *portfolio).apply_rates_of_change() +} + +#[no_mangle] +pub unsafe extern "C" fn portfolio_valid_sizes(portfolio: *mut Portfolio) -> bool { + (&mut *portfolio).valid_sizes() +} + +#[no_mangle] +pub unsafe extern "C" fn portfolio_valid_weights(portfolio: *mut Portfolio) -> bool { + (&mut *portfolio).valid_weights() +} + +#[no_mangle] +pub unsafe extern "C" fn portfolio_is_valid(portfolio: *mut Portfolio) -> bool { + (&mut *portfolio).is_valid() +} + +#[no_mangle] +pub unsafe extern "C" fn portfolio_get_mean_and_std(portfolio: *mut Portfolio) -> Tuple { + match (&mut *portfolio).get_mean_and_std() { + None => Tuple { one: 0.0, two: 0.0, is_valid: false }, + Some((one, two)) => { + Tuple { + one, two, is_valid: true + } + } + } +} + +#[no_mangle] +pub unsafe extern "C" fn portfolio_value_at_risk(portfolio: *mut Portfolio, confidence: f64, initial_investment: f64) -> NullableFloat { + match (&mut *portfolio).value_at_risk(confidence, initial_investment) { + None => NullableFloat { val: 0.0, is_valid: false }, + Some(v) => NullableFloat { val: v, is_valid: true } + } +} + +#[no_mangle] +pub unsafe extern "C" fn portfolio_value_at_risk_percent(portfolio: *mut Portfolio, confidence: f64) -> NullableFloat { + match (&mut *portfolio).value_at_risk_percent(confidence) { + None => NullableFloat { val: 0.0, is_valid: false }, + Some(v) => NullableFloat { val: v, is_valid: true } + } +} diff --git a/finlib/src/options/blackscholes/option_surface.rs b/finlib/src/options/blackscholes/option_surface.rs index cca2100..a77b071 100644 --- a/finlib/src/options/blackscholes/option_surface.rs +++ b/finlib/src/options/blackscholes/option_surface.rs @@ -1,6 +1,5 @@ +use crate::options::blackscholes::{OptionVariables}; use core::ops::Range; -use std::sync::{Arc, Mutex}; -use crate::options::blackscholes::{CallOption, Option, OptionVariables, PutOption}; pub struct OptionSurface { underlying_price: Range<isize>, @@ -84,30 +83,30 @@ impl OptionSurface { #[cfg(test)] mod tests { - use crate::options::blackscholes::{generate_options, CallOption, Option, PutOption}; use super::*; + use crate::options::blackscholes::generate_options; #[test] fn walk_test() { let w = OptionSurface::from( - (0 .. 50), + 0 .. 50, (100., 200.), - (0 .. 50), + 0 .. 50, (100., 200.), - (0 .. 5), + 0 .. 5, (0.25, 0.50), - (0 .. 10), + 0 .. 10, (0.05, 0.08), - (0 .. 1), + 0 .. 1, (0.01, 0.02), - (0 .. 10), + 0 .. 10, (30./365.25, 30./365.25), ); let a = w.walk(); - let options = generate_options(&a); + let _ = generate_options(&a); - let a1 = a.first(); + let _ = a.first(); } } \ No newline at end of file diff --git a/finlib/src/risk/portfolio.rs b/finlib/src/risk/portfolio.rs index 64231ed..fa7e5d6 100644 --- a/finlib/src/risk/portfolio.rs +++ b/finlib/src/risk/portfolio.rs @@ -36,10 +36,10 @@ pub enum ValueType { #[cfg_attr(feature = "ffi", repr(C))] #[derive(Clone, Debug, PartialEq, PartialOrd)] pub struct PortfolioAsset { - portfolio_weight: f64, + pub portfolio_weight: f64, name: String, values: Vec<f64>, - value_type: ValueType + pub value_type: ValueType } impl PortfolioAsset { @@ -84,6 +84,10 @@ impl Portfolio { } } + pub fn add_asset(&mut self, asset: PortfolioAsset) { + self.assets.push(asset); + } + /// Return the proportions of a portfolio's assets /// /// In a properly formed Portfolio these will add up to 1.0 diff --git a/finlib/src/risk/var/varcovar.rs b/finlib/src/risk/var/varcovar.rs index 0be44ad..8b7c1c0 100644 --- a/finlib/src/risk/var/varcovar.rs +++ b/finlib/src/risk/var/varcovar.rs @@ -1,7 +1,6 @@ use crate::stats; use crate::util::roc::rates_of_change; -use rayon::prelude::*; use statrs::distribution::{ContinuousCDF, Normal}; // https://medium.com/@serdarilarslan/value-at-risk-var-and-its-implementation-in-python-5c9150f73b0e