Beta Calculator: Quantifying Systematic Market Risk
Author: Avnit Bambah
Date: 03/12/2018 (Updated)
Concept: Computing stock beta to understand systematic risk and optimize portfolio exposure
What is Beta and Why Does It Matter for Trading?
Beta (β) measures a stock’s systematic risk relative to the market index (typically S&P 500 / SPY). It answers the core question:
How much does this stock move when the market moves?
Beta Interpretation:
- β = 1.0: Stock moves in lockstep with the market. For every 1% the market moves, the stock moves ~1%.
- β > 1.0: Stock is more volatile than the market (amplified moves). Higher risk, higher potential reward.
- β < 1.0: Stock is less volatile than the market (dampened moves). Lower volatility.
- β < 0.0: Stock moves inverse to the market (rare; useful for hedging).
Trading Strategy Implications:
Defensive Portfolio (Lower Beta):
- Choose β < 0.8 stocks for downside protection
- Reduces portfolio volatility
- Better for risk-averse investors or market downturns
Aggressive Portfolio (Higher Beta):
- Overweight β > 1.2 stocks for amplified returns
- Capitalizes on bull markets
- Requires strong conviction and risk tolerance
Market-Neutral Hedging:
- Pair long positions (high β) with short positions (low β)
- Reduces net beta exposure while maintaining alpha capture
- Typical quant fund strategy
Step 1: Load Market Data and Prepare Returns
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from pandas_datareader import data
# Configure inline plotting for Jupyter notebooks
%matplotlib inline
What each import does:
pandas: Data manipulation and time-series analysisnumpy: Numerical computations (covariance, variance calculations)matplotlib.pyplot: Visualization of resultspandas_datareader: Fetches historical stock data from Yahoo Finance
Step 2: Load Your Portfolio Holdings
# Load a CSV file containing your portfolio ticker symbols
# Expected format: single column with ticker symbols (AAPL, MSFT, etc.)
df = pd.read_csv("./holdings-xlk.csv")
This reads your portfolio holdings. The CSV file should contain one ticker symbol per row.
Step 3: Build Complete Ticker List (Portfolio + Market Benchmark)
# Core tickers: market benchmark + representative index holdings
base_tickers = ['AAPL', 'MSFT', 'SPY', 'CME', 'GOOG', 'VVI', 'agg']
# Add holdings from CSV file
symbols_df = df.iloc[:, [0]] # Extract first column
symbols_list = symbols_df.values.tolist()
# Clean up formatting and add to ticker list
tickers = base_tickers.copy()
for i in range(1, len(symbols_list)):
ticker = str(symbols_list[i]).replace('[\'', '').replace('\']', '')
if ticker not in tickers: # Avoid duplicates
tickers.append(ticker)
print(f"Total tickers to analyze: {len(tickers)}")
print(tickers)
Output:
Total tickers to analyze: 26
['AAPL', 'MSFT', 'SPY', 'CME', 'GOOG', 'VVI', 'agg', 'BETR', 'BUFF', ...]
SPY (S&P 500 ETF) acts as our market benchmark — we’ll calculate each stock’s beta relative to SPY’s returns.
Step 4: Fetch Historical Price Data from Yahoo Finance
# Configuration
DATA_SOURCE = 'yahoo'
START_DATE = '2000-01-01'
END_DATE = '2018-03-24'
# Fetch adjusted closing prices for all tickers
try:
panel_data = data.DataReader(tickers, DATA_SOURCE, START_DATE, END_DATE)
print(f"Successfully fetched data from {START_DATE} to {END_DATE}")
except Exception as e:
print(f"Error fetching data: {e}")
# Extract only adjusted closing prices (ignore OHLCV data)
close_prices = panel_data.loc['Adj Close']
# Create complete date range (business days only, matches market trading days)
business_dates = pd.date_range(start=START_DATE, end=END_DATE, freq='B')
# Reindex to fill gaps and align all tickers to same dates
close_prices = close_prices.reindex(business_dates)
print(f"Price data shape: {close_prices.shape}")
print(f"Data quality: {(1 - close_prices.isna().sum().sum() / close_prices.size) * 100:.1f}% complete")
What’s happening:
- Data retrieval: Pulls historical adjusted close prices from Yahoo Finance
- Reindexing: Fills gaps to ensure consistent date alignment across all tickers
- Business days only: Uses ‘B’ frequency to match market trading days (excludes weekends)
Step 5: Calculate Daily Percentage Returns
# Shift prices forward by 1 day to compute returns
# Mathematically: daily_return = (price_today / price_tomorrow - 1) * 100
close_prices_tomorrow = close_prices.shift(-1)
# Calculate daily returns as percentage change
daily_returns_pct = ((close_prices / close_prices_tomorrow) - 1) * 100
print("Daily returns matrix computed successfully")
print(f"Shape: {daily_returns_pct.shape} ({len(business_dates)} trading days × {len(tickers)} stocks)")
Understanding the Return Formula:
\[\text{Return}_{t} = \frac{P_t}{P_{t+1}} - 1\]This gives us the 1-day return. We multiply by 100 to express as percentages.
Example: If Stock A closes at $100 and opens tomorrow at $102, the 1-day return is +2%.
These daily returns become our fundamental data for computing beta — we’ll measure how each stock’s returns co-vary with SPY’s returns over the entire period.
Step 6: Calculate Covariance Matrix
# Compute pairwise covariances between all stocks' daily returns
covariance_matrix = daily_returns_pct.cov()
print("Covariance matrix shape:", covariance_matrix.shape)
print("\nFirst few stocks' covariance with SPY:")
print(covariance_matrix['SPY'].head(10))
What is covariance?
\[\text{Cov}(X, Y) = \frac{1}{n-1} \sum_{i=1}^{n} (X_i - \bar{X})(Y_i - \bar{Y})\]- Positive covariance → stocks tend to move together
- Negative covariance → stocks move in opposite directions
- Magnitude indicates strength of relationship
SPY’s covariance with itself (SPY’s variance) appears on the diagonal.
Step 7: Extract Market Returns and Calculate Variance
# SPY is located at column index 20 (S&P 500 benchmark)
# Market benchmark index position
MARKET_INDEX = 20 # Named constant instead of magic number
# Extract market (SPY) returns
market_returns = daily_returns_pct.iloc[:, MARKET_INDEX]
# Remove NaN values (missing trading days)
market_returns_clean = market_returns.dropna()
# Calculate variance (covariance of a variable with itself)
market_variance = market_returns_clean.var()
print(f"Market (SPY) average daily return: {market_returns_clean.mean():.4f}%")
print(f"Market variance (σ²): {market_variance:.6f}")
print(f"Market std dev (σ): {np.sqrt(market_variance):.4f}%")
Interpreting Market Variance:
Variance tells us how much SPY’s daily returns deviate from their mean. Higher variance = more volatile market environment.
A market variance of ~1.48 means SPY’s daily returns typically deviate by roughly ±1.2% from the mean.
Step 8: Calculate Individual Stock Betas
# Calculate beta for each stock relative to the market
# Formula: β = Cov(stock, market) / Var(market)
beta_values = {}
for stock in daily_returns_pct.columns:
stock_returns = daily_returns_pct[stock].dropna()
# Covariance between this stock and market
covariance_with_market = np.cov(stock_returns, market_returns_clean)[0, 1]
# Beta = Covariance / Market Variance
beta = covariance_with_market / market_variance
beta_values[stock] = beta
# Create DataFrame for visualization
beta_df = pd.DataFrame(list(beta_values.items()), columns=['Stock', 'Beta'])
beta_df = beta_df.sort_values('Beta', ascending=False)
print("Top 10 stocks by beta (most market-sensitive):")
print(beta_df.head(10))
print("\nBottom 10 stocks by beta (least market-sensitive):")
print(beta_df.tail(10))
The Beta Formula Explained:
\[\beta = \frac{\text{Cov}(\text{Stock Return}, \text{Market Return})}{\text{Var}(\text{Market Return})}\]This is the CAPM beta — the fundamental measure of systematic risk. It tells us:
- How many basis points the stock moves for each basis point the market moves
- How much of the stock’s variance is explained by market movements (systematic vs idiosyncratic risk)
Step 9: Interpret Beta Results and Build Trading Strategy
# Categorize stocks by beta range
beta_df['Category'] = pd.cut(beta_df['Beta'],
bins=[-np.inf, 0.8, 1.0, 1.2, np.inf],
labels=['Defensive', 'Market-Tracking', 'Aggressive', 'Very Aggressive'])
print("Beta Distribution by Category:")
print(beta_df['Category'].value_counts().sort_index())
# Example strategy: Construct a hedged portfolio
print("\n" + "="*60)
print("TRADING STRATEGY EXAMPLE: Market-Neutral Hedge")
print("="*60)
# Select defensive and aggressive stocks
defensive_stocks = beta_df[beta_df['Category'] == 'Defensive'].head(3)['Stock'].tolist()
aggressive_stocks = beta_df[beta_df['Category'] == 'Aggressive'].head(3)['Stock'].tolist()
print(f"\nDefensive positions (low β, downside protection):")
for stock in defensive_stocks:
beta = beta_df[beta_df['Stock'] == stock]['Beta'].values[0]
print(f" {stock}: β = {beta:.3f} — Long position")
print(f"\nAggresssive positions (high β, upside amplification):")
for stock in aggressive_stocks:
beta = beta_df[beta_df['Stock'] == stock]['Beta'].values[0]
print(f" {stock}: β = {beta:.3f} — Short position (hedge)")
# Calculate net portfolio beta
portfolio_beta = np.mean([beta_df[beta_df['Stock'] == s]['Beta'].values[0] for s in defensive_stocks]) \
- np.mean([beta_df[beta_df['Stock'] == s]['Beta'].values[0] for s in aggressive_stocks])
print(f"\nPortfolio Net Beta: {portfolio_beta:.3f}")
if portfolio_beta < 0.2:
print("✓ Market-neutral: Protected from market swings while capturing alpha")
elif portfolio_beta < 0.8:
print("✓ Defensively positioned: Reduced downside in market correction")
else:
print("⚠ Bullish bias: Amplifies market movements (higher risk)")
Key Insights:
- Beta clustering: Most stocks have β between 0.8–1.2 (market-like behavior)
- Outliers matter: A few stocks with very high/low beta significantly impact portfolio risk
- Hedging principle: Pair longs and shorts by beta to neutralize market risk
- Regulatory risk: Market conditions change; beta is historical, not predictive
Limitations & Assumptions
- Look-back bias: Historical beta doesn’t guarantee future relationships
- Regime changes: Beta shifts during market crises (loses predictive power in tail events)
- Liquidity mismatch: Works best for large-cap stocks with continuous trading
- Non-linear relationships: Assumes linear correlation with market (fails for tail risk hedging)
Conclusion
Beta is your first line of defense in portfolio construction. Use it to:
- Classify risk: Know which stocks amplify or dampen market moves
- Hedge efficiently: Pair high-β positions with low-β shorts
- Optimize allocation: Match portfolio beta to your risk tolerance
- Monitor drift: Track beta changes to detect regime shifts
In the modern market, combine beta with factor exposure (momentum, value, quality) for a complete risk framework.