{ "cells": [ { "cell_type": "markdown", "id": "8bea070e", "metadata": {}, "source": [ "
" ] }, { "cell_type": "markdown", "id": "59b6b6e4", "metadata": {}, "source": [ "This code performs a multifactor analysis on monthly stock returns, applying the Fama-French three-factor model for financial analysis. It fetches historical factor data, calculates active returns of selected stocks, and estimates their sensitivities to the Fama-French factors. The code also performs rolling regression to analyze the stability of factor exposures over time. Lastly, it calculates and prints the marginal contributions to risk from each factor." ] }, { "cell_type": "code", "execution_count": null, "id": "84290a88", "metadata": {}, "outputs": [], "source": [ "import numpy as np\n", "import pandas as pd" ] }, { "cell_type": "code", "execution_count": null, "id": "2ec2de24", "metadata": {}, "outputs": [], "source": [ "import pandas_datareader as pdr\n", "import yfinance as yf" ] }, { "cell_type": "code", "execution_count": null, "id": "06d39571", "metadata": {}, "outputs": [], "source": [ "import statsmodels.api as sm\n", "from statsmodels import regression\n", "from statsmodels.regression.rolling import RollingOLS" ] }, { "cell_type": "markdown", "id": "bc2a7be0", "metadata": {}, "source": [ "Fetch Fama-French factors data starting from 2000-01-01 and select the SMB and HML factors" ] }, { "cell_type": "code", "execution_count": null, "id": "5aff0b52", "metadata": {}, "outputs": [], "source": [ "factors = pdr.get_data_famafrench(\n", " 'F-F_Research_Data_Factors',\n", " start='2000-01-01'\n", ")[0][1:]" ] }, { "cell_type": "code", "execution_count": null, "id": "65938e3e", "metadata": {}, "outputs": [], "source": [ "SMB = factors.SMB\n", "HML = factors.HML" ] }, { "cell_type": "markdown", "id": "8528b4ce", "metadata": {}, "source": [ "Download monthly adjusted close prices for specified stocks starting from 2000-01-01" ] }, { "cell_type": "code", "execution_count": null, "id": "8518efcf", "metadata": {}, "outputs": [], "source": [ "data = yf.download(\n", " ['SPY', 'MSFT', 'AAPL', 'INTC'], \n", " start=\"2000-01-01\", \n", " interval=\"1mo\"\n", ")['Adj Close']" ] }, { "cell_type": "markdown", "id": "439951e5", "metadata": {}, "source": [ "Calculate the monthly returns and convert them to period-based returns" ] }, { "cell_type": "code", "execution_count": null, "id": "9b300366", "metadata": {}, "outputs": [], "source": [ "monthly_returns = data.pct_change().to_period(\"M\")" ] }, { "cell_type": "markdown", "id": "246d309b", "metadata": {}, "source": [ "Extract the benchmark returns (SPY) and calculate active returns against the benchmark" ] }, { "cell_type": "code", "execution_count": null, "id": "f71342e4", "metadata": {}, "outputs": [], "source": [ "bench = monthly_returns.pop(\"SPY\")\n", "R = monthly_returns.mean(axis=1)\n", "active = R - bench" ] }, { "cell_type": "markdown", "id": "ff54a6cb", "metadata": {}, "source": [ "Create a DataFrame with active returns and Fama-French factors SMB and HML" ] }, { "cell_type": "code", "execution_count": null, "id": "d8ed7a12", "metadata": {}, "outputs": [], "source": [ "df = pd.DataFrame({\n", " 'R': active,\n", " 'F1': SMB,\n", " 'F2': HML,\n", "}).dropna()" ] }, { "cell_type": "markdown", "id": "3201423a", "metadata": {}, "source": [ "Perform Ordinary Least Squares (OLS) regression to estimate sensitivities to the factors" ] }, { "cell_type": "code", "execution_count": null, "id": "d5e1b15c", "metadata": {}, "outputs": [], "source": [ "b1, b2 = regression.linear_model.OLS(\n", " df.R, \n", " df[['F1', 'F2']]\n", ").fit().params" ] }, { "cell_type": "code", "execution_count": null, "id": "f621be1f", "metadata": {}, "outputs": [], "source": [ "print(f'Sensitivities of active returns to factors:\\nSMB: {b1}\\nHML: {b2}')" ] }, { "cell_type": "markdown", "id": "2eab25c3", "metadata": {}, "source": [ "Perform rolling OLS regression to estimate how factor sensitivities change over time" ] }, { "cell_type": "code", "execution_count": null, "id": "c2489be4", "metadata": {}, "outputs": [], "source": [ "exog_vars = [\"SMB\", \"HML\"]\n", "exog = sm.add_constant(factors[exog_vars])\n", "rols = RollingOLS(df.R, exog, window=12)\n", "rres = rols.fit()\n", "fig = rres.plot_recursive_coefficient(variables=exog_vars)" ] }, { "cell_type": "markdown", "id": "f4a1b059", "metadata": {}, "source": [ "Calculate covariance between factors and marginal contributions to active risk (MCAR) for each factor" ] }, { "cell_type": "code", "execution_count": null, "id": "ad0355a8", "metadata": {}, "outputs": [], "source": [ "F1 = df.F1\n", "F2 = df.F2" ] }, { "cell_type": "code", "execution_count": null, "id": "6b0126e7", "metadata": {}, "outputs": [], "source": [ "cov = np.cov(F1, F2)\n", "ar_squared = (active.std())**2\n", "mcar1 = (b1 * (b2 * cov[0,1] + b1 * cov[0,0])) / ar_squared\n", "mcar2 = (b2 * (b1 * cov[0,1] + b2 * cov[1,1])) / ar_squared\n", "print(f'SMB risk contribution: {mcar1}')\n", "print(f'HML risk contribution: {mcar2}')\n", "print(f'Unexplained risk contribution: {1 - (mcar1 + mcar2)}')" ] }, { "cell_type": "markdown", "id": "96df69b9", "metadata": {}, "source": [ "PyQuant News is where finance practitioners level up with Python for quant finance, algorithmic trading, and market data analysis. Looking to get started? Check out the fastest growing, top-selling course to get started with Python for quant finance. For educational purposes. Not investment advise. Use at your own risk." ] } ], "metadata": { "jupytext": { "cell_metadata_filter": "-all", "main_language": "python", "notebook_metadata_filter": "-all" }, "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.10.13" } }, "nbformat": 4, "nbformat_minor": 5 }