Capital Market Theory - CAPM and the Efficient Frontier › Complete CAPM Implementation

Michael BrenndoerferDecember 14, 20257 min read
Quantitative FinanceAll chapters
Part 9 of 15Portfolio Theory and Investment Analysis
Chapter 47Capital Market Theory - CAPM and the Efficient Frontier › Complete CAPM Implementation
Reading section 10 of 14 · Complete CAPM Implementation · 7 minSection 10 of 14 · 7 min

Book contents

Section 10 of 14 · 7 min read

Capital Market Theory - CAPM and the Efficient Frontier › Complete CAPM ImplementationOne section · Sections in this chapter10 / 14 ↓
Reading tools Definitions & inline links

Choose your expertise level to adjust how many terms are explained. Beginners see more tooltips, experts see fewer to maintain reading flow. Hover over underlined terms for instant definitions.

Article links

Make inline references clickable

Let's bring together the concepts from this chapter in a worked implementation that estimates betas, constructs the Security Market Line, and calculates portfolio alphas. This implementation demonstrates how the theoretical framework translates into practical analysis tools.

In[21]:
Code
import numpy as np
from scipy import stats


class CAPMAnalysis:
    """
    Complete CAPM analysis toolkit for portfolio and security analysis.
    """

    def __init__(self, returns_df, market_returns, rf_rate):
        """
        Initialize with return data.

        Parameters:
        -----------
        returns_df : pd.DataFrame
            Asset returns with assets as columns
        market_returns : pd.Series
            Market index returns
        rf_rate : float
            Risk-free rate (same frequency as returns)
        """
        self.returns = returns_df
        self.market_returns = market_returns
        self.rf = rf_rate
        self.excess_returns = returns_df.subtract(rf_rate, axis=0)
        self.market_excess = market_returns - rf_rate
        self.betas = {}
        self.alphas = {}
        self.r_squared = {}

    def estimate_betas(self):
        """Estimate beta for each asset using OLS regression."""
        for asset in self.returns.columns:
            slope, intercept, r_value, _, _ = stats.linregress(
                self.market_excess, self.excess_returns[asset]
            )
            self.betas[asset] = slope
            self.alphas[asset] = intercept
            self.r_squared[asset] = r_value**2
        return self.betas

    def get_capm_expected_return(self, beta):
        """Calculate CAPM expected return for a given beta."""
        market_premium = self.market_returns.mean() - self.rf
        return self.rf + beta * market_premium

    def security_market_line(self, beta_range=None):
        """Generate Security Market Line data."""
        if beta_range is None:
            beta_range = np.linspace(0, 2, 100)
        market_premium = self.market_returns.mean() - self.rf
        expected_returns = self.rf + beta_range * market_premium
        return beta_range, expected_returns

    def portfolio_beta(self, weights):
        """Calculate portfolio beta as weighted average."""
        if not self.betas:
            self.estimate_betas()
        beta_values = [self.betas[asset] for asset in self.returns.columns]
        return np.dot(weights, beta_values)

    def tracking_error(self, weights, target_beta=1.0):
        """Calculate deviation from target beta."""
        return abs(self.portfolio_beta(weights) - target_beta)

With the CAPMAnalysis class defined, we generate a synthetic dataset representing different sectors to demonstrate the workflow. The simulation creates returns that follow the CAPM structure, with each sector having a distinct beta and alpha reflecting its risk characteristics and potential mispricing.

In[22]:
Code
# Generate realistic simulated data for demonstration
np.random.seed(2024)
n_periods_full = 60  # 5 years monthly

# Market returns
market_ret = np.random.normal(0.008, 0.04, n_periods_full)

# Asset characteristics
assets = {
    "Tech_Growth": {"beta": 1.4, "alpha": 0.003, "idio_vol": 0.06},
    "Large_Value": {"beta": 0.9, "alpha": 0.001, "idio_vol": 0.03},
    "Utilities": {"beta": 0.5, "alpha": 0.0005, "idio_vol": 0.02},
    "Healthcare": {"beta": 0.7, "alpha": 0.002, "idio_vol": 0.035},
    "Financials": {"beta": 1.2, "alpha": -0.001, "idio_vol": 0.05},
    "Consumer": {"beta": 0.85, "alpha": 0.0015, "idio_vol": 0.03},
    "Energy": {"beta": 1.1, "alpha": -0.002, "idio_vol": 0.055},
    "Real_Estate": {"beta": 0.95, "alpha": 0.001, "idio_vol": 0.04},
}

