Skip to main content

Estimate time-varying CAPM beta with neural networks — for finance professionals and researchers

Project description

grubeta — Dynamic Beta Estimation

Python 3.9+ License: MIT PyPI version Documentation Status

Estimate how a stock's market sensitivity (beta) changes over time, powered by neural networks with built-in safeguards against lookahead bias.

What is Dynamic Beta?

In the CAPM framework, beta (β) measures how much a stock moves relative to the market:

Beta Meaning Example
β > 1 Amplifies market moves — higher risk, higher expected return Growth/tech stocks
β = 1 Moves with the market Index-tracking ETFs
β < 1 Dampens market moves — lower risk, lower expected return Utilities, consumer staples

Traditional beta is estimated as a single static number over a historical window. But in reality, beta changes over time. A stock's market sensitivity shifts during crises, earnings announcements, sector rotations, and regime changes.

grubeta captures these dynamics using a neural network that learns temporal patterns in beta, while ensuring all estimates are free from lookahead bias through walk-forward validation.

Quick Start

from grubeta import estimate_beta

# Estimate AAPL's time-varying beta relative to S&P 500
result = estimate_beta("AAPL", "SPY")
print(result["summary"])

Output:

AAPL Dynamic Beta Summary (2016-03-19 to 2026-03-19)
─────────────────────────────────────────────────────
  Current Beta:     1.18
  Average Beta:     1.12
  Beta Range:       0.85 → 1.42
  Stability:        0.034 (daily change std)
  Systematic R²:    0.52

  Interpretation: AAPL currently amplifies S&P 500 moves by ~18%.
  A 1% market drop implies a ~1.18% drop in AAPL.

Example dynamic beta trajectory

Installation

pip install grubeta              # core package
pip install grubeta[data]        # + yfinance for ticker symbol support
pip install grubeta[full]        # + technical indicators (RSI, MACD, etc.)

Use Cases

Portfolio Hedging

from grubeta import estimate_beta

result = estimate_beta("AAPL", "SPY")
current_beta = result["beta"].iloc[-1]

# To hedge $100K in AAPL against market risk:
portfolio_value = 100_000
hedge_amount = current_beta * portfolio_value
print(f"Short ${hedge_amount:,.0f} of SPY to neutralize market exposure")

Comparing Stocks

from grubeta import compare_betas

# Compare tech vs defensive stocks
result = compare_betas(["NVDA", "TSLA", "JNJ", "PG"], market="SPY")
print(result["summary"])

Preset Configurations

Most users don't need to tune parameters — just pick a preset:

# For event studies (captures rapid changes around earnings, crises)
result = estimate_beta("AAPL", "SPY", preset="responsive")

# For long-term portfolio construction (stable, low-noise estimates)
result = estimate_beta("AAPL", "SPY", preset="smooth")

# For academic research (all features, maximum rigor)
result = estimate_beta("AAPL", "SPY", preset="research")
Preset Best For Lookback Retraining
default General analysis 3 months (60 days) Semi-annually (126 days)
responsive Event studies, tactical allocation 6 weeks (30 days) Monthly (21 days)
smooth Strategic portfolio construction 6 months (120 days) Annually (252 days)
research Academic papers, enhanced model capacity ~4 months (90 days) Quarterly (63 days)

Command Line

python -m grubeta AAPL SPY
python -m grubeta AAPL SPY --preset responsive
python -m grubeta AAPL MSFT GOOGL --market SPY --compare
python -m grubeta --list-presets

Examples & Tutorials

Notebook Description Audience
Quickstart Estimate beta in 3 lines of code Everyone
Portfolio Hedging Calculate and apply hedge ratios Portfolio managers
Comparing Stocks Cross-sectional beta analysis Analysts
Research Workflow Full academic pipeline with diagnostics Researchers
Complete API Example All API features with synthetic data Developers

Advanced Usage

For users who want fine-grained control, the full API is available:

from grubeta import DynamicBeta, DynamicBetaConfig, DataPreprocessor, FeatureConfig

# Custom feature engineering
config = FeatureConfig(
    include_technicals=True,
    include_macro=True,
    lag_features=True,  # Prevents lookahead bias
)
preprocessor = DataPreprocessor(config)
features = preprocessor.prepare(stock_df, market_df, macro_df)

# Custom model configuration
model = DynamicBeta(config=DynamicBetaConfig(
    lookback=90,
    lambda_beta=0.05,
    lambda_alpha=0.5,
    lambda_alpha_smooth=0.1,
))
results = model.fit_predict(**features)

