├── Projects ├── 1 - Building An Equal-Weight S&P 500 Index Fund │ ├── 001_equal_weight_S&P_500.ipynb │ ├── __pycache__ │ │ └── secrets.cpython-38.pyc │ ├── recommended trades.xlsx │ └── secrets.py ├── 2 - Building A Quantitative Momentum Investing Strategy │ ├── .ipynb_checkpoints │ │ └── 002_quantitative_momentum_strategy-checkpoint.ipynb │ ├── 002_quantitative_momentum_strategy.ipynb │ ├── __pycache__ │ │ └── secrets.cpython-38.pyc │ ├── momentum_strategy.xlsx │ └── secrets.py ├── 3 - Building A Quantitative Value Investing Strategy │ ├── 003_quantitative_value_strategy.ipynb │ ├── __pycache__ │ │ └── secrets.cpython-38.pyc │ ├── secrets.py │ └── value_strategy.xlsx └── sp_500_stocks.csv ├── README.md └── requirements.txt /Projects/1 - Building An Equal-Weight S&P 500 Index Fund/001_equal_weight_S&P_500.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Equal-Weight S&P 500 Index Fund\n", 8 | "\n", 9 | "## Introduction & Library Imports\n", 10 | "\n", 11 | "The S&P 500 is the world's most popular stock market index. The largest fund that is benchmarked to this index is the SPDR® S&P 500® ETF Trust. It has more than US$250 billion of assets under management.\n", 12 | "\n", 13 | "The goal of this section of the course is to create a Python script that will accept the value of your portfolio and tell you how many shares of each S&P 500 constituent you should purchase to get an equal-weight version of the index fund.\n", 14 | "\n", 15 | "## Library Imports\n", 16 | "\n", 17 | "The first thing we need to do is import the open-source software libraries that we'll be using in this tutorial." 18 | ] 19 | }, 20 | { 21 | "cell_type": "code", 22 | "execution_count": 1, 23 | "metadata": {}, 24 | "outputs": [], 25 | "source": [ 26 | "import numpy as np\n", 27 | "import pandas as pd\n", 28 | "import requests\n", 29 | "import xlsxwriter\n", 30 | "import math" 31 | ] 32 | }, 33 | { 34 | "cell_type": "markdown", 35 | "metadata": {}, 36 | "source": [ 37 | "## Importing Our List of Stocks\n", 38 | "\n", 39 | "The next thing we need to do is import the constituents of the S&P 500.\n", 40 | "\n", 41 | "These constituents change over time, so in an ideal world you would connect directly to the index provider (Standard & Poor's) and pull their real-time constituents on a regular basis.\n", 42 | "\n", 43 | "Paying for access to the index provider's API is outside of the scope of this course. \n", 44 | "\n", 45 | "There's a static version of the S&P 500 constituents available here. [Click this link to download them now](https://drive.google.com/file/d/1ZJSpbY69DVckVZlO9cC6KkgfSufybcHN/view?usp=sharing). Move this file into the `starter-files` folder so it can be accessed by other files in that directory.\n", 46 | "\n", 47 | "Now it's time to import these stocks to our Jupyter Notebook file." 48 | ] 49 | }, 50 | { 51 | "cell_type": "code", 52 | "execution_count": 2, 53 | "metadata": {}, 54 | "outputs": [ 55 | { 56 | "output_type": "execute_result", 57 | "data": { 58 | "text/plain": [ 59 | " Ticker\n", 60 | "0 A\n", 61 | "1 AAL\n", 62 | "2 AAP\n", 63 | "3 AAPL\n", 64 | "4 ABBV\n", 65 | ".. ...\n", 66 | "500 YUM\n", 67 | "501 ZBH\n", 68 | "502 ZBRA\n", 69 | "503 ZION\n", 70 | "504 ZTS\n", 71 | "\n", 72 | "[505 rows x 1 columns]" 73 | ], 74 | "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
Ticker
0A
1AAL
2AAP
3AAPL
4ABBV
......
500YUM
501ZBH
502ZBRA
503ZION
504ZTS
\n

505 rows × 1 columns

\n
" 75 | }, 76 | "metadata": {}, 77 | "execution_count": 2 78 | } 79 | ], 80 | "source": [ 81 | "stocks = pd.read_csv('../sp_500_stocks.csv')\n", 82 | "stocks" 83 | ] 84 | }, 85 | { 86 | "cell_type": "markdown", 87 | "metadata": {}, 88 | "source": [ 89 | "## Acquiring an API Token\n", 90 | "\n", 91 | "Now it's time to import our IEX Cloud API token. This is the data provider that we will be using throughout this course.\n", 92 | "\n", 93 | "API tokens (and other sensitive information) should be stored in a `secrets.py` file that doesn't get pushed to your local Git repository. We'll be using a sandbox API token in this course, which means that the data we'll use is randomly-generated and (more importantly) has no cost associated with it.\n", 94 | "\n", 95 | "[Click here](http://nickmccullum.com/algorithmic-trading-python/secrets.py) to download your `secrets.py` file. Move the file into the same directory as this Jupyter Notebook before proceeding." 96 | ] 97 | }, 98 | { 99 | "cell_type": "code", 100 | "execution_count": 3, 101 | "metadata": {}, 102 | "outputs": [], 103 | "source": [ 104 | "from secrets import IEX_CLOUD_API_TOKEN" 105 | ] 106 | }, 107 | { 108 | "cell_type": "markdown", 109 | "metadata": {}, 110 | "source": [ 111 | "## Making Our First API Call\n", 112 | "\n", 113 | "Now it's time to structure our API calls to IEX cloud. \n", 114 | "\n", 115 | "We need the following information from the API:\n", 116 | "\n", 117 | "* Market capitalization for each stock\n", 118 | "* Price of each stock\n", 119 | "\n" 120 | ] 121 | }, 122 | { 123 | "cell_type": "code", 124 | "execution_count": 4, 125 | "metadata": {}, 126 | "outputs": [ 127 | { 128 | "output_type": "stream", 129 | "name": "stdout", 130 | "text": [ 131 | "{'symbol': 'AAPL', 'companyName': 'Apple Inc', 'primaryExchange': ')L/ LRNECSSDEMQN TT (GLAABESOAAKG', 'calculationPrice': 'tops', 'open': None, 'openTime': None, 'openSource': 'fcloifia', 'close': None, 'closeTime': None, 'closeSource': 'ilofcaif', 'high': None, 'highTime': None, 'highSource': None, 'low': None, 'lowTime': None, 'lowSource': None, 'latestPrice': 130.01, 'latestSource': 'IEX real time price', 'latestTime': '10:55:45 AM', 'latestUpdate': 1686273655760, 'latestVolume': None, 'iexRealtimePrice': 132.93, 'iexRealtimeSize': 47, 'iexLastUpdated': 1666997853301, 'delayedPrice': None, 'delayedPriceTime': None, 'oddLotDelayedPrice': None, 'oddLotDelayedPriceTime': None, 'extendedPrice': None, 'extendedChange': None, 'extendedChangePercent': None, 'extendedPriceTime': None, 'previousClose': 130.86, 'previousVolume': 115360722, 'change': -0.03, 'changePercent': -0.00024, 'volume': None, 'iexMarketPercent': 0.01251982318569605, 'iexVolume': 456812, 'avgTotalVolume': 113001741, 'iexBidPrice': 131, 'iexBidSize': 201, 'iexAskPrice': 132.58, 'iexAskSize': 104, 'iexOpen': 132.87, 'iexOpenTime': 1643381440103, 'iexClose': 132.04, 'iexCloseTime': 1625878055912, 'marketCap': 2183518918199, 'peRatio': 38.97, 'week52High': 139.51, 'week52Low': 58.35, 'ytdChange': -0.0439454018971407, 'lastTradeTime': 1662289317201, 'isUSMarketOpen': True}\n" 132 | ] 133 | } 134 | ], 135 | "source": [ 136 | "symbol = 'AAPL'\n", 137 | "api_url = f'https://sandbox.iexapis.com/stable/stock/{symbol}/quote/?token={IEX_CLOUD_API_TOKEN}'\n", 138 | "data = requests.get(api_url).json()\n", 139 | "print(data)" 140 | ] 141 | }, 142 | { 143 | "cell_type": "markdown", 144 | "metadata": {}, 145 | "source": [ 146 | "## Parsing Our API Call\n", 147 | "\n", 148 | "The API call that we executed in the last code block contains all of the information required to build our equal-weight S&P 500 strategy. \n", 149 | "\n", 150 | "With that said, the data isn't in a proper format yet. We need to parse it first." 151 | ] 152 | }, 153 | { 154 | "cell_type": "code", 155 | "execution_count": 5, 156 | "metadata": {}, 157 | "outputs": [], 158 | "source": [ 159 | "price = data['latestPrice']\n", 160 | "market_cap = data['marketCap']" 161 | ] 162 | }, 163 | { 164 | "cell_type": "markdown", 165 | "metadata": {}, 166 | "source": [ 167 | "## Adding Our Stocks Data to a Pandas DataFrame\n", 168 | "\n", 169 | "The next thing we need to do is add our stock's price and market capitalization to a pandas DataFrame. Think of a DataFrame like the Python version of a spreadsheet. It stores tabular data." 170 | ] 171 | }, 172 | { 173 | "cell_type": "code", 174 | "execution_count": 6, 175 | "metadata": {}, 176 | "outputs": [ 177 | { 178 | "output_type": "execute_result", 179 | "data": { 180 | "text/plain": [ 181 | "Empty DataFrame\n", 182 | "Columns: [Ticker, Stock Price, Market Capitalization, Number of Shares to Buy]\n", 183 | "Index: []" 184 | ], 185 | "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n
TickerStock PriceMarket CapitalizationNumber of Shares to Buy
\n
" 186 | }, 187 | "metadata": {}, 188 | "execution_count": 6 189 | } 190 | ], 191 | "source": [ 192 | "my_columns = ['Ticker', 'Stock Price', 'Market Capitalization', 'Number of Shares to Buy']\n", 193 | "final_dataframe = pd.DataFrame(columns = my_columns)\n", 194 | "final_dataframe" 195 | ] 196 | }, 197 | { 198 | "cell_type": "code", 199 | "execution_count": 7, 200 | "metadata": {}, 201 | "outputs": [ 202 | { 203 | "output_type": "execute_result", 204 | "data": { 205 | "text/plain": [ 206 | " Ticker Stock Price Market Capitalization Number of Shares to Buy\n", 207 | "0 AAPL 130.01 2183518918199 N/A" 208 | ], 209 | "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
TickerStock PriceMarket CapitalizationNumber of Shares to Buy
0AAPL130.012183518918199N/A
\n
" 210 | }, 211 | "metadata": {}, 212 | "execution_count": 7 213 | } 214 | ], 215 | "source": [ 216 | "final_dataframe.append(\n", 217 | " pd.Series(\n", 218 | " [\n", 219 | " symbol,\n", 220 | " price,\n", 221 | " market_cap,\n", 222 | " 'N/A'\n", 223 | " ],\n", 224 | " index = my_columns\n", 225 | " ),\n", 226 | " ignore_index = True\n", 227 | ")" 228 | ] 229 | }, 230 | { 231 | "cell_type": "markdown", 232 | "metadata": {}, 233 | "source": [ 234 | "## Looping Through The Tickers in Our List of Stocks\n", 235 | "\n", 236 | "Using the same logic that we outlined above, we can pull data for all S&P 500 stocks and store their data in the DataFrame using a `for` loop." 237 | ] 238 | }, 239 | { 240 | "cell_type": "code", 241 | "execution_count": 8, 242 | "metadata": {}, 243 | "outputs": [], 244 | "source": [ 245 | "final_dataframe = pd.DataFrame(columns = my_columns)\n", 246 | "for stock in stocks['Ticker'][:5]:\n", 247 | " api_url = f'https://sandbox.iexapis.com/stable/stock/{stock}/quote/?token={IEX_CLOUD_API_TOKEN}'\n", 248 | " data = requests.get(api_url).json()\n", 249 | " final_dataframe = final_dataframe.append(\n", 250 | " pd.Series(\n", 251 | " [\n", 252 | " stock,\n", 253 | " data['latestPrice'],\n", 254 | " data['marketCap'],\n", 255 | " 'N/A'\n", 256 | " ],\n", 257 | " index = my_columns\n", 258 | " ),\n", 259 | " ignore_index = True\n", 260 | " )" 261 | ] 262 | }, 263 | { 264 | "cell_type": "code", 265 | "execution_count": 9, 266 | "metadata": {}, 267 | "outputs": [ 268 | { 269 | "output_type": "execute_result", 270 | "data": { 271 | "text/plain": [ 272 | " Ticker Stock Price Market Capitalization Number of Shares to Buy\n", 273 | "0 A 133.030 39366872493 N/A\n", 274 | "1 AAL 16.210 9815553653 N/A\n", 275 | "2 AAP 167.076 11288595782 N/A\n", 276 | "3 AAPL 129.780 2216217542006 N/A\n", 277 | "4 ABBV 115.050 203980777067 N/A" 278 | ], 279 | "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
TickerStock PriceMarket CapitalizationNumber of Shares to Buy
0A133.03039366872493N/A
1AAL16.2109815553653N/A
2AAP167.07611288595782N/A
3AAPL129.7802216217542006N/A
4ABBV115.050203980777067N/A
\n
" 280 | }, 281 | "metadata": {}, 282 | "execution_count": 9 283 | } 284 | ], 285 | "source": [ 286 | "final_dataframe" 287 | ] 288 | }, 289 | { 290 | "cell_type": "markdown", 291 | "metadata": {}, 292 | "source": [ 293 | "## Using Batch API Calls to Improve Performance\n", 294 | "\n", 295 | "Batch API calls are one of the easiest ways to improve the performance of your code.\n", 296 | "\n", 297 | "This is because HTTP requests are typically one of the slowest components of a script.\n", 298 | "\n", 299 | "Also, API providers will often give you discounted rates for using batch API calls since they are easier for the API provider to respond to.\n", 300 | "\n", 301 | "IEX Cloud limits their batch API calls to 100 tickers per request. Still, this reduces the number of API calls we'll make in this section from 500 to 5 - huge improvement! In this section, we'll split our list of stocks into groups of 100 and then make a batch API call for each group." 302 | ] 303 | }, 304 | { 305 | "cell_type": "code", 306 | "execution_count": 10, 307 | "metadata": {}, 308 | "outputs": [], 309 | "source": [ 310 | "def chunks(lst, n):\n", 311 | " \"Yield successive n-sized chunks from list.\"\n", 312 | " for i in range(0, len(lst), n):\n", 313 | " yield lst[i:i+n]" 314 | ] 315 | }, 316 | { 317 | "cell_type": "code", 318 | "execution_count": 11, 319 | "metadata": {}, 320 | "outputs": [ 321 | { 322 | "output_type": "execute_result", 323 | "data": { 324 | "text/plain": [ 325 | " Ticker Stock Price Market Capitalization Number of Shares to Buy\n", 326 | "0 A 130.346 40527794594 N/A\n", 327 | "1 AAL 16.770 10017922788 N/A\n", 328 | "2 AAP 174.092 11746396257 N/A\n", 329 | "3 AAPL 131.400 2203652069531 N/A\n", 330 | "4 ABBV 114.507 198528364449 N/A\n", 331 | ".. ... ... ... ...\n", 332 | "500 YUM 112.210 33075398208 N/A\n", 333 | "501 ZBH 161.590 33476578508 N/A\n", 334 | "502 ZBRA 424.880 22123181524 N/A\n", 335 | "503 ZION 49.730 8210756172 N/A\n", 336 | "504 ZTS 168.180 79794813684 N/A\n", 337 | "\n", 338 | "[505 rows x 4 columns]" 339 | ], 340 | "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
TickerStock PriceMarket CapitalizationNumber of Shares to Buy
0A130.34640527794594N/A
1AAL16.77010017922788N/A
2AAP174.09211746396257N/A
3AAPL131.4002203652069531N/A
4ABBV114.507198528364449N/A
...............
500YUM112.21033075398208N/A
501ZBH161.59033476578508N/A
502ZBRA424.88022123181524N/A
503ZION49.7308210756172N/A
504ZTS168.18079794813684N/A
\n

