A lightweight C11 library for volatility forecasting and position sizing with time-series-safe evaluation utilities.
mlrisk is a lightweight reference implementation of a C11 static library focused on correctness and portability for quantitative finance applications. It provides essential tools for:
- 📊 Volatility Forecasting: EWMA-based volatility estimation with optional linear regression models
- 📈 Position Sizing: Volatility targeting and risk capping with leverage constraints
- 🔄 Time-Series Utilities: Walk-forward splits for backtesting without lookahead bias
- 📉 Rolling Statistics: Numerically stable rolling mean, standard deviation, and EWMA calculations
Perfect for algorithmic trading systems, risk management tools, and quantitative research.
Disclaimer: This project is for educational and research purposes only and does not constitute financial, investment, or trading advice. Trading involves risk, including the possible loss of principal. Use at your own risk.
✨ Focused on Correctness & Portability
- Pure C11, zero external dependencies
- Cross-platform (Windows, macOS, Linux)
- Comprehensive unit tests with 100% pass rate
- Numerically stable algorithms (Welford's method)
🛡️ Robust & Safe
- Complete input validation and error handling
- Time-series safe (no lookahead bias)
- Memory-safe with proper resource management
⚡ Performance
- Static library for minimal overhead
- Optimized for small to medium datasets
- Efficient walk-forward evaluation utilities
git clone https://github.com/haeganm/ML-Library---Risk-Position-Sizing.git
cd ML-Library---Risk-Position-Sizing# Configure and build from repo root
cmake -S . -B build
cmake --build build --config Release# Run all tests
ctest --test-dir build -C Release --output-on-failure
# Or run directly
./build/Release/mlrisk_tests # Linux/macOS
.\build\Release\mlrisk_tests.exe # Windows./build/Release/vol_target_demo # Linux/macOS
.\build\Release\vol_target_demo.exe # Windows- CMake 3.15 or higher (Download)
- C11-compatible compiler:
- GCC 4.9+ or Clang 3.3+ (Linux/macOS)
- MSVC 2015+ (Windows)
- Or any compiler supporting C11 standard
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
cmake --build buildcmake -S . -B build
cmake --build build --config Releasecmake -S . -B build -G "MinGW Makefiles" -DCMAKE_BUILD_TYPE=Release
cmake --build build# From project root directory
ctest --test-dir build -C Release --output-on-failure
# For Debug configuration
ctest --test-dir build -C Debug --output-on-failure#include "mlrisk/mlrisk.h"
#include <stdio.h>
int main(void) {
// Sample returns data
double returns[] = {0.01, -0.02, 0.015, -0.01, 0.02};
double prices[] = {100.0, 99.0, 100.5, 99.5, 101.5};
size_t n = 5;
// 1. Compute EWMA volatility
double sigma[5]; // Fixed size array (MSVC compatible)
mlr_status status = risk_forecast_ewma(returns, n, 0.94, sigma);
if (status != MLR_OK) {
fprintf(stderr, "Error computing volatility\n");
return 1;
}
// 2. Compute position sizes using volatility targeting
double positions[5]; // Fixed size array (MSVC compatible)
status = vol_target_position(
sigma, // volatility forecast
0.01, // target volatility (1% per-period)
100000.0, // account equity ($100k)
prices, // asset prices
2.0, // max leverage (2x)
n,
positions
);
if (status != MLR_OK) {
fprintf(stderr, "Error computing positions\n");
return 1;
}
// 3. Print results
for (size_t i = 0; i < n; i++) {
printf("Period %zu: sigma=%.6f, position=%.2f shares\n",
i, sigma[i], positions[i]);
}
return 0;
}Add to your CMakeLists.txt:
# Find or add mlrisk
add_subdirectory(path/to/mlrisk)
# Link to your target
target_link_libraries(your_target PRIVATE mlrisk)
target_include_directories(your_target PRIVATE path/to/mlrisk/include)// Compute rolling mean
mlr_status rolling_mean(const double *x, size_t n, size_t window, double *out);
// Compute rolling standard deviation (numerically stable)
mlr_status rolling_std(const double *x, size_t n, size_t window, double *out);
// Compute EWMA volatility (per-period sigma)
mlr_status ewma_vol(const double *returns, size_t n, double lambda, double *out);// EWMA-based volatility forecast
mlr_status risk_forecast_ewma(const double *returns, size_t n, double lambda, double *sigma_out);
// Linear model for volatility forecasting
mlr_lin_model model;
mlr_lin_model_init(&model, feature_dim, ridge_param);
// ... use model ...
mlr_lin_model_free(&model);// Volatility targeting
mlr_status vol_target_position(
const double *sigma, // per-period volatility
double target_vol, // target per-period volatility
double equity, // account equity
const double *price, // asset prices
double max_leverage, // maximum leverage multiplier
size_t n,
double *position_out
);
// Risk capping
mlr_status risk_cap_position(
const double *sigma,
double risk_cap, // max risk per position (fraction of equity)
double equity,
const double *price,
double max_leverage,
size_t n,
double *position_out
);// Generate train/test splits for time-series cross-validation
mlr_range ranges[100];
size_t count = walk_forward_ranges(
n, // total data length
train_len, // training window size
test_len, // test window size
step, // step size between splits
ranges // output array
);// Fit ridge regression model
mlr_status linreg_fit(
const double *X, // feature matrix (n rows, d columns)
const double *y, // target vector
size_t n, // number of samples
size_t d, // number of features
double ridge, // ridge regularization parameter
mlr_lin_model *model_out
);
// Predict using fitted model
mlr_status linreg_predict(
const double *X,
size_t n,
size_t d,
const mlr_lin_model *model,
double *out
);All functions return mlr_status:
MLR_OK- SuccessMLR_EINVAL- Invalid argumentMLR_ENOMEM- Memory allocation failureMLR_EBOUNDS- Index out of boundsMLR_EDOMAIN- Domain error
Important: mlrisk uses per-period volatility throughout.
To convert annualized volatility to per-period:
per_period_vol = annualized_vol / sqrt(periods_per_year)
Examples:
- Daily data:
daily_vol = annualized_vol / sqrt(252) - Weekly data:
weekly_vol = annualized_vol / sqrt(52) - Monthly data:
monthly_vol = annualized_vol / sqrt(12)
Typical lambda values:
- Daily data: 0.94 - 0.97
- Higher lambda = slower decay = more persistent volatility
The walk_forward_ranges() function ensures no lookahead bias:
- ✅ Training data always comes before test data
- ✅ No overlap between training and test sets
- ✅
test_start >= train_endfor all ranges
Best Practice: When backtesting, always use walk-forward splits to avoid overfitting and unrealistic performance estimates.
This project is licensed under the MIT License - see the LICENSE file for details.
Contributions are welcome! Please feel free to submit a Pull Request.
Made with ❤️ for quantitative finance