regression.estimate_betas()
Estimate rolling betas.
Usage
regression.estimate_betas(
data,
model,
lookback,
min_obs=None,
id_col="permno",
)Estimates rolling betas for a given model using the provided data. For each stock, the regression specified by ‘model’ is fit over a rolling calendar window of length ‘lookback’ (e.g. ‘“60mo”’ for sixty months).
The estimator avoids refitting a full regression for every window. Instead it accumulates the per-observation cross-products that define the normal equations (the design Gram matrix ‘X’X’ and the moment vector ‘X’y’), takes their rolling sums via cumulative-sum differencing, and solves the resulting small linear system once per window. This closed-form approach follows the fast beta estimation described at https://www.tidy-finance.org/blog/fast-beta-estimation/ and is considerably faster than looping rolling regressions while returning the same coefficients.
Parameters
data: pl.DataFrame-
Data frame containing the data with a date identifier (defaults to ‘date’), a stock identifier (defaults to ‘permno’), and the other variables used in the model.
model: str-
Formula describing the model to be estimated (e.g., ‘ret_excess ~ mkt_excess + hml + smb’). An intercept is included unless the formula ends in ‘- 1’ (or ‘+ 0’).
lookback: str or int-
Rolling window length. Pass a duration string — a positive count followed by one of ‘“mo”’ (months), ‘“d”’ (days), ‘“h”’, ‘“m”’, ‘“s”’ — to roll over calendar periods, e.g. ‘“60mo”’. The window for a period ‘v’ spans every observation falling in the ‘lookback’ periods ending at ‘v’, so gaps in a stock’s history consume window space, and one row is returned per stock and period. Sub-day units require a datetime column.
Passing a plain integer selects the legacy behaviour — a window of that many consecutive observations, one output row per input row — and emits a ‘DeprecationWarning’.
min_obs: int = None-
Minimum number of observations required to estimate the model. Defaults to ‘round(0.8 * lookback)’.
id_col: str = "permno"- Column name representing the stock identifier.
Returns
pl.DataFrame-
Data frame with the estimated betas for each stock and time period. Contains the stock identifier and the ‘date’ column, followed by one column per model term: an ‘intercept’ column (when the model includes one) and one ‘beta_
With a calendar ‘lookback’ there is one row per stock and period, ‘date’ is floored to the start of the period, and windows with fewer than ‘min_obs’ observations are dropped from the output. With the deprecated integer ‘lookback’ there is one row per input row, ‘date’ is the observation’s own date, and sub-‘min_obs’ windows yield null coefficients.’ column per regressor.
Examples
from datetime import date
import numpy as np
import polars as pl
from tidyfinance import estimate_betas
rng = np.random.default_rng(1234)
dates = pl.date_range(
date(2020, 1, 1), date(2020, 12, 1), "1mo", eager=True
)
data_monthly = pl.DataFrame({
'date': np.repeat(dates.to_numpy(), 50),
'permno': np.tile(np.arange(1, 51), 12),
'ret_excess': rng.normal(0, 0.1, 600),
'mkt_excess': rng.normal(0, 0.1, 600),
'smb': rng.normal(0, 0.1, 600),
'hml': rng.normal(0, 0.1, 600),
})
estimate_betas(data_monthly, 'ret_excess ~ mkt_excess', lookback='3mo')