505 rows × 4 columns

\n
" 341 | }, 342 | "metadata": {}, 343 | "execution_count": 11 344 | } 345 | ], 346 | "source": [ 347 | "symbol_groups = list(chunks(stocks['Ticker'], 100))\n", 348 | "symbol_strings = []\n", 349 | "for i in range(0, len(symbol_groups)):\n", 350 | " symbol_strings.append(','.join(symbol_groups[i]))\n", 351 | " # print(symbol_strings[i])\n", 352 | "final_dataframe = pd.DataFrame(columns = my_columns)\n", 353 | "\n", 354 | "for symbol_string in symbol_strings:\n", 355 | " batch_api_call_url = f'https://sandbox.iexapis.com/stable/stock/market/batch/?types=quote&symbols={symbol_string}&token={IEX_CLOUD_API_TOKEN}'\n", 356 | " data = requests.get(batch_api_call_url).json()\n", 357 | " for symbol in symbol_string.split(','):\n", 358 | " final_dataframe = final_dataframe.append(\n", 359 | " pd.Series(\n", 360 | " [\n", 361 | " symbol,\n", 362 | " data[symbol]['quote']['latestPrice'],\n", 363 | " data[symbol]['quote']['marketCap'],\n", 364 | " 'N/A'\n", 365 | " ],\n", 366 | " index = my_columns),\n", 367 | " ignore_index=True\n", 368 | " )\n", 369 | " \n", 370 | "final_dataframe" 371 | ] 372 | }, 373 | { 374 | "cell_type": "markdown", 375 | "metadata": {}, 376 | "source": [ 377 | "## Calculating the Number of Shares to Buy\n", 378 | "\n", 379 | "As you can see in the DataFrame above, we stil haven't calculated the number of shares of each stock to buy.\n", 380 | "\n", 381 | "We'll do that next." 382 | ] 383 | }, 384 | { 385 | "cell_type": "code", 386 | "execution_count": 12, 387 | "metadata": {}, 388 | "outputs": [ 389 | { 390 | "output_type": "stream", 391 | "name": "stdout", 392 | "text": [ 393 | "10000000.0\n" 394 | ] 395 | } 396 | ], 397 | "source": [ 398 | "portfolio_size = input('Enter the value of your portfolio:')\n", 399 | "\n", 400 | "while True:\n", 401 | " try:\n", 402 | " val = float(portfolio_size)\n", 403 | " print(val)\n", 404 | " break\n", 405 | " except ValueError:\n", 406 | " print('Please enter a number.')\n", 407 | " portfolio_size = input('Enter the value of your portfolio: ')" 408 | ] 409 | }, 410 | { 411 | "cell_type": "code", 412 | "execution_count": 13, 413 | "metadata": {}, 414 | "outputs": [ 415 | { 416 | "output_type": "execute_result", 417 | "data": { 418 | "text/plain": [ 419 | " Ticker Stock Price Market Capitalization Number of Shares to Buy\n", 420 | "0 A 130.346 40527794594 151\n", 421 | "1 AAL 16.770 10017922788 1180\n", 422 | "2 AAP 174.092 11746396257 113\n", 423 | "3 AAPL 131.400 2203652069531 150\n", 424 | "4 ABBV 114.507 198528364449 172\n", 425 | ".. ... ... ... ...\n", 426 | "500 YUM 112.210 33075398208 176\n", 427 | "501 ZBH 161.590 33476578508 122\n", 428 | "502 ZBRA 424.880 22123181524 46\n", 429 | "503 ZION 49.730 8210756172 398\n", 430 | "504 ZTS 168.180 79794813684 117\n", 431 | "\n", 432 | "[505 rows x 4 columns]" 433 | ], 434 | "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
TickerStock PriceMarket CapitalizationNumber of Shares to Buy
0A130.34640527794594151
1AAL16.770100179227881180
2AAP174.09211746396257113
3AAPL131.4002203652069531150
4ABBV114.507198528364449172
...............
500YUM112.21033075398208176
501ZBH161.59033476578508122
502ZBRA424.8802212318152446
503ZION49.7308210756172398
504ZTS168.18079794813684117
\n

505 rows × 4 columns

