|
| 1 | +{ |
| 2 | + "cells": [ |
| 3 | + { |
| 4 | + "cell_type": "markdown", |
| 5 | + "metadata": {}, |
| 6 | + "source": [ |
| 7 | + "# Quantitative Momentum Strategy\n", |
| 8 | + "\n", |
| 9 | + "\"Momentum investing\" means investing in the stocks that have increased in price the most.\n", |
| 10 | + "\n", |
| 11 | + "For this project, we're going to build an investing strategy that selects the 50 stocks with the highest price momentum. From there, we will calculate recommended trades for an equal-weight portfolio of these 50 stocks.\n", |
| 12 | + "\n", |
| 13 | + "\n", |
| 14 | + "## Library Imports\n", |
| 15 | + "\n", |
| 16 | + "The first thing we need to do is import the open-source software libraries that we'll be using in this tutorial." |
| 17 | + ] |
| 18 | + }, |
| 19 | + { |
| 20 | + "cell_type": "code", |
| 21 | + "execution_count": null, |
| 22 | + "metadata": {}, |
| 23 | + "outputs": [], |
| 24 | + "source": [] |
| 25 | + }, |
| 26 | + { |
| 27 | + "cell_type": "markdown", |
| 28 | + "metadata": {}, |
| 29 | + "source": [ |
| 30 | + "## Importing Our List of Stocks\n", |
| 31 | + "\n", |
| 32 | + "As before, we'll need to import our list of stocks and our API token before proceeding. Make sure the `.csv` file is still in your working directory and import it with the following command:" |
| 33 | + ] |
| 34 | + }, |
| 35 | + { |
| 36 | + "cell_type": "code", |
| 37 | + "execution_count": null, |
| 38 | + "metadata": {}, |
| 39 | + "outputs": [], |
| 40 | + "source": [] |
| 41 | + }, |
| 42 | + { |
| 43 | + "cell_type": "markdown", |
| 44 | + "metadata": {}, |
| 45 | + "source": [ |
| 46 | + "## Making Our First API Call\n", |
| 47 | + "\n", |
| 48 | + "It's now time to make the first version of our momentum screener!\n", |
| 49 | + "\n", |
| 50 | + "We need to get one-year price returns for each stock in the universe. Here's how." |
| 51 | + ] |
| 52 | + }, |
| 53 | + { |
| 54 | + "cell_type": "code", |
| 55 | + "execution_count": null, |
| 56 | + "metadata": {}, |
| 57 | + "outputs": [], |
| 58 | + "source": [] |
| 59 | + }, |
| 60 | + { |
| 61 | + "cell_type": "markdown", |
| 62 | + "metadata": {}, |
| 63 | + "source": [ |
| 64 | + "## Parsing Our API Call\n", |
| 65 | + "\n", |
| 66 | + "This API call has all the information we need. We can parse it using the same square-bracket notation as in the first project of this course. Here is an example." |
| 67 | + ] |
| 68 | + }, |
| 69 | + { |
| 70 | + "cell_type": "code", |
| 71 | + "execution_count": null, |
| 72 | + "metadata": {}, |
| 73 | + "outputs": [], |
| 74 | + "source": [] |
| 75 | + }, |
| 76 | + { |
| 77 | + "cell_type": "markdown", |
| 78 | + "metadata": {}, |
| 79 | + "source": [ |
| 80 | + "## Executing A Batch API Call & Building Our DataFrame\n", |
| 81 | + "\n", |
| 82 | + "Just like in our first project, it's now time to execute several batch API calls and add the information we need to our DataFrame.\n", |
| 83 | + "\n", |
| 84 | + "We'll start by running the following code cell, which contains some code we already built last time that we can re-use for this project. More specifically, it contains a function called `chunks` that we can use to divide our list of securities into groups of 100." |
| 85 | + ] |
| 86 | + }, |
| 87 | + { |
| 88 | + "cell_type": "code", |
| 89 | + "execution_count": null, |
| 90 | + "metadata": {}, |
| 91 | + "outputs": [], |
| 92 | + "source": [ |
| 93 | + "# Function sourced from \n", |
| 94 | + "# https://stackoverflow.com/questions/312443/how-do-you-split-a-list-into-evenly-sized-chunks\n", |
| 95 | + "def chunks(lst, n):\n", |
| 96 | + " \"\"\"Yield successive n-sized chunks from lst.\"\"\"\n", |
| 97 | + " for i in range(0, len(lst), n):\n", |
| 98 | + " yield lst[i:i + n] \n", |
| 99 | + " \n", |
| 100 | + "symbol_groups = list(chunks(stocks['Ticker'], 100))\n", |
| 101 | + "symbol_strings = []\n", |
| 102 | + "for i in range(0, len(symbol_groups)):\n", |
| 103 | + " symbol_strings.append(','.join(symbol_groups[i]))\n", |
| 104 | + "# print(symbol_strings[i])\n", |
| 105 | + "\n", |
| 106 | + "my_columns = ['Ticker', 'Price', 'One-Year Price Return', 'Number of Shares to Buy']" |
| 107 | + ] |
| 108 | + }, |
| 109 | + { |
| 110 | + "cell_type": "markdown", |
| 111 | + "metadata": {}, |
| 112 | + "source": [ |
| 113 | + "Now we need to create a blank DataFrame and add our data to the data frame one-by-one." |
| 114 | + ] |
| 115 | + }, |
| 116 | + { |
| 117 | + "cell_type": "code", |
| 118 | + "execution_count": null, |
| 119 | + "metadata": {}, |
| 120 | + "outputs": [], |
| 121 | + "source": [] |
| 122 | + }, |
| 123 | + { |
| 124 | + "cell_type": "markdown", |
| 125 | + "metadata": {}, |
| 126 | + "source": [ |
| 127 | + "## Removing Low-Momentum Stocks\n", |
| 128 | + "\n", |
| 129 | + "The investment strategy that we're building seeks to identify the 50 highest-momentum stocks in the S&P 500.\n", |
| 130 | + "\n", |
| 131 | + "Because of this, the next thing we need to do is remove all the stocks in our DataFrame that fall below this momentum threshold. We'll sort the DataFrame by the stocks' one-year price return, and drop all stocks outside the top 50.\n" |
| 132 | + ] |
| 133 | + }, |
| 134 | + { |
| 135 | + "cell_type": "code", |
| 136 | + "execution_count": null, |
| 137 | + "metadata": {}, |
| 138 | + "outputs": [], |
| 139 | + "source": [] |
| 140 | + }, |
| 141 | + { |
| 142 | + "cell_type": "markdown", |
| 143 | + "metadata": {}, |
| 144 | + "source": [ |
| 145 | + "## Calculating the Number of Shares to Buy\n", |
| 146 | + "\n", |
| 147 | + "Just like in the last project, we now need to calculate the number of shares we need to buy. The one change we're going to make is wrapping this functionality inside a function, since we'll be using it again later in this Jupyter Notebook.\n", |
| 148 | + "\n", |
| 149 | + "Since we've already done most of the work on this, try to complete the following two code cells without watching me do it first!" |
| 150 | + ] |
| 151 | + }, |
| 152 | + { |
| 153 | + "cell_type": "code", |
| 154 | + "execution_count": null, |
| 155 | + "metadata": {}, |
| 156 | + "outputs": [], |
| 157 | + "source": [] |
| 158 | + }, |
| 159 | + { |
| 160 | + "cell_type": "code", |
| 161 | + "execution_count": null, |
| 162 | + "metadata": {}, |
| 163 | + "outputs": [], |
| 164 | + "source": [] |
| 165 | + }, |
| 166 | + { |
| 167 | + "cell_type": "markdown", |
| 168 | + "metadata": {}, |
| 169 | + "source": [ |
| 170 | + "## Building a Better (and More Realistic) Momentum Strategy\n", |
| 171 | + "\n", |
| 172 | + "Real-world quantitative investment firms differentiate between \"high quality\" and \"low quality\" momentum stocks:\n", |
| 173 | + "\n", |
| 174 | + "* High-quality momentum stocks show \"slow and steady\" outperformance over long periods of time\n", |
| 175 | + "* Low-quality momentum stocks might not show any momentum for a long time, and then surge upwards.\n", |
| 176 | + "\n", |
| 177 | + "The reason why high-quality momentum stocks are preferred is because low-quality momentum can often be cause by short-term news that is unlikely to be repeated in the future (such as an FDA approval for a biotechnology company).\n", |
| 178 | + "\n", |
| 179 | + "To identify high-quality momentum, we're going to build a strategy that selects stocks from the highest percentiles of: \n", |
| 180 | + "\n", |
| 181 | + "* 1-month price returns\n", |
| 182 | + "* 3-month price returns\n", |
| 183 | + "* 6-month price returns\n", |
| 184 | + "* 1-year price returns\n", |
| 185 | + "\n", |
| 186 | + "Let's start by building our DataFrame. You'll notice that I use the abbreviation `hqm` often. It stands for `high-quality momentum`." |
| 187 | + ] |
| 188 | + }, |
| 189 | + { |
| 190 | + "cell_type": "code", |
| 191 | + "execution_count": null, |
| 192 | + "metadata": {}, |
| 193 | + "outputs": [], |
| 194 | + "source": [] |
| 195 | + }, |
| 196 | + { |
| 197 | + "cell_type": "markdown", |
| 198 | + "metadata": {}, |
| 199 | + "source": [ |
| 200 | + "## Calculating Momentum Percentiles\n", |
| 201 | + "\n", |
| 202 | + "We now need to calculate momentum percentile scores for every stock in the universe. More specifically, we need to calculate percentile scores for the following metrics for every stock:\n", |
| 203 | + "\n", |
| 204 | + "* `One-Year Price Return`\n", |
| 205 | + "* `Six-Month Price Return`\n", |
| 206 | + "* `Three-Month Price Return`\n", |
| 207 | + "* `One-Month Price Return`\n", |
| 208 | + "\n", |
| 209 | + "Here's how we'll do this:" |
| 210 | + ] |
| 211 | + }, |
| 212 | + { |
| 213 | + "cell_type": "code", |
| 214 | + "execution_count": null, |
| 215 | + "metadata": {}, |
| 216 | + "outputs": [], |
| 217 | + "source": [] |
| 218 | + }, |
| 219 | + { |
| 220 | + "cell_type": "markdown", |
| 221 | + "metadata": {}, |
| 222 | + "source": [ |
| 223 | + "## Calculating the HQM Score\n", |
| 224 | + "\n", |
| 225 | + "We'll now calculate our `HQM Score`, which is the high-quality momentum score that we'll use to filter for stocks in this investing strategy.\n", |
| 226 | + "\n", |
| 227 | + "The `HQM Score` will be the arithmetic mean of the 4 momentum percentile scores that we calculated in the last section.\n", |
| 228 | + "\n", |
| 229 | + "To calculate arithmetic mean, we will use the `mean` function from Python's built-in `statistics` module." |
| 230 | + ] |
| 231 | + }, |
| 232 | + { |
| 233 | + "cell_type": "code", |
| 234 | + "execution_count": null, |
| 235 | + "metadata": {}, |
| 236 | + "outputs": [], |
| 237 | + "source": [] |
| 238 | + }, |
| 239 | + { |
| 240 | + "cell_type": "markdown", |
| 241 | + "metadata": {}, |
| 242 | + "source": [ |
| 243 | + "## Selecting the 50 Best Momentum Stocks\n", |
| 244 | + "\n", |
| 245 | + "As before, we can identify the 50 best momentum stocks in our universe by sorting the DataFrame on the `HQM Score` column and dropping all but the top 50 entries." |
| 246 | + ] |
| 247 | + }, |
| 248 | + { |
| 249 | + "cell_type": "code", |
| 250 | + "execution_count": null, |
| 251 | + "metadata": {}, |
| 252 | + "outputs": [], |
| 253 | + "source": [] |
| 254 | + }, |
| 255 | + { |
| 256 | + "cell_type": "markdown", |
| 257 | + "metadata": {}, |
| 258 | + "source": [ |
| 259 | + "## Calculating the Number of Shares to Buy\n", |
| 260 | + "\n", |
| 261 | + "We'll use the `portfolio_input` function that we created earlier to accept our portfolio size. Then we will use similar logic in a `for` loop to calculate the number of shares to buy for each stock in our investment universe." |
| 262 | + ] |
| 263 | + }, |
| 264 | + { |
| 265 | + "cell_type": "code", |
| 266 | + "execution_count": null, |
| 267 | + "metadata": {}, |
| 268 | + "outputs": [], |
| 269 | + "source": [] |
| 270 | + }, |
| 271 | + { |
| 272 | + "cell_type": "code", |
| 273 | + "execution_count": null, |
| 274 | + "metadata": {}, |
| 275 | + "outputs": [], |
| 276 | + "source": [] |
| 277 | + }, |
| 278 | + { |
| 279 | + "cell_type": "markdown", |
| 280 | + "metadata": {}, |
| 281 | + "source": [ |
| 282 | + "## Formatting Our Excel Output\n", |
| 283 | + "\n", |
| 284 | + "We will be using the XlsxWriter library for Python to create nicely-formatted Excel files.\n", |
| 285 | + "\n", |
| 286 | + "XlsxWriter is an excellent package and offers tons of customization. However, the tradeoff for this is that the library can seem very complicated to new users. Accordingly, this section will be fairly long because I want to do a good job of explaining how XlsxWriter works." |
| 287 | + ] |
| 288 | + }, |
| 289 | + { |
| 290 | + "cell_type": "code", |
| 291 | + "execution_count": null, |
| 292 | + "metadata": {}, |
| 293 | + "outputs": [], |
| 294 | + "source": [] |
| 295 | + }, |
| 296 | + { |
| 297 | + "cell_type": "markdown", |
| 298 | + "metadata": {}, |
| 299 | + "source": [ |
| 300 | + "## Creating the Formats We'll Need For Our .xlsx File\n", |
| 301 | + "\n", |
| 302 | + "You'll recall from our first project that formats include colors, fonts, and also symbols like % and $. We'll need four main formats for our Excel document:\n", |
| 303 | + "\n", |
| 304 | + "* String format for tickers\n", |
| 305 | + "* \\$XX.XX format for stock prices\n", |
| 306 | + "* \\$XX,XXX format for market capitalization\n", |
| 307 | + "* Integer format for the number of shares to purchase\n", |
| 308 | + "\n", |
| 309 | + "Since we already built our formats in the last section of this course, I've included them below for you. Run this code cell before proceeding." |
| 310 | + ] |
| 311 | + }, |
| 312 | + { |
| 313 | + "cell_type": "code", |
| 314 | + "execution_count": null, |
| 315 | + "metadata": {}, |
| 316 | + "outputs": [], |
| 317 | + "source": [] |
| 318 | + }, |
| 319 | + { |
| 320 | + "cell_type": "code", |
| 321 | + "execution_count": null, |
| 322 | + "metadata": {}, |
| 323 | + "outputs": [], |
| 324 | + "source": [] |
| 325 | + }, |
| 326 | + { |
| 327 | + "cell_type": "markdown", |
| 328 | + "metadata": {}, |
| 329 | + "source": [ |
| 330 | + "## Saving Our Excel Output\n", |
| 331 | + "\n", |
| 332 | + "As before, saving our Excel output is very easy:" |
| 333 | + ] |
| 334 | + }, |
| 335 | + { |
| 336 | + "cell_type": "code", |
| 337 | + "execution_count": null, |
| 338 | + "metadata": {}, |
| 339 | + "outputs": [], |
| 340 | + "source": [] |
| 341 | + } |
| 342 | + ], |
| 343 | + "metadata": { |
| 344 | + "kernelspec": { |
| 345 | + "display_name": "Python 3", |
| 346 | + "language": "python", |
| 347 | + "name": "python3" |
| 348 | + }, |
| 349 | + "language_info": { |
| 350 | + "codemirror_mode": { |
| 351 | + "name": "ipython", |
| 352 | + "version": 3 |
| 353 | + }, |
| 354 | + "file_extension": ".py", |
| 355 | + "mimetype": "text/x-python", |
| 356 | + "name": "python", |
| 357 | + "nbconvert_exporter": "python", |
| 358 | + "pygments_lexer": "ipython3", |
| 359 | + "version": "3.8.5" |
| 360 | + } |
| 361 | + }, |
| 362 | + "nbformat": 4, |
| 363 | + "nbformat_minor": 4 |
| 364 | +} |
0 commit comments