# Evaluation
from grubeta import BetaEvaluator
evaluator = BetaEvaluator(output_dir='./results')
metrics = evaluator.evaluate(results['beta'], results['stock_return'], results['market_return'])
All Configuration Parameters
Parameter Default Description
lookback 90 How many days of history the model sees at each step
initial_train_size 500 Minimum training samples before first prediction (~2 years)
wf_step_size 126 How often the model retrains (trading days)
batch_size 20 Training batch size
learning_rate 1e-4 Adam optimizer learning rate
gru_units 128 Neural network hidden layer size
dropout_rate 0.2 Regularization strength
lambda_beta 0.05 Beta smoothness — higher = smoother beta
lambda_alpha 0.5 Alpha sparsity — higher = alpha pushed closer to zero
lambda_alpha_smooth 0.1 Alpha smoothness — higher = smoother alpha
epochs_init 40 Training epochs for initial fit
epochs_retrain 4 Training epochs for each retraining step

How It Works

Technical Details (click to expand)

grubeta uses a dual-pathway GRU architecture within the CAPM framework:

Market Features ——→ [GRU] ——→ β(t)
                               ↓
                     R_stock = α(t) + β(t) × R_market
                               ↑
Stock Features ——→ [GRU] ——→ α(t)

The composite loss function balances four objectives:

L = L_accuracy + λ_β × L_stability + λ_α × L_sparsity + λ_α_smooth × L_alpha_stability
  1. Accuracy: Huber loss on return predictions
  2. Beta Stability: L2 penalty on temporal beta changes
  3. Alpha Sparsity: L1 penalty on alpha magnitude (CAPM compliance)
  4. Alpha Stability: L2 penalty on temporal alpha changes

Lookahead Bias Prevention

grubeta implements strict temporal integrity:

  • Feature lagging: All inputs delayed by 1+ days
  • Walk-forward validation: Model only sees historical data at each prediction point
  • Point-in-Time scaling: Feature normalization uses only past data (expanding window)
  • TemporalCertificate: Auditable proof of zero lookahead for academic review
  • Built-in diagnostics: validate_no_lookahead() verifies temporal correctness

Comparison with Other Methods

Method Time-Varying Non-linear Lookahead Free Walk-Forward
Static OLS N/A
Rolling OLS
EWMA
Kalman Filter
DCC-GARCH Partial
grubeta

For Researchers

  • Temporal integrity: All estimates guaranteed lookahead-free via walk-forward validation and point-in-time scaling
  • Reproducibility: TemporalCertificate provides auditable methodology proof
  • Benchmarks: Built-in comparison with rolling OLS, EWMA, and static beta
  • Diagnostics: Lookahead bias tests, ADF stationarity tests, Ljung-Box autocorrelation tests

Full API documentation: grubeta.readthedocs.io

Citation

@software{grubeta2026,
  author = {Yılmaz, Ahmet Selim},
  title = {grubeta: GRU-based Dynamic Beta Estimation for Time-Varying Systematic Risk},
  year = {2026},
  url = {https://github.com/aslmylmz/grubeta}
}

Contributing

Contributions welcome! 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.3.tar.gz (89.4 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.3-py3-none-any.whl (49.4 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: grubeta-0.1.3.tar.gz
  • Upload date:
  • Size: 89.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for grubeta-0.1.3.tar.gz
Algorithm Hash digest
SHA256 9725ddf5f582a2755d0127aab60e16f96fd347b46c9afb61c3ebe569f01f2a28
MD5 bba46b536e6823758c39f24dacadc70b
BLAKE2b-256 88dab216fdb77dd6466e5b48d6cc8ad89a494594919e91a25bfc1b03b3e9b5eb

See more details on using hashes here.

Provenance

The following attestation bundles were made for grubeta-0.1.3.tar.gz:

Publisher: publish.yml on aslmylmz/grubeta

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

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

File metadata

  • Download URL: grubeta-0.1.3-py3-none-any.whl
  • Upload date:
  • Size: 49.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for grubeta-0.1.3-py3-none-any.whl
Algorithm Hash digest
SHA256 88140a4b6d5f5180c974a442d4fce41a301c7f392c3de934eed46334b60dec9a
MD5 e8e64941541786114b76fd50e391bbe4
BLAKE2b-256 4bb2fbfad50ff7d373ad9442e1c18d45edb1a495fc84d6abeebbe30162d162a3

See more details on using hashes here.

Provenance

The following attestation bundles were made for grubeta-0.1.3-py3-none-any.whl:

Publisher: publish.yml on aslmylmz/grubeta

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

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