rf_monthly = 0.003  # ~3.6% annual

# Generate returns
returns_data = {}
for name, params in assets.items():
    idio = np.random.normal(0, params["idio_vol"], n_periods_full)
    returns_data[name] = (
        rf_monthly
        + params["alpha"]
        + params["beta"] * (market_ret - rf_monthly)
        + idio
    )

returns_df = pd.DataFrame(returns_data)
market_series = pd.Series(market_ret, name="Market")

We initialize the analysis with our returns data and estimate the beta for each asset. The estimation process runs separate regressions for each sector, extracting the slope coefficient as the beta estimate.

In[23]:
Code
# Run CAPM analysis
capm = CAPMAnalysis(returns_df, market_series, rf_monthly)
capm.estimate_betas()

# Create summary DataFrame
summary_data = []
for asset in returns_df.columns:
    actual_ret = returns_df[asset].mean()
    expected_ret = capm.get_capm_expected_return(capm.betas[asset])
    summary_data.append(
        {
            "Asset": asset,
            "Beta": capm.betas[asset],
            "R²": capm.r_squared[asset],
            "Actual Return": actual_ret * 12,  # Annualized
            "CAPM Expected": expected_ret * 12,  # Annualized
            "Alpha (Annual)": capm.alphas[asset] * 12,
        }
    )

summary_df = pd.DataFrame(summary_data)

Finally, we display the summary statistics to compare the actual and expected returns.

Out[24]:
Console
CAPM Analysis Summary
=====================================================================================
      Asset  Beta    R²  Actual Return  CAPM Expected  Alpha (Annual)
Tech_Growth 1.516 0.500          0.164          0.170          -0.006
Large_Value 0.755 0.439          0.196          0.103           0.093
  Utilities 0.539 0.504          0.054          0.084          -0.030
 Healthcare 0.843 0.451         -0.008          0.111          -0.119
 Financials 1.351 0.524          0.112          0.156          -0.044
   Consumer 0.870 0.503          0.156          0.113           0.043
     Energy 1.208 0.351          0.062          0.143          -0.081
Real_Estate 1.087 0.485          0.169          0.132           0.037

The summary table displays the key metrics for each asset. 'Tech_Growth' and 'Financials' exhibit betas above 1.0, indicating they carry more systematic risk than the market. Conversely, 'Utilities' has a beta of 0.5, confirming its defensive nature. The alpha values show the divergence between actual and expected returns; positive alphas suggest potential undervaluation or superior performance adjusted for risk. The R-squared values indicate how much of each sector's return variation is explained by market movements, with higher values suggesting greater systematic risk exposure relative to idiosyncratic risk.

We can also visualize these results on the Security Market Line to see the alphas graphically.

Out[25]:
Visualization
Scatter plot of eight sectors showing beta versus annualized return around the Security Market Line, with circles above the line and X markers below it.
Complete CAPM analysis visualizing asset returns against the Security Market Line (SML). High-beta sectors such as Tech_Growth and Financials appear on the right, while defensive Utilities appears on the left. Vertical deviations reveal alpha: Large_Value, Real_Estate, and Consumer are above the line; Tech_Growth, Utilities, Healthcare, Financials, and Energy are below it.

The analysis reveals several patterns consistent with CAPM theory. Higher-beta assets like Tech_Growth and Financials show higher expected returns, while defensive sectors like Utilities exhibit lower betas and returns. The spread of assets around the SML represents alpha, either skill/mispricing or compensation for risks not captured by CAPM. This visualization makes it easy to identify which sectors are outperforming or underperforming their risk-adjusted benchmarks, and gives actionable insights for portfolio construction.

The R-squared values from our CAPM analysis reveal how much of each sector's return variation is explained by market movements. We can visualize these values to compare systematic versus idiosyncratic risk across sectors.

Out[26]:
Visualization
Bar chart showing R-squared values for different sectors from CAPM regression.
R-squared values across sectors show the proportion of return variance explained by market movements. Higher R-squared indicates greater systematic risk exposure, while lower values suggest more idiosyncratic risk that can be diversified away.