edgekit.factors
Regression & factor models — the linear-algebra layer that answers “is this return stream alpha, or just beta to something I already own?”. A from-scratch OLS core, CAPM, multi-factor exposures, rolling beta, and Newey-West standard errors, all in numpy.
What's inside. Everything sits on one engine: ols, a plain least-squares fit that returns coefficients with their t-stats and R². capm is the single-factor case that isolates alpha against a market; factor_exposures generalises it to a factor panel; rolling_beta tracks a time-varying loading; and newey_west_se gives you heteroskedasticity- and autocorrelation-robust standard errors when residuals are not i.i.d. (they rarely are).
tstat values assume large samples and (except via newey_west_se) i.i.d. residuals. Treat |t| > 2 as the rough significance bar, and reach for Newey-West standard errors whenever returns are overlapping or autocorrelated.Core regression#
ols#
Ordinary least squares — regress y on X and get back coefficients, their standard errors and t-stats, fit quality, and the residuals. The primitive every other function in this module is built on.
ols(y, X, add_const=True) -> dict| Param | Type | Default | Meaning |
|---|---|---|---|
y | array (T,) | — | The dependent variable (e.g. asset returns). |
X | array (T, k) | — | The regressor matrix (one column per factor). |
add_const | bool | True | Prepend an intercept column (the alpha term). |
Returns: a dict with keys "beta" (coefficient vector, intercept first if add_const), "se" (standard errors), "tstat" (t-statistics), "r2", "adj_r2", "resid" (residuals), "fitted" (fitted values), and "n" (sample size).
import edgekit as ek
fit = ek.factors.ols(y, X)
fit["beta"], fit["tstat"], fit["adj_r2"]Factor models#
capm#
The single-factor CAPM regression — regress an asset's excess return on the market's and read off alpha and beta with their significance. The first question to ask any new edge: does it have a positive, significant alpha once you strip out market exposure?
capm(asset_returns, market_returns, rf=0.0, periods_per_year=252.0) -> dict| Param | Type | Default | Meaning |
|---|---|---|---|
asset_returns | array (T,) | — | The asset / strategy return series. |
market_returns | array (T,) | — | The market benchmark return series. |
rf | float | 0.0 | Per-period risk-free rate subtracted from both. |
periods_per_year | float | 252.0 | Annualisation factor for alpha_annual. |
Returns: a dict with keys "alpha" (per-period intercept), "beta" (market loading), "alpha_t" and "beta_t" (their t-stats), "r2", and "alpha_annual" (alpha annualised by periods_per_year).
c = ek.factors.capm(strat_rets, mkt_rets, rf=0.0, periods_per_year=365)
c["alpha_annual"], c["alpha_t"], c["beta"] # want alpha_t > 2 and beta near 0factor_exposures#
Multi-factor regression — regress an asset on a panel of factors (market, size, value, momentum, …) to get its loading on each plus the residual alpha. The generalisation of capm to more than one systematic driver.
factor_exposures(asset_returns, factors) -> dict| Param | Type | Default | Meaning |
|---|---|---|---|
asset_returns | array (T,) | — | The asset / strategy return series. |
factors | array (T, k) / DataFrame | — | The factor-return panel (one column per factor). |
Returns: a dict with keys "betas" (loading on each factor), "tstats" (their t-stats), "alpha" (the intercept), "alpha_t" (its t-stat), and "r2".
fx = ek.factors.factor_exposures(strat_rets, factor_panel)
fx["betas"], fx["alpha"], fx["alpha_t"]Rolling & robust#
rolling_beta#
Beta of y on x estimated in a trailing window— a time-varying loading that shows when an exposure drifts (a strategy's market beta creeping up in a trend, say). Leading positions are NaN until the window fills.
rolling_beta(y, x, window) -> np.ndarray| Param | Type | Default | Meaning |
|---|---|---|---|
y | array (T,) | — | Dependent series (e.g. strategy returns). |
x | array (T,) | — | Explanatory series (e.g. market returns). |
window | int | — | Rolling lookback in bars. |
Returns: a numpy array of rolling betas aligned to the inputs (leading positions NaN).
b = ek.factors.rolling_beta(strat_rets, mkt_rets, window=60)newey_west_se#
Newey-West HAC standard errors — heteroskedasticity- and autocorrelation-consistent errors for a fitted regression. Use them whenever residuals are autocorrelated (overlapping returns, persistent series); the plain OLS standard errors understate uncertainty there and make weak edges look significant.
newey_west_se(X, resid, lags) -> np.ndarray| Param | Type | Default | Meaning |
|---|---|---|---|
X | array (T, k) | — | The regressor matrix used in the fit (include the constant column). |
resid | array (T,) | — | The regression residuals (e.g. ols()["resid"]). |
lags | int | — | Bandwidth: number of autocorrelation lags to correct for. |
Returns: a numpy array of HAC standard errors, one per coefficient in X.
fit = ek.factors.ols(y, X)
se_hac = ek.factors.newey_west_se(fit["X"] if "X" in fit else X, fit["resid"], lags=5)
t_hac = fit["beta"] / se_hac # robust t-statsSee also#
- edgekit.optimize — feed the estimated returns into portfolio construction.
- edgekit.timeseries — the autocorrelation battery that tells you when to reach for Newey-West.
- The validation gauntlet — where a significant alpha_t has to survive.