QuantConnect Logo


Optiver lecture: Trading options as a market makerΒΆ

Theory application by Nicola MenardoΒΆ

2nd Dec 2021

I found the lacture by Robbert Puller very interesting as I already had a good grasp of how options worked and the Black-Sholes formula coming in to the lecture but learning about implied volatility skew was very interesting to me.

I wanted to explore this concept further and by visualising it and by writing an algorithms that hedges the option exposure the way it was explained in the lecture.

The analysis consists out of 5 steps:

  1. Retrieving and formatting data
  2. Calculating implied volatility
  3. Visualizing volatility skew and its movements
  4. Calculating the hedge to be market neutral
  5. Visualizing the position taking for hedging

Step 1: Retrieving and formatting dataΒΆ

In [1]:
In [2]:

Let's have a look at the data:ΒΆ

In [3]:
Out[3]:
askclose askhigh asklow askopen asksize bidclose bidhigh bidlow bidopen bidsize close high low open volume
expiry strike type symbol time
2021-12-17 00:00:00 465.0 Call SPY XUERCYF3527A|SPY R735QTJ8XC9X 2021-11-30 09:31:00 5.71 6.50 5.55 5.81 76.0 5.67 5.78 5.00 5.75 25.0 5.62 5.81 5.59 5.78 59.0
2021-11-30 09:32:00 5.66 5.76 5.65 5.71 156.0 5.62 5.73 5.61 5.67 74.0 5.69 5.71 5.68 5.70 41.0
2021-11-30 09:33:00 5.95 5.96 5.64 5.66 20.0 5.90 5.91 5.60 5.62 42.0 5.65 5.66 5.65 5.66 3.0
2021-11-30 09:34:00 5.87 6.00 5.79 5.95 150.0 5.85 5.95 5.75 5.90 15.0 5.82 5.92 5.80 5.89 17.0
2021-11-30 09:35:00 5.84 5.91 5.74 5.87 150.0 5.82 5.88 5.69 5.85 15.0 5.75 5.88 5.75 5.86 18.0
In [4]:
Option expiries in this dataset: 
 [datetime.datetime(2021, 12, 17, 0, 0)]

Selected strike prices: 
 [465.0, 464.0, 455.0, 454.0, 458.0, 457.0, 456.0, 468.0, 459.0, 466.0, 469.0, 467.0, 463.0, 462.0, 472.0, 453.0, 473.0, 471.0, 470.0, 460.0, 461.0, 474.0, 475.0]

Row indexers: 
 ['expiry', 'strike', 'type', 'symbol', 'time']

Columns: 
 Index(['askclose', 'askhigh', 'asklow', 'askopen', 'asksize', 'bidclose',
       'bidhigh', 'bidlow', 'bidopen', 'bidsize', 'close', 'high', 'low',
       'open', 'volume'],
      dtype='object')

For this example I'm only interested in the bid-ask option data of the call options. We also know that we have only one option expiry set which allows us to drop some indexers. Let's regoranize the DataFrame to simplify things.ΒΆ

In [5]:
Out[5]:
strike 453.0 454.0 455.0 456.0 457.0 458.0 459.0 460.0 461.0 462.0 ... 466.0 467.0 468.0 469.0 470.0 471.0 472.0 473.0 474.0 475.0
time
2021-11-30 09:31:00 13.98 13.21 12.43 11.70 10.97 10.25 9.55 8.86 8.18 7.52 ... 5.11 4.56 4.05 3.57 3.13 2.72 2.35 2.01 1.71 1.45
2021-11-30 09:32:00 13.90 13.14 12.38 11.64 10.91 10.19 9.48 8.81 8.13 7.48 ... 5.06 4.51 4.01 3.53 3.09 2.69 2.32 1.99 1.69 1.43
2021-11-30 09:33:00 14.37 13.59 12.82 12.07 11.32 10.58 9.86 9.17 8.48 7.80 ... 5.31 4.75 4.22 3.73 3.27 2.84 2.45 2.09 1.79 1.51
2021-11-30 09:34:00 14.27 13.49 12.72 11.97 11.23 10.50 9.78 9.08 8.41 7.74 ... 5.26 4.71 4.18 3.69 3.23 2.81 2.43 2.08 1.76 1.49
2021-11-30 09:35:00 14.22 13.46 12.68 11.90 11.17 10.46 9.71 9.05 8.37 7.69 ... 5.24 4.68 4.16 3.67 3.22 2.80 2.41 2.07 1.76 1.48

5 rows Γ— 23 columns

In [6]:
Out[6]:
bidclose askclose
time
2021-11-30 09:31:00 461.85 461.86
2021-11-30 09:32:00 461.79 461.80
2021-11-30 09:33:00 462.68 462.70
2021-11-30 09:34:00 462.54 462.55
2021-11-30 09:35:00 462.41 462.43

Step 2: Calculating implied volatilityΒΆ

S = spot price of the underlying asset

X = strike price

t = time to expiration

r = interest rate

Οƒ = implied volatility

Source: https://www.sciencedirect.com/science/article/abs/pii/0378426695000143

Let's start with calculating the implied volatility for one strike:ΒΆ

