<jupyter_start><jupyter_text>Dax Options and Implied VolatilitiesIn this notebook we analyse DAX option prices.<jupyter_code># Make sure you have all relevant Python packages.
# Missing packages can be installed with 'conda install ...' or 'pip install ...'

import numpy as np
import pandas as pd
from scipy.interpolate import CubicSpline
import plotly.express as px
import plotly.graph_objects as go

import matplotlib.pyplot as plt # optional, if Plotly does not work

import QuantLib as ql<jupyter_output><empty_output><jupyter_text>Data preparationDax option data is published by Eurex, see e.g. https://www.eurex.com/ex-en/markets/idx/dax/70044!quotesSingleViewOption?callPut=Call&maturityDate=202212.We read the option data from CSV file. The first 6 lines contain meta information (and comments) about dates and DAX underlying price. The remaining lines contain the actual option price data.<jupyter_code>header = pd.read_csv('DaxOptions.csv', nrows=4)
header
data = pd.read_csv('DaxOptions.csv', skiprows=6)
data
fig = go.Figure()
fig.update_layout(autosize=False, width=800, height=600, title='Option prices', xaxis_title='Strike price', yaxis_title='Price')
fig.add_scatter(x=data['Strike_Price'], y=data['Call_Ask'], name='Call_Ask', line=dict(color='darkred'),   mode='lines')
fig.add_scatter(x=data['Strike_Price'], y=data['Call_Bid'], name='Call_Bid', line=dict(color='red'),       mode='lines')
fig.add_scatter(x=data['Strike_Price'], y=data['Put_Ask'],  name='Put_Ask',  line=dict(color='blue'),      mode='lines')
fig.add_scatter(x=data['Strike_Price'], y=data['Put_Bid'],  name='Put_Bid',  line=dict(color='lightblue'), mode='lines')
fig.show()<jupyter_output><empty_output><jupyter_text>If Plotly figures do not work in your environment you can uncomment below code and use Matplotlib for plotting.<jupyter_code># plt.figure(figsize=(12,8))
# plt.title('Option prices')
# plt.xlabel('Strike price')
# plt.ylabel('Price')
# plt.plot(data['Strike_Price'], data['Call_Ask'], 'r-',  label='Call_Ask')
# plt.plot(data['Strike_Price'], data['Call_Bid'], 'r--', label='Call_Bid')
# plt.plot(data['Strike_Price'], data['Put_Ask'],  'b-',  label='Put_Ask')
# plt.plot(data['Strike_Price'], data['Put_Bid'],  'b--', label='Put_Bid')
# plt.legend()
# plt.show()<jupyter_output><empty_output><jupyter_text>The data contain bid/ask prices for puts and calls for the Dec-22 expiry. This expiry represents the third Friday in December 2022, which is December 10, 2022.Trade date is December 28, 2021 and initial DAX level ($S_0$) at 15.948,77. All prices are denominated in EUR.We can also analyse bid-ask spreads of option prices:<jupyter_code>fig = go.Figure()
fig.update_layout(autosize=False, width=800, height=600, title='Bid/ask spreads', xaxis_title='Strike price', yaxis_title='Price')
fig.add_scatter(x=data['Strike_Price'], y=data['Call_Ask']-data['Call_Bid'], name='B/A Call', line=dict(color='red'),   mode='lines')
fig.add_scatter(x=data['Strike_Price'], y=data['Put_Ask'] -data['Put_Bid'],  name='B/A Put',  line=dict(color='blue'),  mode='lines')
fig.show()
# plt.figure(figsize=(12,8))
# plt.title('Bid/ask spreads')
# plt.xlabel('Strike price')
# plt.ylabel('Price')
# plt.plot(data['Strike_Price'], data['Call_Ask']-data['Call_Bid'], 'r-', label='B/A Call')
# plt.plot(data['Strike_Price'], data['Put_Ask'] -data['Put_Bid'],  'b-', label='B/A Put')
# plt.legend()
# plt.show()<jupyter_output><empty_output><jupyter_text>This view gives some nice insights about index option trading:  - Bid/ask spreads for calls and puts are roughly comparable in the strike range from about 12.000 tro 19.000; This is probably the strike range where trading takes place.  - For strikes below 11.500 and above 19.000 in-the-money options (low-strike calls and high-strike puts) show increasing bid/ask spreads; prices are probably not very liquid in that strike regions.We want to analyse how above option prices translate into implied volatilities. For risk-free rate calculation we consider mid prices. This makes things a bit simpler compared to using bid/ask for calls and puts.<jupyter_code>data['Call_Mid'] = 0.5 * (data['Call_Ask'] + data['Call_Bid'])
data['Put_Mid']  = 0.5 * (data['Put_Ask']  + data['Put_Bid'])
data<jupyter_output><empty_output><jupyter_text>Calculate Forward Index and Risk-free Rate from Put-Call Parity- Consider $\pi^C(K)$ and $\pi^P(K)$ the (mid) call and put option price for a given strike $K$. - Apply put-call parity with $K_{ATM}=\mathbb{E}^*\left[ S_T \right]$. How can we calculate $\mathbb{E}^*\left[ S_T \right]$ model-independent from call and put option prices?- Use the option data to numerically compute $\mathbb{E}^*\left[ S_T \right]$. Use e.g. *CubicSpline* and method *roots* from Scipy, https://docs.scipy.org/doc/scipy/reference/generated/scipy.interpolate.CubicSpline.roots.html.<jupyter_code># Add forward calculation here.<jupyter_output><empty_output><jupyter_text>Use $K_{ATM}=\mathbb{E}^*\left[ S_T \right]$ to determine the effective continuous compounded risk-free rate.- Use *ql.Date* to specify trading date and expiry date, https://quantlib-python-docs.readthedocs.io/en/latest/dates.htmldate- Use *ql.Actual365Fixed* DayCounter (https://quantlib-python-docs.readthedocs.io/en/latest/dates.htmldaycounter) and method *yearFraction(d1,d2) to calculate time-to-expiry $T$ in years.- Finally, use $S_0$, $K_{ATM}$ and $T$ to compute risk-free rate $r$ from the martingale condition$$  S_0 = \mathbb{E}^*\left[ \frac{S_T}{e^{rT}} \right].$$<jupyter_code># Add time to expiry calculation here
# Add risk-free rate calculation here.<jupyter_output><empty_output><jupyter_text>It turns out that the effective risk-free rate for Dec22 is about -15bp.  Implied Volatility Calculation with QuantLibCalculate implied volatilities for the quoted call bid/ask and put bid/ask prices. You need to setup a couple of QuantLib objects for this step.  1.  You need a *ql.BlackScholesProcess*, https://quantlib-python-docs.readthedocs.io/en/latest/stochastic_processes.html. This takes the following inputs:       - *ql.SimpleQuote* to store market data values, and *ql.QuoteHandle* to allow for later update of quotes (not relevant for our example)      - *ql.FlatForward* to store a risk-free rate curve with (trivial) business day calendar *ql.NullCalendar* and day count convention *ql.Actual365Fixed*      - *ql.YieldTermStructureHandle* to allow for later replacement of a curve (not relevant for our example)      - *ql.BlackConstantVol* to store volatility parameter with (trivial) business day calendar *ql.NullCalendar* and day count convention *ql.Actual365Fixed*      - *ql.BlackVolTermStructureHandle*  to allow for later replacement of a volatility (not relevant for our example)  2. You need to specify the option instruments, https://quantlib-python-docs.readthedocs.io/en/latest/instruments.htmlvanilla-options      - *ql.EuropeanExercise* to specify option expiry time            - *ql.PlainVanillaPayoff* to specify call (*ql.Option.Call*) and put (*ql.Option.Put*) options for various strikes  3. Finally, you can use the *option.impliedVolatility(price, process)* method for a given *option* instrument, input *price* and Black-Scholes *process*. This step needs a little care because not all prices allow for an implied volatility calculation. This point can be handled via try/except blocks, see function *safeImpliedVolatility*.You can also follow the steps explained here:https://stackoverflow.com/questions/4891490/calculating-europeanoptionimpliedvolatility-in-quantlib-python<jupyter_code>def safeImpliedVolatility(option, price, process):
    try:
        vol = option.impliedVolatility(price, process)
    except:
        vol = None
    return vol
# Set evaluation date, setup market data and define stochastic process.
# Setup options.
# Calculate implied volatilities.
# Note: If implied volatilities cannot be calculated QuantLib throws an exception. You can handle that case via try-expcept blocks in Python.<jupyter_output><empty_output><jupyter_text>Plot implied volatilities (and bid ask spreads) for calls and puts in terms of implied volatilities.<jupyter_code># Adapt the option price plots to plot the resulting implied volatilities.<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.12383375742154368, 'fraction_numerical': 0.01320731855083, 'mean_word_length': 5.67799352750809, 'pattern_counts': {'":': 0, '<': 37, '<?xml version=': 0, '>': 37, 'https://': 7, 'lorem ipsum': 0, 'www.': 1, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '9', 'dir': 'jupyter-structured-clean-dedup', 'id': '280075', 'license': 'MIT', 'n_tokens_mistral': 2720, 'n_tokens_neox': 2584, 'n_words': 806, 'path': 'BlackScholesModel/DaxOptions.ipynb', 'repo_name': 'sschlenkrich/MathematicalFinance_examples', 'size': '8253'}