GRU-based Dynamic Beta Estimation for Time-Varying Systematic Risk
Project description
GRU Dynamic Beta
GRU Dynamic Beta is a Python library for estimating time-varying systematic risk (beta) using Gated Recurrent Unit neural networks within the CAPM framework.
Key Features
- 🧠 GRU-based estimation: Captures complex temporal patterns in beta dynamics
- 📊 Walk-forward validation: Prevents lookahead bias with proper out-of-sample testing
- ⚖️ Composite loss function: Balances prediction accuracy, beta stability, and alpha sparsity
- 🔧 Flexible input modes: Use simple returns or full feature engineering
- 📈 Built-in evaluation: Comprehensive metrics and benchmark comparisons
- 🚀 Production-ready: GPU support, model persistence, and extensive documentation
Installation
Basic Installation
pip install grubeta
Full Installation (with technical analysis features)
pip install grubeta[full]
Development Installation
git clone https://github.com/aslmylmz/grubeta.git
cd grubeta
pip install -e ".[dev]"
Quick Start
Simple Usage (Returns Only)
The simplest way to estimate dynamic beta using only stock and market returns:
import pandas as pd
from grubeta import DynamicBeta
# Load your data
stock_returns = pd.read_csv('stock_data.csv')['return']
market_returns = pd.read_csv('market_data.csv')['return']
# Estimate dynamic beta
model = DynamicBeta(lookback=60)
results = model.fit_predict(stock_returns, market_returns)
# View results
print(results[['beta', 'alpha']].dropna().describe())
# Plot beta evolution
model.plot_beta(results, title='Dynamic Beta - AAPL')
Advanced Usage (With Features)
For better results, use the full preprocessing pipeline:
from grubeta import DynamicBeta, DataPreprocessor, FeatureConfig
# Configure feature engineering
config = FeatureConfig(
include_technicals=True,
include_macro=True,
lag_features=True # Critical for preventing lookahead bias
)
# Prepare features
preprocessor = DataPreprocessor(config)
features = preprocessor.prepare(
stock_df=stock_ohlcv, # OHLCV data
market_df=market_ohlcv, # Market index OHLCV
macro_df=macro_data # Macroeconomic indicators
)
# Estimate with features
model = DynamicBeta(
lookback=90,
lambda_beta=0.05, # Beta stability weight
lambda_alpha=0.5, # Alpha sparsity weight
)
results = model.fit_predict(**features)
Model Evaluation
from grubeta import BetaEvaluator
from grubeta.utils import rolling_ols_beta
# Create evaluator
evaluator = BetaEvaluator(output_dir='./results')
# Compute benchmark
ols_beta = rolling_ols_beta(
results['stock_return'],
results['market_return'],
window=252
)
# Compare models
comparison = evaluator.compare_models(
{
'GRU': results['beta'].values,
'Rolling OLS': ols_beta,
},
results['stock_return'].values,
results['market_return'].values,
)
print(comparison)
Architecture
The GRU Dynamic Beta model uses a dual-pathway architecture:
Market Features ──→ [GRU] ──→ Beta(t)
↓
R_stock = α + β × R_market
↑
Stock Features ──→ [GRU] ──→ Alpha(t)
Loss Function
The composite loss balances three objectives:
- Accuracy: Huber loss on return predictions
- Stability: L2 penalty on beta changes (temporal smoothness)
- Sparsity: L1 penalty on alpha (CAPM compliance)
L = L_accuracy + λ_β × L_stability + λ_α × L_sparsity
Configuration
Model Hyperparameters
| Parameter | Default | Description |
|---|---|---|
lookback |
90 | Sequence length for GRU input |
initial_train_size |
500 | Samples for initial training |
wf_step_size |
126 | Walk-forward step size |
learning_rate |
1e-4 | Adam optimizer learning rate |
gru_units |
128 | GRU hidden units |
dropout_rate |
0.2 | Dropout regularization |
lambda_beta |
0.05 | Beta stability weight |
lambda_alpha |
0.5 | Alpha sparsity weight |
Feature Configuration
from grubeta import FeatureConfig
config = FeatureConfig(
lag_features=True, # Prevent lookahead bias
include_technicals=True, # RSI, MACD, ADX, etc.
include_macro=True, # Macro indicators
include_volume=True, # Volume-based features
include_calendar=True, # Day of week, month end, etc.
ma_windows=[5, 10, 20, 50, 100, 200],
volatility_windows=[5, 10, 20, 60],
)
API Reference
Core Classes
DynamicBeta
Main estimator class for GRU-based dynamic beta estimation.
DynamicBeta(
config: DynamicBetaConfig = None,
lookback: int = 90,
lambda_beta: float = 0.05,
lambda_alpha: float = 0.5,
**kwargs
)
Methods:
fit(stock_returns, market_returns, ...)- Fit the modelpredict(stock_returns, market_returns, ...)- Predict beta for new datafit_predict(stock_returns, market_returns, ...)- Fit and predict with walk-forward validationsave(path)- Save model to diskload(path)- Load model from diskplot_beta(results, ...)- Visualize beta trajectory
DataPreprocessor
Feature engineering pipeline with lookahead bias prevention.
DataPreprocessor(config: FeatureConfig = None)
Methods:
prepare_simple(stock_returns, market_returns, ...)- Minimal preprocessingprepare(stock_df, market_df, macro_df)- Full feature engineering
BetaEvaluator
Evaluation suite for dynamic beta estimates.
BetaEvaluator(output_dir: str = None)
Methods:
evaluate(betas, stock_returns, market_returns, ...)- Comprehensive evaluationcompare_models(betas_dict, stock_returns, market_returns)- Model comparison
Utility Functions
from grubeta.utils import (
validate_no_lookahead, # Check for lookahead bias
rolling_ols_beta, # Benchmark beta calculation
align_series, # Align multiple time series
compute_information_coefficient, # Calculate IC
)
Input Data Formats
Simple Mode (Returns Only)
# Pandas Series or NumPy arrays
stock_returns = df['close'].pct_change()
market_returns = market_df['close'].pct_change()
Full Mode (OHLCV + Macro)
Stock/Market OHLCV DataFrame:
| Date | Open | High | Low | Close | Volume |
|------------|--------|--------|--------|--------|-----------|
| 2020-01-02 | 100.00 | 101.50 | 99.50 | 101.00 | 1000000 |
| 2020-01-03 | 101.00 | 102.00 | 100.50 | 101.50 | 1200000 |
Macro DataFrame (optional):
| Date | vix | fed_rate | unemployment | ... |
|------------|--------|----------|--------------|------|
| 2020-01-02 | 12.5 | 1.75 | 3.6 | ... |
| 2020-01-03 | 13.2 | 1.75 | 3.6 | ... |
Lookahead Bias Prevention
GRU Dynamic Beta implements strict lookahead bias prevention:
- Feature Lagging: All features are lagged by 1 day (configurable)
- Walk-Forward Validation: Model only sees historical data at each prediction point
- Expanding Window Scaling: Scalers are refit on expanding historical data only
- Built-in Validation:
validate_no_lookahead()function to verify
from grubeta.utils import validate_no_lookahead
# Check for lookahead bias
passed = validate_no_lookahead(
results['beta'].values,
results['stock_return'].values,
results['market_return'].values,
initial_size=500
)
# Output:
# ✓ Beta correlation with return t+1: 0.023
# ✓ Beta correlation with return t+5: 0.018
# ✓ Beta correlation with return t+10: 0.012
# ✓ Beta correlation with return t+20: 0.008
Examples
Portfolio Hedging
from grubeta import DynamicBeta
from grubeta.utils import beta_to_hedge_ratio
# Estimate beta
model = DynamicBeta()
results = model.fit_predict(stock_returns, market_returns)
# Current beta
current_beta = results['beta'].iloc[-1]
# Calculate hedge ratio
stock_position = 100_000 # $100k position
spy_price = 450 # SPY price
hedge = beta_to_hedge_ratio(current_beta, stock_position, spy_price)
print(f"To hedge: short {hedge:.0f} shares of SPY")
Multi-Asset Analysis
from grubeta import DynamicBeta, BetaEvaluator
stocks = ['AAPL', 'GOOGL', 'MSFT', 'AMZN']
results = {}
model = DynamicBeta(lookback=60)
for stock in stocks:
stock_ret = load_returns(stock)
results[stock] = model.fit_predict(stock_ret, market_returns)
# Compare beta dynamics
import matplotlib.pyplot as plt
fig, ax = plt.subplots(figsize=(12, 6))
for stock, res in results.items():
ax.plot(res['date'], res['beta'], label=stock)
ax.legend()
ax.set_title('Dynamic Beta Comparison')
plt.show()
Comparison with Other Methods
| Method | Time-Varying | Captures Non-linearity | Lookahead Free | Walk-Forward |
|---|---|---|---|---|
| Static OLS | ❌ | ❌ | ✅ | N/A |
| Rolling OLS | ✅ | ❌ | ✅ | ✅ |
| EWMA | ✅ | ❌ | ✅ | ✅ |
| Kalman Filter | ✅ | ❌ | ✅ | ✅ |
| DCC-GARCH | ✅ | Partial | ✅ | ✅ |
| GRU Dynamic Beta | ✅ | ✅ | ✅ | ✅ |
Citation
If you use GRU Dynamic Beta in your research, please cite:
@software{grubeta2025,
author = {Yılmaz, Ahmet Selim},
title = {GRU Dynamic Beta: Neural Network-Based Time-Varying Systematic Risk Estimation},
year = {2025},
url = {https://github.com/aslmylmz/grubeta}
}
Related Work
- arch - GARCH models
- pykalman - Kalman Filter
- statsmodels - Statistical models
- ta - Technical Analysis library
Contributing
Contributions are welcome! Please see CONTRIBUTING.md for guidelines.
License
MIT License - see LICENSE for details.
Changelog
See CHANGELOG.md for version history.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file grubeta-0.1.0.tar.gz.
File metadata
- Download URL: grubeta-0.1.0.tar.gz
- Upload date:
- Size: 61.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a08fdbfebfd30ca0c6452b374b9f43defc7ab33555e3781ada85cc9978753daf
|
|
| MD5 |
ce1afabcbb1fb54129c2e3cacedc6b9b
|
|
| BLAKE2b-256 |
5dbddc0ad141e3769824950c22b4ebba7a34fedd99a1294ac799e5be39dc2180
|
File details
Details for the file grubeta-0.1.0-py3-none-any.whl.
File metadata
- Download URL: grubeta-0.1.0-py3-none-any.whl
- Upload date:
- Size: 31.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
70e13b48953cfd94661888c7f3f462a74810d335c44a6d9838ae344699ea35e6
|
|
| MD5 |
600edfb8f938ab177fac92197b3307c8
|
|
| BLAKE2b-256 |
cdd63998038993f0f2f541a67244de385c78a1c370f189e9db29735e387985aa
|