Skip to content
python.financial

Alpha and beta compare a strategy with a benchmark. Beta estimates how strongly the strategy's excess return changes with the benchmark's excess return. Alpha is the regression intercept left after accounting for that relationship under the chosen model. A positive alpha can reflect skill, an omitted factor, a poor benchmark, stale prices, leverage, data errors, or luck. It is not proof of skill by itself.

Michael Jensen's 1968 mutual-fund study developed the risk-adjusted performance measure now called Jensen's alpha from the capital asset pricing model (CAPM). The single-factor model is still useful, but modern equity research often adds size, value, profitability, investment, momentum, or other factors when those exposures could explain returns.

The single-factor regression

For each period (t), estimate:

R_strategy,t - R_f,t = alpha + beta * (R_benchmark,t - R_f,t) + epsilon_t

The variables are:

  • (R_{strategy,t}): strategy return in period (t)
  • (R_{benchmark,t}): benchmark return in the same period
  • (R_{f,t}): risk-free return for that period
  • (alpha): periodic regression intercept
  • (beta): linear sensitivity to benchmark excess return
  • (epsilon_t): return not explained by this regression

With an intercept, ordinary least squares produces:

beta = cov(strategy_excess, benchmark_excess) / var(benchmark_excess)
alpha = mean(strategy_excess) - beta * mean(benchmark_excess)

These formulas require nonzero benchmark variance. They also require returns aligned to the same timestamps and frequency. A constant risk-free return does not change covariance or beta, but subtracting the correctly matched series matters when risk-free returns vary.

How to interpret beta

A beta of 0.7 means that a one-percentage-point change in benchmark excess return is associated with an estimated 0.7-percentage-point change in strategy excess return, on average under the fitted linear model. It does not mean the strategy has 70% of the benchmark's volatility or risk.

  • beta = 1: the fitted benchmark sensitivity is one-for-one.
  • 0 < beta < 1: positive but lower fitted sensitivity.
  • beta = 0: no fitted linear sensitivity. Nonlinear or changing exposure may still exist.
  • beta < 0: returns have moved against the benchmark on average in the sample.
  • beta > 1: fitted sensitivity exceeds the benchmark's move, often because of leverage or concentrated exposure.

Beta is not necessarily stable. Rebalancing, options, stop rules, volatility targeting, and changing asset weights can make one full-sample estimate hide important regime changes. Rolling estimates can expose drift, although short windows also make estimates noisier.

Python example with uncertainty

This example creates 120 monthly observations with a planted monthly alpha of 0.10%, beta of 0.70, and random residual returns. It converts the 3% annual risk-free rate to an effective monthly rate, aligns both series, fits an intercept, and requests heteroskedasticity and autocorrelation consistent (HAC) standard errors.

python -m pip install numpy pandas statsmodels
import numpy as np
import pandas as pd
import statsmodels.api as sm


rng = np.random.default_rng(16)
index = pd.period_range("2016-01", periods=120, freq="M").to_timestamp("M")
risk_free_annual = 0.03
risk_free_monthly = (1 + risk_free_annual) ** (1 / 12) - 1

benchmark = pd.Series(rng.normal(0.006, 0.04, len(index)), index=index)
strategy = pd.Series(
    risk_free_monthly
    + 0.001
    + 0.70 * (benchmark - risk_free_monthly)
    + rng.normal(0, 0.015, len(index)),
    index=index,
)

sample = pd.concat(
    {"strategy": strategy, "benchmark": benchmark}, axis=1
).dropna()
y = sample["strategy"] - risk_free_monthly
x = sm.add_constant(sample["benchmark"] - risk_free_monthly)
fit = sm.OLS(y, x).fit(cov_type="HAC", cov_kwds={"maxlags": 3})

alpha_monthly = fit.params["const"]
print(f"observations: {fit.nobs:.0f}")
print(f"beta: {fit.params['benchmark']:.3f}")
print(f"alpha monthly: {alpha_monthly:.4%}")
print(f"alpha annual, arithmetic: {12 * alpha_monthly:.2%}")
print(f"alpha HAC p-value: {fit.pvalues['const']:.3f}")

The executed output with statsmodels 0.14.5 is:

observations: 120
beta: 0.701
alpha monthly: 0.1208%
alpha annual, arithmetic: 1.45%
alpha HAC p-value: 0.431

The point estimates are close to the planted parameters, but the alpha p-value is high. Even in this controlled sample, the data do not reject a zero intercept at conventional significance levels. The example validates the calculation, not a strategy or an investment result. The statsmodels regression documentation describes OLS and models for non-independent or non-constant error variance.

Annualizing alpha

The regression intercept has the same frequency as its input returns. A monthly intercept of 0.1208% becomes 1.45% under arithmetic annualization:

annual alpha = periodic alpha * periods per year

Some performance libraries instead compound the average unexplained periodic return:

compounded alpha = (1 + periodic alpha) ** periods per year - 1

Those conventions answer slightly different questions and diverge as the periodic value grows. State the return frequency, risk-free-rate convention, and annualization rule before comparing two reported alphas. Community VectorBT 1.1.0 compounds its mean alpha series, while its risk_free argument is a per-period rate. Do not pass an annual rate to that argument without converting it first.

Benchmark and factor choice determine the answer

Alpha is always relative to a model. An equity strategy may appear to have CAPM alpha while loading on small-cap, value, momentum, or profitability factors omitted from a market-only regression. Kenneth French's official factor definitions define market excess return, SMB, and HML, and the Data Library provides additional research factors and historical archives.

Choose a benchmark and factors that match the actual opportunity set and investable alternative. Examples include a total-return equity index for an equity portfolio, a currency-matched cash rate for excess returns, and the underlying asset for a single-asset timing strategy. A price-only index compared with a dividend-inclusive strategy creates a mechanical mismatch.

Use net strategy returns when evaluating what an investor could have earned. Fees, spreads, slippage, borrow expense, funding, and taxes can turn gross alpha into negative net alpha. Align calendars, timezones, currencies, and missing observations before fitting the model. Never forward-fill returns across dates when one side did not trade without understanding the induced stale-price bias.

Why a positive estimate is not enough

Before interpreting alpha as evidence, check:

  1. Uncertainty. Report a standard error, confidence interval, or p-value. Time-series returns often need heteroskedasticity and autocorrelation aware inference.
  2. Model specification. Test plausible factors and nonlinear exposures. Options can have low linear beta while retaining large crash exposure.
  3. Stability. Compare subperiod and rolling estimates. A full-sample average can combine opposing regimes.
  4. Selection bias. If many strategies, factors, windows, or benchmarks were tried, one attractive alpha may be a multiple-testing result.
  5. Out-of-sample evidence. Refit only with information available at each decision time and evaluate on untouched data.
  6. Economic size. Compare net alpha with capacity, turnover, drawdown, tail loss, and operational cost. Statistical significance alone does not make a strategy usable.

Where to calculate it

The VectorBT family calculates alpha, beta, and rolling variants on labeled return arrays. Zipline Reloaded can generate the point-in-time portfolio return series, but benchmark and factor regressions still need correctly aligned inputs and an explicit model.

Choose which optional services may run. You can change these settings at any time.