\n
" 435 | }, 436 | "metadata": {}, 437 | "execution_count": 13 438 | } 439 | ], 440 | "source": [ 441 | "position_size = val / len(final_dataframe.index)\n", 442 | "for i in range(0, len(final_dataframe.index)):\n", 443 | " final_dataframe.loc[i, 'Number of Shares to Buy'] = math.floor(position_size / final_dataframe['Stock Price'][i])\n", 444 | " \n", 445 | "final_dataframe" 446 | ] 447 | }, 448 | { 449 | "cell_type": "markdown", 450 | "metadata": {}, 451 | "source": [ 452 | "## Formatting Our Excel Output\n", 453 | "\n", 454 | "We will be using the XlsxWriter library for Python to create nicely-formatted Excel files.\n", 455 | "\n", 456 | "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.\n", 457 | "\n", 458 | "### Initializing our XlsxWriter Object" 459 | ] 460 | }, 461 | { 462 | "cell_type": "code", 463 | "execution_count": 14, 464 | "metadata": {}, 465 | "outputs": [], 466 | "source": [ 467 | "writer = pd.ExcelWriter('recommended trades.xlsx', engine = 'xlsxwriter')\n", 468 | "final_dataframe.to_excel(writer, 'Recommended Trades', index = False)" 469 | ] 470 | }, 471 | { 472 | "cell_type": "markdown", 473 | "metadata": {}, 474 | "source": [ 475 | "### Creating the Formats We'll Need For Our `.xlsx` File\n", 476 | "\n", 477 | "Formats include colors, fonts, and also symbols like `%` and `$`. We'll need four main formats for our Excel document:\n", 478 | "* String format for tickers\n", 479 | "* \\\\$XX.XX format for stock prices\n", 480 | "* \\\\$XX,XXX format for market capitalization\n", 481 | "* Integer format for the number of shares to purchase" 482 | ] 483 | }, 484 | { 485 | "cell_type": "code", 486 | "execution_count": 15, 487 | "metadata": {}, 488 | "outputs": [], 489 | "source": [ 490 | "background_color = '#0a0a23'\n", 491 | "font_color = '#ffffff'\n", 492 | "\n", 493 | "string_format = writer.book.add_format(\n", 494 | "{\n", 495 | " 'font_color': font_color,\n", 496 | " 'bg_color': background_color,\n", 497 | " 'border': 1\n", 498 | "})\n", 499 | "\n", 500 | "dollar_format = writer.book.add_format(\n", 501 | "{\n", 502 | " 'num_format': '$0.00',\n", 503 | " 'font_color': font_color,\n", 504 | " 'bg_color': background_color,\n", 505 | " 'border': 1\n", 506 | "})\n", 507 | "\n", 508 | "integer_format = writer.book.add_format(\n", 509 | "{\n", 510 | " 'num_format': '0',\n", 511 | " 'font_color': font_color,\n", 512 | " 'bg_color': background_color,\n", 513 | " 'border': 1\n", 514 | "})" 515 | ] 516 | }, 517 | { 518 | "cell_type": "markdown", 519 | "metadata": {}, 520 | "source": [ 521 | "### Applying the Formats to the Columns of Our `.xlsx` File\n", 522 | "\n", 523 | "We can use the `set_column` method applied to the `writer.sheets['Recommended Trades']` object to apply formats to specific columns of our spreadsheets.\n", 524 | "\n", 525 | "Here's an example:\n", 526 | "\n", 527 | "```python\n", 528 | "writer.sheets['Recommended Trades'].set_column('B:B', #This tells the method to apply the format to column B\n", 529 | " 18, #This tells the method to apply a column width of 18 pixels\n", 530 | " string_template #This applies the format 'string_template' to the column\n", 531 | " )\n", 532 | "```" 533 | ] 534 | }, 535 | { 536 | "cell_type": "code", 537 | "execution_count": 16, 538 | "metadata": {}, 539 | "outputs": [], 540 | "source": [ 541 | "# writer.sheets['Recommended Trades'].set_column('A:A', 18, string_format)\n", 542 | "# writer.sheets['Recommended Trades'].set_column('B:B', 18, string_format)\n", 543 | "# writer.sheets['Recommended Trades'].set_column('C:C', 18, string_format)\n", 544 | "# writer.sheets['Recommended Trades'].set_column('D:D', 18, string_format)\n", 545 | "# writer.save()\n", 546 | "\n", 547 | "# writer.sheets['Recommended Trades'].write('A1','Ticker',string_format)\n", 548 | "# writer.sheets['Recommended Trades'].write('B1','Stock Price',dollar_format)\n", 549 | "# writer.sheets['Recommended Trades'].write('C1','Market Capitalization',dollar_format)\n", 550 | "# writer.sheets['Recommended Trades'].write('D1','Number of Shares to Buy',integer_format)" 551 | ] 552 | }, 553 | { 554 | "cell_type": "markdown", 555 | "metadata": {}, 556 | "source": [ 557 | "This code works, but it violates the software principle of \"Don't Repeat Yourself\". \n", 558 | "\n", 559 | "Let's simplify this by putting it in 2 loops:" 560 | ] 561 | }, 562 | { 563 | "cell_type": "code", 564 | "execution_count": 17, 565 | "metadata": {}, 566 | "outputs": [], 567 | "source": [ 568 | "column_formats = {\n", 569 | " 'A': ['Ticker', string_format],\n", 570 | " 'B': ['Stock Price', dollar_format],\n", 571 | " 'C': ['Market Capitalization', dollar_format],\n", 572 | " 'D': ['Number of Shares to Buy', integer_format],\n", 573 | "}\n", 574 | "\n", 575 | "for column in column_formats.keys():\n", 576 | " writer.sheets['Recommended Trades'].set_column(f'{column}:{column}', 18, column_formats[column][1])\n", 577 | " writer.sheets['Recommended Trades'].write(f'{column}1', column_formats[column][0], string_format)" 578 | ] 579 | }, 580 | { 581 | "cell_type": "markdown", 582 | "metadata": {}, 583 | "source": [ 584 | "## Saving Our Excel Output\n", 585 | "\n", 586 | "Saving our Excel file is very easy:" 587 | ] 588 | }, 589 | { 590 | "cell_type": "code", 591 | "execution_count": 18, 592 | "metadata": {}, 593 | "outputs": [], 594 | "source": [ 595 | "writer.save()" 596 | ] 597 | } 598 | ], 599 | "metadata": { 600 | "kernelspec": { 601 | "display_name": "Python 3", 602 | "language": "python", 603 | "name": "python3" 604 | }, 605 | "language_info": { 606 | "codemirror_mode": { 607 | "name": "ipython", 608 | "version": 3 609 | }, 610 | "file_extension": ".py", 611 | "mimetype": "text/x-python", 612 | "name": "python", 613 | "nbconvert_exporter": "python", 614 | "pygments_lexer": "ipython3", 615 | "version": "3.8.5-final" 616 | } 617 | }, 618 | "nbformat": 4, 619 | "nbformat_minor": 4 620 | } -------------------------------------------------------------------------------- /Projects/1 - Building An Equal-Weight S&P 500 Index Fund/__pycache__/secrets.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cengizozel/Algorithmic-Trading-In-Python/b6e5e89ab0d7682fe72fc31c6461c43435e05944/Projects/1 - Building An Equal-Weight S&P 500 Index Fund/__pycache__/secrets.cpython-38.pyc -------------------------------------------------------------------------------- /Projects/1 - Building An Equal-Weight S&P 500 Index Fund/recommended trades.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cengizozel/Algorithmic-Trading-In-Python/b6e5e89ab0d7682fe72fc31c6461c43435e05944/Projects/1 - Building An Equal-Weight S&P 500 Index Fund/recommended trades.xlsx -------------------------------------------------------------------------------- /Projects/1 - Building An Equal-Weight S&P 500 Index Fund/secrets.py: -------------------------------------------------------------------------------- 1 | IEX_CLOUD_API_TOKEN = 'Tpk_059b97af715d417d9f49f50b51b1c448' -------------------------------------------------------------------------------- /Projects/2 - Building A Quantitative Momentum Investing Strategy/.ipynb_checkpoints/002_quantitative_momentum_strategy-checkpoint.ipynb: -------------------------------------------------------------------------------- 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": 1, 22 | "metadata": {}, 23 | "outputs": [], 24 | "source": [ 25 | "import numpy as np\n", 26 | "import pandas as pd\n", 27 | "import requests\n", 28 | "import math\n", 29 | "from scipy.stats import percentileofscore as score\n", 30 | "import xlsxwriter" 31 | ] 32 | }, 33 | { 34 | "cell_type": "markdown", 35 | "metadata": {}, 36 | "source": [ 37 | "## Importing Our List of Stocks\n", 38 | "\n", 39 | "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:" 40 | ] 41 | }, 42 | { 43 | "cell_type": "code", 44 | "execution_count": 2, 45 | "metadata": {}, 46 | "outputs": [], 47 | "source": [ 48 | "stocks = pd.read_csv('../sp_500_stocks.csv')\n", 49 | "from secrets import IEX_CLOUD_API_TOKEN" 50 | ] 51 | }, 52 | { 53 | "cell_type": "markdown", 54 | "metadata": {}, 55 | "source": [ 56 | "## Making Our First API Call\n", 57 | "\n", 58 | "It's now time to make the first version of our momentum screener!\n", 59 | "\n", 60 | "We need to get one-year price returns for each stock in the universe. Here's how." 61 | ] 62 | }, 63 | { 64 | "cell_type": "code", 65 | "execution_count": 3, 66 | "metadata": {}, 67 | "outputs": [ 68 | { 69 | "data": { 70 | "text/plain": [ 71 | "{'companyName': 'Apple Inc',\n", 72 | " 'marketcap': 2240822993565,\n", 73 | " 'week52high': 137.37,\n", 74 | " 'week52low': 58.2,\n", 75 | " 'week52change': 0.6203713408091166,\n", 76 | " 'sharesOutstanding': 17470220436,\n", 77 | " 'float': 0,\n", 78 | " 'avg10Volume': 98237793,\n", 79 | " 'avg30Volume': 112388028,\n", 80 | " 'day200MovingAvg': 117.02,\n", 81 | " 'day50MovingAvg': 130.74,\n", 82 | " 'employees': 139872,\n", 83 | " 'ttmEPS': 3.29,\n", 84 | " 'ttmDividendRate': 0.8450244681570834,\n", 85 | " 'dividendYield': 0.006533995899319038,\n", 86 | " 'nextDividendDate': '0',\n", 87 | " 'exDividendDate': '2020-10-30',\n", 88 | " 'nextEarningsDate': '0',\n", 89 | " 'peRatio': 37.94786088494096,\n", 90 | " 'beta': 1.1908001555917906,\n", 91 | " 'maxChangePercent': 48.12283139856177,\n", 92 | " 'year5ChangePercent': 4.795515076639973,\n", 93 | " 'year2ChangePercent': 2.428101488382046,\n", 94 | " 'year1ChangePercent': 0.6250879078599509,\n", 95 | " 'ytdChangePercent': -0.04366622552990689,\n", 96 | " 'month6ChangePercent': 0.3401194044344011,\n", 97 | " 'month3ChangePercent': 0.07165346728629379,\n", 98 | " 'month1ChangePercent': 0.003876725184340821,\n", 99 | " 'day30ChangePercent': 0.003873047983851693,\n", 100 | " 'day5ChangePercent': -0.014296124067737798}" 101 | ] 102 | }, 103 | "execution_count": 3, 104 | "metadata": {}, 105 | "output_type": "execute_result" 106 | } 107 | ], 108 | "source": [ 109 | "symbol = 'AAPL'\n", 110 | "api_url = f'https://sandbox.iexapis.com/stable/stock/{symbol}/stats?token={IEX_CLOUD_API_TOKEN}'\n", 111 | "data = requests.get(api_url).json()\n", 112 | "data" 113 | ] 114 | }, 115 | { 116 | "cell_type": "markdown", 117 | "metadata": {}, 118 | "source": [ 119 | "## Parsing Our API Call\n", 120 | "\n", 121 | "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." 122 | ] 123 | }, 124 | { 125 | "cell_type": "code", 126 | "execution_count": 4, 127 | "metadata": {}, 128 | "outputs": [ 129 | { 130 | "data": { 131 | "text/plain": [ 132 | "0.6250879078599509" 133 | ] 134 | }, 135 | "execution_count": 4, 136 | "metadata": {}, 137 | "output_type": "execute_result" 138 | } 139 | ], 140 | "source": [ 141 | "data['year1ChangePercent']" 142 | ] 143 | }, 144 | { 145 | "cell_type": "markdown", 146 | "metadata": {}, 147 | "source": [ 148 | "## Executing A Batch API Call & Building Our DataFrame\n", 149 | "\n", 150 | "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", 151 | "\n", 152 | "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." 153 | ] 154 | }, 155 | { 156 | "cell_type": "code", 157 | "execution_count": 5, 158 | "metadata": {}, 159 | "outputs": [], 160 | "source": [ 161 | "# Function sourced from \n", 162 | "# https://stackoverflow.com/questions/312443/how-do-you-split-a-list-into-evenly-sized-chunks\n", 163 | "def chunks(lst, n):\n", 164 | " \"\"\"Yield successive n-sized chunks from lst.\"\"\"\n", 165 | " for i in range(0, len(lst), n):\n", 166 | " yield lst[i:i + n] \n", 167 | " \n", 168 | "symbol_groups = list(chunks(stocks['Ticker'], 100))\n", 169 | "symbol_strings = []\n", 170 | "for i in range(0, len(symbol_groups)):\n", 171 | " symbol_strings.append(','.join(symbol_groups[i]))\n", 172 | "# print(symbol_strings[i])\n", 173 | "\n", 174 | "my_columns = ['Ticker', 'Price', 'One-Year Price Return', 'Number of Shares to Buy']" 175 | ] 176 | }, 177 | { 178 | "cell_type": "markdown", 179 | "metadata": {}, 180 | "source": [ 181 | "Now we need to create a blank DataFrame and add our data to the data frame one-by-one." 182 | ] 183 | }, 184 | { 185 | "cell_type": "code", 186 | "execution_count": 6, 187 | "metadata": {}, 188 | "outputs": [ 189 | { 190 | "data": { 191 | "text/html": [ 192 | "
\n", 193 | "\n", 206 | "\n", 207 | " \n", 208 | " \n", 209 | " \n", 210 | " \n", 211 | " \n", 212 | " \n", 213 | " \n", 214 | " \n", 215 | " \n", 216 | " \n", 217 | " \n", 218 | " \n", 219 | " \n", 220 | " \n", 221 | " \n", 222 | " \n", 223 | " \n", 224 | " \n", 225 | " \n", 226 | " \n", 227 | " \n", 228 | " \n", 229 | " \n", 230 | " \n", 231 | " \n", 232 | " \n", 233 | " \n", 234 | " \n", 235 | " \n", 236 | " \n", 237 | " \n", 238 | " \n", 239 | " \n", 240 | " \n", 241 | " \n", 242 | " \n", 243 | " \n", 244 | " \n", 245 | " \n", 246 | " \n", 247 | " \n", 248 | " \n", 249 | " \n", 250 | " \n", 251 | " \n", 252 | " \n", 253 | " \n", 254 | " \n", 255 | " \n", 256 | " \n", 257 | " \n", 258 | " \n", 259 | " \n", 260 | " \n", 261 | " \n", 262 | " \n", 263 | " \n", 264 | " \n", 265 | " \n", 266 | " \n", 267 | " \n", 268 | " \n", 269 | " \n", 270 | " \n", 271 | " \n", 272 | " \n", 273 | " \n", 274 | " \n", 275 | " \n", 276 | " \n", 277 | " \n", 278 | " \n", 279 | " \n", 280 | " \n", 281 | " \n", 282 | " \n", 283 | " \n", 284 | " \n", 285 | " \n", 286 | " \n", 287 | " \n", 288 | " \n", 289 | " \n", 290 | " \n", 291 | " \n", 292 | " \n", 293 | " \n", 294 | " \n", 295 | "
TickerPriceOne-Year Price ReturnNumber of Shares to Buy
0A{'symbol': 'A', 'companyName': 'Agilent Techno...0.421176N/A
1AAL{'symbol': 'AAL', 'companyName': 'American Air...-0.459204N/A
2AAP{'symbol': 'AAP', 'companyName': 'Advance Auto...0.105095N/A
3AAPL{'symbol': 'AAPL', 'companyName': 'Apple Inc',...0.626905N/A
4ABBV{'symbol': 'ABBV', 'companyName': 'Abbvie Inc'...0.330988N/A
...............
500YUM{'symbol': 'YUM', 'companyName': 'Yum Brands I...0.031391N/A
501ZBH{'symbol': 'ZBH', 'companyName': 'Zimmer Biome...0.082741N/A
502ZBRA{'symbol': 'ZBRA', 'companyName': 'Zebra Techn...0.638815N/A
503ZION{'symbol': 'ZION', 'companyName': 'Zions Banco...0.005153N/A
504ZTS{'symbol': 'ZTS', 'companyName': 'Zoetis Inc -...0.164193N/A
\n", 296 | "

505 rows × 4 columns

\n", 297 | "
" 298 | ], 299 | "text/plain": [ 300 | " Ticker Price \\\n", 301 | "0 A {'symbol': 'A', 'companyName': 'Agilent Techno... \n", 302 | "1 AAL {'symbol': 'AAL', 'companyName': 'American Air... \n", 303 | "2 AAP {'symbol': 'AAP', 'companyName': 'Advance Auto... \n", 304 | "3 AAPL {'symbol': 'AAPL', 'companyName': 'Apple Inc',... \n", 305 | "4 ABBV {'symbol': 'ABBV', 'companyName': 'Abbvie Inc'... \n", 306 | ".. ... ... \n", 307 | "500 YUM {'symbol': 'YUM', 'companyName': 'Yum Brands I... \n", 308 | "501 ZBH {'symbol': 'ZBH', 'companyName': 'Zimmer Biome... \n", 309 | "502 ZBRA {'symbol': 'ZBRA', 'companyName': 'Zebra Techn... \n", 310 | "503 ZION {'symbol': 'ZION', 'companyName': 'Zions Banco... \n", 311 | "504 ZTS {'symbol': 'ZTS', 'companyName': 'Zoetis Inc -... \n", 312 | "\n", 313 | " One-Year Price Return Number of Shares to Buy \n", 314 | "0 0.421176 N/A \n", 315 | "1 -0.459204 N/A \n", 316 | "2 0.105095 N/A \n", 317 | "3 0.626905 N/A \n", 318 | "4 0.330988 N/A \n", 319 | ".. ... ... \n", 320 | "500 0.031391 N/A \n", 321 | "501 0.082741 N/A \n", 322 | "502 0.638815 N/A \n", 323 | "503 0.005153 N/A \n", 324 | "504 0.164193 N/A \n", 325 | "\n", 326 | "[505 rows x 4 columns]" 327 | ] 328 | }, 329 | "execution_count": 6, 330 | "metadata": {}, 331 | "output_type": "execute_result" 332 | } 333 | ], 334 | "source": [ 335 | "final_dataframe = pd.DataFrame(columns = my_columns)\n", 336 | "\n", 337 | "for symbol_string in symbol_strings:\n", 338 | " batch_api_call_url = f'https://sandbox.iexapis.com/stable/stock/market/batch/?types=stats,quote&symbols={symbol_string}&token={IEX_CLOUD_API_TOKEN}'\n", 339 | " data = requests.get(batch_api_call_url).json()\n", 340 | " for symbol in symbol_string.split(','):\n", 341 | " final_dataframe = final_dataframe.append(\n", 342 | " pd.Series([symbol, \n", 343 | " data[symbol]['quote'],\n", 344 | " data[symbol]['stats']['year1ChangePercent'],\n", 345 | " 'N/A'\n", 346 | " ], \n", 347 | " index = my_columns), \n", 348 | " ignore_index = True)\n", 349 | " \n", 350 | " \n", 351 | "final_dataframe" 352 | ] 353 | }, 354 | { 355 | "cell_type": "markdown", 356 | "metadata": {}, 357 | "source": [ 358 | "## Removing Low-Momentum Stocks\n", 359 | "\n", 360 | "The investment strategy that we're building seeks to identify the 50 highest-momentum stocks in the S&P 500.\n", 361 | "\n", 362 | "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" 363 | ] 364 | }, 365 | { 366 | "cell_type": "code", 367 | "execution_count": 7, 368 | "metadata": {}, 369 | "outputs": [ 370 | { 371 | "data": { 372 | "text/html": [ 373 | "
\n", 374 | "\n", 387 | "\n", 388 | " \n", 389 | " \n", 390 | " \n", 391 | " \n", 392 | " \n", 393 | " \n", 394 | " \n", 395 | " \n", 396 | " \n", 397 | " \n", 398 | " \n", 399 | " \n", 400 | " \n", 401 | " \n", 402 | " \n", 403 | " \n", 404 | " \n", 405 | " \n", 406 | " \n", 407 | " \n", 408 | " \n", 409 | " \n", 410 | " \n", 411 | " \n", 412 | " \n", 413 | " \n", 414 | " \n", 415 | " \n", 416 | " \n", 417 | " \n", 418 | " \n", 419 | " \n", 420 | " \n", 421 | " \n", 422 | " \n", 423 | " \n", 424 | " \n", 425 | " \n", 426 | " \n", 427 | " \n", 428 | " \n", 429 | " \n", 430 | " \n", 431 | " \n", 432 | " \n", 433 | " \n", 434 | " \n", 435 | " \n", 436 | " \n", 437 | " \n", 438 | " \n", 439 | " \n", 440 | " \n", 441 | " \n", 442 | " \n", 443 | " \n", 444 | " \n", 445 | " \n", 446 | " \n", 447 | " \n", 448 | " \n", 449 | " \n", 450 | " \n", 451 | " \n", 452 | " \n", 453 | " \n", 454 | " \n", 455 | " \n", 456 | " \n", 457 | " \n", 458 | " \n", 459 | " \n", 460 | " \n", 461 | " \n", 462 | " \n", 463 | " \n", 464 | " \n", 465 | " \n", 466 | " \n", 467 | " \n", 468 | " \n", 469 | " \n", 470 | " \n", 471 | " \n", 472 | " \n", 473 | " \n", 474 | " \n", 475 | " \n", 476 | " \n", 477 | " \n", 478 | " \n", 479 | " \n", 480 | " \n", 481 | " \n", 482 | " \n", 483 | " \n", 484 | " \n", 485 | " \n", 486 | " \n", 487 | " \n", 488 | " \n", 489 | " \n", 490 | " \n", 491 | " \n", 492 | " \n", 493 | " \n", 494 | " \n", 495 | " \n", 496 | " \n", 497 | " \n", 498 | " \n", 499 | " \n", 500 | " \n", 501 | " \n", 502 | " \n", 503 | " \n", 504 | " \n", 505 | " \n", 506 | " \n", 507 | " \n", 508 | " \n", 509 | " \n", 510 | " \n", 511 | " \n", 512 | " \n", 513 | " \n", 514 | " \n", 515 | " \n", 516 | " \n", 517 | " \n", 518 | " \n", 519 | " \n", 520 | " \n", 521 | " \n", 522 | " \n", 523 | " \n", 524 | " \n", 525 | " \n", 526 | " \n", 527 | " \n", 528 | " \n", 529 | " \n", 530 | " \n", 531 | " \n", 532 | " \n", 533 | " \n", 534 | " \n", 535 | " \n", 536 | " \n", 537 | " \n", 538 | " \n", 539 | " \n", 540 | " \n", 541 | " \n", 542 | " \n", 543 | " \n", 544 | " \n", 545 | " \n", 546 | " \n", 547 | " \n", 548 | " \n", 549 | " \n", 550 | " \n", 551 | " \n", 552 | " \n", 553 | " \n", 554 | " \n", 555 | " \n", 556 | " \n", 557 | " \n", 558 | " \n", 559 | " \n", 560 | " \n", 561 | " \n", 562 | " \n", 563 | " \n", 564 | " \n", 565 | " \n", 566 | " \n", 567 | " \n", 568 | " \n", 569 | " \n", 570 | " \n", 571 | " \n", 572 | " \n", 573 | " \n", 574 | " \n", 575 | " \n", 576 | " \n", 577 | " \n", 578 | " \n", 579 | " \n", 580 | " \n", 581 | " \n", 582 | " \n", 583 | " \n", 584 | " \n", 585 | " \n", 586 | " \n", 587 | " \n", 588 | " \n", 589 | " \n", 590 | " \n", 591 | " \n", 592 | " \n", 593 | " \n", 594 | " \n", 595 | " \n", 596 | " \n", 597 | " \n", 598 | " \n", 599 | " \n", 600 | " \n", 601 | " \n", 602 | " \n", 603 | " \n", 604 | " \n", 605 | " \n", 606 | " \n", 607 | " \n", 608 | " \n", 609 | " \n", 610 | " \n", 611 | " \n", 612 | " \n", 613 | " \n", 614 | " \n", 615 | " \n", 616 | " \n", 617 | " \n", 618 | " \n", 619 | " \n", 620 | " \n", 621 | " \n", 622 | " \n", 623 | " \n", 624 | " \n", 625 | " \n", 626 | " \n", 627 | " \n", 628 | " \n", 629 | " \n", 630 | " \n", 631 | " \n", 632 | " \n", 633 | " \n", 634 | " \n", 635 | " \n", 636 | " \n", 637 | " \n", 638 | " \n", 639 | " \n", 640 | " \n", 641 | " \n", 642 | " \n", 643 | " \n", 644 | " \n", 645 | " \n", 646 | " \n", 647 | " \n", 648 | " \n", 649 | " \n", 650 | " \n", 651 | " \n", 652 | " \n", 653 | " \n", 654 | " \n", 655 | " \n", 656 | " \n", 657 | " \n", 658 | " \n", 659 | " \n", 660 | " \n", 661 | " \n", 662 | " \n", 663 | " \n", 664 | " \n", 665 | " \n", 666 | " \n", 667 | " \n", 668 | " \n", 669 | " \n", 670 | " \n", 671 | " \n", 672 | " \n", 673 | " \n", 674 | " \n", 675 | " \n", 676 | " \n", 677 | " \n", 678 | " \n", 679 | " \n", 680 | " \n", 681 | " \n", 682 | " \n", 683 | " \n", 684 | " \n", 685 | " \n", 686 | " \n", 687 | " \n", 688 | " \n", 689 | " \n", 690 | " \n", 691 | " \n", 692 | " \n", 693 | " \n", 694 | " \n", 695 | " \n", 696 | " \n", 697 | " \n", 698 | " \n", 699 | " \n", 700 | " \n", 701 | " \n", 702 | " \n", 703 | " \n", 704 | " \n", 705 | " \n", 706 | " \n", 707 | " \n", 708 | " \n", 709 | " \n", 710 | " \n", 711 | " \n", 712 | " \n", 713 | " \n", 714 | " \n", 715 | " \n", 716 | " \n", 717 | " \n", 718 | " \n", 719 | " \n", 720 | " \n", 721 | " \n", 722 | " \n", 723 | " \n", 724 | " \n", 725 | " \n", 726 | " \n", 727 | " \n", 728 | " \n", 729 | " \n", 730 | " \n", 731 | " \n", 732 | " \n", 733 | " \n", 734 | " \n", 735 | " \n", 736 | " \n", 737 | " \n", 738 | " \n", 739 | " \n", 740 | " \n", 741 | " \n", 742 | " \n", 743 | " \n", 744 | " \n", 745 | " \n", 746 | " \n", 747 | " \n", 748 | " \n", 749 | " \n", 750 | " \n", 751 | " \n", 752 | " \n", 753 | " \n", 754 | " \n", 755 | " \n", 756 | " \n", 757 | " \n", 758 | " \n", 759 | " \n", 760 | " \n", 761 | " \n", 762 | " \n", 763 | " \n", 764 | " \n", 765 | " \n", 766 | " \n", 767 | " \n", 768 | " \n", 769 | " \n", 770 | " \n", 771 | " \n", 772 | " \n", 773 | " \n", 774 | " \n", 775 | " \n", 776 | " \n", 777 | " \n", 778 | " \n", 779 | " \n", 780 | " \n", 781 | " \n", 782 | " \n", 783 | " \n", 784 | " \n", 785 | " \n", 786 | " \n", 787 | " \n", 788 | " \n", 789 | " \n", 790 | " \n", 791 | " \n", 792 | " \n", 793 | " \n", 794 | " \n", 795 | " \n", 796 | " \n", 797 | " \n", 798 | " \n", 799 | " \n", 800 | "
indexTickerPriceOne-Year Price ReturnNumber of Shares to Buy
078CARR{'symbol': 'CARR', 'companyName': 'Carrier Glo...2.372000N/A
1179FCX{'symbol': 'FCX', 'companyName': 'Freeport-McM...1.398449N/A
2275LB{'symbol': 'LB', 'companyName': 'L Brands Inc'...1.317416N/A
323ALB{'symbol': 'ALB', 'companyName': 'Albemarle Co...1.285509N/A
4345NVDA{'symbol': 'NVDA', 'companyName': 'NVIDIA Corp...1.099806N/A
5387PYPL{'symbol': 'PYPL', 'companyName': 'PayPal Hold...1.075444N/A
624ALGN{'symbol': 'ALGN', 'companyName': 'Align Techn...1.059351N/A
7490WST{'symbol': 'WST', 'companyName': 'West Pharmac...0.914538N/A
8385PWR{'symbol': 'PWR', 'companyName': 'Quanta Servi...0.895436N/A
985CDNS{'symbol': 'CDNS', 'companyName': 'Cadence Des...0.853129N/A
106ABMD{'symbol': 'ABMD', 'companyName': 'Abiomed Inc...0.845840N/A
11288LRCX{'symbol': 'LRCX', 'companyName': 'Lam Researc...0.820363N/A
12410SIVB{'symbol': 'SIVB', 'companyName': 'SVB Financi...0.789187N/A
1331AMD{'symbol': 'AMD', 'companyName': 'Advanced Mic...0.766951N/A
14266KLAC{'symbol': 'KLAC', 'companyName': 'KLA Corp.',...0.764740N/A
15444TSCO{'symbol': 'TSCO', 'companyName': 'Tractor Sup...0.745959N/A
16236IDXX{'symbol': 'IDXX', 'companyName': 'Idexx Labor...0.741275N/A
17436TGT{'symbol': 'TGT', 'companyName': 'Target Corp'...0.726739N/A
18128DE{'symbol': 'DE', 'companyName': 'Deere & Co.',...0.724275N/A
19415SNPS{'symbol': 'SNPS', 'companyName': 'Synopsys, I...0.709623N/A
20246IPGP{'symbol': 'IPGP', 'companyName': 'IPG Photoni...0.704526N/A
21388QCOM{'symbol': 'QCOM', 'companyName': 'Qualcomm, I...0.691453N/A
2229AMAT{'symbol': 'AMAT', 'companyName': 'Applied Mat...0.686041N/A
2336AMZN{'symbol': 'AMZN', 'companyName': 'Amazon.com ...0.680633N/A
24339NOW{'symbol': 'NOW', 'companyName': 'ServiceNow I...0.647236N/A
25502ZBRA{'symbol': 'ZBRA', 'companyName': 'Zebra Techn...0.638815N/A
263AAPL{'symbol': 'AAPL', 'companyName': 'Apple Inc',...0.626905N/A
2763BIO{'symbol': 'BIO', 'companyName': 'Bio-Rad Labo...0.623846N/A
28101CMG{'symbol': 'CMG', 'companyName': 'Chipotle Mex...0.616703N/A
2913ADSK{'symbol': 'ADSK', 'companyName': 'Autodesk In...0.615915N/A
30180FDX{'symbol': 'FDX', 'companyName': 'Fedex Corp',...0.612854N/A
31462URI{'symbol': 'URI', 'companyName': 'United Renta...0.610011N/A
32400ROL{'symbol': 'ROL', 'companyName': 'Rollins, Inc...0.599151N/A
33325MXIM{'symbol': 'MXIM', 'companyName': 'Maxim Integ...0.590771N/A
3445APTV{'symbol': 'APTV', 'companyName': 'Aptiv PLC',...0.587889N/A
35440TMUS{'symbol': 'TMUS', 'companyName': 'T-Mobile US...0.587534N/A
36120CTVA{'symbol': 'CTVA', 'companyName': 'Corteva Inc...0.576934N/A
37152EBAY{'symbol': 'EBAY', 'companyName': 'EBay Inc.',...0.571282N/A
38389QRVO{'symbol': 'QRVO', 'companyName': 'Qorvo Inc',...0.556746N/A
39150DXCM{'symbol': 'DXCM', 'companyName': 'Dexcom Inc'...0.549310N/A
40319MSCI{'symbol': 'MSCI', 'companyName': 'MSCI Inc', ...0.521268N/A
41447TTWO{'symbol': 'TTWO', 'companyName': 'Take-Two In...0.513972N/A
4250AVGO{'symbol': 'AVGO', 'companyName': 'Broadcom In...0.512355N/A
43351ODFL{'symbol': 'ODFL', 'companyName': 'Old Dominio...0.511270N/A
4448ATVI{'symbol': 'ATVI', 'companyName': 'Activision ...0.510283N/A
45147DVA{'symbol': 'DVA', 'companyName': 'DaVita Inc',...0.494770N/A
46439TMO{'symbol': 'TMO', 'companyName': 'Thermo Fishe...0.488762N/A
47332NFLX{'symbol': 'NFLX', 'companyName': 'NetFlix Inc...0.476668N/A
48133DHR{'symbol': 'DHR', 'companyName': 'Danaher Corp...0.474731N/A
49323MTD{'symbol': 'MTD', 'companyName': 'Mettler-Tole...0.474263N/A
\n", 801 | "
" 802 | ], 803 | "text/plain": [ 804 | " index Ticker Price \\\n", 805 | "0 78 CARR {'symbol': 'CARR', 'companyName': 'Carrier Glo... \n", 806 | "1 179 FCX {'symbol': 'FCX', 'companyName': 'Freeport-McM... \n", 807 | "2 275 LB {'symbol': 'LB', 'companyName': 'L Brands Inc'... \n", 808 | "3 23 ALB {'symbol': 'ALB', 'companyName': 'Albemarle Co... \n", 809 | "4 345 NVDA {'symbol': 'NVDA', 'companyName': 'NVIDIA Corp... \n", 810 | "5 387 PYPL {'symbol': 'PYPL', 'companyName': 'PayPal Hold... \n", 811 | "6 24 ALGN {'symbol': 'ALGN', 'companyName': 'Align Techn... \n", 812 | "7 490 WST {'symbol': 'WST', 'companyName': 'West Pharmac... \n", 813 | "8 385 PWR {'symbol': 'PWR', 'companyName': 'Quanta Servi... \n", 814 | "9 85 CDNS {'symbol': 'CDNS', 'companyName': 'Cadence Des... \n", 815 | "10 6 ABMD {'symbol': 'ABMD', 'companyName': 'Abiomed Inc... \n", 816 | "11 288 LRCX {'symbol': 'LRCX', 'companyName': 'Lam Researc... \n", 817 | "12 410 SIVB {'symbol': 'SIVB', 'companyName': 'SVB Financi... \n", 818 | "13 31 AMD {'symbol': 'AMD', 'companyName': 'Advanced Mic... \n", 819 | "14 266 KLAC {'symbol': 'KLAC', 'companyName': 'KLA Corp.',... \n", 820 | "15 444 TSCO {'symbol': 'TSCO', 'companyName': 'Tractor Sup... \n", 821 | "16 236 IDXX {'symbol': 'IDXX', 'companyName': 'Idexx Labor... \n", 822 | "17 436 TGT {'symbol': 'TGT', 'companyName': 'Target Corp'... \n", 823 | "18 128 DE {'symbol': 'DE', 'companyName': 'Deere & Co.',... \n", 824 | "19 415 SNPS {'symbol': 'SNPS', 'companyName': 'Synopsys, I... \n", 825 | "20 246 IPGP {'symbol': 'IPGP', 'companyName': 'IPG Photoni... \n", 826 | "21 388 QCOM {'symbol': 'QCOM', 'companyName': 'Qualcomm, I... \n", 827 | "22 29 AMAT {'symbol': 'AMAT', 'companyName': 'Applied Mat... \n", 828 | "23 36 AMZN {'symbol': 'AMZN', 'companyName': 'Amazon.com ... \n", 829 | "24 339 NOW {'symbol': 'NOW', 'companyName': 'ServiceNow I... \n", 830 | "25 502 ZBRA {'symbol': 'ZBRA', 'companyName': 'Zebra Techn... \n", 831 | "26 3 AAPL {'symbol': 'AAPL', 'companyName': 'Apple Inc',... \n", 832 | "27 63 BIO {'symbol': 'BIO', 'companyName': 'Bio-Rad Labo... \n", 833 | "28 101 CMG {'symbol': 'CMG', 'companyName': 'Chipotle Mex... \n", 834 | "29 13 ADSK {'symbol': 'ADSK', 'companyName': 'Autodesk In... \n", 835 | "30 180 FDX {'symbol': 'FDX', 'companyName': 'Fedex Corp',... \n", 836 | "31 462 URI {'symbol': 'URI', 'companyName': 'United Renta... \n", 837 | "32 400 ROL {'symbol': 'ROL', 'companyName': 'Rollins, Inc... \n", 838 | "33 325 MXIM {'symbol': 'MXIM', 'companyName': 'Maxim Integ... \n", 839 | "34 45 APTV {'symbol': 'APTV', 'companyName': 'Aptiv PLC',... \n", 840 | "35 440 TMUS {'symbol': 'TMUS', 'companyName': 'T-Mobile US... \n", 841 | "36 120 CTVA {'symbol': 'CTVA', 'companyName': 'Corteva Inc... \n", 842 | "37 152 EBAY {'symbol': 'EBAY', 'companyName': 'EBay Inc.',... \n", 843 | "38 389 QRVO {'symbol': 'QRVO', 'companyName': 'Qorvo Inc',... \n", 844 | "39 150 DXCM {'symbol': 'DXCM', 'companyName': 'Dexcom Inc'... \n", 845 | "40 319 MSCI {'symbol': 'MSCI', 'companyName': 'MSCI Inc', ... \n", 846 | "41 447 TTWO {'symbol': 'TTWO', 'companyName': 'Take-Two In... \n", 847 | "42 50 AVGO {'symbol': 'AVGO', 'companyName': 'Broadcom In... \n", 848 | "43 351 ODFL {'symbol': 'ODFL', 'companyName': 'Old Dominio... \n", 849 | "44 48 ATVI {'symbol': 'ATVI', 'companyName': 'Activision ... \n", 850 | "45 147 DVA {'symbol': 'DVA', 'companyName': 'DaVita Inc',... \n", 851 | "46 439 TMO {'symbol': 'TMO', 'companyName': 'Thermo Fishe... \n", 852 | "47 332 NFLX {'symbol': 'NFLX', 'companyName': 'NetFlix Inc... \n", 853 | "48 133 DHR {'symbol': 'DHR', 'companyName': 'Danaher Corp... \n", 854 | "49 323 MTD {'symbol': 'MTD', 'companyName': 'Mettler-Tole... \n", 855 | "\n", 856 | " One-Year Price Return Number of Shares to Buy \n", 857 | "0 2.372000 N/A \n", 858 | "1 1.398449 N/A \n", 859 | "2 1.317416 N/A \n", 860 | "3 1.285509 N/A \n", 861 | "4 1.099806 N/A \n", 862 | "5 1.075444 N/A \n", 863 | "6 1.059351 N/A \n", 864 | "7 0.914538 N/A \n", 865 | "8 0.895436 N/A \n", 866 | "9 0.853129 N/A \n", 867 | "10 0.845840 N/A \n", 868 | "11 0.820363 N/A \n", 869 | "12 0.789187 N/A \n", 870 | "13 0.766951 N/A \n", 871 | "14 0.764740 N/A \n", 872 | "15 0.745959 N/A \n", 873 | "16 0.741275 N/A \n", 874 | "17 0.726739 N/A \n", 875 | "18 0.724275 N/A \n", 876 | "19 0.709623 N/A \n", 877 | "20 0.704526 N/A \n", 878 | "21 0.691453 N/A \n", 879 | "22 0.686041 N/A \n", 880 | "23 0.680633 N/A \n", 881 | "24 0.647236 N/A \n", 882 | "25 0.638815 N/A \n", 883 | "26 0.626905 N/A \n", 884 | "27 0.623846 N/A \n", 885 | "28 0.616703 N/A \n", 886 | "29 0.615915 N/A \n", 887 | "30 0.612854 N/A \n", 888 | "31 0.610011 N/A \n", 889 | "32 0.599151 N/A \n", 890 | "33 0.590771 N/A \n", 891 | "34 0.587889 N/A \n", 892 | "35 0.587534 N/A \n", 893 | "36 0.576934 N/A \n", 894 | "37 0.571282 N/A \n", 895 | "38 0.556746 N/A \n", 896 | "39 0.549310 N/A \n", 897 | "40 0.521268 N/A \n", 898 | "41 0.513972 N/A \n", 899 | "42 0.512355 N/A \n", 900 | "43 0.511270 N/A \n", 901 | "44 0.510283 N/A \n", 902 | "45 0.494770 N/A \n", 903 | "46 0.488762 N/A \n", 904 | "47 0.476668 N/A \n", 905 | "48 0.474731 N/A \n", 906 | "49 0.474263 N/A " 907 | ] 908 | }, 909 | "execution_count": 7, 910 | "metadata": {}, 911 | "output_type": "execute_result" 912 | } 913 | ], 914 | "source": [ 915 | "final_dataframe.sort_values('One-Year Price Return', ascending = False, inplace = True)\n", 916 | "final_dataframe = final_dataframe[:50]\n", 917 | "final_dataframe.reset_index(inplace = True)\n", 918 | "final_dataframe" 919 | ] 920 | }, 921 | { 922 | "cell_type": "markdown", 923 | "metadata": {}, 924 | "source": [ 925 | "## Calculating the Number of Shares to Buy\n", 926 | "\n", 927 | "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", 928 | "\n", 929 | "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!" 930 | ] 931 | }, 932 | { 933 | "cell_type": "code", 934 | "execution_count": 8, 935 | "metadata": {}, 936 | "outputs": [ 937 | { 938 | "name": "stdout", 939 | "output_type": "stream", 940 | "text": [ 941 | "Enter the size of your portfolio: 10000000\n", 942 | "10000000.0\n" 943 | ] 944 | } 945 | ], 946 | "source": [ 947 | "def portfolio_input():\n", 948 | " global portfolio_size\n", 949 | " portfolio_size = input(\"Enter the size of your portfolio: \")\n", 950 | "\n", 951 | " while True:\n", 952 | " try:\n", 953 | " val = float(portfolio_size)\n", 954 | " print(val)\n", 955 | " break\n", 956 | " except ValueError:\n", 957 | " print('Please enter a number.')\n", 958 | " portfolio_size = input('Enter the size of your portfolio: ')\n", 959 | "\n", 960 | "portfolio_input()" 961 | ] 962 | }, 963 | { 964 | "cell_type": "code", 965 | "execution_count": 9, 966 | "metadata": {}, 967 | "outputs": [ 968 | { 969 | "ename": "TypeError", 970 | "evalue": "unsupported operand type(s) for /: 'float' and 'dict'", 971 | "output_type": "error", 972 | "traceback": [ 973 | "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", 974 | "\u001b[1;31mTypeError\u001b[0m Traceback (most recent call last)", 975 | "\u001b[1;32m\u001b[0m in \u001b[0;36m\u001b[1;34m\u001b[0m\n\u001b[0;32m 1\u001b[0m \u001b[0mposition_size\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mfloat\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mportfolio_size\u001b[0m\u001b[1;33m)\u001b[0m \u001b[1;33m/\u001b[0m \u001b[0mlen\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mfinal_dataframe\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mindex\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 2\u001b[0m \u001b[1;32mfor\u001b[0m \u001b[0mi\u001b[0m \u001b[1;32min\u001b[0m \u001b[0mrange\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;36m0\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mlen\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mfinal_dataframe\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m----> 3\u001b[1;33m \u001b[0mfinal_dataframe\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mloc\u001b[0m\u001b[1;33m[\u001b[0m\u001b[0mi\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;34m'Number of Shares to Buy'\u001b[0m\u001b[1;33m]\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mmath\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mfloor\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mposition_size\u001b[0m \u001b[1;33m/\u001b[0m \u001b[0mfinal_dataframe\u001b[0m\u001b[1;33m[\u001b[0m\u001b[1;34m'Price'\u001b[0m\u001b[1;33m]\u001b[0m\u001b[1;33m[\u001b[0m\u001b[0mi\u001b[0m\u001b[1;33m]\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 4\u001b[0m \u001b[0mfinal_dataframe\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", 976 | "\u001b[1;31mTypeError\u001b[0m: unsupported operand type(s) for /: 'float' and 'dict'" 977 | ] 978 | } 979 | ], 980 | "source": [ 981 | "position_size = float(portfolio_size) / len(final_dataframe.index)\n", 982 | "for i in range(0, len(final_dataframe)):\n", 983 | " final_dataframe.loc[i, 'Number of Shares to Buy'] = math.floor(position_size / final_dataframe['Price'][i])\n", 984 | "final_dataframe" 985 | ] 986 | }, 987 | { 988 | "cell_type": "markdown", 989 | "metadata": {}, 990 | "source": [ 991 | "## Building a Better (and More Realistic) Momentum Strategy\n", 992 | "\n", 993 | "Real-world quantitative investment firms differentiate between \"high quality\" and \"low quality\" momentum stocks:\n", 994 | "\n", 995 | "* High-quality momentum stocks show \"slow and steady\" outperformance over long periods of time\n", 996 | "* Low-quality momentum stocks might not show any momentum for a long time, and then surge upwards.\n", 997 | "\n", 998 | "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", 999 | "\n", 1000 | "To identify high-quality momentum, we're going to build a strategy that selects stocks from the highest percentiles of: \n", 1001 | "\n", 1002 | "* 1-month price returns\n", 1003 | "* 3-month price returns\n", 1004 | "* 6-month price returns\n", 1005 | "* 1-year price returns\n", 1006 | "\n", 1007 | "Let's start by building our DataFrame. You'll notice that I use the abbreviation `hqm` often. It stands for `high-quality momentum`." 1008 | ] 1009 | }, 1010 | { 1011 | "cell_type": "code", 1012 | "execution_count": null, 1013 | "metadata": {}, 1014 | "outputs": [], 1015 | "source": [ 1016 | "hqm_columns = [\n", 1017 | " 'Ticker',\n", 1018 | " 'Price',\n", 1019 | " 'Number of Shares to Buy',\n", 1020 | " 'One-Year Price Return',\n", 1021 | " 'One-Year Return Percentile',\n", 1022 | " 'Six-Month Price Return',\n", 1023 | " 'Six-Month Return Percentile',\n", 1024 | " 'Three-Month Price Return',\n", 1025 | " 'Three-Month Return Percentile',\n", 1026 | " 'One-Month Price Return',\n", 1027 | " 'One-Month Return Percentile',\n", 1028 | " 'HQM Score'\n", 1029 | "]\n", 1030 | "\n", 1031 | "hqm_dataframe = pd.DataFrame(columns=hqm_columns)\n", 1032 | "\n", 1033 | "for symbol_string in symbol_strings:\n", 1034 | " batch_api_call_url = f'https://sandbox.iexapis.com/stable/stock/market/batch/?types=stats,quote&symbols={symbol_string}&token={IEX_CLOUD_API_TOKEN}'\n", 1035 | " data = requests.get(batch_api_call_url).json()\n", 1036 | " for symbol in symbol_string.split(','):\n", 1037 | " hqm_dataframe = hqm_dataframe.append(\n", 1038 | " pd.Series([\n", 1039 | " symbol,\n", 1040 | " data[symbol]['quote']['latestPrice'],\n", 1041 | " 'N/A',\n", 1042 | " data[symbol]['stats']['year1ChangePercent'],\n", 1043 | " 'N/A',\n", 1044 | " data[symbol]['stats']['month6ChangePercent'],\n", 1045 | " 'N/A',\n", 1046 | " data[symbol]['stats']['month3ChangePercent'],\n", 1047 | " 'N/A',\n", 1048 | " data[symbol]['stats']['month1ChangePercent'],\n", 1049 | " 'N/A',\n", 1050 | " 'N/A'\n", 1051 | " ],\n", 1052 | " index = hqm_columns),\n", 1053 | " ignore_index = True\n", 1054 | " )\n", 1055 | "\n", 1056 | "hqm_dataframe" 1057 | ] 1058 | }, 1059 | { 1060 | "cell_type": "markdown", 1061 | "metadata": {}, 1062 | "source": [ 1063 | "## Calculating Momentum Percentiles\n", 1064 | "\n", 1065 | "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", 1066 | "\n", 1067 | "* `One-Year Price Return`\n", 1068 | "* `Six-Month Price Return`\n", 1069 | "* `Three-Month Price Return`\n", 1070 | "* `One-Month Price Return`\n", 1071 | "\n", 1072 | "Here's how we'll do this:" 1073 | ] 1074 | }, 1075 | { 1076 | "cell_type": "code", 1077 | "execution_count": null, 1078 | "metadata": {}, 1079 | "outputs": [], 1080 | "source": [ 1081 | "time_periods = [\n", 1082 | " 'One-Year',\n", 1083 | " 'Six-Month',\n", 1084 | " 'Three-Month',\n", 1085 | " 'One-Month'\n", 1086 | "]\n", 1087 | "\n", 1088 | "for row in hqm_dataframe.index:\n", 1089 | " for time_period in time_periods:\n", 1090 | " change_col = f'{time_period} Price Return'\n", 1091 | " percentile_col = f'{time_period} Return Percentile'\n", 1092 | " hqm_dataframe.loc[row, percentile_col] = score(hqm_dataframe[change_col], hqm_dataframe.loc[row, change_col])\n", 1093 | "\n", 1094 | "hqm_dataframe" 1095 | ] 1096 | }, 1097 | { 1098 | "cell_type": "markdown", 1099 | "metadata": {}, 1100 | "source": [ 1101 | "## Calculating the HQM Score\n", 1102 | "\n", 1103 | "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", 1104 | "\n", 1105 | "The `HQM Score` will be the arithmetic mean of the 4 momentum percentile scores that we calculated in the last section.\n", 1106 | "\n", 1107 | "To calculate arithmetic mean, we will use the `mean` function from Python's built-in `statistics` module." 1108 | ] 1109 | }, 1110 | { 1111 | "cell_type": "code", 1112 | "execution_count": null, 1113 | "metadata": {}, 1114 | "outputs": [], 1115 | "source": [ 1116 | "from statistics import mean\n", 1117 | "\n", 1118 | "for row in hqm_dataframe.index[:1]:\n", 1119 | " momentum_percentiles = []\n", 1120 | " for time_period in time_periods:\n", 1121 | " momentum_percentiles.append(hqm_dataframe.loc[row, f'{time_period} Return Percentile'])\n", 1122 | "\n", 1123 | "momentum_percentiles\n" 1124 | ] 1125 | }, 1126 | { 1127 | "cell_type": "markdown", 1128 | "metadata": {}, 1129 | "source": [ 1130 | "## Selecting the 50 Best Momentum Stocks\n", 1131 | "\n", 1132 | "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." 1133 | ] 1134 | }, 1135 | { 1136 | "cell_type": "code", 1137 | "execution_count": null, 1138 | "metadata": {}, 1139 | "outputs": [], 1140 | "source": [] 1141 | }, 1142 | { 1143 | "cell_type": "markdown", 1144 | "metadata": {}, 1145 | "source": [ 1146 | "## Calculating the Number of Shares to Buy\n", 1147 | "\n", 1148 | "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." 1149 | ] 1150 | }, 1151 | { 1152 | "cell_type": "code", 1153 | "execution_count": null, 1154 | "metadata": {}, 1155 | "outputs": [], 1156 | "source": [] 1157 | }, 1158 | { 1159 | "cell_type": "code", 1160 | "execution_count": null, 1161 | "metadata": {}, 1162 | "outputs": [], 1163 | "source": [] 1164 | }, 1165 | { 1166 | "cell_type": "markdown", 1167 | "metadata": {}, 1168 | "source": [ 1169 | "## Formatting Our Excel Output\n", 1170 | "\n", 1171 | "We will be using the XlsxWriter library for Python to create nicely-formatted Excel files.\n", 1172 | "\n", 1173 | "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." 1174 | ] 1175 | }, 1176 | { 1177 | "cell_type": "code", 1178 | "execution_count": null, 1179 | "metadata": {}, 1180 | "outputs": [], 1181 | "source": [] 1182 | }, 1183 | { 1184 | "cell_type": "markdown", 1185 | "metadata": {}, 1186 | "source": [ 1187 | "## Creating the Formats We'll Need For Our .xlsx File\n", 1188 | "\n", 1189 | "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", 1190 | "\n", 1191 | "* String format for tickers\n", 1192 | "* \\$XX.XX format for stock prices\n", 1193 | "* \\$XX,XXX format for market capitalization\n", 1194 | "* Integer format for the number of shares to purchase\n", 1195 | "\n", 1196 | "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." 1197 | ] 1198 | }, 1199 | { 1200 | "cell_type": "code", 1201 | "execution_count": null, 1202 | "metadata": {}, 1203 | "outputs": [], 1204 | "source": [ 1205 | "background_color = '#0a0a23'\n", 1206 | "font_color = '#ffffff'\n", 1207 | "\n", 1208 | "string_template = writer.book.add_format(\n", 1209 | " {\n", 1210 | " 'font_color': font_color,\n", 1211 | " 'bg_color': background_color,\n", 1212 | " 'border': 1\n", 1213 | " }\n", 1214 | " )\n", 1215 | "\n", 1216 | "dollar_template = writer.book.add_format(\n", 1217 | " {\n", 1218 | " 'num_format':'$0.00',\n", 1219 | " 'font_color': font_color,\n", 1220 | " 'bg_color': background_color,\n", 1221 | " 'border': 1\n", 1222 | " }\n", 1223 | " )\n", 1224 | "\n", 1225 | "integer_template = writer.book.add_format(\n", 1226 | " {\n", 1227 | " 'num_format':'0',\n", 1228 | " 'font_color': font_color,\n", 1229 | " 'bg_color': background_color,\n", 1230 | " 'border': 1\n", 1231 | " }\n", 1232 | " )\n", 1233 | "\n", 1234 | "percent_template = writer.book.add_format(\n", 1235 | " {\n", 1236 | " 'num_format':'0.0%',\n", 1237 | " 'font_color': font_color,\n", 1238 | " 'bg_color': background_color,\n", 1239 | " 'border': 1\n", 1240 | " }\n", 1241 | " )" 1242 | ] 1243 | }, 1244 | { 1245 | "cell_type": "code", 1246 | "execution_count": null, 1247 | "metadata": {}, 1248 | "outputs": [], 1249 | "source": [] 1250 | }, 1251 | { 1252 | "cell_type": "markdown", 1253 | "metadata": {}, 1254 | "source": [ 1255 | "## Saving Our Excel Output\n", 1256 | "\n", 1257 | "As before, saving our Excel output is very easy:" 1258 | ] 1259 | }, 1260 | { 1261 | "cell_type": "code", 1262 | "execution_count": null, 1263 | "metadata": {}, 1264 | "outputs": [], 1265 | "source": [] 1266 | } 1267 | ], 1268 | "metadata": { 1269 | "kernelspec": { 1270 | "display_name": "Python 3", 1271 | "language": "python", 1272 | "name": "python3" 1273 | }, 1274 | "language_info": { 1275 | "codemirror_mode": { 1276 | "name": "ipython", 1277 | "version": 3 1278 | }, 1279 | "file_extension": ".py", 1280 | "mimetype": "text/x-python", 1281 | "name": "python", 1282 | "nbconvert_exporter": "python", 1283 | "pygments_lexer": "ipython3", 1284 | "version": "3.8.5" 1285 | } 1286 | }, 1287 | "nbformat": 4, 1288 | "nbformat_minor": 4 1289 | } 1290 | -------------------------------------------------------------------------------- /Projects/2 - Building A Quantitative Momentum Investing Strategy/__pycache__/secrets.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cengizozel/Algorithmic-Trading-In-Python/b6e5e89ab0d7682fe72fc31c6461c43435e05944/Projects/2 - Building A Quantitative Momentum Investing Strategy/__pycache__/secrets.cpython-38.pyc -------------------------------------------------------------------------------- /Projects/2 - Building A Quantitative Momentum Investing Strategy/momentum_strategy.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cengizozel/Algorithmic-Trading-In-Python/b6e5e89ab0d7682fe72fc31c6461c43435e05944/Projects/2 - Building A Quantitative Momentum Investing Strategy/momentum_strategy.xlsx -------------------------------------------------------------------------------- /Projects/2 - Building A Quantitative Momentum Investing Strategy/secrets.py: -------------------------------------------------------------------------------- 1 | IEX_CLOUD_API_TOKEN = 'Tpk_059b97af715d417d9f49f50b51b1c448' -------------------------------------------------------------------------------- /Projects/3 - Building A Quantitative Value Investing Strategy/__pycache__/secrets.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cengizozel/Algorithmic-Trading-In-Python/b6e5e89ab0d7682fe72fc31c6461c43435e05944/Projects/3 - Building A Quantitative Value Investing Strategy/__pycache__/secrets.cpython-38.pyc -------------------------------------------------------------------------------- /Projects/3 - Building A Quantitative Value Investing Strategy/secrets.py: -------------------------------------------------------------------------------- 1 | IEX_CLOUD_API_TOKEN = 'Tpk_059b97af715d417d9f49f50b51b1c448' -------------------------------------------------------------------------------- /Projects/3 - Building A Quantitative Value Investing Strategy/value_strategy.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cengizozel/Algorithmic-Trading-In-Python/b6e5e89ab0d7682fe72fc31c6461c43435e05944/Projects/3 - Building A Quantitative Value Investing Strategy/value_strategy.xlsx -------------------------------------------------------------------------------- /Projects/sp_500_stocks.csv: -------------------------------------------------------------------------------- 1 | Ticker 2 | A 3 | AAL 4 | AAP 5 | AAPL 6 | ABBV 7 | ABC 8 | ABMD 9 | ABT 10 | ACN 11 | ADBE 12 | ADI 13 | ADM 14 | ADP 15 | ADSK 16 | AEE 17 | AEP 18 | AES 19 | AFL 20 | AIG 21 | AIV 22 | AIZ 23 | AJG 24 | AKAM 25 | ALB 26 | ALGN 27 | ALK 28 | ALL 29 | ALLE 30 | ALXN 31 | AMAT 32 | AMCR 33 | AMD 34 | AME 35 | AMGN 36 | AMP 37 | AMT 38 | AMZN 39 | ANET 40 | ANSS 41 | ANTM 42 | AON 43 | AOS 44 | APA 45 | APD 46 | APH 47 | APTV 48 | ARE 49 | ATO 50 | ATVI 51 | AVB 52 | AVGO 53 | AVY 54 | AWK 55 | AXP 56 | AZO 57 | BA 58 | BAC 59 | BAX 60 | BBY 61 | BDX 62 | BEN 63 | BF.B 64 | BIIB 65 | BIO 66 | BK 67 | BKNG 68 | BKR 69 | BLK 70 | BLL 71 | BMY 72 | BR 73 | BRK.B 74 | BSX 75 | BWA 76 | BXP 77 | C 78 | CAG 79 | CAH 80 | CARR 81 | CAT 82 | CB 83 | CBOE 84 | CBRE 85 | CCI 86 | CCL 87 | CDNS 88 | CDW 89 | CE 90 | CERN 91 | CF 92 | CFG 93 | CHD 94 | CHRW 95 | CHTR 96 | CI 97 | CINF 98 | CL 99 | CLX 100 | CMA 101 | CMCSA 102 | CME 103 | CMG 104 | CMI 105 | CMS 106 | CNC 107 | CNP 108 | COF 109 | COG 110 | COO 111 | COP 112 | COST 113 | COTY 114 | CPB 115 | CPRT 116 | CRM 117 | CSCO 118 | CSX 119 | CTAS 120 | CTL 121 | CTSH 122 | CTVA 123 | CTXS 124 | CVS 125 | CVX 126 | CXO 127 | D 128 | DAL 129 | DD 130 | DE 131 | DFS 132 | DG 133 | DGX 134 | DHI 135 | DHR 136 | DIS 137 | DISCA 138 | DISCK 139 | DISH 140 | DLR 141 | DLTR 142 | DOV 143 | DOW 144 | DPZ 145 | DRE 146 | DRI 147 | DTE 148 | DUK 149 | DVA 150 | DVN 151 | DXC 152 | DXCM 153 | EA 154 | EBAY 155 | ECL 156 | ED 157 | EFX 158 | EIX 159 | EL 160 | EMN 161 | EMR 162 | EOG 163 | EQIX 164 | EQR 165 | ES 166 | ESS 167 | ETFC 168 | ETN 169 | ETR 170 | EVRG 171 | EW 172 | EXC 173 | EXPD 174 | EXPE 175 | EXR 176 | F 177 | FANG 178 | FAST 179 | FB 180 | FBHS 181 | FCX 182 | FDX 183 | FE 184 | FFIV 185 | FIS 186 | FISV 187 | FITB 188 | FLIR 189 | FLS 190 | FLT 191 | FMC 192 | FOX 193 | FOXA 194 | FRC 195 | FRT 196 | FTI 197 | FTNT 198 | FTV 199 | GD 200 | GE 201 | GILD 202 | GIS 203 | GL 204 | GLW 205 | GM 206 | GOOG 207 | GOOGL 208 | GPC 209 | GPN 210 | GPS 211 | GRMN 212 | GS 213 | GWW 214 | HAL 215 | HAS 216 | HBAN 217 | HBI 218 | HCA 219 | HD 220 | HES 221 | HFC 222 | HIG 223 | HII 224 | HLT 225 | HOLX 226 | HON 227 | HPE 228 | HPQ 229 | HRB 230 | HRL 231 | HSIC 232 | HST 233 | HSY 234 | HUM 235 | HWM 236 | IBM 237 | ICE 238 | IDXX 239 | IEX 240 | IFF 241 | ILMN 242 | INCY 243 | INFO 244 | INTC 245 | INTU 246 | IP 247 | IPG 248 | IPGP 249 | IQV 250 | IR 251 | IRM 252 | ISRG 253 | IT 254 | ITW 255 | IVZ 256 | J 257 | JBHT 258 | JCI 259 | JKHY 260 | JNJ 261 | JNPR 262 | JPM 263 | K 264 | KEY 265 | KEYS 266 | KHC 267 | KIM 268 | KLAC 269 | KMB 270 | KMI 271 | KMX 272 | KO 273 | KR 274 | KSS 275 | KSU 276 | L 277 | LB 278 | LDOS 279 | LEG 280 | LEN 281 | LH 282 | LHX 283 | LIN 284 | LKQ 285 | LLY 286 | LMT 287 | LNC 288 | LNT 289 | LOW 290 | LRCX 291 | LUV 292 | LVS 293 | LW 294 | LYB 295 | LYV 296 | MA 297 | MAA 298 | MAR 299 | MAS 300 | MCD 301 | MCHP 302 | MCK 303 | MCO 304 | MDLZ 305 | MDT 306 | MET 307 | MGM 308 | MHK 309 | MKC 310 | MKTX 311 | MLM 312 | MMC 313 | MMM 314 | MNST 315 | MO 316 | MOS 317 | MPC 318 | MRK 319 | MRO 320 | MS 321 | MSCI 322 | MSFT 323 | MSI 324 | MTB 325 | MTD 326 | MU 327 | MXIM 328 | MYL 329 | NBL 330 | NCLH 331 | NDAQ 332 | NEE 333 | NEM 334 | NFLX 335 | NI 336 | NKE 337 | NLOK 338 | NLSN 339 | NOC 340 | NOV 341 | NOW 342 | NRG 343 | NSC 344 | NTAP 345 | NTRS 346 | NUE 347 | NVDA 348 | NVR 349 | NWL 350 | NWS 351 | NWSA 352 | O 353 | ODFL 354 | OKE 355 | OMC 356 | ORCL 357 | ORLY 358 | OTIS 359 | OXY 360 | PAYC 361 | PAYX 362 | PBCT 363 | PCAR 364 | PEAK 365 | PEG 366 | PEP 367 | PFE 368 | PFG 369 | PG 370 | PGR 371 | PH 372 | PHM 373 | PKG 374 | PKI 375 | PLD 376 | PM 377 | PNC 378 | PNR 379 | PNW 380 | PPG 381 | PPL 382 | PRGO 383 | PRU 384 | PSA 385 | PSX 386 | PVH 387 | PWR 388 | PXD 389 | PYPL 390 | QCOM 391 | QRVO 392 | RCL 393 | RE 394 | REG 395 | REGN 396 | RF 397 | RHI 398 | RJF 399 | RL 400 | RMD 401 | ROK 402 | ROL 403 | ROP 404 | ROST 405 | RSG 406 | RTX 407 | SBAC 408 | SBUX 409 | SCHW 410 | SEE 411 | SHW 412 | SIVB 413 | SJM 414 | SLB 415 | SLG 416 | SNA 417 | SNPS 418 | SO 419 | SPG 420 | SPGI 421 | SRE 422 | STE 423 | STT 424 | STX 425 | STZ 426 | SWK 427 | SWKS 428 | SYF 429 | SYK 430 | SYY 431 | T 432 | TAP 433 | TDG 434 | TDY 435 | TEL 436 | TFC 437 | TFX 438 | TGT 439 | TIF 440 | TJX 441 | TMO 442 | TMUS 443 | TPR 444 | TROW 445 | TRV 446 | TSCO 447 | TSN 448 | TT 449 | TTWO 450 | TWTR 451 | TXN 452 | TXT 453 | TYL 454 | UA 455 | UAA 456 | UAL 457 | UDR 458 | UHS 459 | ULTA 460 | UNH 461 | UNM 462 | UNP 463 | UPS 464 | URI 465 | USB 466 | V 467 | VAR 468 | VFC 469 | VIAC 470 | VLO 471 | VMC 472 | VNO 473 | VRSK 474 | VRSN 475 | VRTX 476 | VTR 477 | VZ 478 | WAB 479 | WAT 480 | WBA 481 | WDC 482 | WEC 483 | WELL 484 | WFC 485 | WHR 486 | WLTW 487 | WM 488 | WMB 489 | WMT 490 | WRB 491 | WRK 492 | WST 493 | WU 494 | WY 495 | WYNN 496 | XEL 497 | XLNX 498 | XOM 499 | XRAY 500 | XRX 501 | XYL 502 | YUM 503 | ZBH 504 | ZBRA 505 | ZION 506 | ZTS -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Algorithmic Trading In Python 2 | 3 | Collection of 3 quantitative finance projects in Python that uses algorithmic trading. 4 | I completed these projects by watching a full course by the software developer Nick McCullum on 5 | [YouTube](https://youtu.be/xfzGZB4HhEE). 6 | The projects consist of instructions given on a Jupyter notebook from 7 | [this repo](https://github.com/nickmccullum/algorithmic-trading-python) 8 | and I did the coding part by following the course. 9 | 10 | ### Projects 11 | 1. [Building An Equal-Weight SP 500 Index Fund](https://github.com/cengizozel/algorithmic-trading-in-python#building-an-equal-weight-sp-500-index-fund) 12 | 2. [Building A Quantitative Momentum Investing Strategy](https://github.com/cengizozel/algorithmic-trading-in-python#building-a-quantitative-momentum-investing-strategy) 13 | 3. [Building A Quantitative Value Investing Strategy](https://github.com/cengizozel/algorithmic-trading-in-python#building-a-quantitative-value-investing-strategy) 14 | 15 | All three projects use the sandbox version of 16 | [IEX Cloud API](https://iexcloud.io/docs/api/). 17 | The free sandbox version is for testing purposes and gives random values. 18 | 19 | ## Building An Equal-Weight S&P 500 Index Fund 20 | #### [View project](https://github.com/cengizozel/Algorithmic-Trading-In-Python/blob/main/Projects/1%20-%20Building%20An%20Equal-Weight%20S%26P%20500%20Index%20Fund/001_equal_weight_S%26P_500.ipynb) 21 | The goal of this project is to build an equal-weight version of the S&P 500 index fund by taking the value of a portfolio as input and tell how many shares of each S&P 500 stock should be purchased. The script exports an excel file with the equal-weight version of the index fund as a result. 22 | 23 | ![121](https://user-images.githubusercontent.com/60388555/105229513-0e385f80-5b32-11eb-890b-77d11e34c656.PNG) 24 | 25 | ## Building A Quantitative Momentum Investing Strategy 26 | #### [View project](https://github.com/cengizozel/Algorithmic-Trading-In-Python/blob/main/Projects/2%20-%20Building%20A%20Quantitative%20Momentum%20Investing%20Strategy/002_quantitative_momentum_strategy.ipynb) 27 | The goal of this project is to build a quantitative momentum strategy that decides how many of each of the best 50 stocks to buy based on their HQM scores (high-quality momentum) that indicate that they have increased in price the most. 28 | 29 | ![122](https://user-images.githubusercontent.com/60388555/105230527-7471b200-5b33-11eb-90a6-eb62679c96d8.PNG) 30 | 31 | ## Building A Quantitative Value Investing Strategy 32 | #### [View project](https://github.com/cengizozel/Algorithmic-Trading-In-Python/blob/main/Projects/3%20-%20Building%20A%20Quantitative%20Value%20Investing%20Strategy/003_quantitative_value_strategy.ipynb) 33 | The goal of this project is to build a quantitative value strategy that decides how many of each of the best 50 stocks to buy based on their RV scores (robust value) that indicate how cheap they are relative to common measures of business value. 34 | 35 | ![123](https://user-images.githubusercontent.com/60388555/105230806-e77b2880-5b33-11eb-9b37-d1190f8623d5.PNG) 36 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | jupyter==1.0.0 2 | jupyter-client==6.1.3 3 | jupyter-console==6.1.0 4 | jupyter-core==4.6.3 5 | numpy==1.17.4 6 | pandas==0.25.3 7 | requests==2.22.0 8 | scipy==1.5.2 9 | XlsxWriter==1.2.2 10 | --------------------------------------------------------------------------------