In [7]:
Out[7]:
C S X t r
time
2021-11-30 09:31:00 13.98 461.85 453.0 0.063492 0.21
2021-11-30 09:32:00 13.90 461.79 453.0 0.063492 0.21
2021-11-30 09:33:00 14.37 462.68 453.0 0.063492 0.21
2021-11-30 09:34:00 14.27 462.54 453.0 0.063492 0.21
2021-11-30 09:35:00 14.22 462.41 453.0 0.063492 0.21
In [8]:
Out[8]:
C S X t r IV
time
2021-11-30 09:31:00 13.98 461.85 453.0 0.063492 0.21 0.206182
2021-11-30 09:32:00 13.90 461.79 453.0 0.063492 0.21 0.205111
2021-11-30 09:33:00 14.37 462.68 453.0 0.063492 0.21 0.205295
2021-11-30 09:34:00 14.27 462.54 453.0 0.063492 0.21 0.204695
2021-11-30 09:35:00 14.22 462.41 453.0 0.063492 0.21 0.205076

Cool! Now we need to handle multiple strike pricesΒΆ

In [9]:
Out[9]:
strike 453.0 454.0 455.0 456.0 457.0 458.0 459.0 460.0 461.0 462.0 ... 466.0 467.0 468.0 469.0 470.0 471.0 472.0 473.0 474.0 475.0
time
2021-11-30 09:31:00 0.206182 0.200233 0.194087 0.189054 0.184041 0.179265 0.174942 0.170855 0.167002 0.163597 ... 0.155046 0.154051 0.153907 0.154399 0.155731 0.157688 0.160475 0.163878 0.168105 0.173151
2021-11-30 09:32:00 0.205111 0.199382 0.193674 0.188424 0.183412 0.178637 0.174099 0.170447 0.166594 0.163406 ... 0.154644 0.153650 0.153720 0.154211 0.155544 0.157711 0.160498 0.164112 0.168338 0.173384
2021-11-30 09:33:00 0.205295 0.199129 0.193204 0.187737 0.182290 0.177083 0.172331 0.168249 0.164185 0.160356 ... 0.150131 0.148936 0.148382 0.148676 0.149601 0.151152 0.153533 0.156532 0.160771 0.165411
2021-11-30 09:34:00 0.204695 0.198530 0.192605 0.187139 0.181911 0.176922 0.172171 0.167873 0.164242 0.160628 ... 0.150613 0.149629 0.149074 0.149367 0.150290 0.152049 0.154638 0.157844 0.161663 0.166510
2021-11-30 09:35:00 0.205076 0.199349 0.193204 0.187082 0.182073 0.177518 0.172115 0.168682 0.164834 0.161003 ... 0.151625 0.150426 0.150080 0.150370 0.151501 0.153257 0.155634 0.159047 0.163073 0.167709

5 rows Γ— 23 columns

Now we can visualize the evolution of the volatility skew over a 30-minute period!ΒΆ

In [10]:
In [11]:
In [12]:
/opt/miniconda3/lib/python3.6/site-packages/pandas/plotting/_matplotlib/converter.py:103: FutureWarning: Using an implicitly registered datetime converter for a matplotlib plotting method. The converter was registered by pandas on import. Future versions of pandas will require you to explicitly register matplotlib converters.

To register the converters:
	>>> from pandas.plotting import register_matplotlib_converters
	>>> register_matplotlib_converters()
  warnings.warn(msg, FutureWarning)
In [13]:

Step 4: Calculate the hedge to be market-neutralΒΆ

In [14]:
Out[14]:
C S X t r IV
time
2021-11-30 09:31:00 1.45 461.85 475.0 0.063492 0.21 0.173151
2021-11-30 09:32:00 1.43 461.79 475.0 0.063492 0.21 0.173384
2021-11-30 09:33:00 1.51 462.68 475.0 0.063492 0.21 0.165411
2021-11-30 09:34:00 1.49 462.54 475.0 0.063492 0.21 0.166510
2021-11-30 09:35:00 1.48 462.41 475.0 0.063492 0.21 0.167709

To know how much of the underlying we need to sell to hedge our position if we were to buy this option we need to look at the option's delta.ΒΆ

The delta is the rate of chnage of the option's price relative to the change in the underlying asset's price.ΒΆ

\Large

Note: If the underlying is not dividend-paying

In [15]:
In [16]:
Out[16]:
C S X t r IV delta
time
2021-11-30 09:31:00 1.45 461.85 475.0 0.063492 0.21 0.173151 0.375980
2021-11-30 09:32:00 1.43 461.79 475.0 0.063492 0.21 0.173384 0.375035
2021-11-30 09:33:00 1.51 462.68 475.0 0.063492 0.21 0.165411 0.385999
2021-11-30 09:34:00 1.49 462.54 475.0 0.063492 0.21 0.166510 0.384078
2021-11-30 09:35:00 1.48 462.41 475.0 0.063492 0.21 0.167709 0.382458

Step 5: Visualizing the position taking for hedgingΒΆ

In [17]:

Position taking when hedgingΒΆ

In [18]:
-0.3759801916822377
Out[18]:
trades price
time
2021-11-30 09:31:00 -0.010000 461.85
2021-11-30 09:32:00 0.000945 461.79
2021-11-30 09:33:00 -0.010964 462.68
2021-11-30 09:34:00 0.001921 462.54
2021-11-30 09:35:00 0.001620 462.41
In [19]:

Is this hedging resulting in profitable trades?ΒΆ

In [20]:
Out[20]:
trades price 0
time
2021-11-30 09:31:00 -0.375980 461.85 -0.01
2021-11-30 09:32:00 0.000945 461.79 -0.01
2021-11-30 09:33:00 -0.010964 462.68 -0.01
2021-11-30 09:34:00 0.001921 462.54 -0.01
2021-11-30 09:35:00 0.001620 462.41 -0.01
In [21]:
-0.46%