← All posts

What Is Adjusted Beta? Merrill Lynch Beta Shrinkage in Python

TSLA shrinks from 2.04 to 1.69. XOM flips from -0.25 to +0.17. The Merrill Lynch formula pulls noisy betas toward 1.0.

What’s the question?

Raw beta from OLS regression measures a stock’s historical market sensitivity. But raw betas are noisy — they fluctuate substantially depending on the estimation period. The Merrill Lynch adjusted beta (also called Bloomberg beta) addresses this by shrinking the raw estimate toward 1.0 using the formula: Adjusted Beta = (2/3) × Raw Beta + (1/3) × 1.0. The rationale: betas tend to mean-revert toward 1.0 over time (high-beta stocks get less volatile, low-beta stocks become more correlated with the market). The 2/3 weight is empirically calibrated. Adjustment of this kind is one of the settings, along with window, return frequency and benchmark, that the guide to why beta differs between sources takes in turn.

The approach

Select 10 stocks across risk profiles — from high-volatility names (TSLA, NVDA) to defensive consumer staples (PG, JNJ). Run OLS regression of each stock’s daily returns against SPY over 1 year to obtain the raw beta, then apply the Merrill Lynch shrinkage formula. Compare both estimates side by side with the R-squared from each regression.

import xfinlink as xfl
import pandas as pd
import numpy as np
from scipy import stats

xfl.api_key = "YOUR_API_KEY"  # free at https://xfinlink.com/signup

# -- Configuration ----------------------------------------------------------
tickers = ["AAPL", "MSFT", "NVDA", "TSLA", "XOM", "JNJ", "PG", "JPM", "GS", "UNH"]
benchmark = "SPY"

# -- Fetch 1Y daily returns -------------------------------------------------
df = xfl.prices(tickers + [benchmark], period="1y", fields=["return_daily"])
returns = df.pivot_table(index="date", columns="ticker", values="return_daily").dropna()

# -- OLS regression + Merrill Lynch adjustment ------------------------------
market = returns[benchmark]
results = []

for ticker in tickers:
    slope, intercept, r_value, p_value, std_err = stats.linregress(
        market, returns[ticker]
    )
    adj_beta = (2 / 3) * slope + (1 / 3) * 1.0
    results.append({
        "ticker": ticker,
        "raw_beta": slope,
        "adj_beta": adj_beta,
        "r_squared": r_value ** 2,
        "shrinkage": slope - adj_beta,
    })

rdf = pd.DataFrame(results).sort_values("raw_beta", ascending=False)

print("=== Raw Beta vs Merrill Lynch Adjusted Beta (1Y vs SPY) ===")
print(f"{'Ticker':6s}  {'Raw Beta':>9s}  {'Adj Beta':>9s}  {'Shrinkage':>10s}  {'R²':>6s}")
print("-" * 46)
for _, r in rdf.iterrows():
    print(
        f"{r['ticker']:6s}  {r['raw_beta']:>9.3f}  {r['adj_beta']:>9.3f}  "
        f"{r['shrinkage']:>+10.3f}  {r['r_squared']:>6.3f}"
    )
print(f"\nAdjustment formula: Adj Beta = (2/3) * Raw Beta + (1/3) * 1.0")
print(f"Average raw beta:  {rdf['raw_beta'].mean():.3f}")
print(f"Average adj beta:  {rdf['adj_beta'].mean():.3f}")

Output:

=== Raw Beta vs Merrill Lynch Adjusted Beta (1Y vs SPY) ===
Ticker   Raw Beta   Adj Beta   Shrinkage      R²
----------------------------------------------
TSLA        2.041      1.694      +0.347   0.280
NVDA        1.803      1.535      +0.268   0.429
GS          1.471      1.314      +0.157   0.444
AAPL        0.984      0.989      -0.005   0.277
JPM         0.982      0.988      -0.006   0.315
MSFT        0.901      0.934      -0.033   0.218
UNH         0.706      0.804      -0.098   0.039
PG          0.097      0.398      -0.301   0.004
JNJ         0.054      0.369      -0.315   0.002
XOM        -0.246      0.169      -0.415   0.016

Adjustment formula: Adj Beta = (2/3) * Raw Beta + (1/3) * 1.0
Average raw beta:  0.879
Average adj beta:  0.919

What this tells us

The adjustment has asymmetric effects depending on which side of 1.0 the raw beta sits. High-beta stocks get pulled down: TSLA shrinks from 2.04 to 1.69. Low-beta stocks get pulled up: XOM’s negative beta (-0.25) becomes a modest positive (0.17). Near-1.0 betas barely change: AAPL moves from 0.984 to 0.989. The R-squared column reveals another dimension: JNJ and PG have near-zero R-squared values (0.002 and 0.004), meaning their raw betas are estimated from essentially no signal. The adjustment pushes these unreliable estimates toward 1.0, which is a safer prior than trusting a noisy regression. The Bayesian interpretation: the formula treats 1.0 as a prior and updates it with the data, with the 2/3 weight reflecting how much to trust the observed beta.

So what?

When constructing portfolios or hedging, use adjusted beta instead of raw beta. Raw beta overestimates the market sensitivity of volatile stocks and underestimates it for defensive stocks. For risk models that compute expected returns via CAPM (Expected Return = Rf + Beta × Market Premium), using raw betas produces forecasts that are too extreme in both directions. The Merrill Lynch adjustment is a one-line correction that improves out-of-sample prediction.