Understanding Market Exposure: A CAPM-Based Stock Evaluation

This project emerged from a natural curiosity sparked during my earlier stock market analysis, where I explored daily return patterns and volatility trends. That initial exploration raised deeper questions: How do individual stocks behave in relation to market-wide movements? Can risk be quantified and priced? These questions led me to explore Beta (market sensitivity), Alpha (excess returns), and the Capital Asset Pricing Model (CAPM)โ€”a foundational framework in modern finance.

๐Ÿ’ป Tech Stack:

๐Ÿงช Data Pipeline:

๐Ÿ“Š Code Snippets & Visualisations:

# Function to calculate the daily returns
def daily_returns(df):
    df_daily_return = df.copy()
    for i in df.columns[1:]:
        for j in range(1, len(df)):
            df_daily_return[i][j] = ((df[i][j] - df[i][j-1])/df[i][j-1])*100
        
        df_daily_return[i][0] = 0
    return df_daily_return

# Plot a scatter plot between the selected stock and the S&P500 (Market) (Figure 1)
plt.scatter(stocks_daily_return['sp500'], stocks_daily_return['AAPL'])
plt.xlabel('sp500')
plt.ylabel('AAPL')
plt.grid()

# Add beta & alpha to plot
beta, alpha = np.polyfit(stocks_daily_return['sp500'], stocks_daily_return['AAPL'], 1)

# Add regression line (beta) - y = beta [stockvsmkt- stock volatility] * rm [stock daily return] + alpha [excess return on top of mkt return]
plt.plot(stocks_daily_return['sp500'], beta * stocks_daily_return['sp500'] + alpha, '-', color = 'r')
plt.show()

# Let's calculate the annualized rate of return for S&P500 (Assume 252 working days per year)
rm = stocks_daily_return['sp500'].mean() * 252
rm

# Assume risk free rate is zero (Used the yield of a 10-years U.S. Government bond as a risk free rate)
rf = 0

# Calculate return for any security (APPL) using CAPM
Exp_return_AAPL = rf + (beta * (rm - rf))
Exp_return_AAPL

# Create a placeholder for all betas and alphas
beta = {}
alpha = {}

for i in stocks_daily_return.columns[1:]:
    if i != 'sp500' and i != 'Date':
        
        stocks_daily_return.plot(kind = 'scatter', x = 'sp500', y = i, title = i)
        plt.scatter(stocks_daily_return['sp500'], stocks_daily_return[i])
        plt.xlabel('sp500')
        plt.ylabel(i)
        beta[i], alpha[i] = np.polyfit(stocks_daily_return['sp500'], stocks_daily_return[i], 1)
        plt.plot(stocks_daily_return['sp500'], beta[i] * stocks_daily_return['sp500'] + alpha[i], '-', color = 'r')
        
        plt.grid()
        plt.show()

        
print('Beta for {} stock is {} & alpha is = {}'.format('AAPL', beta, alpha))

# Apply CAPM formula to calculate the return for the Portfolio
# Obtain a list of all stock names
stock_names = list(beta.keys())
stock_names

# Define the expected return dictionary
ER = {}

rf = 0
rm = stocks_daily_return['sp500'].mean() * 252
for i in stock_names:
    ER[i] = rf + (beta[i] * (rm - rf))
ER

for i in stock_names:
    print('Expected return for {} is {}%'.format(i, ER[i]))

Portfolio_weights = 1/8 * np.ones(8)

# Assume equal weights in the portfolio, calculate returns
ER_portfolio = sum(list(ER.values()) * Portfolio_weights)
ER_portfolio
						

๐ŸŒŸ Key Insights:

๐Ÿง—๐Ÿพ Challenge Faced:

I initially struggled with the loop logic for batch processing all stocks because I was accidentally including the S&P 500 index in the analysis against itself, which created perfect correlation (beta = 1, alpha = 0) and distorted my results. After debugging, I realized I needed to exclude both 'sp500' and 'Date' columns using compound conditional statements if i != 'sp500' and i != 'Date'. This solution ensured I only analyzed actual stocks against the market benchmark, providing meaningful beta and alpha calculations for investment decision-making.

View on GitHub

โ† Back to Projects