├── .gitignore ├── Dual Listing Arbitrage ├── Dual Listing Algorithmic Trading.ipynb ├── HWG.csv ├── OTZ.csv ├── README.md ├── RLI.csv └── RMCC.csv ├── LICENSE ├── Options Arbitrage ├── Options Arbitrage.csv ├── Options Arbitrage.ipynb ├── README.md └── black_scholes.py ├── README.md ├── Statistical Arbitrage ├── Pairs Trading Jupyter Notebook.ipynb ├── Pairs Trading.csv ├── README.md └── cointegration_analysis.py └── requirements.txt /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | .idea 6 | 7 | # C extensions 8 | *.so 9 | 10 | # Distribution / packaging 11 | .Python 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | wheels/ 24 | pip-wheel-metadata/ 25 | share/python-wheels/ 26 | *.egg-info/ 27 | .installed.cfg 28 | *.egg 29 | MANIFEST 30 | 31 | # PyInstaller 32 | # Usually these files are written by a python script from a template 33 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 34 | *.manifest 35 | *.spec 36 | 37 | # Installer logs 38 | pip-log.txt 39 | pip-delete-this-directory.txt 40 | 41 | # Unit test / coverage reports 42 | htmlcov/ 43 | .tox/ 44 | .nox/ 45 | .coverage 46 | .coverage.* 47 | .cache 48 | nosetests.xml 49 | coverage.xml 50 | *.cover 51 | *.py,cover 52 | .hypothesis/ 53 | .pytest_cache/ 54 | 55 | # Translations 56 | *.mo 57 | *.pot 58 | 59 | # Django stuff: 60 | *.log 61 | local_settings.py 62 | db.sqlite3 63 | db.sqlite3-journal 64 | 65 | # Flask stuff: 66 | instance/ 67 | .webassets-cache 68 | 69 | # Scrapy stuff: 70 | .scrapy 71 | 72 | # Sphinx documentation 73 | docs/_build/ 74 | 75 | # PyBuilder 76 | target/ 77 | 78 | # Jupyter Notebook 79 | .ipynb_checkpoints 80 | 81 | # IPython 82 | profile_default/ 83 | ipython_config.py 84 | 85 | # pyenv 86 | .python-version 87 | 88 | # pipenv 89 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 90 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 91 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 92 | # install all needed dependencies. 93 | #Pipfile.lock 94 | 95 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 96 | __pypackages__/ 97 | 98 | # Celery stuff 99 | celerybeat-schedule 100 | celerybeat.pid 101 | 102 | # SageMath parsed files 103 | *.sage.py 104 | 105 | # Environments 106 | .env 107 | .venv 108 | env/ 109 | venv/ 110 | ENV/ 111 | env.bak/ 112 | venv.bak/ 113 | 114 | # Spyder project settings 115 | .spyderproject 116 | .spyproject 117 | 118 | # Rope project settings 119 | .ropeproject 120 | 121 | # mkdocs documentation 122 | /site 123 | 124 | # mypy 125 | .mypy_cache/ 126 | .dmypy.json 127 | dmypy.json 128 | 129 | # Pyre type checker 130 | .pyre/ 131 | -------------------------------------------------------------------------------- /Dual Listing Arbitrage/README.md: -------------------------------------------------------------------------------- 1 | # Dual Listing Arbitrage 2 | A Project to identify arbitrage opportunities between two stock exchanges trading the same stock. 3 | The algorithm searches for the possibility of a mismatch and trades on it. Next to that, 4 | it takes into account certain limits, which is set to a max position of 250 to 5 | prevent massive losses if the algorithm malfunctions. 6 | 7 | It requires specific datasets as added in the directory. 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Jeroen Bouma 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Options Arbitrage/Options Arbitrage.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Options Arbitrage" 8 | ] 9 | }, 10 | { 11 | "cell_type": "code", 12 | "execution_count": 1, 13 | "metadata": {}, 14 | "outputs": [], 15 | "source": [ 16 | "# Import Dependencies\n", 17 | "import pandas as pd\n", 18 | "import numpy as np\n", 19 | "import matplotlib.pyplot as plt\n", 20 | "from black_scholes import call_value, put_value, call_delta, put_delta, call_vega, put_vega\n", 21 | "%matplotlib inline" 22 | ] 23 | }, 24 | { 25 | "cell_type": "code", 26 | "execution_count": 2, 27 | "metadata": {}, 28 | "outputs": [], 29 | "source": [ 30 | "def read_data(filename):\n", 31 | " df = pd.read_csv(filename, index_col=0)\n", 32 | "\n", 33 | " time_to_expiry = df.filter(like='TimeToExpiry')\n", 34 | "\n", 35 | " stock = df.filter(like='Stock')\n", 36 | " stock.columns = [stock.columns.str[-5:], stock.columns.str[:-6]]\n", 37 | "\n", 38 | " options = pd.concat((df.filter(like='-P'), df.filter(like='-C')), axis=1)\n", 39 | " options.columns = [options.columns.str[-3:], options.columns.str[:-4]]\n", 40 | "\n", 41 | " market_data = pd.concat((stock, options), axis=1)\n", 42 | "\n", 43 | " return time_to_expiry, market_data" 44 | ] 45 | }, 46 | { 47 | "cell_type": "code", 48 | "execution_count": 3, 49 | "metadata": { 50 | "scrolled": false 51 | }, 52 | "outputs": [], 53 | "source": [ 54 | "# Read the market data\n", 55 | "filename = 'Options Arbitrage.csv'\n", 56 | "time_to_expiry, market_data = read_data(filename)" 57 | ] 58 | }, 59 | { 60 | "cell_type": "code", 61 | "execution_count": 4, 62 | "metadata": { 63 | "scrolled": false 64 | }, 65 | "outputs": [ 66 | { 67 | "name": "stdout", 68 | "output_type": "stream", 69 | "text": [ 70 | "['Stock', 'P60', 'P70', 'P80', 'C60', 'C70', 'C80']\n", 71 | "['P60', 'P70', 'P80', 'C60', 'C70', 'C80']\n" 72 | ] 73 | } 74 | ], 75 | "source": [ 76 | "# Get a list of all instrument names including the stock, and of the options only\n", 77 | "instrument_names = list(market_data.columns.get_level_values(0).unique())\n", 78 | "print(instrument_names)\n", 79 | "\n", 80 | "option_names = instrument_names[1:]\n", 81 | "print(option_names)" 82 | ] 83 | }, 84 | { 85 | "cell_type": "code", 86 | "execution_count": 5, 87 | "metadata": {}, 88 | "outputs": [], 89 | "source": [ 90 | "# Add time_to_expiry to market_data\n", 91 | "market_data['TTE'] = time_to_expiry['TimeToExpiry']\n", 92 | "\n", 93 | "# Store timestamp in variable to prevent\n", 94 | "# errors with multiplications and such\n", 95 | "timestamp = market_data.index\n", 96 | "\n", 97 | "# Set the Time to Expiry as Index\n", 98 | "market_data = market_data.set_index('TTE')" 99 | ] 100 | }, 101 | { 102 | "cell_type": "code", 103 | "execution_count": 6, 104 | "metadata": { 105 | "scrolled": true 106 | }, 107 | "outputs": [], 108 | "source": [ 109 | "# Create Empty Dictionaries\n", 110 | "short_call_values = {}\n", 111 | "long_call_values = {}\n", 112 | "long_put_values = {}\n", 113 | "short_put_values = {}\n", 114 | "short_call_deltas = {}\n", 115 | "long_call_deltas = {}\n", 116 | "long_put_deltas = {}\n", 117 | "short_put_deltas = {}\n", 118 | "option_values = {}\n", 119 | "option_deltas = {}\n", 120 | "\n", 121 | "# Set known attributes\n", 122 | "r = 0\n", 123 | "sigma = 0.20\n", 124 | "\n", 125 | "# Forloop to create new columns with Call/Put names\n", 126 | "for option in option_names:\n", 127 | " # Retrieve K from the Option\n", 128 | " K = int(option[-2:])\n", 129 | "\n", 130 | " if 'C' in option:\n", 131 | " short_call_values[option] = []\n", 132 | " long_call_values[option] = []\n", 133 | " short_call_deltas[option] = []\n", 134 | " long_call_deltas[option] = []\n", 135 | "\n", 136 | " # Forloop to calculate short/long call values and deltas\n", 137 | " for time, stock_value in market_data.iterrows():\n", 138 | " short_call_values[option].append(call_value(\n", 139 | " stock_value['Stock', 'AskPrice'], K, time, r, sigma))\n", 140 | " long_call_values[option].append(call_value(\n", 141 | " stock_value['Stock', 'BidPrice'], K, time, r, sigma))\n", 142 | " long_call_deltas[option].append(call_delta(\n", 143 | " stock_value['Stock', 'BidPrice'], K, time, r, sigma))\n", 144 | " short_call_deltas[option].append(-call_delta(\n", 145 | " stock_value['Stock', 'AskPrice'], K, time, r, sigma))\n", 146 | "\n", 147 | " option_values['Short Call', option] = short_call_values[option]\n", 148 | " option_values['Long Call', option] = long_call_values[option]\n", 149 | " option_deltas['Short Call', option] = short_call_deltas[option]\n", 150 | " option_deltas['Long Call', option] = long_call_deltas[option]\n", 151 | "\n", 152 | " if 'P' in option:\n", 153 | " long_put_values[option] = []\n", 154 | " short_put_values[option] = []\n", 155 | " long_put_deltas[option] = []\n", 156 | " short_put_deltas[option] = []\n", 157 | "\n", 158 | " # Forloop to calculate short/long put values and deltas\n", 159 | " for time, stock_value in market_data.iterrows():\n", 160 | " long_put_values[option].append(\n", 161 | " put_value(stock_value['Stock', 'AskPrice'], K, time, r, sigma))\n", 162 | " short_put_values[option].append(\n", 163 | " put_value(stock_value['Stock', 'BidPrice'], K, time, r, sigma))\n", 164 | " long_put_deltas[option].append(\n", 165 | " put_delta(stock_value['Stock', 'AskPrice'], K, time, r, sigma))\n", 166 | " short_put_deltas[option].append(-put_delta(\n", 167 | " stock_value['Stock', 'BidPrice'], K, time, r, sigma))\n", 168 | "\n", 169 | " option_values['Long Put', option] = long_put_values[option]\n", 170 | " option_values['Short Put', option] = short_put_values[option]\n", 171 | " option_deltas['Long Put', option] = long_put_deltas[option]\n", 172 | " option_deltas['Short Put', option] = short_put_deltas[option]" 173 | ] 174 | }, 175 | { 176 | "cell_type": "code", 177 | "execution_count": 7, 178 | "metadata": { 179 | "scrolled": false 180 | }, 181 | "outputs": [ 182 | { 183 | "name": "stdout", 184 | "output_type": "stream", 185 | "text": [ 186 | "Option Values\n" 187 | ] 188 | }, 189 | { 190 | "data": { 191 | "text/html": [ 192 | "
\n", 193 | "\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 | " \n", 296 | " \n", 297 | " \n", 298 | " \n", 299 | " \n", 300 | " \n", 301 | " \n", 302 | " \n", 303 | " \n", 304 | " \n", 305 | " \n", 306 | " \n", 307 | " \n", 308 | " \n", 309 | " \n", 310 | " \n", 311 | " \n", 312 | " \n", 313 | " \n", 314 | " \n", 315 | " \n", 316 | " \n", 317 | " \n", 318 | " \n", 319 | " \n", 320 | " \n", 321 | " \n", 322 | " \n", 323 | " \n", 324 | " \n", 325 | " \n", 326 | " \n", 327 | "
Long CallLong PutShort CallShort Put
C60C70C80P60P70P80C60C70C80P60P70P80
TTE
0.91162512.045.712.241.314.9211.3912.215.822.291.345.0111.54
0.91161512.085.742.251.294.8811.3212.295.882.321.334.9911.50
0.91160612.175.792.281.284.8611.2912.335.912.341.324.9411.43
0.91159612.125.762.271.284.8611.2912.335.912.341.324.9611.47
0.91158712.085.742.251.294.8811.3212.295.882.321.334.9911.50
\n", 328 | "
" 329 | ], 330 | "text/plain": [ 331 | " Long Call Long Put Short Call \\\n", 332 | " C60 C70 C80 P60 P70 P80 C60 C70 C80 \n", 333 | "TTE \n", 334 | "0.911625 12.04 5.71 2.24 1.31 4.92 11.39 12.21 5.82 2.29 \n", 335 | "0.911615 12.08 5.74 2.25 1.29 4.88 11.32 12.29 5.88 2.32 \n", 336 | "0.911606 12.17 5.79 2.28 1.28 4.86 11.29 12.33 5.91 2.34 \n", 337 | "0.911596 12.12 5.76 2.27 1.28 4.86 11.29 12.33 5.91 2.34 \n", 338 | "0.911587 12.08 5.74 2.25 1.29 4.88 11.32 12.29 5.88 2.32 \n", 339 | "\n", 340 | " Short Put \n", 341 | " P60 P70 P80 \n", 342 | "TTE \n", 343 | "0.911625 1.34 5.01 11.54 \n", 344 | "0.911615 1.33 4.99 11.50 \n", 345 | "0.911606 1.32 4.94 11.43 \n", 346 | "0.911596 1.32 4.96 11.47 \n", 347 | "0.911587 1.33 4.99 11.50 " 348 | ] 349 | }, 350 | "metadata": {}, 351 | "output_type": "display_data" 352 | }, 353 | { 354 | "name": "stdout", 355 | "output_type": "stream", 356 | "text": [ 357 | "Option Deltas\n" 358 | ] 359 | }, 360 | { 361 | "data": { 362 | "text/html": [ 363 | "
\n", 364 | "\n", 381 | "\n", 382 | " \n", 383 | " \n", 384 | " \n", 385 | " \n", 386 | " \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 | "
Long CallLong PutShort CallShort Put
C60C70C80P60P70P80C60C70C80P60P70P80
TTE
0.9116250.8301690.5586650.290582-0.166116-0.435503-0.704329-0.833884-0.564497-0.2956710.1698310.4413350.709418
0.9116150.8311050.5601260.291851-0.164281-0.432599-0.701776-0.835719-0.567401-0.2982240.1688950.4398740.708149
0.9116060.8329630.5630410.294394-0.163369-0.431150-0.700497-0.836631-0.568850-0.2995030.1670370.4369590.705606
0.9115960.8320380.5615840.293120-0.163368-0.431150-0.700499-0.836632-0.568850-0.2995010.1679620.4384160.706880
0.9115870.8311080.5601260.291847-0.164278-0.432599-0.701779-0.835722-0.567401-0.2982210.1688920.4398740.708153
\n", 499 | "
" 500 | ], 501 | "text/plain": [ 502 | " Long Call Long Put \\\n", 503 | " C60 C70 C80 P60 P70 P80 \n", 504 | "TTE \n", 505 | "0.911625 0.830169 0.558665 0.290582 -0.166116 -0.435503 -0.704329 \n", 506 | "0.911615 0.831105 0.560126 0.291851 -0.164281 -0.432599 -0.701776 \n", 507 | "0.911606 0.832963 0.563041 0.294394 -0.163369 -0.431150 -0.700497 \n", 508 | "0.911596 0.832038 0.561584 0.293120 -0.163368 -0.431150 -0.700499 \n", 509 | "0.911587 0.831108 0.560126 0.291847 -0.164278 -0.432599 -0.701779 \n", 510 | "\n", 511 | " Short Call Short Put \n", 512 | " C60 C70 C80 P60 P70 P80 \n", 513 | "TTE \n", 514 | "0.911625 -0.833884 -0.564497 -0.295671 0.169831 0.441335 0.709418 \n", 515 | "0.911615 -0.835719 -0.567401 -0.298224 0.168895 0.439874 0.708149 \n", 516 | "0.911606 -0.836631 -0.568850 -0.299503 0.167037 0.436959 0.705606 \n", 517 | "0.911596 -0.836632 -0.568850 -0.299501 0.167962 0.438416 0.706880 \n", 518 | "0.911587 -0.835722 -0.567401 -0.298221 0.168892 0.439874 0.708153 " 519 | ] 520 | }, 521 | "metadata": {}, 522 | "output_type": "display_data" 523 | } 524 | ], 525 | "source": [ 526 | "# Create DataFrames with index market_data\n", 527 | "option_values = pd.DataFrame(option_values, index=market_data.index)\n", 528 | "option_deltas = pd.DataFrame(option_deltas, index=market_data.index)\n", 529 | "\n", 530 | "# Sort the DataFrames\n", 531 | "option_values = option_values.reindex(sorted(option_values.columns), axis=1)\n", 532 | "option_deltas = option_deltas.reindex(sorted(option_deltas.columns), axis=1)\n", 533 | "\n", 534 | "# Rounding\n", 535 | "option_values = round(option_values, 2)\n", 536 | "\n", 537 | "# Show DataFrames\n", 538 | "print('Option Values')\n", 539 | "display(option_values.head())\n", 540 | "print('Option Deltas')\n", 541 | "display(option_deltas.head())" 542 | ] 543 | }, 544 | { 545 | "cell_type": "code", 546 | "execution_count": 8, 547 | "metadata": {}, 548 | "outputs": [], 549 | "source": [ 550 | "# Create Columns for Black Scholes Value in the Data Set\n", 551 | "# This is used for later calculations (algorithm and such)\n", 552 | "\n", 553 | "for option in option_names:\n", 554 | " if \"C\" in option:\n", 555 | " market_data[option,\n", 556 | " 'Expected AskPrice'] = option_values['Short Call', option]\n", 557 | " market_data[option,\n", 558 | " 'Expected BidPrice'] = option_values['Long Call', option]\n", 559 | " market_data[option,\n", 560 | " 'Delta Short'] = option_deltas['Short Call', option].values\n", 561 | " market_data[option,\n", 562 | " 'Delta Long'] = option_deltas['Long Call', option].values\n", 563 | "\n", 564 | " elif \"P\" in option:\n", 565 | " market_data[option,\n", 566 | " 'Expected AskPrice'] = option_values['Short Put', option]\n", 567 | " market_data[option,\n", 568 | " 'Expected BidPrice'] = option_values['Long Put', option]\n", 569 | " market_data[option,\n", 570 | " 'Delta Short'] = option_deltas['Short Put', option].values\n", 571 | " market_data[option,\n", 572 | " 'Delta Long'] = option_deltas['Long Put', option].values\n", 573 | "\n", 574 | "# Sort Columns\n", 575 | "market_data = market_data.reindex(sorted(market_data.columns), axis=1)" 576 | ] 577 | }, 578 | { 579 | "cell_type": "code", 580 | "execution_count": 9, 581 | "metadata": { 582 | "scrolled": false 583 | }, 584 | "outputs": [ 585 | { 586 | "name": "stdout", 587 | "output_type": "stream", 588 | "text": [ 589 | "BidPrice is at least 0.10 higher than Expected AskPrice for Option C80\n" 590 | ] 591 | }, 592 | { 593 | "data": { 594 | "text/html": [ 595 | "
\n", 596 | "\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 | "
AskPriceAskVolumeBidPriceBidVolumeDelta LongDelta ShortExpected AskPrice
TTE
0.9106162.4722.02.40161.00.287906-0.2942572.28
0.9087902.4919.02.4119.00.292730-0.2965572.30
0.8639752.3421.02.2715.00.282434-0.2888922.17
0.8468512.2914.02.2218.00.281149-0.2850462.11
\n", 675 | "
" 676 | ], 677 | "text/plain": [ 678 | " AskPrice AskVolume BidPrice BidVolume Delta Long Delta Short \\\n", 679 | "TTE \n", 680 | "0.910616 2.47 22.0 2.40 161.0 0.287906 -0.294257 \n", 681 | "0.908790 2.49 19.0 2.41 19.0 0.292730 -0.296557 \n", 682 | "0.863975 2.34 21.0 2.27 15.0 0.282434 -0.288892 \n", 683 | "0.846851 2.29 14.0 2.22 18.0 0.281149 -0.285046 \n", 684 | "\n", 685 | " Expected AskPrice \n", 686 | "TTE \n", 687 | "0.910616 2.28 \n", 688 | "0.908790 2.30 \n", 689 | "0.863975 2.17 \n", 690 | "0.846851 2.11 " 691 | ] 692 | }, 693 | "metadata": {}, 694 | "output_type": "display_data" 695 | }, 696 | { 697 | "name": "stdout", 698 | "output_type": "stream", 699 | "text": [ 700 | "AskPrice is at least 0.10 lower than Expected BidPrice for Option C80\n" 701 | ] 702 | }, 703 | { 704 | "data": { 705 | "text/html": [ 706 | "
\n", 707 | "\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 | " \n", 801 | " \n", 802 | " \n", 803 | " \n", 804 | " \n", 805 | " \n", 806 | " \n", 807 | " \n", 808 | " \n", 809 | " \n", 810 | " \n", 811 | " \n", 812 | " \n", 813 | " \n", 814 | " \n", 815 | "
AskPriceAskVolumeBidPriceBidVolumeDelta LongDelta ShortExpected BidPrice
TTE
0.9063552.0317.01.9619.00.282249-0.2885742.14
0.8909251.70122.01.6321.00.258776-0.2624831.87
0.8693681.3186.01.2519.00.218205-0.2228751.45
0.8471461.96143.01.8819.00.279899-0.2850902.06
0.8310882.4314.02.3516.00.324083-0.3295402.54
0.7556893.1322.03.0521.00.388099-0.3954523.23
0.7540143.0995.03.0018.00.386434-0.3923183.21
\n", 816 | "
" 817 | ], 818 | "text/plain": [ 819 | " AskPrice AskVolume BidPrice BidVolume Delta Long Delta Short \\\n", 820 | "TTE \n", 821 | "0.906355 2.03 17.0 1.96 19.0 0.282249 -0.288574 \n", 822 | "0.890925 1.70 122.0 1.63 21.0 0.258776 -0.262483 \n", 823 | "0.869368 1.31 86.0 1.25 19.0 0.218205 -0.222875 \n", 824 | "0.847146 1.96 143.0 1.88 19.0 0.279899 -0.285090 \n", 825 | "0.831088 2.43 14.0 2.35 16.0 0.324083 -0.329540 \n", 826 | "0.755689 3.13 22.0 3.05 21.0 0.388099 -0.395452 \n", 827 | "0.754014 3.09 95.0 3.00 18.0 0.386434 -0.392318 \n", 828 | "\n", 829 | " Expected BidPrice \n", 830 | "TTE \n", 831 | "0.906355 2.14 \n", 832 | "0.890925 1.87 \n", 833 | "0.869368 1.45 \n", 834 | "0.847146 2.06 \n", 835 | "0.831088 2.54 \n", 836 | "0.755689 3.23 \n", 837 | "0.754014 3.21 " 838 | ] 839 | }, 840 | "metadata": {}, 841 | "output_type": "display_data" 842 | }, 843 | { 844 | "name": "stdout", 845 | "output_type": "stream", 846 | "text": [ 847 | "The amount of trades are 11\n" 848 | ] 849 | } 850 | ], 851 | "source": [ 852 | "def option_opportunities(option):\n", 853 | " '''\n", 854 | " This function gives arbitrage opportunities based on whether the price\n", 855 | " of the option is too high or too low. The results are used to 'eyeball' \n", 856 | " if our final results match what this function displays. This works for \n", 857 | " all Calls and Puts.\n", 858 | " '''\n", 859 | " if \"C\" in option:\n", 860 | " expected1 = market_data[option][(market_data[option, 'BidPrice'] - market_data[option,\n", 861 | " 'Expected AskPrice']) >= 0.10].drop('Expected BidPrice', axis=1)\n", 862 | " expected2 = market_data[option][(market_data[option, 'Expected BidPrice'] -\n", 863 | " market_data[option, 'AskPrice']) >= 0.10].drop('Expected AskPrice', axis=1)\n", 864 | "\n", 865 | " elif \"P\" in option:\n", 866 | " expected1 = market_data[option][(market_data[option, 'BidPrice'] - market_data[option,\n", 867 | " 'Expected AskPrice']) >= 0.10].drop('Expected BidPrice', axis=1)\n", 868 | " expected2 = market_data[option][(market_data[option, 'Expected BidPrice'] -\n", 869 | " market_data[option, 'AskPrice']) >= 0.10].drop('Expected AskPrice', axis=1)\n", 870 | "\n", 871 | " print('BidPrice is at least 0.10 higher than Expected AskPrice for Option ' + option)\n", 872 | " display(expected1)\n", 873 | " print('AskPrice is at least 0.10 lower than Expected BidPrice for Option ' + option)\n", 874 | " display(expected2)\n", 875 | " print('The amount of trades are', len(expected1) + len(expected2))\n", 876 | "\n", 877 | "option_opportunities('C80')" 878 | ] 879 | }, 880 | { 881 | "cell_type": "code", 882 | "execution_count": 10, 883 | "metadata": {}, 884 | "outputs": [], 885 | "source": [ 886 | "# Create a Dictionary with Timestamp and Time to Expiry\n", 887 | "# Index of market_data was changed earlier to time to expiry\n", 888 | "trades = {('Timestamp', ''): timestamp,\n", 889 | " ('Time to Expiry', ''): market_data.index}\n", 890 | "\n", 891 | "# Forloop that adds columns for the Call/Put Positions and Deltas\n", 892 | "# Global function is a changing variable name based on the option\n", 893 | "# For option C60 it will create a variable named positions_call_C60\n", 894 | "for option in option_names:\n", 895 | "\n", 896 | " if 'C' in option:\n", 897 | " trades['Call Position', option] = []\n", 898 | " trades['Call Delta', option] = []\n", 899 | " globals()['positions_call_' + option] = 0\n", 900 | "\n", 901 | " if 'P' in option:\n", 902 | " trades['Put Position', option] = []\n", 903 | " trades['Put Delta', option] = []\n", 904 | " globals()['positions_put_' + option] = 0" 905 | ] 906 | }, 907 | { 908 | "cell_type": "code", 909 | "execution_count": 11, 910 | "metadata": {}, 911 | "outputs": [], 912 | "source": [ 913 | "# Forloop over the rows of market_data\n", 914 | "for time, data in market_data.iterrows():\n", 915 | "\n", 916 | " max_delta = min(data['Stock', 'AskVolume'], data['Stock', 'BidVolume'])\n", 917 | "\n", 918 | " # Forloop over the option_names with conditions\n", 919 | " # if-statements if Call or Put + if Short/Long in Call or Put\n", 920 | " for option in option_names:\n", 921 | "\n", 922 | " if 'C' in option:\n", 923 | "\n", 924 | " # Short Call\n", 925 | " if (data[option, 'BidPrice'] - data[option, 'Expected AskPrice']) >= 0.10:\n", 926 | " short_call_volume = data[option, 'BidVolume']\n", 927 | " long_call_volume = 0\n", 928 | "\n", 929 | " # Long Call\n", 930 | " elif (data[option, 'Expected BidPrice'] - data[option, 'AskPrice']) >= 0.10:\n", 931 | " long_call_volume = data[option, 'AskVolume']\n", 932 | " short_call_volume = 0\n", 933 | "\n", 934 | " else:\n", 935 | " long_call_volume = short_call_volume = 0\n", 936 | "\n", 937 | " call_trade = long_call_volume - short_call_volume\n", 938 | "\n", 939 | " # Define variable, as set earlier. Note the first position is set to zero otherwise\n", 940 | " # One would get an error here since the variable is then not yet defined.\n", 941 | " globals()['positions_call_' + option] = call_trade + \\\n", 942 | " globals()['positions_call_' + option]\n", 943 | "\n", 944 | " # Add Positions (cumulative)\n", 945 | " trades['Call Position', option].append(\n", 946 | " globals()['positions_call_' + option])\n", 947 | "\n", 948 | " if globals()['positions_call_' + option] >= 0:\n", 949 | " long_call_delta = data[option, 'Delta Long']\n", 950 | " short_call_delta = 0\n", 951 | "\n", 952 | " elif globals()['positions_call_' + option] < 0:\n", 953 | " short_call_delta = data[option, 'Delta Short']\n", 954 | " long_call_delta = 0\n", 955 | "\n", 956 | " # Add Deltas (cumulative)\n", 957 | " trades['Call Delta', option].append(\n", 958 | " abs(globals()['positions_call_' + option]) * (long_call_delta + short_call_delta))\n", 959 | "\n", 960 | " if 'P' in option:\n", 961 | "\n", 962 | " # Short Put\n", 963 | " if (data[option, 'BidPrice'] - data[option, 'Expected AskPrice']) >= 0.10:\n", 964 | " short_put_volume = data[option, 'BidVolume']\n", 965 | " long_put_volume = 0\n", 966 | "\n", 967 | " # Long Put\n", 968 | " elif (data[option, 'Expected BidPrice'] - data[option, 'AskPrice']) >= 0.10:\n", 969 | " long_put_volume = data[option, 'AskVolume']\n", 970 | " short_put_volume = 0\n", 971 | "\n", 972 | " else:\n", 973 | " long_put_volume = short_put_volume = 0\n", 974 | "\n", 975 | " put_trade = long_put_volume - short_put_volume\n", 976 | "\n", 977 | " globals()['positions_put_' + option] = put_trade + \\\n", 978 | " globals()['positions_put_' + option]\n", 979 | "\n", 980 | " trades['Put Position', option].append(\n", 981 | " globals()['positions_put_' + option])\n", 982 | "\n", 983 | " if globals()['positions_put_' + option] >= 0:\n", 984 | " long_put_delta = data[option, 'Delta Long']\n", 985 | " short_put_delta = 0\n", 986 | "\n", 987 | " elif globals()['positions_put_' + option] < 0:\n", 988 | " short_put_delta = data[option, 'Delta Short']\n", 989 | " long_put_delta = 0\n", 990 | "\n", 991 | " trades['Put Delta', option].append(\n", 992 | " abs(globals()['positions_put_' + option]) * (long_put_delta + short_put_delta))" 993 | ] 994 | }, 995 | { 996 | "cell_type": "code", 997 | "execution_count": 12, 998 | "metadata": {}, 999 | "outputs": [ 1000 | { 1001 | "data": { 1002 | "text/html": [ 1003 | "
\n", 1004 | "\n", 1021 | "\n", 1022 | " \n", 1023 | " \n", 1024 | " \n", 1025 | " \n", 1026 | " \n", 1027 | " \n", 1028 | " \n", 1029 | " \n", 1030 | " \n", 1031 | " \n", 1032 | " \n", 1033 | " \n", 1034 | " \n", 1035 | " \n", 1036 | " \n", 1037 | " \n", 1038 | " \n", 1039 | " \n", 1040 | " \n", 1041 | " \n", 1042 | " \n", 1043 | " \n", 1044 | " \n", 1045 | " \n", 1046 | " \n", 1047 | " \n", 1048 | " \n", 1049 | " \n", 1050 | " \n", 1051 | " \n", 1052 | " \n", 1053 | " \n", 1054 | " \n", 1055 | " \n", 1056 | " \n", 1057 | " \n", 1058 | " \n", 1059 | " \n", 1060 | " \n", 1061 | " \n", 1062 | " \n", 1063 | " \n", 1064 | " \n", 1065 | " \n", 1066 | " \n", 1067 | " \n", 1068 | " \n", 1069 | " \n", 1070 | " \n", 1071 | " \n", 1072 | " \n", 1073 | " \n", 1074 | " \n", 1075 | " \n", 1076 | " \n", 1077 | " \n", 1078 | " \n", 1079 | " \n", 1080 | " \n", 1081 | " \n", 1082 | " \n", 1083 | " \n", 1084 | " \n", 1085 | " \n", 1086 | " \n", 1087 | " \n", 1088 | " \n", 1089 | " \n", 1090 | " \n", 1091 | " \n", 1092 | " \n", 1093 | " \n", 1094 | " \n", 1095 | " \n", 1096 | " \n", 1097 | " \n", 1098 | " \n", 1099 | " \n", 1100 | " \n", 1101 | " \n", 1102 | " \n", 1103 | " \n", 1104 | " \n", 1105 | " \n", 1106 | " \n", 1107 | " \n", 1108 | " \n", 1109 | " \n", 1110 | " \n", 1111 | " \n", 1112 | " \n", 1113 | " \n", 1114 | " \n", 1115 | " \n", 1116 | " \n", 1117 | " \n", 1118 | " \n", 1119 | " \n", 1120 | " \n", 1121 | " \n", 1122 | " \n", 1123 | " \n", 1124 | " \n", 1125 | " \n", 1126 | " \n", 1127 | " \n", 1128 | " \n", 1129 | " \n", 1130 | " \n", 1131 | " \n", 1132 | " \n", 1133 | " \n", 1134 | " \n", 1135 | " \n", 1136 | " \n", 1137 | " \n", 1138 | " \n", 1139 | " \n", 1140 | " \n", 1141 | " \n", 1142 | " \n", 1143 | " \n", 1144 | " \n", 1145 | " \n", 1146 | " \n", 1147 | " \n", 1148 | " \n", 1149 | " \n", 1150 | " \n", 1151 | " \n", 1152 | " \n", 1153 | " \n", 1154 | " \n", 1155 | " \n", 1156 | " \n", 1157 | " \n", 1158 | " \n", 1159 | " \n", 1160 | " \n", 1161 | " \n", 1162 | " \n", 1163 | " \n", 1164 | " \n", 1165 | " \n", 1166 | " \n", 1167 | " \n", 1168 | " \n", 1169 | " \n", 1170 | "
Call DeltaCall PositionPut DeltaPut PositionTime to ExpiryTotal Option DeltaStock PositionRemaining Option Delta
C60C70C80C60C70C80P60P70P80P60P70P80Stock
Timestamp
2018-02-28 23:35:00281.812848375.708432117.148014305.0532.0286.0-7.017632-10.388177247.96445695.036.0-420.00.7500381005.227940-1005.00.227940
2018-02-28 23:40:00281.646451375.009738116.724180305.0532.0286.0-7.017525-10.388147248.58686895.036.0-420.00.7500291004.561566-1004.00.561566
2018-02-28 23:45:00281.646800375.010163116.723895305.0532.0286.0-7.017417-10.388117248.58728795.036.0-420.00.7500191004.562612-1004.00.562612
2018-02-28 23:50:00281.647150375.010589116.723609305.0532.0286.0-7.017310-10.388087248.58770795.036.0-420.00.7500101004.563657-1004.00.563657
2018-02-28 23:55:00281.479734374.309980116.299862305.0532.0286.0-7.118870-10.481772249.20999395.036.0-420.00.7500001003.698927-1003.00.698927
\n", 1171 | "
" 1172 | ], 1173 | "text/plain": [ 1174 | " Call Delta Call Position \\\n", 1175 | " C60 C70 C80 C60 C70 \n", 1176 | "Timestamp \n", 1177 | "2018-02-28 23:35:00 281.812848 375.708432 117.148014 305.0 532.0 \n", 1178 | "2018-02-28 23:40:00 281.646451 375.009738 116.724180 305.0 532.0 \n", 1179 | "2018-02-28 23:45:00 281.646800 375.010163 116.723895 305.0 532.0 \n", 1180 | "2018-02-28 23:50:00 281.647150 375.010589 116.723609 305.0 532.0 \n", 1181 | "2018-02-28 23:55:00 281.479734 374.309980 116.299862 305.0 532.0 \n", 1182 | "\n", 1183 | " Put Delta Put Position \\\n", 1184 | " C80 P60 P70 P80 P60 \n", 1185 | "Timestamp \n", 1186 | "2018-02-28 23:35:00 286.0 -7.017632 -10.388177 247.964456 95.0 \n", 1187 | "2018-02-28 23:40:00 286.0 -7.017525 -10.388147 248.586868 95.0 \n", 1188 | "2018-02-28 23:45:00 286.0 -7.017417 -10.388117 248.587287 95.0 \n", 1189 | "2018-02-28 23:50:00 286.0 -7.017310 -10.388087 248.587707 95.0 \n", 1190 | "2018-02-28 23:55:00 286.0 -7.118870 -10.481772 249.209993 95.0 \n", 1191 | "\n", 1192 | " Time to Expiry Total Option Delta \\\n", 1193 | " P70 P80 \n", 1194 | "Timestamp \n", 1195 | "2018-02-28 23:35:00 36.0 -420.0 0.750038 1005.227940 \n", 1196 | "2018-02-28 23:40:00 36.0 -420.0 0.750029 1004.561566 \n", 1197 | "2018-02-28 23:45:00 36.0 -420.0 0.750019 1004.562612 \n", 1198 | "2018-02-28 23:50:00 36.0 -420.0 0.750010 1004.563657 \n", 1199 | "2018-02-28 23:55:00 36.0 -420.0 0.750000 1003.698927 \n", 1200 | "\n", 1201 | " Stock Position Remaining Option Delta \n", 1202 | " Stock \n", 1203 | "Timestamp \n", 1204 | "2018-02-28 23:35:00 -1005.0 0.227940 \n", 1205 | "2018-02-28 23:40:00 -1004.0 0.561566 \n", 1206 | "2018-02-28 23:45:00 -1004.0 0.562612 \n", 1207 | "2018-02-28 23:50:00 -1004.0 0.563657 \n", 1208 | "2018-02-28 23:55:00 -1003.0 0.698927 " 1209 | ] 1210 | }, 1211 | "execution_count": 12, 1212 | "metadata": {}, 1213 | "output_type": "execute_result" 1214 | } 1215 | ], 1216 | "source": [ 1217 | "# Create DataFrame with Index Timestamp\n", 1218 | "trades = pd.DataFrame(trades).set_index('Timestamp')\n", 1219 | "\n", 1220 | "# Sort Columns\n", 1221 | "trades = trades.reindex(sorted(trades.columns), axis=1)\n", 1222 | "\n", 1223 | "# Calculate Total Option Delta (based on sorted columns)\n", 1224 | "trades['Total Option Delta', ''] = np.sum(\n", 1225 | " trades['Call Delta'], axis=1) + np.sum(trades['Put Delta'], axis=1)\n", 1226 | "\n", 1227 | "# Calculate Cumulative Stock Position (floored if positive, ceiled if negative)\n", 1228 | "trades['Stock Position', 'Stock'] = -np.where(trades['Total Option Delta', ''] >= 0, np.floor(\n", 1229 | " trades['Total Option Delta', '']), np.ceil(trades['Total Option Delta', '']))\n", 1230 | "\n", 1231 | "# Calculate remaining option delta (that remains unhedged)\n", 1232 | "# This delta is included in the Total Option Delta again which ensures\n", 1233 | "# It always remains below zero\n", 1234 | "trades['Remaining Option Delta', ''] = trades['Total Option Delta',\n", 1235 | " ''] + trades['Stock Position', 'Stock']\n", 1236 | "\n", 1237 | "# Show DataFrame\n", 1238 | "trades.tail()" 1239 | ] 1240 | }, 1241 | { 1242 | "cell_type": "code", 1243 | "execution_count": 13, 1244 | "metadata": { 1245 | "scrolled": false 1246 | }, 1247 | "outputs": [ 1248 | { 1249 | "name": "stdout", 1250 | "output_type": "stream", 1251 | "text": [ 1252 | "Actual Trades/Volumes\n" 1253 | ] 1254 | }, 1255 | { 1256 | "data": { 1257 | "text/html": [ 1258 | "
\n", 1259 | "\n", 1272 | "\n", 1273 | " \n", 1274 | " \n", 1275 | " \n", 1276 | " \n", 1277 | " \n", 1278 | " \n", 1279 | " \n", 1280 | " \n", 1281 | " \n", 1282 | " \n", 1283 | " \n", 1284 | " \n", 1285 | " \n", 1286 | " \n", 1287 | " \n", 1288 | " \n", 1289 | " \n", 1290 | " \n", 1291 | " \n", 1292 | " \n", 1293 | " \n", 1294 | " \n", 1295 | " \n", 1296 | " \n", 1297 | " \n", 1298 | " \n", 1299 | " \n", 1300 | " \n", 1301 | " \n", 1302 | " \n", 1303 | " \n", 1304 | " \n", 1305 | " \n", 1306 | " \n", 1307 | " \n", 1308 | " \n", 1309 | " \n", 1310 | " \n", 1311 | " \n", 1312 | " \n", 1313 | " \n", 1314 | " \n", 1315 | " \n", 1316 | " \n", 1317 | " \n", 1318 | " \n", 1319 | " \n", 1320 | " \n", 1321 | " \n", 1322 | " \n", 1323 | " \n", 1324 | " \n", 1325 | " \n", 1326 | " \n", 1327 | " \n", 1328 | " \n", 1329 | " \n", 1330 | " \n", 1331 | " \n", 1332 | " \n", 1333 | " \n", 1334 | " \n", 1335 | " \n", 1336 | " \n", 1337 | " \n", 1338 | " \n", 1339 | " \n", 1340 | " \n", 1341 | " \n", 1342 | " \n", 1343 | " \n", 1344 | " \n", 1345 | " \n", 1346 | " \n", 1347 | "
C60C70C80P60P70P80Stock
Timestamp
2018-01-01 00:10:000.00.00.00.00.00.00.0
2018-01-01 00:15:000.00.00.00.00.00.00.0
2018-01-01 00:20:000.00.00.00.00.00.00.0
2018-01-01 00:25:000.00.00.00.00.00.00.0
2018-01-01 00:30:000.00.00.00.00.00.00.0
\n", 1348 | "
" 1349 | ], 1350 | "text/plain": [ 1351 | " C60 C70 C80 P60 P70 P80 Stock\n", 1352 | "Timestamp \n", 1353 | "2018-01-01 00:10:00 0.0 0.0 0.0 0.0 0.0 0.0 0.0\n", 1354 | "2018-01-01 00:15:00 0.0 0.0 0.0 0.0 0.0 0.0 0.0\n", 1355 | "2018-01-01 00:20:00 0.0 0.0 0.0 0.0 0.0 0.0 0.0\n", 1356 | "2018-01-01 00:25:00 0.0 0.0 0.0 0.0 0.0 0.0 0.0\n", 1357 | "2018-01-01 00:30:00 0.0 0.0 0.0 0.0 0.0 0.0 0.0" 1358 | ] 1359 | }, 1360 | "metadata": {}, 1361 | "output_type": "display_data" 1362 | }, 1363 | { 1364 | "name": "stdout", 1365 | "output_type": "stream", 1366 | "text": [ 1367 | "Final Positions that we currently 'own'\n" 1368 | ] 1369 | }, 1370 | { 1371 | "data": { 1372 | "text/html": [ 1373 | "
\n", 1374 | "\n", 1387 | "\n", 1388 | " \n", 1389 | " \n", 1390 | " \n", 1391 | " \n", 1392 | " \n", 1393 | " \n", 1394 | " \n", 1395 | " \n", 1396 | " \n", 1397 | " \n", 1398 | " \n", 1399 | " \n", 1400 | " \n", 1401 | " \n", 1402 | " \n", 1403 | " \n", 1404 | " \n", 1405 | " \n", 1406 | " \n", 1407 | " \n", 1408 | " \n", 1409 | " \n", 1410 | " \n", 1411 | " \n", 1412 | " \n", 1413 | " \n", 1414 | " \n", 1415 | " \n", 1416 | " \n", 1417 | " \n", 1418 | " \n", 1419 | " \n", 1420 | " \n", 1421 | " \n", 1422 | "
C60C70C80P60P70P80Stock
Timestamp
2018-02-28 23:55:00305.0532.0286.095.036.0-420.0-1003.0
\n", 1423 | "
" 1424 | ], 1425 | "text/plain": [ 1426 | " C60 C70 C80 P60 P70 P80 Stock\n", 1427 | "Timestamp \n", 1428 | "2018-02-28 23:55:00 305.0 532.0 286.0 95.0 36.0 -420.0 -1003.0" 1429 | ] 1430 | }, 1431 | "metadata": {}, 1432 | "output_type": "display_data" 1433 | } 1434 | ], 1435 | "source": [ 1436 | "# Create trades_diff dataframe that gives all actual trades (not positions)\n", 1437 | "# Also drop columns that are not required for PnL calculations and such\n", 1438 | "trades_diff = trades.diff()[1:].drop(\n", 1439 | " ['Call Delta', 'Put Delta', 'Time to Expiry', 'Total Option Delta', 'Remaining Option Delta'], axis=1)\n", 1440 | "\n", 1441 | "# Drop the 'Call Position','Put Position' and 'Stock Position' top level\n", 1442 | "# Makes forlooping easier\n", 1443 | "trades_diff.columns = trades_diff.columns.droplevel(level=0)\n", 1444 | "\n", 1445 | "# Since positions are not neccesarily zero at the last timestamp, final positions are calculated to be able to valuate these\n", 1446 | "final_positions = trades[-1:].drop(['Call Delta', 'Put Delta', 'Time to Expiry',\n", 1447 | " 'Total Option Delta', 'Remaining Option Delta'], axis=1)\n", 1448 | "\n", 1449 | "final_positions.columns = final_positions.columns.droplevel(level=0)\n", 1450 | "\n", 1451 | "# Show DataFrames\n", 1452 | "print('Actual Trades/Volumes')\n", 1453 | "display(trades_diff.head())\n", 1454 | "\n", 1455 | "print(\"Final Positions that we currently 'own'\")\n", 1456 | "display(final_positions)" 1457 | ] 1458 | }, 1459 | { 1460 | "cell_type": "code", 1461 | "execution_count": 14, 1462 | "metadata": {}, 1463 | "outputs": [], 1464 | "source": [ 1465 | "# Including Timestamp again to match trades_diff index\n", 1466 | "market_data['Timestamp'] = timestamp\n", 1467 | "market_data = market_data.set_index('Timestamp')" 1468 | ] 1469 | }, 1470 | { 1471 | "cell_type": "code", 1472 | "execution_count": 15, 1473 | "metadata": { 1474 | "scrolled": true 1475 | }, 1476 | "outputs": [ 1477 | { 1478 | "data": { 1479 | "text/html": [ 1480 | "
\n", 1481 | "\n", 1494 | "\n", 1495 | " \n", 1496 | " \n", 1497 | " \n", 1498 | " \n", 1499 | " \n", 1500 | " \n", 1501 | " \n", 1502 | " \n", 1503 | " \n", 1504 | " \n", 1505 | " \n", 1506 | " \n", 1507 | " \n", 1508 | " \n", 1509 | " \n", 1510 | " \n", 1511 | " \n", 1512 | " \n", 1513 | " \n", 1514 | " \n", 1515 | " \n", 1516 | " \n", 1517 | " \n", 1518 | " \n", 1519 | " \n", 1520 | " \n", 1521 | " \n", 1522 | " \n", 1523 | " \n", 1524 | " \n", 1525 | " \n", 1526 | " \n", 1527 | " \n", 1528 | " \n", 1529 | " \n", 1530 | " \n", 1531 | " \n", 1532 | " \n", 1533 | " \n", 1534 | " \n", 1535 | " \n", 1536 | " \n", 1537 | " \n", 1538 | " \n", 1539 | " \n", 1540 | " \n", 1541 | " \n", 1542 | " \n", 1543 | " \n", 1544 | " \n", 1545 | " \n", 1546 | " \n", 1547 | " \n", 1548 | " \n", 1549 | " \n", 1550 | " \n", 1551 | " \n", 1552 | " \n", 1553 | " \n", 1554 | " \n", 1555 | " \n", 1556 | " \n", 1557 | " \n", 1558 | " \n", 1559 | " \n", 1560 | " \n", 1561 | " \n", 1562 | " \n", 1563 | " \n", 1564 | " \n", 1565 | " \n", 1566 | " \n", 1567 | " \n", 1568 | " \n", 1569 | "
StockP60P70P80C60C70C80
Timestamp
2018-01-01 00:10:00-0.0-0.0-0.0-0.0-0.0-0.0-0.0
2018-01-01 00:15:00-0.0-0.0-0.0-0.0-0.0-0.0-0.0
2018-01-01 00:20:00-0.0-0.0-0.0-0.0-0.0-0.0-0.0
2018-01-01 00:25:00-0.0-0.0-0.0-0.0-0.0-0.0-0.0
2018-01-01 00:30:00-0.0-0.0-0.0-0.0-0.0-0.0-0.0
\n", 1570 | "
" 1571 | ], 1572 | "text/plain": [ 1573 | " Stock P60 P70 P80 C60 C70 C80\n", 1574 | "Timestamp \n", 1575 | "2018-01-01 00:10:00 -0.0 -0.0 -0.0 -0.0 -0.0 -0.0 -0.0\n", 1576 | "2018-01-01 00:15:00 -0.0 -0.0 -0.0 -0.0 -0.0 -0.0 -0.0\n", 1577 | "2018-01-01 00:20:00 -0.0 -0.0 -0.0 -0.0 -0.0 -0.0 -0.0\n", 1578 | "2018-01-01 00:25:00 -0.0 -0.0 -0.0 -0.0 -0.0 -0.0 -0.0\n", 1579 | "2018-01-01 00:30:00 -0.0 -0.0 -0.0 -0.0 -0.0 -0.0 -0.0" 1580 | ] 1581 | }, 1582 | "metadata": {}, 1583 | "output_type": "display_data" 1584 | }, 1585 | { 1586 | "name": "stdout", 1587 | "output_type": "stream", 1588 | "text": [ 1589 | "The total Cashflow is: € 68985.54\n" 1590 | ] 1591 | } 1592 | ], 1593 | "source": [ 1594 | "# Create Dataframe with index market_data (timestamp)\n", 1595 | "cashflow_dataframe = pd.DataFrame(index=market_data.index[1:])\n", 1596 | "\n", 1597 | "# Forloop on all instruments (including stock) to calculate PnL\n", 1598 | "for instrument in instrument_names:\n", 1599 | "\n", 1600 | " Instrument_AskPrice = market_data[instrument, 'AskPrice'][1:]\n", 1601 | " Instrument_BidPrice = market_data[instrument, 'BidPrice'][1:]\n", 1602 | "\n", 1603 | " cashflow_dataframe[instrument] = np.where(trades_diff[instrument] >= 0,\n", 1604 | " trades_diff[instrument] * -\n", 1605 | " Instrument_AskPrice,\n", 1606 | " trades_diff[instrument] * -Instrument_BidPrice)\n", 1607 | "\n", 1608 | "# Show DataFrame & PnL\n", 1609 | "display(cashflow_dataframe.head())\n", 1610 | "total_cashflow = cashflow_dataframe.sum().sum()\n", 1611 | "\n", 1612 | "print('The total Cashflow is: €', round(total_cashflow, 2))" 1613 | ] 1614 | }, 1615 | { 1616 | "cell_type": "code", 1617 | "execution_count": 16, 1618 | "metadata": {}, 1619 | "outputs": [ 1620 | { 1621 | "data": { 1622 | "text/html": [ 1623 | "
\n", 1624 | "\n", 1637 | "\n", 1638 | " \n", 1639 | " \n", 1640 | " \n", 1641 | " \n", 1642 | " \n", 1643 | " \n", 1644 | " \n", 1645 | " \n", 1646 | " \n", 1647 | " \n", 1648 | " \n", 1649 | " \n", 1650 | " \n", 1651 | " \n", 1652 | " \n", 1653 | " \n", 1654 | " \n", 1655 | " \n", 1656 | " \n", 1657 | " \n", 1658 | " \n", 1659 | " \n", 1660 | " \n", 1661 | " \n", 1662 | " \n", 1663 | " \n", 1664 | " \n", 1665 | " \n", 1666 | " \n", 1667 | " \n", 1668 | " \n", 1669 | " \n", 1670 | " \n", 1671 | " \n", 1672 | " \n", 1673 | " \n", 1674 | " \n", 1675 | " \n", 1676 | " \n", 1677 | " \n", 1678 | " \n", 1679 | " \n", 1680 | " \n", 1681 | " \n", 1682 | " \n", 1683 | " \n", 1684 | " \n", 1685 | " \n", 1686 | " \n", 1687 | " \n", 1688 | " \n", 1689 | " \n", 1690 | " \n", 1691 | " \n", 1692 | " \n", 1693 | " \n", 1694 | " \n", 1695 | " \n", 1696 | " \n", 1697 | " \n", 1698 | " \n", 1699 | " \n", 1700 | " \n", 1701 | " \n", 1702 | " \n", 1703 | " \n", 1704 | " \n", 1705 | " \n", 1706 | " \n", 1707 | " \n", 1708 | " \n", 1709 | " \n", 1710 | " \n", 1711 | " \n", 1712 | "
StockP60P70P80C60C70C80
Timestamp
2018-02-28 23:35:0071187.00-116.36762.034326.29-3141.69-3354.85-525.08
2018-02-28 23:40:0071111.05-116.36762.034326.29-3141.69-3354.85-525.08
2018-02-28 23:45:0071111.05-116.36762.034326.29-3141.69-3354.85-525.08
2018-02-28 23:50:0071111.05-116.36762.034326.29-3141.69-3354.85-525.08
2018-02-28 23:55:0071035.20-116.36762.034326.29-3141.69-3354.85-525.08
\n", 1713 | "
" 1714 | ], 1715 | "text/plain": [ 1716 | " Stock P60 P70 P80 C60 C70 \\\n", 1717 | "Timestamp \n", 1718 | "2018-02-28 23:35:00 71187.00 -116.36 762.03 4326.29 -3141.69 -3354.85 \n", 1719 | "2018-02-28 23:40:00 71111.05 -116.36 762.03 4326.29 -3141.69 -3354.85 \n", 1720 | "2018-02-28 23:45:00 71111.05 -116.36 762.03 4326.29 -3141.69 -3354.85 \n", 1721 | "2018-02-28 23:50:00 71111.05 -116.36 762.03 4326.29 -3141.69 -3354.85 \n", 1722 | "2018-02-28 23:55:00 71035.20 -116.36 762.03 4326.29 -3141.69 -3354.85 \n", 1723 | "\n", 1724 | " C80 \n", 1725 | "Timestamp \n", 1726 | "2018-02-28 23:35:00 -525.08 \n", 1727 | "2018-02-28 23:40:00 -525.08 \n", 1728 | "2018-02-28 23:45:00 -525.08 \n", 1729 | "2018-02-28 23:50:00 -525.08 \n", 1730 | "2018-02-28 23:55:00 -525.08 " 1731 | ] 1732 | }, 1733 | "metadata": {}, 1734 | "output_type": "display_data" 1735 | }, 1736 | { 1737 | "name": "stdout", 1738 | "output_type": "stream", 1739 | "text": [ 1740 | "This number should match the above number: € 68985.54\n" 1741 | ] 1742 | } 1743 | ], 1744 | "source": [ 1745 | "# Cumulative Cashflows\n", 1746 | "cashflow_cumulative = {}\n", 1747 | "\n", 1748 | "for column in cashflow_dataframe.columns:\n", 1749 | "\n", 1750 | " cashflow_cumulative[column] = cashflow_dataframe[column].cumsum()\n", 1751 | "\n", 1752 | "cashflow_cumulative = pd.DataFrame(cashflow_cumulative)\n", 1753 | "\n", 1754 | "# Show Cumulative Cashflow\n", 1755 | "display(cashflow_cumulative.tail())\n", 1756 | "\n", 1757 | "# Checking for Match\n", 1758 | "print('This number should match the above number: €',\n", 1759 | " round(cashflow_cumulative[-1:].sum().sum(), 2))" 1760 | ] 1761 | }, 1762 | { 1763 | "cell_type": "code", 1764 | "execution_count": 17, 1765 | "metadata": { 1766 | "scrolled": true 1767 | }, 1768 | "outputs": [ 1769 | { 1770 | "data": { 1771 | "text/html": [ 1772 | "
\n", 1773 | "\n", 1786 | "\n", 1787 | " \n", 1788 | " \n", 1789 | " \n", 1790 | " \n", 1791 | " \n", 1792 | " \n", 1793 | " \n", 1794 | " \n", 1795 | " \n", 1796 | " \n", 1797 | " \n", 1798 | " \n", 1799 | " \n", 1800 | " \n", 1801 | " \n", 1802 | " \n", 1803 | " \n", 1804 | " \n", 1805 | " \n", 1806 | " \n", 1807 | " \n", 1808 | " \n", 1809 | " \n", 1810 | " \n", 1811 | " \n", 1812 | " \n", 1813 | " \n", 1814 | " \n", 1815 | " \n", 1816 | " \n", 1817 | " \n", 1818 | " \n", 1819 | " \n", 1820 | " \n", 1821 | " \n", 1822 | " \n", 1823 | " \n", 1824 | " \n", 1825 | " \n", 1826 | " \n", 1827 | " \n", 1828 | " \n", 1829 | " \n", 1830 | " \n", 1831 | " \n", 1832 | " \n", 1833 | " \n", 1834 | " \n", 1835 | " \n", 1836 | " \n", 1837 | " \n", 1838 | " \n", 1839 | " \n", 1840 | " \n", 1841 | " \n", 1842 | " \n", 1843 | " \n", 1844 | " \n", 1845 | " \n", 1846 | " \n", 1847 | " \n", 1848 | " \n", 1849 | " \n", 1850 | " \n", 1851 | " \n", 1852 | " \n", 1853 | " \n", 1854 | " \n", 1855 | " \n", 1856 | " \n", 1857 | " \n", 1858 | " \n", 1859 | " \n", 1860 | " \n", 1861 | "
StockP60P70P80C60C70C80
Timestamp
2018-02-28 23:35:00-76329.7546.5593.24-3238.24971.504500.72986.70
2018-02-28 23:40:00-76253.8045.6091.80-3234.04974.554516.68975.26
2018-02-28 23:45:00-76253.8046.5590.36-3246.64962.354495.40978.12
2018-02-28 23:50:00-76253.8043.7090.00-3234.04974.554532.64989.56
2018-02-28 23:55:00-76077.5544.6590.36-3280.24944.054500.72975.26
\n", 1862 | "
" 1863 | ], 1864 | "text/plain": [ 1865 | " Stock P60 P70 P80 C60 C70 C80\n", 1866 | "Timestamp \n", 1867 | "2018-02-28 23:35:00 -76329.75 46.55 93.24 -3238.2 4971.50 4500.72 986.70\n", 1868 | "2018-02-28 23:40:00 -76253.80 45.60 91.80 -3234.0 4974.55 4516.68 975.26\n", 1869 | "2018-02-28 23:45:00 -76253.80 46.55 90.36 -3246.6 4962.35 4495.40 978.12\n", 1870 | "2018-02-28 23:50:00 -76253.80 43.70 90.00 -3234.0 4974.55 4532.64 989.56\n", 1871 | "2018-02-28 23:55:00 -76077.55 44.65 90.36 -3280.2 4944.05 4500.72 975.26" 1872 | ] 1873 | }, 1874 | "metadata": {}, 1875 | "output_type": "display_data" 1876 | }, 1877 | { 1878 | "name": "stdout", 1879 | "output_type": "stream", 1880 | "text": [ 1881 | "Total valuation of our Position is currently: € -68802.71\n" 1882 | ] 1883 | } 1884 | ], 1885 | "source": [ 1886 | "# Create a new dataframe of trades with most columns dropped\n", 1887 | "trades_minimal = trades.drop(['Call Delta', 'Put Delta', 'Time to Expiry', 'Total Option Delta',\n", 1888 | " 'Remaining Option Delta'], axis=1)\n", 1889 | "\n", 1890 | "trades_minimal.columns = trades_minimal.columns.droplevel(level=0)\n", 1891 | "\n", 1892 | "# Create Dataframe with market_data as index\n", 1893 | "valuation_dataframe = pd.DataFrame(index=market_data.index)\n", 1894 | "\n", 1895 | "# Forloop to calculate valuations on every timestamp\n", 1896 | "for instrument in instrument_names:\n", 1897 | "\n", 1898 | " if 'C' in instrument:\n", 1899 | "\n", 1900 | " Instrument_AskPrice = market_data[instrument, 'AskPrice']\n", 1901 | " Instrument_BidPrice = market_data[instrument, 'BidPrice']\n", 1902 | "\n", 1903 | " valuation_dataframe[instrument] = np.where(trades_minimal[instrument] > 0,\n", 1904 | " trades_minimal[instrument] *\n", 1905 | " Instrument_BidPrice,\n", 1906 | " trades_minimal[instrument] * Instrument_AskPrice)\n", 1907 | "\n", 1908 | " if 'P' in instrument:\n", 1909 | "\n", 1910 | " Instrument_AskPrice = market_data[instrument, 'AskPrice']\n", 1911 | " Instrument_BidPrice = market_data[instrument, 'BidPrice']\n", 1912 | "\n", 1913 | " valuation_dataframe[instrument] = np.where(trades_minimal[instrument] > 0,\n", 1914 | " trades_minimal[instrument] *\n", 1915 | " Instrument_BidPrice,\n", 1916 | " trades_minimal[instrument] * Instrument_AskPrice)\n", 1917 | "\n", 1918 | " if 'S' in instrument:\n", 1919 | "\n", 1920 | " Instrument_AskPrice = market_data[instrument, 'AskPrice']\n", 1921 | " Instrument_BidPrice = market_data[instrument, 'BidPrice']\n", 1922 | "\n", 1923 | " valuation_dataframe[instrument] = np.where(trades_minimal[instrument] > 0,\n", 1924 | " trades_minimal[instrument] *\n", 1925 | " Instrument_BidPrice,\n", 1926 | " trades_minimal[instrument] * Instrument_AskPrice)\n", 1927 | "\n", 1928 | "# Show DataFrame & Calculate total valuation\n", 1929 | "display(valuation_dataframe.tail())\n", 1930 | "total_valuation = valuation_dataframe[-1:].sum().sum()\n", 1931 | "\n", 1932 | "print(\"Total valuation of our Position is currently: €\", round(total_valuation, 2))" 1933 | ] 1934 | }, 1935 | { 1936 | "cell_type": "code", 1937 | "execution_count": 18, 1938 | "metadata": {}, 1939 | "outputs": [ 1940 | { 1941 | "name": "stdout", 1942 | "output_type": "stream", 1943 | "text": [ 1944 | "The total profit from Black Scholes is: € 547.08\n" 1945 | ] 1946 | } 1947 | ], 1948 | "source": [ 1949 | "# Create Empty DataFrame\n", 1950 | "blackscholes_dataframe = {}\n", 1951 | "\n", 1952 | "# Create Columns based on Option Names\n", 1953 | "for option in option_names:\n", 1954 | "\n", 1955 | " if 'C' in option:\n", 1956 | " blackscholes_dataframe[option] = []\n", 1957 | "\n", 1958 | " if 'P' in option:\n", 1959 | " blackscholes_dataframe[option] = []\n", 1960 | "\n", 1961 | "# Forloop that calculates the margins and thus profits\n", 1962 | "for time, data in market_data.iterrows():\n", 1963 | " \n", 1964 | " for option in option_names:\n", 1965 | "\n", 1966 | " if \"C\" in option:\n", 1967 | " margin1 = data[option, 'BidPrice'] - data[option, 'Expected AskPrice']\n", 1968 | " margin2 = data[option, 'Expected BidPrice'] - data[option, 'AskPrice']\n", 1969 | "\n", 1970 | " if margin1 > 0.10:\n", 1971 | " blackscholes_dataframe[option].append(margin1)\n", 1972 | "\n", 1973 | " elif margin2 > 0.10:\n", 1974 | " blackscholes_dataframe[option].append(margin2)\n", 1975 | "\n", 1976 | " else:\n", 1977 | " blackscholes_dataframe[option].append(0)\n", 1978 | "\n", 1979 | " elif \"P\" in option:\n", 1980 | " margin1 = data[option, 'BidPrice'] - data[option, 'Expected AskPrice']\n", 1981 | " margin2 = data[option, 'Expected BidPrice'] - data[option, 'AskPrice']\n", 1982 | "\n", 1983 | " if margin1 > 0.10:\n", 1984 | " blackscholes_dataframe[option].append(margin1)\n", 1985 | "\n", 1986 | " elif margin2 > 0.10:\n", 1987 | " blackscholes_dataframe[option].append(margin2)\n", 1988 | "\n", 1989 | " else:\n", 1990 | " blackscholes_dataframe[option].append(0)\n", 1991 | "\n", 1992 | "# Create DataFrame with index of market_data\n", 1993 | "blackscholes_dataframe = pd.DataFrame(blackscholes_dataframe, index=market_data.index)\n", 1994 | "\n", 1995 | "# Calculate Black_Scholes Profit\n", 1996 | "total_blackscholes = (abs(trades_diff).drop('Stock', axis=1) * blackscholes_dataframe).sum().sum()\n", 1997 | "print('The total profit from Black Scholes is: €',round(total_blackscholes,2))" 1998 | ] 1999 | }, 2000 | { 2001 | "cell_type": "code", 2002 | "execution_count": 19, 2003 | "metadata": {}, 2004 | "outputs": [ 2005 | { 2006 | "data": { 2007 | "text/html": [ 2008 | "
\n", 2009 | "\n", 2022 | "\n", 2023 | " \n", 2024 | " \n", 2025 | " \n", 2026 | " \n", 2027 | " \n", 2028 | " \n", 2029 | " \n", 2030 | " \n", 2031 | " \n", 2032 | " \n", 2033 | " \n", 2034 | " \n", 2035 | " \n", 2036 | " \n", 2037 | " \n", 2038 | " \n", 2039 | " \n", 2040 | " \n", 2041 | " \n", 2042 | " \n", 2043 | " \n", 2044 | " \n", 2045 | " \n", 2046 | " \n", 2047 | " \n", 2048 | " \n", 2049 | " \n", 2050 | " \n", 2051 | " \n", 2052 | " \n", 2053 | " \n", 2054 | " \n", 2055 | " \n", 2056 | " \n", 2057 | " \n", 2058 | " \n", 2059 | " \n", 2060 | " \n", 2061 | " \n", 2062 | " \n", 2063 | " \n", 2064 | " \n", 2065 | " \n", 2066 | " \n", 2067 | " \n", 2068 | " \n", 2069 | " \n", 2070 | " \n", 2071 | " \n", 2072 | " \n", 2073 | " \n", 2074 | " \n", 2075 | " \n", 2076 | " \n", 2077 | " \n", 2078 | " \n", 2079 | " \n", 2080 | " \n", 2081 | " \n", 2082 | " \n", 2083 | " \n", 2084 | " \n", 2085 | " \n", 2086 | " \n", 2087 | " \n", 2088 | " \n", 2089 | " \n", 2090 | "
C60C70C80P60P70P80
Timestamp
2018-02-28 23:35:0061.9577.2988.9814.33135.36169.17
2018-02-28 23:40:0061.9577.2988.9814.33135.36169.17
2018-02-28 23:45:0061.9577.2988.9814.33135.36169.17
2018-02-28 23:50:0061.9577.2988.9814.33135.36169.17
2018-02-28 23:55:0061.9577.2988.9814.33135.36169.17
\n", 2091 | "
" 2092 | ], 2093 | "text/plain": [ 2094 | " C60 C70 C80 P60 P70 P80\n", 2095 | "Timestamp \n", 2096 | "2018-02-28 23:35:00 61.95 77.29 88.98 14.33 135.36 169.17\n", 2097 | "2018-02-28 23:40:00 61.95 77.29 88.98 14.33 135.36 169.17\n", 2098 | "2018-02-28 23:45:00 61.95 77.29 88.98 14.33 135.36 169.17\n", 2099 | "2018-02-28 23:50:00 61.95 77.29 88.98 14.33 135.36 169.17\n", 2100 | "2018-02-28 23:55:00 61.95 77.29 88.98 14.33 135.36 169.17" 2101 | ] 2102 | }, 2103 | "execution_count": 19, 2104 | "metadata": {}, 2105 | "output_type": "execute_result" 2106 | } 2107 | ], 2108 | "source": [ 2109 | "# Blackscholes Dataframe times Volumes\n", 2110 | "blackscholes_dataframe = pd.DataFrame(\n", 2111 | " abs(trades_diff).drop('Stock', axis=1) * blackscholes_dataframe)\n", 2112 | "\n", 2113 | "# Cumulative Blackscholes\n", 2114 | "blackscholes_cumulative = {}\n", 2115 | "\n", 2116 | "for column in blackscholes_dataframe.columns:\n", 2117 | "\n", 2118 | " blackscholes_cumulative[column] = blackscholes_dataframe[column].cumsum()\n", 2119 | "\n", 2120 | "blackscholes_cumulative = pd.DataFrame(blackscholes_cumulative)\n", 2121 | "\n", 2122 | "# Show Dataframe\n", 2123 | "blackscholes_cumulative.tail()" 2124 | ] 2125 | }, 2126 | { 2127 | "cell_type": "code", 2128 | "execution_count": 20, 2129 | "metadata": {}, 2130 | "outputs": [ 2131 | { 2132 | "name": "stdout", 2133 | "output_type": "stream", 2134 | "text": [ 2135 | "The total profit generated from the Option Arbitrage strategy is: € 729.91\n" 2136 | ] 2137 | } 2138 | ], 2139 | "source": [ 2140 | "# Last PnL in the Table should match the below value\n", 2141 | "print('The total profit generated from the Option Arbitrage strategy is: €',\n", 2142 | " round(total_cashflow + total_valuation + total_blackscholes, 2))" 2143 | ] 2144 | } 2145 | ], 2146 | "metadata": { 2147 | "kernelspec": { 2148 | "display_name": "Python 3", 2149 | "language": "python", 2150 | "name": "python3" 2151 | }, 2152 | "language_info": { 2153 | "codemirror_mode": { 2154 | "name": "ipython", 2155 | "version": 3 2156 | }, 2157 | "file_extension": ".py", 2158 | "mimetype": "text/x-python", 2159 | "name": "python", 2160 | "nbconvert_exporter": "python", 2161 | "pygments_lexer": "ipython3", 2162 | "version": "3.7.1" 2163 | }, 2164 | "latex_envs": { 2165 | "LaTeX_envs_menu_present": true, 2166 | "autoclose": false, 2167 | "autocomplete": true, 2168 | "bibliofile": "biblio.bib", 2169 | "cite_by": "apalike", 2170 | "current_citInitial": 1, 2171 | "eqLabelWithNumbers": true, 2172 | "eqNumInitial": 1, 2173 | "hotkeys": { 2174 | "equation": "Ctrl-E", 2175 | "itemize": "Ctrl-I" 2176 | }, 2177 | "labels_anchors": false, 2178 | "latex_user_defs": false, 2179 | "report_style_numbering": false, 2180 | "user_envs_cfg": false 2181 | }, 2182 | "toc": { 2183 | "base_numbering": 1, 2184 | "nav_menu": {}, 2185 | "number_sections": true, 2186 | "sideBar": true, 2187 | "skip_h1_title": false, 2188 | "title_cell": "Table of Contents", 2189 | "title_sidebar": "Contents", 2190 | "toc_cell": false, 2191 | "toc_position": {}, 2192 | "toc_section_display": true, 2193 | "toc_window_display": false 2194 | }, 2195 | "varInspector": { 2196 | "cols": { 2197 | "lenName": 16, 2198 | "lenType": 16, 2199 | "lenVar": 40 2200 | }, 2201 | "kernels_config": { 2202 | "python": { 2203 | "delete_cmd_postfix": "", 2204 | "delete_cmd_prefix": "del ", 2205 | "library": "var_list.py", 2206 | "varRefreshCmd": "print(var_dic_list())" 2207 | }, 2208 | "r": { 2209 | "delete_cmd_postfix": ") ", 2210 | "delete_cmd_prefix": "rm(", 2211 | "library": "var_list.r", 2212 | "varRefreshCmd": "cat(var_dic_list()) " 2213 | } 2214 | }, 2215 | "types_to_exclude": [ 2216 | "module", 2217 | "function", 2218 | "builtin_function_or_method", 2219 | "instance", 2220 | "_Feature" 2221 | ], 2222 | "window_display": false 2223 | } 2224 | }, 2225 | "nbformat": 4, 2226 | "nbformat_minor": 2 2227 | } 2228 | -------------------------------------------------------------------------------- /Options Arbitrage/README.md: -------------------------------------------------------------------------------- 1 | # Option Arbitrage 2 | This project includes the Notebook, which entails identifying arbitrage opportunities and acting on them, 3 | a seperate Python file used to perform the Black Scholes calculations and a datafile. 4 | 5 | The options and stocks can be mispriced relative to each other (Black Scholes), and if you trade on this 6 | arbitrage correctly there is (small) margin to be made. Arbitrage options trading is a market-neutral strategy 7 | that seeks to neutralize certain market risks by taking offsetting long and short related securities. 8 | -------------------------------------------------------------------------------- /Options Arbitrage/black_scholes.py: -------------------------------------------------------------------------------- 1 | from scipy import stats 2 | import numpy as np 3 | 4 | _norm_cdf = stats.norm(0, 1).cdf 5 | _norm_pdf = stats.norm(0, 1).pdf 6 | 7 | def _d1(S, K, T, r, sigma): 8 | return (np.log(S / K) + (r + 0.5 * sigma ** 2) * T) / (sigma * np.sqrt(T)) 9 | 10 | 11 | def _d2(S, K, T, r, sigma): 12 | return _d1(S, K, T, r, sigma) - sigma * np.sqrt(T) 13 | 14 | 15 | def call_value(S, K, T, r, sigma): 16 | ''' 17 | The fair value of a call option paying max(S-K, 0) at expiry, under the Black-scholes model, 18 | for an option with strike , expiring in years, under a fixed interest rate , 19 | a stock volatility , and when the current price of the underlying stock is . 20 | 21 | Parameters 22 | ---------- 23 | S : float 24 | The current value of the underlying stock. 25 | 26 | K : float 27 | The strike price of the option. 28 | 29 | T : float 30 | Time to expiry in years. 31 | 32 | r : float 33 | The fixed interest rate valid between now and expiry. 34 | 35 | sigma : float 36 | The volatility of the underlying stock process. 37 | 38 | Returns 39 | ------- 40 | call_value : float 41 | The fair present value of the option. 42 | 43 | ''' 44 | 45 | return S * _norm_cdf(_d1(S, K, T, r, sigma)) - K * np.exp(-r * T) * _norm_cdf(_d2(S, K, T, r, sigma)) 46 | 47 | 48 | def put_value(S, K, T, r, sigma): 49 | ''' 50 | The fair value of a put option paying max(K-S, 0) at expiry, under the Black-scholes model, 51 | for an option with strike , expiring in years, under a fixed interest rate , 52 | a stock volatility , and when the current price of the underlying stock is . 53 | 54 | Parameters 55 | ---------- 56 | S : float 57 | The value of the underlying stock. 58 | 59 | K : float 60 | The strike price of the option. 61 | 62 | T : float 63 | Time to expiry in years. 64 | 65 | r : float 66 | The fixed interest rate valid between now and expiry. 67 | 68 | sigma : float 69 | The volatility of the underlying stock process. 70 | 71 | Returns 72 | ------- 73 | put_value : float 74 | The fair present value of the option. 75 | ''' 76 | 77 | return np.exp(-r * T) * K * _norm_cdf(-_d2(S, K, T, r, sigma)) - S * _norm_cdf(-_d1(S, K, T, r, sigma)) 78 | 79 | 80 | def call_delta(S, K, T, r, sigma): 81 | ''' 82 | The delta, i.e. the first derivative of the option value with respect to the underlying, 83 | of a call option paying max(S-K, 0) at expiry, under the Black-scholes model, for an option 84 | with strike , expiring in years, under a fixed interest rate , a stock 85 | volatility , and when the current price of the underlying stock is . 86 | 87 | Parameters 88 | ---------- 89 | S : float 90 | The value of the underlying stock. 91 | 92 | K : float 93 | The strike price of the option. 94 | 95 | T : float 96 | Time to expiry in years. 97 | 98 | r : float 99 | The fixed interest rate valid between now and expiry. 100 | 101 | sigma : float 102 | The volatility of the underlying stock process. 103 | 104 | Returns 105 | ------- 106 | call_delta : float 107 | The fair present value of the option. 108 | ''' 109 | 110 | return _norm_cdf(_d1(S, K, T, r, sigma)) 111 | 112 | 113 | def put_delta(S, K, T, r, sigma): 114 | ''' 115 | The delta, i.e. the first derivative of the option value with respect to the underlying, 116 | of a put option paying max(K-S, 0) at expiry, under the Black-scholes model, for an option 117 | with strike , expiring in years, under a fixed interest rate , a stock 118 | volatility , and when the current price of the underlying stock is . 119 | 120 | Parameters 121 | ---------- 122 | S : float 123 | The value of the underlying stock. 124 | 125 | K : float 126 | The strike price of the option. 127 | 128 | T : float 129 | Time to expiry in years. 130 | 131 | r : float 132 | The fixed interest rate valid between now and expiry. 133 | 134 | sigma : float 135 | The volatility of the underlying stock process. 136 | 137 | Returns 138 | ------- 139 | put_delta : float 140 | The fair present value of the option. 141 | ''' 142 | 143 | return call_delta(S, K, T, r, sigma) - 1 144 | 145 | 146 | def call_vega(S, K, T, r, sigma): 147 | ''' 148 | The vega, i.e. the derivative of the option value with respect to the volatility, 149 | of a call option paying max(S-K, 0) at expiry, under the Black-scholes model, for an option 150 | with strike , expiring in years, under a fixed interest rate , a stock 151 | volatility , and when the current price of the underlying stock is . 152 | 153 | Parameters 154 | ---------- 155 | S : float 156 | The value of the underlying stock. 157 | 158 | K : float 159 | The strike price of the option. 160 | 161 | T : float 162 | Time to expiry in years. 163 | 164 | r : float 165 | The fixed interest rate valid between now and expiry. 166 | 167 | sigma : float 168 | The volatility of the underlying stock process. 169 | 170 | Returns 171 | ------- 172 | call_delta : float 173 | The fair present value of the option. 174 | ''' 175 | 176 | return S * _norm_pdf(_d1(S, K, T, r, sigma)) * np.sqrt(T) 177 | 178 | 179 | def put_vega(S, K, T, r, sigma): 180 | ''' 181 | The vega, i.e. the derivative of the option value with respect to the volatility, 182 | of a put option paying max(K-S, 0) at expiry, under the Black-scholes model, for an option 183 | with strike , expiring in years, under a fixed interest rate , a stock 184 | volatility , and when the current price of the underlying stock is . 185 | 186 | Parameters 187 | ---------- 188 | S : float 189 | The value of the underlying stock. 190 | 191 | K : float 192 | The strike price of the option. 193 | 194 | T : float 195 | Time to expiry in years. 196 | 197 | r : float 198 | The fixed interest rate valid between now and expiry. 199 | 200 | sigma : float 201 | The volatility of the underlying stock process. 202 | 203 | Returns 204 | ------- 205 | call_delta : float 206 | The fair present value of the option. 207 | ''' 208 | 209 | return call_vega(S, K, T, r, sigma) -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Algorithmic Trading 2 | This repository contains three ways to obtain arbitrage: 3 | - Dual Listing Arbitrage 4 | - Options Arbitrage 5 | - Statistical Arbitrage 6 | 7 | These are projects in collaboration with [Optiver](https://www.optiver.com/) and have been peer-reviewed by staff 8 | members of Optiver. Therefore, much of the analysis are correct and give an indication how these methods work. 9 | 10 | Please note that these methods can only be effective when written in C++ as speed is of utmost performance. Next to 11 | that it requires a lightning fast connection (talking in nanoseconds) which is not feasible for the retail investor. 12 | Any profits made by using these strategies is therefore by pure chance. 13 | 14 | I suggest using my [FinanceDatabase](https://github.com/JerBouma/FinanceDatabase) to do fundamental analysis and base 15 | your investment decisions on that since it has proven to be profitable. -------------------------------------------------------------------------------- /Statistical Arbitrage/README.md: -------------------------------------------------------------------------------- 1 | # Statistical Arbitrage 2 | This repository includes the Notebook, which entails the data analysis and algorithm(s), a seperate python 3 | file that is used to do the Engle-Granger cointegration test and a datafile. 4 | 5 | The key to success in pairs trading lies in the identification of security pairs (Vidyamurthy, 2004). 6 | Therefore, first an analysis on the data provided is done in order to determine which pairs of stocks can 7 | be traded profitably. Thereafter, implement and backtest a trading strategy to trade the selected pairs. 8 | -------------------------------------------------------------------------------- /Statistical Arbitrage/cointegration_analysis.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | 3 | from statsmodels.api import OLS, add_constant 4 | from statsmodels.tsa.stattools import adfuller 5 | 6 | def estimate_long_run_short_run_relationships(y, x): 7 | """Estimates long-run and short-run cointegration relationship for series y and x. 8 | 9 | Uses a 2-step process to first estimate coefficients for the long-run relationship 10 | y_t = c + gamma * x_t + z_t 11 | 12 | and then the short-term relationship, 13 | y_t - y_(t-1) = alpha * z_(t-1) + epsilon_t, 14 | 15 | with z the found residuals of the first equation. 16 | 17 | Parameters 18 | ---------- 19 | y : pd.Series 20 | The first time series of the pair to analyse. 21 | 22 | x : pd.Series 23 | The second time series of the pair to analyse. 24 | 25 | Returns 26 | ------- 27 | c : float 28 | The constant term in the long-run relationship y_t = c + gamma * x_t + z_t. This 29 | describes the static shift of y with respect to gamma * x. 30 | 31 | gamma : float 32 | The gamma term in the long-run relationship y_t = c + gamma * x_t + z_t. This 33 | describes the ratio between the const-shifted y and x. 34 | 35 | alpha : float 36 | The alpha term in the short-run relationship y_t - y_(t-1) = alpha * z_(t-1) + epsilon. This 37 | gives an indication of the strength of the error correction toward the long-run mean. 38 | 39 | z : pd.Series 40 | Series of residuals z_t from the long-run relationship y_t = c + gamma * x_t + z_t, representing 41 | the value of the error correction term. 42 | 43 | """ 44 | 45 | assert isinstance(y, pd.Series), 'Input series y should be of type pd.Series' 46 | assert isinstance(x, pd.Series), 'Input series x should be of type pd.Series' 47 | assert sum(y.isnull()) == 0, 'Input series y has nan-values. Unhandled case.' 48 | assert sum(x.isnull()) == 0, 'Input series x has nan-values. Unhandled case.' 49 | assert y.index.equals(x.index), 'The two input series y and x do not have the same index.' 50 | 51 | long_run_ols = OLS(y, add_constant(x), has_const=True) 52 | long_run_ols_fit = long_run_ols.fit() 53 | 54 | c, gamma = long_run_ols_fit.params 55 | z = long_run_ols_fit.resid 56 | 57 | short_run_ols = OLS(y.diff().iloc[1:], (z.shift().iloc[1:])) 58 | short_run_ols_fit = short_run_ols.fit() 59 | 60 | alpha = short_run_ols_fit.params[0] 61 | 62 | return c, gamma, alpha, z 63 | 64 | 65 | def engle_granger_two_step_cointegration_test(y, x): 66 | """Applies the two-step Engle & Granger test for cointegration. 67 | 68 | First fits the long-run relationship 69 | y_t = c + gamma * x_t + z_t 70 | 71 | and then tests, by Dickey-Fuller phi=1 vs phi < 1 in 72 | z_t = phi * z_(t-1) + eta_t 73 | 74 | If this implies phi < 1, the z series is stationary is concluded to be 75 | stationary, and thus the series y and x are concluded to be cointegrated. 76 | 77 | Parameters 78 | ---------- 79 | y : pd.Series 80 | the first time series of the pair to analyse 81 | 82 | x : pd.Series 83 | the second time series of the pair to analyse 84 | 85 | Returns 86 | ------- 87 | dfstat : float 88 | The Dickey Fuller test-statistic for phi = 1 vs phi < 1 in the second equation. A more 89 | negative value implies the existence of stronger cointegration. 90 | 91 | pvalue : float 92 | The p-value corresponding to the Dickey Fuller test-statistic. A lower value implies 93 | stronger rejection of no-cointegration, thus stronger evidence of cointegration. 94 | 95 | """ 96 | 97 | assert isinstance(y, pd.Series), 'Input series y should be of type pd.Series' 98 | assert isinstance(x, pd.Series), 'Input series x should be of type pd.Series' 99 | assert sum(y.isnull()) == 0, 'Input series y has nan-values. Unhandled case.' 100 | assert sum(x.isnull()) == 0, 'Input series x has nan-values. Unhandled case.' 101 | assert y.index.equals(x.index), 'The two input series y and x do not have the same index.' 102 | 103 | c, gamma, alpha, z = estimate_long_run_short_run_relationships(y, x) 104 | 105 | # NOTE: The p-value returned by the adfuller function assumes we do not estimate z first, but test 106 | # stationarity of an unestimated series directly. This assumption should have limited effect for high N, 107 | # so for the purposes of this course this p-value can be used for the EG-test. Critical values taking 108 | # this into account more accurately are provided in e.g. McKinnon (1990) and Engle & Yoo (1987). 109 | 110 | adfstat, pvalue, usedlag, nobs, crit_values = adfuller(z, maxlag=1, autolag=None) 111 | 112 | return adfstat, pvalue 113 | 114 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | scipy==1.10.0 2 | pandas==1.1.2 3 | numpy==1.22.0 4 | statsmodels==0.12.1 5 | --------------------------------------------------------------------------------