Skip to main content

GRU-based Dynamic Beta Estimation for Time-Varying Systematic Risk

Project description

GRU Dynamic Beta

PyPI version Python 3.8+ License: MIT Documentation Status

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:

  1. Accuracy: Huber loss on return predictions
  2. Stability: L2 penalty on beta changes (temporal smoothness)
  3. 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 model
  • predict(stock_returns, market_returns, ...) - Predict beta for new data
  • fit_predict(stock_returns, market_returns, ...) - Fit and predict with walk-forward validation
  • save(path) - Save model to disk
  • load(path) - Load model from disk
  • plot_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 preprocessing
  • prepare(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 evaluation
  • compare_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:

  1. Feature Lagging: All features are lagged by 1 day (configurable)
  2. Walk-Forward Validation: Model only sees historical data at each prediction point
  3. Expanding Window Scaling: Scalers are refit on expanding historical data only
  4. 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{grubeta2026,
  author = {Yılmaz, Ahmet Selim},
  title = {GRU Dynamic Beta: Neural Network-Based Time-Varying Systematic Risk Estimation},
  year = {2026},
  url = {https://github.com/aslmylmz/grubeta}
}

Related Work

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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

grubeta-0.1.1.tar.gz (60.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

grubeta-0.1.1-py3-none-any.whl (31.8 kB view details)

Uploaded Python 3

File details

Details for the file grubeta-0.1.1.tar.gz.

File metadata

  • Download URL: grubeta-0.1.1.tar.gz
  • Upload date:
  • Size: 60.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.11.14

File hashes

Hashes for grubeta-0.1.1.tar.gz
Algorithm Hash digest
SHA256 b7504e42f023185d73d70d227d26fbe8292c3bb4f8bc3a015b7e61ef388896f2
MD5 45cc97d69ab90f1ef80720fe705ac631
BLAKE2b-256 2ff8009c18c0f29d6e39f2af47fdb9fddc6d34bf6a9ad9f118ee95f5b04c2ab2

See more details on using hashes here.

File details

Details for the file grubeta-0.1.1-py3-none-any.whl.

File metadata

  • Download URL: grubeta-0.1.1-py3-none-any.whl
  • Upload date:
  • Size: 31.8 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.11.14

File hashes

Hashes for grubeta-0.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 8e7d6c26b6d178d36eca4fc3ca260b6a325e0ad0fc644008314e4996defc62de
MD5 d0bb241abddf7f277d8be9fb8f4a6d91
BLAKE2b-256 172303536ca70593158063f94a57bf96a8c02ca5abb3e86e9bbdf3bd1b4e0c5b

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page