{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Lesson 3- Calculating a Stock's Beta and Alpha" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In this example, we will retrieve historical stock data from Yahoo! Finance and calculate a stock's beta and alpha using Python.\n", "\n", "Recall that a stock's beta is a measure of non-diversifiable (or systematic) risk. To estimate a stock's beta, we make use of the Capital Asset Pricing Model (CAPM):\n", "\n", "$$\\begin{eqnarray}\n", "E(R_i) = R_f + \\beta_i(E(R_M) - R_f) \\\\\n", "\\end{eqnarray}$$\n", "\n", "$E(R_i)$ is the expected return for stock $i$, $R_f$ is the risk-free rate, $E(R_M)$ is the return of the market portfolio, and $\\beta_i$ is the systematic risk, or the tendency of a security's returns to respond to swings in the overall market.\n", "\n", "To estimate $\\beta_i$ for a stock, we can regress the excess returns of a stock on the excess returns of the market. The intercept in this case will be a stock's $\\alpha$. \n", "\n", "$$\\begin{equation}\n", "R_i - R_f = \\alpha_i + \\beta_i(R_M - R_f) + \\epsilon_i \\\\\n", "\\end{equation}$$\n", "\n", "Generally:\n", "- $\\beta_i > 1$: The stock moves in the same direction as the market, but at a greater magnitude.\n", "- $\\beta_i = 1$: The stock moves in the same direction and magnitude as the market.\n", "- $0 < \\beta_i < 1$: The stock moves in the same direction as the market, but with a lower magnitude.\n", "- $\\beta_i = 0$: The stock's movement is uncorrelated with the market.\n", "- $-1 < \\beta_i < 0$: The stock moves in the opposite direction as the market, but with a lower magnitude.\n", "- $\\beta_i < -1$: The stock moves in the opposite direction as the market, and by a greater magnitude.\n", "\n", "and\n", "\n", "- $\\alpha_i > 0$: The stock has a return in excess of the return we would expect given its risk\n", "- $\\alpha_i = 0$: The stock has a return appropriate given its level of risk\n", "- $\\alpha_i < 0$: The stock has a return less than what is required given its level of risk\n", "\n", "Make sure you install the following libraries in your Anaconda Prompt:\n", "\n", "pip install requests_html
\n", "pip install yahoo_fin
\n", "pip install pandas
\n", "pip install pandas_datareader
\n", "pip install statsmodels
\n", "pip install seaborn
" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "***" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Importing Libraries\n", "\n", "The statsmodels library is used to run our regression. pandas and pandas_datareader are used to work with dataframes and import data from the web to dataframes, respectively. The librarydatetime will be used to adjust some dates in our dataframes. Finally, seaborn will help us graph our returns." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from yahoo_fin.stock_info import get_data\n", "import statsmodels.formula.api as sm\n", "import pandas as pd\n", "import pandas_datareader.data as web\n", "import datetime\n", "import seaborn as sns" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "***" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Retrieving Market Data\n", "Ken French of the Tuck School of Business at Darmouth maintains a data library of the daily, weekly, monthly, and annual market risk premiums $E(r_M) - r_f$, risk free rates $r_f$, and a number of other factors. The pandas_datareader allows us to retrieve the data available at http://mba.tuck.dartmouth.edu/pages/faculty/ken.french/data_library.html. We will use the monthly data and store it in a dataframe." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#https://pandas-datareader.readthedocs.io/en/latest/readers/famafrench.html\n", "mkt_rf = web.DataReader('F-F_Research_Data_5_Factors_2x3', 'famafrench')\n", "mkt_rf.head()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let's only keep the columns we need and pull the month and year from the date index which we need to do to align the dates with our stock returns dataframe we create later." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "mkt_rf = mkt_rf.drop(['SMB','HML', 'RMW', 'CMA'], axis=1)\n", "mkt_rf = mkt_rf.rename(columns={'Mkt-RF':'mktrf', 'RF':'rf'}) #rename some columns\n", "mkt_rf['month'] = mkt_rf.index.month #create a month column\n", "mkt_rf['year'] = mkt_rf.index.year #create a year column\n", "mkt_rf = mkt_rf.set_index(['year', 'month'])\n", "mkt_rf.head()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "***" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Retrieving Stock Data\n", "\n", "Now, we obtain stock data from Yahoo! Finance on a ticker we choose. We'll get data for the last 3 years because the beta as reported in Yahoo! Finance is the monthly 3 year beta. Again, we create some month and year columns in order to align this data with the monthly data on the market risk premium and risk free rate.\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "ticker = input('Input the ticker of the stock: ')\n", "share = get_data(ticker, start_date = '01/21/2016' , end_date = '12/31/2018', index_as_date=True)\n", "\n", "share['index1'] = share.index\n", "#https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DatetimeIndex.html\n", "share['month'] = pd.DatetimeIndex(share['index1']).month #obtain the month\n", "share['year'] = pd.DatetimeIndex(share['index1']).year #obtain the year\n", "\n", "share.head()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now, let's convert the daily stock data to monthly data." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "share_dict = {'open':'first','high':'max','low':'min','close': 'last','volume': 'sum','adjclose': 'last', 'month':'last', 'year': 'last'}\n", "share.index = pd.to_datetime(share.index) #tells pandas that the index is a date\n", "share = share.resample('M', how=share_dict) #take only monthly data as we specified above\n", "share = share.set_index(['year', 'month'])\n", "share.head()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "***" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Obtaining Stock Returns\n", "\n", "Similar to the previous graphing example, we calculate the stock returns, but here we convert to a percent to match the units of the Ken French data. We use the adjusted close price.\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "stock = share['adjclose']\n", "stock_lag = stock.shift(1)\n", "stock_lag.name = 'adjcloselag'\n", "stock = pd.concat([stock, stock_lag], axis=1)\n", "print(stock.head())\n", "stock['return_stock'] = (stock['adjclose'] - stock['adjcloselag'])/stock['adjcloselag']\n", "stock = (stock['return_stock']*100) #Put returns in same units as the returns in the market data file\n", "print(stock.head())" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Here we combine the stock returns and the market data and subtract the risk free rate from the returns." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "returns = pd.concat([stock, mkt_rf], axis=1, sort=True) #concatenate the dataframes\n", "returns['r_rf'] = returns['return_stock']-returns['rf']\n", "returns.dropna(inplace=True)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Run the Regression\n", "\n", "In the combined dataframe, we run the regression to obtain beta. This uses the statsmodels library we imported." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#OLS Regression: ttps://www.statsmodels.org/dev/generated/statsmodels.regression.linear_model.OLS.html\n", "result = sm.ols(formula=\"r_rf ~ mktrf\", data = returns).fit()\n", "\n", "beta = result.params.mktrf\n", "print (result.summary())\n", "print ('')\n", "print('*********Beta***********')\n", "print (beta)\n", "print('************************')\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "***" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Plot the Regression Line\n", "\n", "Here, we use the seaborn library to plot the monthly excess returns of our stock against the monthly excess returns of the market. We can see the graphical representation of the slope ($\\beta$) and the intercept ($\\alpha$)." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "ax = sns.regplot(x=\"mktrf\", y= \"r_rf\", data=returns )\n", "#ax.set(ylim=(0, 10))\n", "#ax.set(xlim=(0, 10))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "***" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## The Complete Code\n", "\n", "Try out the code from this notebook in Spyder. Use the variable explorer to view the variables and dataframes you create." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from yahoo_fin.stock_info import get_data\n", "import statsmodels.formula.api as sm\n", "import pandas as pd\n", "import pandas_datareader.data as web\n", "import datetime\n", "import seaborn as sns\n", "\n", "\n", "mkt_rf = web.DataReader('F-F_Research_Data_5_Factors_2x3', 'famafrench')\n", "\n", "mkt_rf = mkt_rf.drop(['SMB','HML', 'RMW', 'CMA'], axis=1)\n", "mkt_rf = mkt_rf.rename(columns={'Mkt-RF':'mktrf', 'RF':'rf'}) #rename some columns\n", "mkt_rf['month'] = mkt_rf.index.month #create a month column\n", "mkt_rf['year'] = mkt_rf.index.year #create a year column\n", "mkt_rf = mkt_rf.set_index(['year', 'month'])\n", "mkt_rf.head()\n", "\n", "ticker = input('Input the ticker of the stock: ')\n", "share = get_data(ticker, start_date = '01/21/2016' , end_date = '12/31/2018', index_as_date=True)\n", "\n", "share['index1'] = share.index\n", "share['month'] = pd.DatetimeIndex(share['index1']).month #obtain the month\n", "share['year'] = pd.DatetimeIndex(share['index1']).year #obtain the year\n", "\n", "share_dict = {'open':'first','high':'max','low':'min','close': 'last','volume': 'sum','adjclose': 'last', 'month':'last', 'year': 'last'}\n", "share.index = pd.to_datetime(share.index) #tells pandas that the index is a date\n", "share = share.resample('M', how=share_dict) #take only monthly data as we specified above\n", "share = share.set_index(['year', 'month'])\n", "\n", "stock = share['adjclose']\n", "stock_lag = stock.shift(1)\n", "stock_lag.name = 'adjcloselag'\n", "stock = pd.concat([stock, stock_lag], axis=1)\n", "stock['return_stock'] = (stock['adjclose'] - stock['adjcloselag'])/stock['adjcloselag']\n", "stock = (stock['return_stock']*100) #Put returns in same units as the returns in the market data file\n", "\n", "returns = pd.concat([stock, mkt_rf], axis=1, sort=True) #concatenate the dataframes\n", "returns['r_rf'] = returns['return_stock']-returns['rf']\n", "returns.dropna(inplace=True)\n", "\n", "result = sm.ols(formula=\"r_rf ~ mktrf\", data = returns).fit()\n", "\n", "beta = result.params.mktrf\n", "print (result.summary())\n", "\n", "ax = sns.regplot(x=\"mktrf\", y= \"r_rf\", data=returns )\n", "display(ax)\n", "#ax.set(ylim=(0, 10))\n", "#ax.set(xlim=(0, 10))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "***" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Further Reading\n", "\n", "Be sure to check out the documentation of the libraries we've used. Ken French's data library has additional datasets that you may find useful.\n", "\n", "Statsmodels documentation: https://www.statsmodels.org/dev/index.html
\n", "Seaborn documentation: https://pypi.org/project/seaborn/
\n", "Ken French's website: http://mba.tuck.dartmouth.edu/pages/faculty/ken.french/data_library.html
" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "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.7.1" } }, "nbformat": 4, "nbformat_minor": 2 }