Python Type Hint Finance Use Case: Implied Volatility and Greeks of Index 'At The Money' Options: Part 2

Authors:

Jonathan Legrand
Developer Advocate Developer Advocate

In part 1: we showed how one can (i) automatically find the Option (of choice) closest to At The Money (ATM) and (ii) calculate its Implied Volatility & Greeks.

In part 2: We will implement a functionality allowing us to apply all we did in Part 1 to expired options. You'll see, it's not as simple as it seems. We will then put it all in one function using Type Hints. This, in itself, will also be rather new and exciting!

 

Content

 

 

Code Setup

    	
            

import refinitiv.data as rd  # This is LSEG's Data and Analytics' API wrapper, called the Refinitiv Data Library for Python. You can update this library with the comand `!pip install refinitiv-data --upgrade`

from refinitiv.data.content import historical_pricing  # We will use this Python Class in `rd` to show the Implied Volatility data already available before our work.

from refinitiv.data.content import search  # We will use this Python Class in `rd` to fid the instrument we are after, closest to At The Money.

import refinitiv.data.content.ipa.financial_contracts as rdf  # We're going to need thtis to use the content layer of the RD library and the calculators of greeks and Impl Volat in Instrument Pricing Analytics (IPA) and Exchange Traded Instruments (ETI)

from refinitiv.data.content.ipa.financial_contracts import option  # We're going to need thtis to use the content layer of the RD library and the calculators of greeks and Impl Volat in IPA & ETI

 

import numpy as np  # We need `numpy` for mathematical and array manipilations.

import pandas as pd  # We need `pandas` for datafame and array manipilations.

import calendar  # We use `calendar` to identify holidays and maturity dates of intruments of interest.

import pytz  # We use `pytz` to manipulate time values aiding `calendar` library. to import its types, you might need to run `!python3 -m pip install types-pytz`

import pandas_market_calendars as mcal  # Used to identify holidays. See `https://github.com/rsheftel/pandas_market_calendars/blob/master/examples/usage.ipynb` for info on this market calendar library

from datetime import datetime, timedelta, timezone  # We use these to manipulate time values

from dateutil.relativedelta import relativedelta  # We use `relativedelta` to manipulate time values aiding `calendar` library.

import requests  # We'll need this to send requests to servers vie a the delivery layer - more on that below

 

# `plotly` is a library used to render interactive graphs:

import plotly.graph_objects as go

import plotly.express as px  # This is just to see the implied vol graph when that field is available

import matplotlib.pyplot as plt  # We use `matplotlib` to just in case users do not have an environment suited to `plotly`.

from IPython.display import clear_output, display  # We use `clear_output` for users who wish to loop graph production on a regular basis. We'll use this to `display` data (e.g.: pandas data-frames).

from plotly import subplots

import plotly

Now let's open our session with RD. You can find more information about sessions on EX-4.01.01-Sessions.ipynb.

    	
            

try:

    rd.open_session(

        name="desktop.workspace4",  # "platform.rdph", "desktop.workspace4"

        config_name="C:/Example.DataLibrary.Python-main/Configuration/refinitiv-data.config.json")

    print("We're on 'desktop.workspace4' Session")

except:

    rd.open_session()

    print("We're on Desktop Session")

# For more info on the session, use `rd.get_config().as_dict()`

We're on 'desktop.workspace4' Session

Let's test our connection with 'STXE42500D3.EX^D23', an expired option:

    	
            

tst = rd.content.historical_pricing.summaries.Definition(

    universe='STXE42500D3.EX^D23', start='2021-08-29', end='2023-04-21', interval='PT10M', fields=['TRDPRC_1', 'SETTLE', 'BID', 'ASK']).get_data().data.df

    	
            tst
        
        
    
STXE42500D3.EX^D23 TRDPRC_1 SETTLE BID ASK
Timestamp        
2022-11-17 08:40:00 55.0 <NA> <NA> <NA>
2022-11-28 15:40:00 59.9 <NA> <NA> <NA>
... ... ... ... ...
2023-04-20 15:20:00 <NA> <NA> 134.0 136.1
2023-04-20 15:30:00 <NA> <NA> 132.2 137.8
    	
            

for i, j in zip(

    ['refinitiv.data', 'numpy', 'pandas', 'pandas_market_calendars' 'pytz', 'requests', 'plotly'],

    [rd, np, pd, mcal, pytz, requests, plotly]):

    print(f"{i} used in this code is version {j.__version__}")

refinitiv.data used in this code is version 1.3.0
numpy used in this code is version 1.23.1
pandas used in this code is version 1.3.5
pandas_market_calendarspytz used in this code is version 4.1.4
requests used in this code is version 2022.1
plotly used in this code is version 2.28.1

FYI (For Your Information), this is out Python version:

    	
            !python -V
        
        
    

Python 3.10.5

Finding Expired Options

The code in the cell below was written expertly by Haykaz Aramyan in the article 'Functions to find Option RICs traded on different exchanges'. I wanted to introduce in part 2 of this (current) article as it uses complex Python notions such as Classes. We look into reconstructing expired option RICs which have different nomenclatures to live ones:

Below, we put ourselves in the shoes of an analyst backtesting a strategy involving past historical Implied Volatilities. E.g.: if the average 3-business-day historical Implied Volatility of an Option contract is too high, (s)he would not consider it in his(/her) portfolio.

Something to keep in mind is that no intraday price data is available for options that expired 3 months (or more) prior, therefore, when intraday data is not available, daily data will be used.

STOXX50E Usecase

Let's focuss on STOXX50E.

    	
            

calc_time, underlying = "2022-04-01", ".STOXX50E"

calc_time_datetime = datetime.strptime(calc_time, '%Y-%m-%d')

current_underlying_prc = rd.get_history(

    universe=[underlying],

    start=calc_time,  # , end: "OptDateTime"=None

    fields=["TRDPRC_1"],

    interval="tick").iloc[-1][0]

current_underlying_prc

4141.63

    	
            

if underlying == ".STOXX50E":

    exchange, exchangeRIC, mcal_cal = "EUX", "STX", "EUREX"

elif underlying == ".SPX":

    exchange, exchangeRIC, mcal_cal = "OPQ", "SPX", "CBOE_Futures"

exchange, exchangeRIC, mcal_cal

('EUX', 'STX', 'EUREX')

Now we can ge the expiry dates for our new senario, based on a time of calculation on "2022-04-01":

    	
            

def Get_exp_dates(year, days=True, mcal_get_calendar='EUREX'):

 

    # get CBOE market holidays

    EUREXCal = mcal.get_calendar(mcal_get_calendar)

    holidays = EUREXCal.holidays().holidays

 

    # set calendar starting from Saturday

    c = calendar.Calendar(firstweekday=calendar.SATURDAY)

 

    # get the 3rd Friday of each month

    exp_dates = {}

    for i in range(1, 13):

        monthcal = c.monthdatescalendar(year, i)

        date = monthcal[2][-1]

        # check if found date is an holiday and get the previous date if it is

        if date in holidays:

            date = date + timedelta(-1)

        # append the date to the dictionary

        if year in exp_dates:

            ### Changed from original code from here on by Jonathan Legrand on 2022-10-11

            if days: exp_dates[year].append(date.day)

            else: exp_dates[year].append(date)

        else:

            if days: exp_dates[year] = [date.day]

            else: exp_dates[year] = [date]

    return exp_dates

    	
            

calc_full_dates_time = Get_exp_dates(

    year=2022, days=False,

    mcal_get_calendar=mcal_cal)

calc_full_dates_time

{2022: [datetime.date(2022, 1, 21),
datetime.date(2022, 2, 18),
datetime.date(2022, 3, 18),
datetime.date(2022, 4, 14),
datetime.date(2022, 5, 20),
datetime.date(2022, 6, 17),
datetime.date(2022, 7, 15),
datetime.date(2022, 8, 19),
datetime.date(2022, 9, 16),
datetime.date(2022, 10, 21),
datetime.date(2022, 11, 18),
datetime.date(2022, 12, 16)]}

    	
            

calc_full_dates_time_datetime = [

    datetime(i.year, i.month, i.day)

    for i in calc_full_dates_time[list(calc_full_dates_time.keys())[0]]]

calc_full_dates_time_datetime

[datetime.datetime(2022, 1, 21, 0, 0),
datetime.datetime(2022, 2, 18, 0, 0),
datetime.datetime(2022, 3, 18, 0, 0),
datetime.datetime(2022, 4, 14, 0, 0),
datetime.datetime(2022, 5, 20, 0, 0),
datetime.datetime(2022, 6, 17, 0, 0),
datetime.datetime(2022, 7, 15, 0, 0),
datetime.datetime(2022, 8, 19, 0, 0),
datetime.datetime(2022, 9, 16, 0, 0),
datetime.datetime(2022, 10, 21, 0, 0),
datetime.datetime(2022, 11, 18, 0, 0),
datetime.datetime(2022, 12, 16, 0, 0)]

Remember, in this use-case, I would like to know what is the next Future (Monthly) Option (i) on the Index '.STOXX50E' (ii) closest to ATM (i.e.: with an underlying spot price closest to the option's strike price) and (iii) Expiring in more than x days (i.e.: not too close to calculated time 't'), let's say 15 days:

    	
            x = 15
        
        
    
    	
            

expiry_date_of_intrst = [i for i in calc_full_dates_time_datetime

                    if i > calc_time_datetime + relativedelta(days=x)][0]

expiry_date_of_intrst

datetime.datetime(2022, 5, 20, 0, 0)

We'll need new functions `Get_exp_month` and `Check_RIC`:

    	
            

!python -Vdef Get_exp_month(exp_date, opt_type):

 

    # define option expiration identifiers

    ident = {

        '1':  {'exp': 'A', 'C': 'A', 'P': 'M'},

        '2':  {'exp': 'B', 'C': 'B', 'P': 'N'},

        '3':  {'exp': 'C', 'C': 'C', 'P': 'O'},

        '4':  {'exp': 'D', 'C': 'D', 'P': 'P'},

        '5':  {'exp': 'E', 'C': 'E', 'P': 'Q'},

        '6':  {'exp': 'F', 'C': 'F', 'P': 'R'},

        '7':  {'exp': 'G', 'C': 'G', 'P': 'S'},

        '8':  {'exp': 'H', 'C': 'H', 'P': 'T'},

        '9':  {'exp': 'I', 'C': 'I', 'P': 'U'},

        '10': {'exp': 'J', 'C': 'J', 'P': 'V'},

        '11': {'exp': 'K', 'C': 'K', 'P': 'W'},

        '12': {'exp': 'L', 'C': 'L', 'P': 'X'}}

 

    # get expiration month code for a month

    if opt_type.upper() == 'C':

        exp_month = ident[str(exp_date.month)]['C']

    elif opt_type.upper() == 'P':

        exp_month = ident[str(exp_date.month)]['P']

 

    return ident, exp_month

Now things are getting tricky.

Certain Expiered Options do not have `TRDPRC_1` data historically. Some don't have `SETTLE`. Some have both...

The below should capture `TRDPRC_1` when it is available, but `SETTLE` might still be present in these instances.

So we will need to build a logic to focus on the series with the most datapoints.

Specifically: Get `TRDPRC_1`. If there are fewer `TRDPRC_1` datapoionts than days (i.e.: if there's only daily data for this field), get `SETTLE`. Same again, if not `SETTLE`, then get the midpoint between `BID` and `ASK`.

    	
            

def Check_RIC(

        ric,

        maturity,

        ident,

        interval=rd.content.historical_pricing.Intervals.DAILY):

 

    exp_date = pd.Timestamp(maturity)

 

    # get start and end date for get_historical_price_summaries

    # query (take current date minus 90 days period)

    sdate = (datetime.now() - timedelta(90)).strftime('%Y-%m-%d')

    edate = datetime.now().strftime('%Y-%m-%d')

 

    # check if option is matured. If yes, add expiration syntax and recalculate

    # start and end date of the query (take expiration day minus 90 days period)

    if pd.Timestamp(maturity) < datetime.now():

        ric = ric + '^' + ident[str(exp_date.month)]['exp'] + str(exp_date.year)[-2:]

        sdate = (exp_date - timedelta(90)).strftime('%Y-%m-%d')

        edate = exp_date.strftime('%Y-%m-%d')

 

    # Now things are getting tricky.

    # Certain Expiered Options do not have 'TRDPRC_1' data historically. Some don't have 'SETTLE'. Some have both...

    # The below should capture 'SETTLE' when it is available, then 'TRDPRC_1' if not, then MID (calculated with 'ASK' and 'BID') otherwise.

    prices = rd.content.historical_pricing.summaries.Definition(

            universe=ric,

            start=sdate,

            end=edate,

            interval=interval,

            fields=['TRDPRC_1', 'SETTLE', 'BID', 'ASK']

            ).get_data().data.df

    pr_cnt = prices.count()

    if pr_cnt.TRDPRC_1 > 0:

        prices = pd.DataFrame(

            data={'TRDPRC_1': prices.TRDPRC_1}).dropna()

    elif pr_cnt.SETTLE > 0:

        prices = pd.DataFrame(

            data={'SETTLE': prices.SETTLE}).dropna()

    elif pr_cnt.BID > 0:

        prices = pd.DataFrame(

            data={'MID': (prices.BID + prices.ASK)/2}).dropna()

    prices.columns.name = ric

 

    return ric, prices

Now we can get EUREX RIC:

    	
            

def Get_RIC_eurex(asset, maturity, strike, opt_type, interval='PT10M'):

    exp_date = pd.Timestamp(maturity)

 

    if asset[0] == '.':

        asset_name = asset[1:]

        if asset_name == 'FTSE':

            asset_name = 'OTUK'

        elif asset_name == 'SSMI':

            asset_name = 'OSMI'

        elif asset_name == 'GDAXI':

            asset_name = 'GDAX'

        elif asset_name == 'ATX':

            asset_name = 'FATXA'

        elif asset_name == 'STOXX50E':

            asset_name = 'STXE'

    else:

        asset_name = asset.split('.')[0]

 

    ident, exp_month = Get_exp_month(

        exp_date=exp_date, opt_type=opt_type)

 

    if type(strike) == float:

        int_part = int(strike)

        dec_part = str(str(strike).split('.')[1])[0]

    else:

        int_part = int(strike)

        dec_part = '0'

 

    if len(str(int(strike))) == 1:

        strike_ric = '0' + str(int_part) + dec_part

    else:

        strike_ric = str(int_part) + dec_part

 

    possible_rics = []

    generations = ['', 'a', 'b', 'c', 'd']

    for gen in generations:

        ric = asset_name + strike_ric + gen + exp_month + str(exp_date.year)[-1:] + '.EX'

        ric, prices = Check_RIC(ric=ric, maturity=maturity, ident=ident,

                                interval=rd.content.historical_pricing.Intervals.DAILY)

        if prices is not None:

            return ric, prices

        else:

            possible_rics.append(ric)

    print(f'Here is a list of possible RICs {possible_rics}, however we could not find any prices for those!')

    return ric, prices

Note that this function, `Get_RIC_eurex`, needs a round number as strike price:

    	
            print(f"{current_underlying_prc} as int: {int(round(current_underlying_prc, -2))}")
        
        
    

4141.63 as int: 4100

    	
            

instrument, instrument_pr = Get_RIC_eurex(

    asset='.STOXX50E', opt_type='P',

    maturity=expiry_date_of_intrst.strftime('%Y-%m-%d'),

    strike=int(round(current_underlying_prc, -2)))

    	
            instrument
        
        
    

'STXE41000Q2.EX^E22'

    	
            instrument_pr
        
        
    
STXE41000Q2.EX^E22 TRDPRC_1
Date  
2022-02-22 269.8
2022-02-28 363.0
2022-03-01 380.7
2022-03-04 576.8
2022-03-15 551.2
2022-03-22 286.5
2022-04-04 286.1
2022-04-12 345.9
2022-04-13 350.0
2022-04-25 378.2
2022-04-28 374.8
2022-05-18 373.0

General Usecase

Above we looked at the specific usecase with EUREX, let's generalise it:

Creating a class with PEP 3107 (a.k.a.: Type Hints)

We are now going to look into using PEP 3107 (and PEP 484) (and some decorators). In line with PEP, I will also now use PEP8 naming conventions.

    	
            # pip install --trusted-host files.pythonhosted.org nb_mypy # This line can be used to install `nb_mypy`.
        
        
    
    	
            

import nb_mypy  # !pip3 install nb_mypy --trusted-host pypi.org # https://pypi.org/project/nb-mypy/ # https://gitlab.tue.nl/jupyter-projects/nb_mypy/-/blob/master/Nb_Mypy.ipynb

%load_ext nb_mypy

%reload_ext nb_mypy

%nb_mypy On

%nb_mypy DebugOff

Version 1.0.5
Version 1.0.5

Let's test our Type Hint library:

    	
            

def myfunc(myparam: int) -> None:

    print(myparam)

 

myfunc('test')

<cell>4: error: Argument 1 to "myfunc" has incompatible type "str"; expected "int" [arg-type]

test

Now let's recreate what we did above, but with Type Hints. We will alsop continue using them from now on.

    	
            

import refinitiv.data as rd  # This is LSEG's Data and Analytics' API wrapper, called the Refinitiv Data Library for Python. You can update this library with the comand `!pip install refinitiv-data --upgrade`

from refinitiv.data.content import historical_pricing  # We will use this Python Class in `rd` to show the Implied Volatility data already available before our work.

from refinitiv.data.content import search  # We will use this Python Class in `rd` to fid the instrument we are after, closest to At The Money.

import refinitiv.data.content.ipa.financial_contracts as rdf  # We're going to need this to use the content layer of the RD library and the calculators of greeks and Impl Volat in Instrument Pricing Analytics (IPA) and Exchange Traded Instruments (ETI)

from refinitiv.data.content.ipa.financial_contracts import option  # We're going to need thtis to use the content layer of the RD library and the calculators of greeks and Impl Volat in IPA & ETI

 

import numpy as np  # We need `numpy` for mathematical and array manipilations.

import pandas as pd  # We need `pandas` for datafame and array manipilations.

import calendar  # We use `calendar` to identify holidays and maturity dates of intruments of interest.

import pytz  # We use `pytz` to manipulate time values aiding `calendar` library. to import its types, you might need to run `!python3 -m pip install types-pytz`. To get the Type Hints for this library, try `pip install types-pytz`.

import pandas_market_calendars as mcal  # Used to identify holidays. See `https://github.com/rsheftel/pandas_market_calendars/blob/master/examples/usage.ipynb` for info on this market calendar library

from datetime import datetime, date, timedelta, timezone  # We use these to manipulate time values

from dateutil.relativedelta import relativedelta  # We use `relativedelta` to manipulate time values aiding `calendar` library. May also need `pip install types-python-dateutil`.

import requests  # We'll need this to send requests to servers vie a the delivery layer - more on that below. Might also need `pip install types-requests`.

 

# `plotly` is a library used to render interactive graphs:

import plotly.graph_objects as go

import plotly.express as px  # This is just to see the implied vol graph when that field is available

import matplotlib.pyplot as plt  # We use `matplotlib` to just in case users do not have an environment suited to `plotly`.

from IPython.display import clear_output, display  # We use `clear_output` for users who wish to loop graph production on a regular basis. We'll use this to `display` data (e.g.: pandas data-frames).

from plotly import subplots

import plotly

 

from typing import Generator, Any

from types import ModuleType  # FrameType, TracebackType

    	
            

try:

    rd.open_session(

        name="desktop.workspace",

        config_name="C:/Example.DataLibrary.Python-main/Configuration/refinitiv-data.config.json")

except:

    rd.open_session()

# For more info on the session, use `rd.get_config().as_dict()`

    	
            

i_str: str

i_ModType: ModuleType

 

for i_str, i_ModType in zip(

    ['refinitiv.data', 'numpy', 'pandas', 'pandas_market_calendars' 'pytz', 'requests', 'plotly'],

    [rd, np, pd, mcal, pytz, requests, plotly]):

    print(f"{i_str} used in this code is version {i_ModType.__version__}")

refinitiv.data used in this code is version 1.3.0
numpy used in this code is version 1.23.1
pandas used in this code is version 1.3.5
pandas_market_calendarspytz used in this code is version 4.1.4
requests used in this code is version 2022.1
plotly used in this code is version 2.28.1

    	
            

calc_time: str = "2022-04-01"

underlying: str = ".STOXX50E"

 

calc_time_datetime: datetime = datetime.strptime(calc_time, '%Y-%m-%d')

current_underlying_prc: float = rd.get_history(

    universe=[underlying],

    start=calc_time,  # , end: "OptDateTime"=None

    fields=["TRDPRC_1"],

    interval="tick").iloc[-1][0]

 

current_underlying_prc

4139.28

    	
            

exchange: str

exchangeRIC: str

mcal_cal: str

if underlying == ".STOXX50E":

    exchange, exchangeRIC, mcal_cal = "EUX", "STX", "EUREX"

elif underlying == ".SPX":

    exchange, exchangeRIC, mcal_cal = "OPQ", "SPX", "CBOE_Futures"

exchange, exchangeRIC, mcal_cal

('EUX', 'STX', 'EUREX')

    	
            

def Get_exp_dates(year: int,

                  days: bool = True,

                  mcal_get_calendar: str = 'EUREX'

                  ) -> dict[int, list[Any]]:

 

    # get CBOE market holidays

    holidays: tuple[date] = mcal.get_calendar(mcal_get_calendar).holidays().holidays

 

    # set calendar starting from Saturday

    c: calendar.Calendar = calendar.Calendar(firstweekday=calendar.SATURDAY)

 

    # get the 3rd Friday of each month

    exp_dates: dict[int, list[Any]]

    _date: date

    i_int: int

 

    _date = c.monthdatescalendar(year, 1)[2][-1]

    if _date in holidays: # check if found date is an holiday and get the previous date if it is

        _date = _date + timedelta(-1)

    if days:

        exp_dates = {year:[_date.day]}

    else:

        exp_dates = {year: [_date]}

    for i_int in range(2, 13):

        _date = c.monthdatescalendar(year, i_int)[2][-1]

        if _date in holidays: # check if found date is an holiday and get the previous date if it is

            _date = _date + timedelta(-1)

        # append the date to the dictionary

        if days:

            exp_dates[year].append(_date.day)

        else:

            exp_dates[year].append(_date)

 

    return exp_dates

    	
            Get_exp_dates.__annotations__
        
        
    

{'year': int,
'days': bool,
'mcal_get_calendar': str,
'return': dict[int, list[typing.Any]]}

    	
            

calc_full_dates_time: dict[int, list[Any]] = Get_exp_dates(

    year=2022, days=False,

    mcal_get_calendar=mcal_cal)

    	
            calc_full_dates_time[list(calc_full_dates_time.keys())[0]]
        
        
    

[datetime.date(2022, 1, 21),
datetime.date(2022, 2, 18),
datetime.date(2022, 3, 18),
datetime.date(2022, 4, 14),
datetime.date(2022, 5, 20),
datetime.date(2022, 6, 17),
datetime.date(2022, 7, 15),
datetime.date(2022, 8, 19),
datetime.date(2022, 9, 16),
datetime.date(2022, 10, 21),
datetime.date(2022, 11, 18),
datetime.date(2022, 12, 16)]

    	
            

x: int = 15

expiry_date_of_intrst: datetime = [

    datetime(i_date.year, i_date.month, i_date.day)

    for i_date in calc_full_dates_time[list(calc_full_dates_time.keys())[0]]

    if datetime(i_date.year, i_date.month, i_date.day) > calc_time_datetime + relativedelta(days=x)][0]

expiry_date_of_intrst

datetime.datetime(2022, 5, 20, 0, 0)

    	
            

def Get_exp_month(

    exp_date: datetime,

    opt_type: str

    ) -> tuple[dict[int, dict[str, str]], str]:

 

    # define option expiration identifiers

    ident: dict[int, dict[str, str]] = {

        1:  {'exp': 'A', 'C': 'A', 'P': 'M'},

        2:  {'exp': 'B', 'C': 'B', 'P': 'N'},

        3:  {'exp': 'C', 'C': 'C', 'P': 'O'},

        4:  {'exp': 'D', 'C': 'D', 'P': 'P'},

        5:  {'exp': 'E', 'C': 'E', 'P': 'Q'},

        6:  {'exp': 'F', 'C': 'F', 'P': 'R'},

        7:  {'exp': 'G', 'C': 'G', 'P': 'S'},

        8:  {'exp': 'H', 'C': 'H', 'P': 'T'},

        9:  {'exp': 'I', 'C': 'I', 'P': 'U'},

        10: {'exp': 'J', 'C': 'J', 'P': 'V'},

        11: {'exp': 'K', 'C': 'K', 'P': 'W'},

        12: {'exp': 'L', 'C': 'L', 'P': 'X'}}

 

    # get expiration month code for a month

    exp_month: str

    if opt_type.upper() == 'C' or opt_type.upper() == 'CALL':

        exp_month = ident[int(exp_date.month)]['C']

    elif opt_type.upper() == 'P' or opt_type.upper() == 'PUT':

        exp_month = ident[int(exp_date.month)]['P']

 

    return ident, exp_month

    	
            

Get_exp_month(

    exp_date=datetime(2022, 5, 20, 0, 0),

    opt_type='P'

)

({1: {'exp': 'A', 'C': 'A', 'P': 'M'},
2: {'exp': 'B', 'C': 'B', 'P': 'N'},
3: {'exp': 'C', 'C': 'C', 'P': 'O'},
4: {'exp': 'D', 'C': 'D', 'P': 'P'},
5: {'exp': 'E', 'C': 'E', 'P': 'Q'},
6: {'exp': 'F', 'C': 'F', 'P': 'R'},
7: {'exp': 'G', 'C': 'G', 'P': 'S'},
8: {'exp': 'H', 'C': 'H', 'P': 'T'},
9: {'exp': 'I', 'C': 'I', 'P': 'U'},
10: {'exp': 'J', 'C': 'J', 'P': 'V'},
11: {'exp': 'K', 'C': 'K', 'P': 'W'},
12: {'exp': 'L', 'C': 'L', 'P': 'X'}},
'Q')

    	
            datetime.strptime(expiry_date_of_intrst.strftime('%Y-%m-%d'), '%Y-%m-%d')
        
        
    

datetime.datetime(2022, 5, 20, 0, 0)

    	
            (datetime.strptime(expiry_date_of_intrst.strftime('%Y-%m-%d'), '%Y-%m-%d')  - timedelta(90)).strftime('%Y-%m-%d')
        
        
    

'2022-02-19'

    	
            

def Check_RIC(

        ric: str,

        maturity: str,

        ident: dict[int, dict[str, str]],

        interval: rd.content.historical_pricing.Intervals = rd.content.historical_pricing.Intervals.DAILY

        ) -> tuple[str, pd.DataFrame]:

 

    exp_date: pd.Timestamp = pd.Timestamp(maturity)

 

    # get start and end date for get_historical_price_summaries

    # query (take maturity date minus 90 days period)

    sdate: str = (datetime.strptime(maturity, '%Y-%m-%d') - timedelta(90)).strftime('%Y-%m-%d')

    edate: str = maturity

 

    # check if option is matured. If yes, add expiration syntax and recalculate

    # start and end date of the query (take expiration day minus 90 days period)

    if pd.Timestamp(maturity) < datetime.now():

        ric = ric + '^' + ident[int(exp_date.month)]['exp'] + str(exp_date.year)[-2:]

        sdate = (exp_date - timedelta(90)).strftime('%Y-%m-%d')

        edate = exp_date.strftime('%Y-%m-%d')

 

    # Now things are getting tricky.

    # Certain Expiered Options do not have 'TRDPRC_1' data historically. Some don't have 'SETTLE'. Some have both...

    # The below should capture 'SETTLE' when it is available, then 'TRDPRC_1' if not, then MID (calculated with 'ASK' and 'BID') otherwise.

    prices: pd.DataFrame | None

    prices = rd.content.historical_pricing.summaries.Definition(

            universe=ric,

            start=sdate,

            end=edate,

            interval=interval,

            fields=['TRDPRC_1', 'SETTLE', 'BID', 'ASK']

            ).get_data().data.df

    pr_cnt: pd.Series = prices.count()

    if pr_cnt.TRDPRC_1 > 0:

        prices = pd.DataFrame(

            data={'TRDPRC_1': prices.TRDPRC_1}).dropna()

    elif pr_cnt.SETTLE > 0:

        prices = pd.DataFrame(

            data={'SETTLE': prices.SETTLE}).dropna()

    elif pr_cnt.BID > 0:

        prices = pd.DataFrame(

            data={'MID': (prices.BID + prices.ASK)/2}).dropna()

    prices.columns.name = ric

 

    return ric, prices

    	
            

def Get_RIC_eurex(

        asset: str,

        maturity: str,

        strike: int,

        opt_type: str,

        interval: rd.content.historical_pricing.Intervals = rd.content.historical_pricing.Intervals.DAILY

        ) -> tuple[str, pd.DataFrame]:

    exp_date = pd.Timestamp(maturity)

 

    asset_name: str

    ident: dict[int, dict[str, str]]

    exp_month: str

    dec_part: str

    strike_ric: str

 

    if asset[0] == '.':

        asset_name = asset[1:]

        if asset_name == 'FTSE':

            asset_name = 'OTUK'

        elif asset_name == 'SSMI':

            asset_name = 'OSMI'

        elif asset_name == 'GDAXI':

            asset_name = 'GDAX'

        elif asset_name == 'ATX':

            asset_name = 'FATXA'

        elif asset_name == 'STOXX50E':

            asset_name = 'STXE'

    else:

        asset_name = asset.split('.')[0]

 

    ident, exp_month = Get_exp_month(

        exp_date=exp_date, opt_type=opt_type)

 

    if type(strike) == float:

        int_part = int(strike)

        dec_part = str(str(strike).split('.')[1])[0]

    else:

        int_part = int(strike)

        dec_part = '0'

 

    if len(str(int(strike))) == 1:

        strike_ric = '0' + str(int_part) + dec_part

    else:

        strike_ric = str(int_part) + dec_part

 

    possible_rics: list = []

    generations: list[str] = ['', 'a', 'b', 'c', 'd']

    prices: pd.DataFrame

    for gen in generations:

        ric: str = asset_name + strike_ric + gen + exp_month + str(exp_date.year)[-1:] + '.EX'

        ric, prices = Check_RIC(ric=ric, maturity=maturity, ident=ident, interval=interval)

        if prices is not None:

            return ric, prices

        else:

            possible_rics.append(ric)

    print(f'Here is a list of possible RICs {possible_rics}, however we could not find any prices for those!')

    return ric, prices

    	
            

instrument: str

instrument_pr: pd.DataFrame

instrument, instrument_pr = Get_RIC_eurex(

    asset='.STOXX50E', opt_type='P',

    maturity=expiry_date_of_intrst.strftime('%Y-%m-%d'),

    strike=int(round(current_underlying_prc, -2)))

    	
            instrument
        
        
    

'STXE41000Q2.EX^E22'

    	
            instrument_pr
        
        
    
STXE41000Q2.EX^E22 TRDPRC_1
Date  
2022-02-22 269.8
2022-02-28 363.0
... ...
2022-04-28 374.8
2022-05-18 373.0

Class 1: `OptionRIC`

 

See the notebook in GitHub fot he entire Class.

 

 

 

General Use Case Test with HSI

Let's try it all again with the senario where we calculate values as of '2023-02-01' for index 'Hang Seng Index':

    	
            

HSI_underlying_RIC: str = '.HSI'

HSI_curr: pd.DataFrame = rd.get_data(

    universe=HSI_underlying_RIC,

    fields=["CF_CURR"])

HSI_curr

  Instrument CF_CURR
0 .HSI HKD
    	
            

time_of_calc3: str = "2023-02-02"

HSI_test0 = OptionRIC(

    maturity=time_of_calc3,

    strike=20200,  # could be: `int(round(rd.get_history(universe=[SPX_underlying_RIC], start=time_of_calc3, fields=["TRDPRC_1"], interval="tick").iloc[-1][0], -2))` but this would get the price right now, which may not be appropriate for a 'past'/expired option.

    opt_type='P',

    asset=HSI_underlying_RIC,

    debug=False)

HSI_test: dict[str, list[dict[str, pd.DataFrame]] | list] | pd.DataFrame

HSI_test = HSI_test0.Construct_RIC()

HSI_test

{'valid_ric': [{'HSI20200N3.HF^B23': HSI20200N3.HF^B23 TRDPRC_1
Timestamp
2022-12-28 03:06:00 864
2022-12-28 03:08:00 855
... ...
2023-02-01 15:39:00 76
2023-02-01 16:59:00 74

[645 rows x 1 columns]}],
'potential_rics': []}

    	
            

HSI_test_df: pd.DataFrame

HSI_test_df = HSI_test['valid_ric'][0][list(HSI_test['valid_ric'][0].keys())[0]]

HSI_test_df.head()

HSI20200N3.HF^B23 TRDPRC_1
Timestamp  
2022-12-28 03:06:00 864
2022-12-28 03:08:00 855
2023-01-03 15:14:00 805
2023-01-04 05:02:00 661
2023-01-04 05:04:00 655

 

 

General Use Case Test with SPX

 

Now let's look at SPX:

    	
            

# # 1st: get the current underlying price

time_of_calc4: str = '2022-02-10'

SPX_underlying_RIC: str = ".SPX"

# time_of_calc_datetime3: date = datetime.strptime(time_of_calc4, '%Y-%m-%d')

current_underlying_SPX_prc: float = rd.get_history(

    universe=[SPX_underlying_RIC],

    start=time_of_calc4,  # , end: "OptDateTime"=None

    fields=["TRDPRC_1"],

    interval="tick").iloc[-1][0]

current_underlying_SPX_prc

4273.53

    	
            

# # 2nd: get data via the `OptionRIC` function we created

SPX_test: dict[str, list[dict[str, pd.DataFrame]] | list] | pd.DataFrame = OptionRIC(

    maturity='2022-07-15',

    strike=int(round(current_underlying_SPX_prc, -2)),

    opt_type='P',

    asset=SPX_underlying_RIC,

    debug=False,

    interval=rd.content.historical_pricing.Intervals.DAILY

    ).Construct_RIC()

SPX_test

{'valid_ric': [{'SPXs152243000.U^G22': SPXs152243000.U^G22 TRDPRC_1
Date
2021-06-29 320.93
2021-07-01 320.64
... ...
2022-07-13 491.8
2022-07-14 505.0

[181 rows x 1 columns]}],
'potential_rics': []}

    	
            

# # test df for verification:

# tdf: pd.DataFrame = rd.content.historical_pricing.summaries.Definition(

#     universe="SPXs152245000.U^G22",

#     start='2020-11-22T09:00:00.000',

#     end="2022-07-15T20:40:00.000",

#     fields=['CLOSE', 'TRDPRC_1', 'BID', 'ASK'],

#     interval=rd.content.historical_pricing.Intervals.DAILY).get_data().data.df

# tdf

    	
            

# # Note that the output is a dictionary:

SPX_test['valid_ric'][0].keys()

dict_keys(['SPXs152243000.U^G22'])

    	
            

# # You can find the keys of this dictionary:

list(SPX_test['valid_ric'][0].keys())[0]

'SPXs152243000.U^G22'

    	
            

# # With dictionary keys, you can itterate through what `OptionRIC` found:

SPX_test['valid_ric'][0][list(SPX_test['valid_ric'][0].keys())[0]]

SPXs152243000.U^G22 TRDPRC_1
Date  
2021-06-29 320.93
2021-07-01 320.64
... ...
2022-07-13 491.8
2022-07-14 505.0

 

 

Implied Volatility and Greeks of Expired Options (Historical Daily series)

 

The most granular Historical Options' price data kept are daily time-series. This daily data is captured by the above `OptionRIC().Construct_RIC()` function. Some Options' historical price data is most "wholesome" (in this case, "has the least amount of `NaN`s" - Not a Number) under the field name `TRDPRC_1`, some under `SETTLE`. While our preference - ceteris paribus (all else equal) - is `TRDPRC_1`, more "wholesome" data-sets are still preferable, so the "fullest prices" in `OptionRIC().Construct_RIC()` picks the series with fewest `NaN`s.

    	
            

# Now we will try and find the strike price for the option found.

# If we were to use the logic above, in the function `OptionRIC().Get_strike()`, we would find a list of all the possible prices for options on this underlying, which is too large of a group.

# We will use the name of the outputed option, which includes the strike:

import re  # native Python library that allows us to manipulate strings

hist_opt_found_strk_pr: str = re.findall(

    '(\d+|[A-Za-z]+)',  # This will split the string out of its numerical and non-numerical characters.

    list(HSI_test['valid_ric'][0].keys())[0])[1] #`[1]` here skips through 'HSI' and to the numbers.

hist_opt_found_strk_pr

'20200'

    	
            

def hk_rf_f():

    return 100 - rd.get_history(

    universe=['HK3MT=RR'],  # other examples could include: `HK10YGB=EODF`, `HKGOV3MZ=R`, `HK3MT=RR`

    fields=['TR.MIDPRICE'],

    start=HSI_test_df.index[0].strftime('%Y-%m-%d %H:%M:%S'),

    end=HSI_test_df.index[-1].strftime('%Y-%m-%d %H:%M:%S'))

    	
            

# try:

hk_rf: pd.DataFrame = 100 - rd.get_history(

universe=['HK3MT=RR'],  # other examples could include: `HK10YGB=EODF`, `HKGOV3MZ=R`, `HK3MT=RR`

fields=['TR.MIDPRICE'],

start=HSI_test_df.index[0].strftime('%Y-%m-%d %H:%M:%S'),

end=HSI_test_df.index[-1].strftime('%Y-%m-%d %H:%M:%S'))  # .iloc[::-1]  # `.iloc[::-1]` is here so that the resulting data-frame is the same order as `HSI_test_df1` so we can merge them later

# except RDError:

#     hk_rf = 100 - rd.get_history(

#     universe=['HK3MT=RR'],

#     fields=['TR.MIDPRICE'],

#     start=HSI_test_df.index[0].strftime('%Y-%m-%d %H:%M:%S'),

#     end=HSI_test_df.index[-1].strftime('%Y-%m-%d %H:%M:%S'))

hk_rf.head(3)

HK3MT=RR Mid Price
Date  
2022-12-28 0.6565
2022-12-29 0.666
2022-12-30 0.678
    	
            

def Insert_daily_data(

        intra_day_df: pd.DataFrame,

        intra_day_col: str,

        daily_df: pd.DataFrame,

        daily_df_col: str

        ) -> pd.DataFrame:

    df: pd.DataFrame = intra_day_df.copy()

    df[intra_day_col] = [pd.NA for i_int in range(len(df))]

    i_int: int; j_int: int

    for i_int in range(len(df)):

        for j_int in range(len(daily_df)):

            if daily_df.index[j_int].strftime('%Y-%m-%d') in df.index[i_int].strftime('%Y-%m-%d'):

                df[intra_day_col].iloc[i_int] = daily_df[daily_df_col].iloc[j_int]

    return df

    	
            

HSI_test_df1: pd.DataFrame = Insert_daily_data(

    intra_day_df=HSI_test_df, intra_day_col="RfRatePrct",

    daily_df=hk_rf, daily_df_col="Mid Price")

# Rename the field name 'TRDPRC_1' with something that makes a litle more sense, like 'OptionPrice'

HSI_test_df1: pd.DataFrame = HSI_test_df1.rename(

    columns={"TRDPRC_1": "OptionPrice"})

HSI_test_df1.head()

HSI20200N3.HF^B23 OptionPrice RfRatePrct
Timestamp    
2022-12-28 03:06:00 864 0.6565
2022-12-28 03:08:00 855 0.6565
2023-01-03 15:14:00 805 0.7605
2023-01-04 05:02:00 661 0.7255
2023-01-04 05:04:00 655 0.7255
    	
            

# Now let's get the underlying price:

hist_HSI_undrlying_pr: pd.DataFrame = rd.get_history(

    universe=[HSI_underlying_RIC],

    fields=["TRDPRC_1"],

    # interval="1D",

    start=HSI_test_df.index[0].strftime('%Y-%m-%d'),

    end=HSI_test_df.index[-1].strftime('%Y-%m-%d'))  # .iloc[::-1]  # `.iloc[::-1]` is here so that the resulting data-frame is the same order as `HSI_test_df1` so we can merge them later

hist_HSI_undrlying_pr.head(2)

.HSI TRDPRC_1
Date  
2022-12-29 19741.14
2022-12-30 19781.41
    	
            

# Now let's put it all together:

HSI_test_df2: pd.DataFrame = Insert_daily_data(

    intra_day_df=HSI_test_df1, intra_day_col="UndrlyingPr",

    daily_df=hist_HSI_undrlying_pr, daily_df_col="TRDPRC_1")

HSI_test_df2

HSI20200N3.HF^B23 OptionPrice RfRatePrct UndrlyingPr
Timestamp      
2022-12-28 03:06:00 864 0.6565 <NA>
2022-12-28 03:08:00 855 0.6565 <NA>
... ... ... ...
2023-02-01 15:39:00 76 0.5925 22072.18
2023-02-01 16:59:00 74 0.5925 22072.18

Content Layer

    	
            

HSI_test2_exp_date: str = HSI_test0.maturity.strftime('%Y-%m-%d')

HSI_test2_exp_date

'2023-02-02'

    	
            

hist_daily_universe_l: list[option._definition.Definition] = [

    option.Definition( # You can find details on this with `help(option.Definition)`.

        # instrument_tag="Option",

        # instrument_code='STXE42000D3.EX',  # 'STXE42000D3.EX' #  'HSI19300N3.HF^B23',  # list(HSI_test2['valid_ric'][0].keys())[0], # # `instrument_code` is ambiguous here because we ought to use our expired option RIC, but when we use it we automatically get ann NaNs back. Putting in a live option's RIC resolves the problem, but we're not investigating a live option... So you can simply not define it; which is what we're doing here.

        strike=float(hist_opt_found_strk_pr),

        buy_sell='Buy',

        call_put='Call',

        exercise_style="EURO", # optional, str # AMER, EURO or BERM. AMER: the owner has the right to exercise on any date before the option expires, EURO: the owner has the right to exercise only on EndDate, BERM: the owner has the right to exercise on any of several specified dates before the option expires.

        end_date=datetime.strptime(HSI_test2_exp_date, '%Y-%m-%d').strftime('%Y-%m-%dT%H:%M:%SZ'), # optional, date-time # The maturity or expiry date of the instrument. The value is expressed in ISO 8601 format: YYYY-MM-DDT[hh]:[mm]:[ss]Z (e.g., '2021-01-01T00:00:00Z'). Mandatory for OTC ETI options and FX options (if tenor is not defined).,

        lot_size=1.0, # optional, double (i.e.: float) # The number of the underlying asset unit on which the option is written. It can be overriden only for Commodity options. If instrumentCode of listed ETI option is defined the value comes from the instrument reference data. The default value is '1' for OTC ETI options.

        deal_contract=1, # optional, int # The number of contracts bought or sold in the deal. The default value is '1'.

        time_zone_offset=0, # optional, int # The offset in minutes between UTC and the time of the exchange where the contract is traded. No default value applies.

        underlying_type=option.UnderlyingType.ETI,

        underlying_definition=option.EtiUnderlyingDefinition(

            instrument_code=HSI_underlying_RIC),

        # tenor=str((HSI_test1.maturity - HSI_test_start).days/365),  # Expecting this to `YearsToExpiry` which is in 'DaysToExpiry / 365'.

        # notional_ccy='HKD',

        # notional_amount=None,

        # asian_definition=None,

        # barrier_definition=None,

        # binary_definition=None,

        # double_barrier_definition=None,

        # double_binary_definition=None,

        # dual_currency_definition=None,

        # forward_start_definition=None,

        # underlying_definition=None,

        # delivery_date=HSI_test1.maturity.strftime('%Y-%m-%d'),

        # cbbc_definition=None,

        # double_barriers_definition=None,

        # end_date_time=None,

        # offset=None,

        # extended_params=None,

        pricing_parameters=option.PricingParameters( # mode on this with `help(option.PricingParameters)`

            valuation_date=HSI_test_df2.dropna().index[i_int].strftime('%Y-%m-%dT%H:%M:%SZ'), # The date at which the instrument is valued. the value is expressed in iso 8601 format: yyyy-mm-ddt[hh]:[mm]:[ss]z (e.g., '2021-01-01t00:00:00z'). by default, marketdatadate is used. if marketdatadate is not specified, the default value is today.

            report_ccy='HKD',

            market_value_in_deal_ccy=float(HSI_test_df2.dropna()['OptionPrice'][i_int]),

            # market_value_in_report_ccy=None,

            pricing_model_type='BlackScholes',

            # dividend_type=None,  # APPARENTLY NOT APPLICABLE IN IPA/QA IN RD LIB. None, 'ForecastTable', 'HistoricalYield', 'ForecastYield', 'ImpliedYield', or 'ImpliedTable'.

            # dividend_yield_percent=None,

            # volatility_percent=None,  # The degree of the underlying asset's price variations over a specified time period, used for the option pricing. the value is expressed in percentages. it is used to compute marketvalueindealccy.if marketvalueindealccy is defined, volatilitypercent is not taken into account. optional. by default, it is computed from marketvalueindealccy. if volsurface fails to return a volatility, it defaults to '20'.

            risk_free_rate_percent=float(HSI_test_df2.dropna()['RfRatePrct'][i_int]),

            underlying_price=float(HSI_test_df2.dropna()['UndrlyingPr'][i_int]),

            volatility_type='Implied', # 'option.OptionVolatilityType.IMPLIED,  # option.OptionVolatilityType.IMPLIED, 'Implied'

            option_price_side='Mid', # option.PriceSide.LAST,  # 'bid', 'ask', 'mid','last', option.PriceSide.LAST

            underlying_time_stamp='Close', # option.TimeStamp.SETTLE,  # 'Close', 'Default',

            # underlying_price_side='Last' # option.PriceSide.LAST

            # volatility_model=option.VolatilityModel.SVI

        ))

    for i_int in range(len(HSI_test_df2.dropna()))]

    	
            

def Chunks(lst, n) -> Generator[list[Any], None, None]:

    """Yield successive n-sized chunks from lst."""

    i_int: int

    for i_int in range(0, len(lst), n):

        yield lst[i_int:i_int + n]

    	
            request_fields: list[str] = ['ErrorMessage', 'AverageSoFar', 'AverageType', 'BarrierLevel', 'BarrierType', 'BreakEvenDeltaAmountInDealCcy', 'BreakEvenDeltaAmountInReportCcy', 'BreakEvenPriceInDealCcy', 'BreakEvenPriceInReportCcy', 'CallPut', 'CbbcOptionType', 'CbbcType', 'CharmAmountInDealCcy', 'CharmAmountInReportCcy', 'ColorAmountInDealCcy', 'ColorAmountInReportCcy', 'ConversionRatio', 'DailyVolatility', 'DailyVolatilityPercent', 'DaysToExpiry', 'DealCcy', 'DeltaAmountInDealCcy', 'DeltaAmountInReportCcy', 'DeltaExposureInDealCcy', 'DeltaExposureInReportCcy', 'DeltaHedgePositionInDealCcy', 'DeltaHedgePositionInReportCcy', 'DeltaPercent', 'DividendType', 'DividendYieldPercent', 'DvegaDtimeAmountInDealCcy', 'DvegaDtimeAmountInReportCcy', 'EndDate', 'ExerciseStyle', 'FixingCalendar', 'FixingDateArray', 'FixingEndDate', 'FixingFrequency', 'FixingNumbers', 'FixingStartDate', 'ForecastDividendYieldPercent', 'GammaAmountInDealCcy', 'GammaAmountInReportCcy', 'GammaPercent', 'Gearing', 'HedgeRatio', 'InstrumentCode', 'InstrumentDescription', 'InstrumentTag', 'Leverage', 'LotSize', 'LotsUnits', 'MarketDataDate', 'MarketValueInDealCcy', 'MoneynessAmountInDealCcy', 'MoneynessAmountInReportCcy', 'OptionPrice', 'OptionPriceSide', 'OptionTimeStamp', 'OptionType', 'PremiumOverCashInDealCcy', 'PremiumOverCashInReportCcy', 'PremiumOverCashPercent', 'PremiumPerAnnumInDealCcy', 'PremiumPerAnnumInReportCcy', 'PremiumPerAnnumPercent', 'PremiumPercent', 'PricingModelType', 'PricingModelTypeList', 'ResidualAmountInDealCcy', 'ResidualAmountInReportCcy', 'RhoAmountInDealCcy', 'RhoAmountInReportCcy', 'RhoPercent', 'RiskFreeRatePercent', 'SevenDaysThetaAmountInDealCcy', 'SevenDaysThetaAmountInReportCcy', 'SevenDaysThetaPercent', 'SpeedAmountInDealCcy', 'SpeedAmountInReportCcy', 'Strike', 'ThetaAmountInDealCcy', 'ThetaAmountInReportCcy', 'ThetaPercent', 'TimeValueInDealCcy', 'TimeValueInReportCcy', 'TimeValuePercent', 'TimeValuePerDay', 'TotalMarketValueInDealCcy', 'TotalMarketValueInDealCcy', 'TotalMarketValueInReportCcy', 'TotalMarketValueInReportCcy', 'UltimaAmountInDealCcy', 'UltimaAmountInReportCcy', 'UnderlyingCcy', 'UnderlyingPrice', 'UnderlyingPriceSide', 'UnderlyingRIC', 'UnderlyingTimeStamp', 'ValuationDate', 'VannaAmountInDealCcy', 'VannaAmountInReportCcy', 'VegaAmountInDealCcy', 'VegaAmountInReportCcy', 'VegaPercent', 'Volatility', 'VolatilityPercent', 'VolatilityType', 'VolgaAmountInDealCcy', 'VolgaAmountInReportCcy', 'YearsToExpiry', 'ZommaAmountInDealCcy', 'ZommaAmountInReportCcy']
        
        
    
    	
            

# # Collect data from IPA in batches (since it only accepts a max. of 100 requests per call)

batch_of: int = 100

hist_daily_universe_l_ch: list[list[option._definition.Definition]] = [

    i for i in Chunks(hist_daily_universe_l, batch_of)]

i_rd_o_def: option._definition.Definition

response2: rd.content.ipa.financial_contracts._definition.Definitions

_request_fields: list[str]

for i_int, i_rd_o_def in enumerate(hist_daily_universe_l_ch):

    print(f"Batch of {len(i_rd_o_def)} requests no. {i_int+1}/{len(hist_daily_universe_l_ch)} started")

    # Example request with Body Parameter - Symbology Lookup

    response2def = rdf.Definitions(universe=i_rd_o_def, fields=request_fields)

    try:  # One issue we may encounter here is that the field 'ErrorMessage' in `request_fields` may break the `get_data()` call and therefore the for loop. we may therefore have to remove 'ErrorMessage' in the call; ironically when it is most useful.

        response2 = response2.get_data()

    except:  # https://stackoverflow.com/questions/11520492/difference-between-del-remove-and-pop-on-lists

        _request_fields = request_fields.copy()

        _request_fields.remove('ErrorMessage')

        response2 = rdf.Definitions(universe=i_rd_o_def, fields=_request_fields).get_data()

    if i_int == 0:

        _response2df: pd.DataFrame = response2.data.df

    else:

        _response2df = _response2df.append(response2.data.df, ignore_index=True)

    # print(f"Batch of {len(i_rd_o_def)} requests no. {i_int+1}/{len(hist_daily_universe_l_ch)} ended")

# # Rename the column 'Volatility' with 'ImpliedVolatility', since that's the Volatility we asked for

response2df: pd.DataFrame = _response2df.rename(columns={'Volatility': 'ImpliedVolatility'})

# # Keep only the columns we're after

try:

    response2df = response2df[

        ['ErrorMessage', 'ImpliedVolatility', 'MarketValueInDealCcy',

        'RiskFreeRatePercent', 'UnderlyingPrice', 'DeltaPercent',

        'GammaPercent', 'RhoPercent', 'ThetaPercent', 'VegaPercent']]

except:

    response2df = response2df[

        ['ImpliedVolatility', 'MarketValueInDealCcy',

        'RiskFreeRatePercent', 'UnderlyingPrice', 'DeltaPercent',

        'GammaPercent', 'RhoPercent', 'ThetaPercent', 'VegaPercent']]

# # Add an index of dates

response2df.index = HSI_test_df2.dropna().index

# # Drop the na (not applicable) values

response2df = response2df.dropna()

# # Give a name to our data frame

response2df.columns.name = HSI_test_df2.columns.name

response2df

Batch of 100 requests no. 1/7 started
Batch of 100 requests no. 2/7 started
Batch of 100 requests no. 3/7 started
Batch of 100 requests no. 4/7 started
Batch of 100 requests no. 5/7 started
Batch of 100 requests no. 6/7 started
Batch of 43 requests no. 7/7 started

HSI20200N3.HF^B23 ImpliedVolatility MarketValueInDealCcy RiskFreeRatePercent UnderlyingPrice DeltaPercent GammaPercent RhoPercent ThetaPercent VegaPercent
Timestamp                  
2023-01-03 15:14:00 37.699964 805 0.7605 20145.29 0.499444 0.000185 7.447067 -13.639386 22.720151
2023-01-04 05:02:00 14.075574 661 0.7255 20793.11 0.751228 0.000382 11.799572 -3.060431 18.355369
... ... ... ... ... ... ... ... ... ...
2023-01-04 08:19:00 8.970256 578 0.7255 20793.11 0.852918 0.000434 13.468556 -0.449749 13.198607
2023-01-04 08:27:00 9.194755 581 0.7255 20793.11 0.847142 0.000434 13.369321 -0.569076 13.55007

Sort Out Time Zones

 

All data from the RDP endpoints are in GMT. We will keep the index for GMT, so we don't loose this information, but we will also add a Local Timezone, as this is what interests us. To do so, let's use MultyIndex:

    	
            

HSI_test_df3: pd.DataFrame = response2df.copy()

# Get timezone of the exchange where option is traded:

i_pd_mltyidx: pd.core.indexes.multi.MultiIndex

j_pd_mltyidx: pd.core.indexes.multi.MultiIndex

k_pd_mltyidx: pd.core.indexes.multi.MultiIndex

HSI_test_df3.index = pd.MultiIndex.from_tuples(

    [(i_pd_mltyidx, j_pd_mltyidx, k_pd_mltyidx) for i_pd_mltyidx, j_pd_mltyidx, k_pd_mltyidx in zip(

        HSI_test_df3.index,

        HSI_test_df3.index.tz_localize('GMT').tz_convert('Europe/Berlin'),

        HSI_test_df3.index.tz_localize('GMT').tz_convert('Asia/Hong_Kong'))],

    names=["gmt", "cet", "localHSI/HK"])

Now let's focus on data within trading hours (just in case there are 'Ask's or 'BID's sent outside of trading hours):

    	
            

# Get only trading hours

mrkt_exhng_open_time: str = '9:30'

mrkt_exhng_lnch_time_start: str = '12:00'

mrkt_exhng_lnch_time_end: str = '13:00'

mrkt_exhng_close_time: str = '12:00'

HSI_test_morn_df3: pd.DataFrame = HSI_test_df3.droplevel(["gmt", "cet"] # unfortunately, we have to drop levels we just added to apply the useful `between_time` function.

    ).between_time(mrkt_exhng_open_time, mrkt_exhng_lnch_time_start)

HSI_test_afternoon_df3: pd.DataFrame = HSI_test_df3.droplevel(["gmt", "cet"]

    ).between_time(mrkt_exhng_lnch_time_end, mrkt_exhng_close_time)

HSI_test_th_df3: pd.DataFrame = HSI_test_morn_df3.append(HSI_test_afternoon_df3)

 

HSI_test_th_df3.index = pd.MultiIndex.from_tuples(

    [(i_pd_mltyidx, j_pd_mltyidx, k_pd_mltyidx) for i_pd_mltyidx, j_pd_mltyidx, k_pd_mltyidx in zip(

        HSI_test_th_df3.index,

        HSI_test_th_df3.index.tz_convert('GMT'),

        HSI_test_th_df3.index.tz_convert('Europe/Berlin'))],

    names=["localHSI/HK", "gmt", "cet"])

HSI_test_th_df3

    HSI20200N3.HF^B23 ImpliedVolatility MarketValueInDealCcy RiskFreeRatePercent UnderlyingPrice DeltaPercent GammaPercent RhoPercent ThetaPercent VegaPercent
localHSI/HK gmt cet                  
2023-01-03 23:14:00+08:00 2023-01-03 15:14:00+00:00 2023-01-03 16:14:00+01:00 37.699964 805 0.7605 20145.29 0.499444 0.000185 7.447067 -13.639386 22.720151
2023-01-04 13:02:00+08:00 2023-01-04 05:02:00+00:00 2023-01-04 06:02:00+01:00 14.075574 661 0.7255 20793.11 0.751228 0.000382 11.799572 -3.060431 18.355369
... ... ... ... ... ... ... ... ... ... ... ...
2023-01-04 16:19:00+08:00 2023-01-04 08:19:00+00:00 2023-01-04 09:19:00+01:00 8.970256 578 0.7255 20793.11 0.852918 0.000434 13.468556 -0.449749 13.198607
2023-01-04 16:27:00+08:00 2023-01-04 08:27:00+00:00 2023-01-04 09:27:00+01:00 9.194755 581 0.7255 20793.11 0.847142 0.000434 13.369321 -0.569076 13.55007

Delivery Layer

    	
            

# We will pre-alocate space for list objects to speed up its populating:

HSI_test_df2_del_lay_list: list[dict[str, str | float | int | dict[str, str | float | int | dict[str, str | float | int]]]] = [

    {'0': '0'}] * len(HSI_test_df2.dropna())

for i_int in range(len(HSI_test_df2.dropna())):

    HSI_test_df2_del_lay_list[i_int] = {

        "instrumentType": "Option",

        "instrumentDefinition": {

            # "instrumentTag": "HSITest6DelLay", # optional, str

            # "instrumentCode": "STXE44000G3.EX", # list(HSI_test2['valid_ric'][0].keys())[0],  # "instrumentCode": None # optional, str

            "strike": float(hist_opt_found_strk_pr), # optional, double (i.e.: float)

            "buySell": "Buy", # optional, str

            "callPut": "Call", # optional, str

            "exerciseStyle": "EURO", # optional, str # AMER, EURO or BERM. AMER: the owner has the right to exercise on any date before the option expires, EURO: the owner has the right to exercise only on EndDate, BERM: the owner has the right to exercise on any of several specified dates before the option expires.

            "endDate": datetime.strptime(HSI_test2_exp_date, '%Y-%m-%d').strftime('%Y-%m-%dT%H:%M:%SZ'), # optional, date-time # The maturity or expiry date of the instrument. The value is expressed in ISO 8601 format: YYYY-MM-DDT[hh]:[mm]:[ss]Z (e.g., '2021-01-01T00:00:00Z'). Mandatory for OTC ETI options and FX options (if tenor is not defined).

            "lotSize": 1.0, # optional, double (i.e.: float) # The number of the underlying asset unit on which the option is written. It can be overriden only for Commodity options. If instrumentCode of listed ETI option is defined the value comes from the instrument reference data. The default value is '1' for OTC ETI options.

            "dealContract": 1, # optional, int # The number of contracts bought or sold in the deal. The default value is '1'.

            "timeZoneOffset": 0, # optional, int # The offset in minutes between UTC and the time of the exchange where the contract is traded. No default value applies.

            "underlyingType": "Eti", # optional, str # Eti or Fx; Eti: ETI (Exchanged Traded Instruments) options, Fx: FX options.

            "underlyingDefinition": {

                "instrumentCode": HSI_underlying_RIC # optional, str # The code (a RIC) used to define the underlying asset. Mandatory for OTC ETI options. No default value applies.

            }

            # "asianDefinition": ""

            # "barrierDefinition": ""

            # "binaryDefinition": ""

            # "cbbcDefinition": ""

        },

        "pricingParameters": {

            "reportCcy": "HKD",

            "marketValueInDealCcy": float(HSI_test_df2.dropna()['OptionPrice'][i_int]),  # 14361.694905

            "valuationDate": HSI_test_df2.dropna().index[i_int].strftime('%Y-%m-%dT%H:%M:%SZ'),

            # # "marketDataDate": str(HSI_test_df2.dropna().index[i]).replace(" ", "T") + "Z", # optional, date-time

            "pricingModelType": "BlackScholes",

            "dividendType": "None",

            # # "dividendYieldPercent": 0.0, # optional, double (i.e.: float) # The ratio of annualized dividends to the underlying asset's price. The value is expressed in percentages.

            # # "marketValueInReportCcy": float(HSI_test_df2.dropna()['OptionPrice'][i]) * 1, # optional, double (i.e.: float) # The market value (premium) of the instrument. It is computed as [MarketValueInDealCcy × FxSpot]. The value is expressed in the reporting currency.

            # # "volatilityPercent": , # optional, double (i.e.: float) # The degree of the underlying asset's price variations over a specified time period, used for the option pricing. The value is expressed in percentages.It is used to compute MarketValueInDealCcy. If marketValueInDealCcy is defined, volatilityPercent is not taken into account. By default, it is computed from MarketValueInDealCcy; if VolSurface fails to return a volatility, it defaults to '20'.

            "riskFreeRatePercent": float(HSI_test_df2.dropna()['RfRatePrct'][i_int]),

            "underlyingPrice": float(HSI_test_df2.dropna()['UndrlyingPr'][i_int]),

            "volatilityType": "Implied", # optional, str # Implied, SVISurface, Historical. The value 'Implied' is available only for listed options. If volatilityPercent is defined, volatilityType is not taken into account.

            "optionPriceSide": "Mid", # optional, str # Bid, Ask, Mid, Last. Ask: if buySell is set to 'Buy', Bid: if buySell is set to 'Sell', Last: if buySell is not provided.

            "optionTimeStamp": "Close" # optional, str # Open: the opening value of valuationDate, or if it is not available, the close of the previous day is used; Close: the close value of valuationDate is used; Default: the latest snapshot is used when valuationDate is today, and the close price when valuationDate is in the past.

            # # "underlyingPriceSide": "Mid", # optional, str # Bid, Ask, Mid, Last. Ask: if buySell is set to 'Buy', Bid: if buySell is set to 'Sell', Last: if buySell is not provided.

            # # "underlyingTimeStamp": "Close" # optional, str # The mode of the underlying asset's timestamp selection. The possible values are: Open: the opening value of valuationDate, or if it is not available, the close of the previous day is used; Close: the close value of valuationDate is used; 

        }

      }

    	
            

batch_of: int = 100

i_chunks: Any # dict[str, str | float | int | dict[str, str | float | int | dict[str, str | float | int]]]

HSI_test_df2_chunks: list[Any] = [

    i_chunks for i_chunks in Chunks(HSI_test_df2_del_lay_list, batch_of)]

response3: rd.delivery._data._response.Response

headers_name: list[str]

_response3df: pd.DataFrame

for i_int, i_chunks in enumerate(HSI_test_df2_chunks):

    if len(HSI_test_df2_chunks) > 50 and i_int % 50 == 0: # This is there just in case you have a lot of calls

        print(f"Batch of {batch_of} requests no.{str(i_int+1)}/{str(len(HSI_test_df2_chunks))} started")

    # Example request with Body Parameter - Symbology Lookup

    request_definition = rd.delivery.endpoint_request.Definition(

        method=rd.delivery.endpoint_request.RequestMethod.POST,

        url='https://api.refinitiv.com/data/quantitative-analytics/v1/financial-contracts',

        body_parameters={

            "fields": request_fields,

            "universe": i_chunks,

            "outputs": ["Data", "Headers"]})

 

    # print({"fields": request_fields, "outputs": ["Data", "Headers"], "universe": j})

 

    response3 = request_definition.get_data()

 

    if response3.is_success:

        headers_name = [h['name'] for h in response3.data.raw['headers']]

        if i_int == 0:

            response3df = pd.DataFrame(

                data=response3.data.raw['data'], columns=headers_name)

            # print({"fields": request_fields, "outputs": ["Data", "Headers"], "universe": j})

        else:

            _response3df = pd.DataFrame(

                data=response3.data.raw['data'], columns=headers_name)

            response3df = response3df.append(_response3df, ignore_index=True)

        # display(response3df)

        if len(HSI_test_df2_chunks) > 50 and i_int % 50 == 0: # This is there just in case you have a lot of calls

            print(f"Batch of {batch_of} requests no.{str(i_int+1)}/{str(len(HSI_test_df2_chunks))} ended")

    # else:

    #     display(response3)

    	
            response3df
        
        
    
  ErrorMessage AverageSoFar AverageType BarrierLevel BarrierType BreakEvenDeltaAmountInDealCcy BreakEvenDeltaAmountInReportCcy BreakEvenPriceInDealCcy BreakEvenPriceInReportCcy CallPut ... VegaAmountInReportCcy VegaPercent Volatility VolatilityPercent VolatilityType VolgaAmountInDealCcy VolgaAmountInReportCcy YearsToExpiry ZommaAmountInDealCcy ZommaAmountInReportCcy
0   None   NaN None 0.363338 0.363338 21005.0 21005.0 CALL ... 22.784895 22.784895 36.213723 36.213723 Calculated -13.959693 -13.959693 0.079452 -0.000533 -0.000533
1   None   NaN None 0.471196 0.471196 20861.0 20861.0 CALL ... 14.460623 14.460623 10.930215 10.930215 Calculated 12222.331425 12222.331425 0.079452 -0.00027 -0.00027
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
641 Unable to calculate the Implied Volatility. None   NaN None NaN NaN 20276.0 20276.0 CALL ... NaN NaN None None Calculated NaN NaN 0.002666 NaN NaN
642 Unable to calculate the Implied Volatility. None   NaN None NaN NaN 20274.0 20274.0 CALL ... NaN NaN None None Calculated NaN NaN 0.002513 NaN NaN
    	
            

# Rename the column 'Volatility' with 'ImpliedVolatility' since that's the one we calculated

HSI_IPA_df: pd.DataFrame = response3df.rename(columns={'Volatility': 'ImpliedVolatility'})

# Name our data frame

HSI_IPA_df.columns.name = HSI_test_df2.columns.name

# Get timezone of the exchange where option is traded:

HSI_IPA_df.index = pd.MultiIndex.from_tuples(

    [(i_pd_mltyidx, j_pd_mltyidx, k_pd_mltyidx) for i_pd_mltyidx, j_pd_mltyidx, k_pd_mltyidx in zip(

        HSI_test_df2.dropna().index,

        HSI_test_df2.dropna().index.tz_localize('GMT').tz_convert('Europe/Berlin'),

        HSI_test_df2.dropna().index.tz_localize('GMT').tz_convert('Asia/Hong_Kong'))],

    names=["gmt", "cet", "localHSI/HK"])

# Get only trading hours

HSI_IPA_morn_df: pd.DataFrame = HSI_IPA_df.droplevel(["gmt", "cet"] # unfortunately, we have to drop levels we just added to apply the useful `between_time` function.

    ).between_time(mrkt_exhng_open_time, mrkt_exhng_lnch_time_start)

HSI_IPA_afternoon_df: pd.DataFrame = HSI_IPA_df.droplevel(["gmt", "cet"]

    ).between_time(mrkt_exhng_lnch_time_end, mrkt_exhng_close_time)

HSI_IPA_th_df: pd.DataFrame = HSI_IPA_morn_df.append(HSI_IPA_afternoon_df)

HSI_IPA_th_df.index = pd.MultiIndex.from_tuples(

    [(i_pd_mltyidx, j_pd_mltyidx, k_pd_mltyidx) for i_pd_mltyidx, j_pd_mltyidx, k_pd_mltyidx in zip(

        HSI_IPA_th_df.index,

        HSI_IPA_th_df.index.tz_convert('GMT'),

        HSI_IPA_th_df.index.tz_convert('Europe/Berlin'))],

    names=["localHSI/HK", "gmt", "cet"])

    	
            

HSI_IPA_df_graph0: pd.DataFrame = HSI_IPA_th_df.droplevel(

    ["gmt", "cet"])[

        ['ImpliedVolatility', 'MarketValueInDealCcy',

        'RiskFreeRatePercent', 'UnderlyingPrice', 'DeltaPercent',

        'GammaPercent', 'RhoPercent', 'ThetaPercent', 'VegaPercent']].dropna()

HSI_IPA_df_graph0

HSI20200N3.HF^B23 ImpliedVolatility MarketValueInDealCcy RiskFreeRatePercent UnderlyingPrice DeltaPercent GammaPercent RhoPercent ThetaPercent VegaPercent
localHSI/HK                  
2023-01-03 23:14:00+08:00 36.213723 805.0 0.7605 20145.29 0.512321 0.000193 7.655776 -14.246864 22.784895
2023-01-04 13:02:00+08:00 10.930215 661.0 0.7255 20793.11 0.835625 0.000388 13.183778 -3.076009 14.460623
... ... ... ... ... ... ... ... ... ...
2023-01-04 14:59:00+08:00 8.869543 634.0 0.7255 20793.11 0.884672 0.000376 13.969961 -2.1038 11.342056
2023-01-04 15:11:00+08:00 8.692169 632.0 0.7255 20793.11 0.889297 0.000373 14.04309 -2.021522 11.01396
    	
            

def Get_weekend_days(

    df: pd.DataFrame,

    tz: str | None = None) -> list[list[date]]:

    """Get_weekend_days(df, tz=None)

 

    df (pandas dataframe):

        Must have dates as `pd.Timestamp`s in its index.

    

    returns:

        List of list of two objects: the start second of each weekend day (saturnday 1st, sunday 2nd).

    """

    weekends: list[list[date]] = []

    i_pd_dtindx: pd.core.indexes.datetimes.DatetimeIndex

    bdate_range: list[i_pd_dtindx]

    for i_int in range(len(df)-1):

        if len(pd.bdate_range(start=df.index[i_int], end=df.index[i_int+1], freq="C", weekmask="Sat Sun")) > 0:

            bdate_range = [j for j in pd.bdate_range(start=df.index[i_int], end=df.index[i_int+1], freq="C", weekmask="Sat")]

            for saturday in bdate_range:

                # Append with the the first second of that Saturday

                weekends.append([

                    pd.Timestamp(

                        year=saturday.year,

                        month=saturday.month,

                        day=saturday.day,

                        tzinfo=tz),

                    pd.Timestamp(

                        year=saturday.year,

                        month=saturday.month,

                        day=saturday.day,

                        hour=23,

                        minute=59,

                        second=59,

                        tzinfo=tz)])

            bdate_range = [j for j in pd.bdate_range(start=df.index[i_int], end=df.index[i_int+1], freq="C", weekmask="Sun")]

            for sunday in bdate_range:

                weekends.append([

                    pd.Timestamp(

                        year=sunday.year,

                        month=sunday.month,

                        day=sunday.day,

                        tzinfo=tz),

                    pd.Timestamp(

                        year=sunday.year,

                        month=sunday.month,

                        day=sunday.day,

                        hour=23,

                        minute=59,

                        second=59,

                        tzinfo=tz)])

            

    return weekends

    	
            

# # Create Graph's Dataframe

 

for i_str in HSI_IPA_df_graph0.columns:

    HSI_IPA_df_graph0[i_str] = pd.to_numeric(HSI_IPA_df_graph0[i_str])

 

 

# # Create plot layout

 

fig: plotly.graph_objs._figure.Figure = px.line(HSI_IPA_df_graph0)  # This is just to see the implied vol graph when that field is available

 

# Format Graph: https://plotly.com/python/tick-formatting/

fig.update_layout(

    title=list(HSI_test['valid_ric'][0].keys())[0],

    template='plotly_dark')

 

# Make it so that only one line is shown by default: # https://stackoverflow.com/questions/73384807/plotly-express-plot-subset-of-dataframe-columns-by-default-and-the-rest-as-opt

fig.for_each_trace(lambda t: t.update(

    visible=True if t.name in HSI_IPA_df_graph0.columns[:1] else "legendonly"))

 

 

# # Add shade in Non Trading Hours

 

# Create purple areas for weekends:

shapes: list[dict[str, str | dict[str, str | int] | pd._libs.tslibs.timestamps.Timestamp]] = [

    {'type': "rect", 'xref': 'x', 'yref': 'paper',

     'fillcolor': 'purple', 'opacity': 0.35,

     "line": {"color": "purple", "width": 0},

     'x0': i[0], 'y0': 0,

     'x1': i[1], 'y1': 1}

     for i in Get_weekend_days(df=HSI_IPA_df_graph0, tz=None)]

 

fig.update_layout(shapes=shapes,)

 

fig.show()

Creating a 'master' class `IndxImpVolatNGreeksIPACalc`

The whole Class was too large to leave here, on this article. Do not hesitate to read it in GitHub.

    	
            

session: rd.session.Session

# session = rd.open_session(

#     name="desktop.workspace4",  # name="desktop.workspace4", "platform.rdph"

#     config_name="C:/Example.DataLibrary.Python-main/Configuration/refinitiv-data.config.json")

try:

    session = rd.open_session(

        name="desktop.workspace4",  # name="desktop.workspace4", "platform.rdph"

        config_name="C:/Example.DataLibrary.Python-main/Configuration/refinitiv-data.config.json")

except:

    try:

        app_key: str; rd_login:str; rd_password: str

        # # You could specify your session with:

        session = rd.session.platform.Definition(

            app_key=app_key,

            grant=rd.session.platform.GrantPassword(

                username=rd_login, password=rd_password),

                ).get_session()

        session.open()

    except:

        session = rd.open_session()

        print("We're on Desktop Session")

rd.session.set_default(session) # Letting the RD API know that this is the session we're on

# session.open()

    	
            

print("Index Implied Volatility aNd Greeks Instrument Pricing Analytics Calculation (IndxImpVolatNGreeksIPACalc) (IIVNGIPAC) expiered option test:")

IIVNGIPAC_exp_optn_test1 = IndxImpVolatNGreeksIPACalc(

    time_of_calc_datetime=datetime.strptime("2023-04-01", '%Y-%m-%d'),

    after=15,

    index_underlying=".STOXX50E",

    call_put='Call')

IIVNGIPAC_exp_optn_test2: IndxImpVolatNGreeksIPACalc = IIVNGIPAC_exp_optn_test1.search_index_opt_ATM(

    debug=False,

    call_or_put='Put',

    search_fields=["ExchangeCode", "UnderlyingQuoteName"],

    include_weekly_opts=False,

    top_nu_srch_results=10)

IIVNGIPAC_exp_optn_test3: IndxImpVolatNGreeksIPACalc = IIVNGIPAC_exp_optn_test2.IPA_calc(

    debug=False)

print(f"IIVNGIPAC_exp_optn_test3.instrument: {IIVNGIPAC_exp_optn_test3.instrument}")

print(f"IIVNGIPAC_exp_optn_test3.ATM_opt: {IIVNGIPAC_exp_optn_test3.ATM_opt}")

print(f"IIVNGIPAC_exp_optn_test3.maturity: {IIVNGIPAC_exp_optn_test3.maturity}")

if pd.to_datetime(IIVNGIPAC_exp_optn_test3.maturity) > datetime.now():

    print(f"IIVNGIPAC_exp_optn_test3.instrument_info:")

    display(IIVNGIPAC_exp_optn_test3.instrument_info)

Index Implied Volatility aNd Greeks Instrument Pricing Analytics Calculation (IndxImpVolatNGreeksIPACalc) (IIVNGIPAC) expiered option test:
IIVNGIPAC_exp_optn_test3.instrument: STXE41500aD3.EX^D23
IIVNGIPAC_exp_optn_test3.ATM_opt: STXE41500aD3.EX^D23
IIVNGIPAC_exp_optn_test3.maturity: 2023-04-21 00:00:00

    	
            IIVNGIPAC_exp_optn_test3.IPA_df.dropna()
        
        
    
  STXE41500aD3.EX^D23 MarketValueInDealCcy RiskFreeRatePercent UnderlyingPrice Volatility DeltaPercent GammaPercent RhoPercent ThetaPercent VegaPercent
gmt Romance Summer Time                  
2022-09-30 15:10:00 2022-09-30 17:10:00+02:00 11.9 1.173 3321.43 19.663222 0.059856 0.000243 1.036283 -0.127825 2.927326
2022-11-24 09:20:00 2022-11-24 11:20:00+02:00 102.3 1.908 3968.03 18.368408 0.348107 0.000791 5.172449 -0.513421 9.247155
... ... ... ... ... ... ... ... ... ... ...
2023-04-20 08:50:00 2023-04-20 10:50:00+02:00 229.0 3.052 4377.74 48.463872 0.970619 0.000535 0.13844 -3.261882 0.171266
2023-04-20 14:10:00 2023-04-20 16:10:00+02:00 235.8 3.052 4380.89 69.58682 0.930349 0.000823 0.108857 -10.44042 0.311665
    	
            

request_fields = [

            "DeltaPercent", "GammaPercent", "RhoPercent",

            "ThetaPercent", "VegaPercent"]

for i_str in ["ErrorMessage", "MarketValueInDealCcy", "RiskFreeRatePercent",

                      "UnderlyingPrice", "Volatility"][::-1]:

            request_fields.insert(0, i_str)

print(request_fields)

['ErrorMessage', 'MarketValueInDealCcy', 'RiskFreeRatePercent', 'UnderlyingPrice', 'Volatility', 'DeltaPercent', 'GammaPercent', 'RhoPercent', 'ThetaPercent', 'VegaPercent']

    	
            IIVNGIPAC_exp_optn_test3.simple_graph()
        
        
    
    	
            IIVNGIPAC_exp_optn_test3.graph(mrkt_exhng_open_time='9:00', mrkt_exhng_close_time='17:00').overlay().fig
        
        
    
    	
            IIVNGIPAC_exp_optn_test3.graph(mrkt_exhng_open_time='9:00', mrkt_exhng_close_time='17:00').stack3().fig
        
        
    
    	
            

print("Index Implied Volatility aNd Greeks Instrument Pricing Analytics Calculation (IndxImpVolatNGreeksIPACalc) (IIVNGIPAC) expiered option test At Trade only:")

IIVNGIPAC_exp_optn_AT_test1 = IndxImpVolatNGreeksIPACalc(

    time_of_calc_datetime=datetime.strptime("2023-04-01", '%Y-%m-%d'),

    after=15,

    index_underlying=".STOXX50E",

    call_put='Call')

IIVNGIPAC_exp_optn_AT_test2: IndxImpVolatNGreeksIPACalc = IIVNGIPAC_exp_optn_AT_test1.search_index_opt_ATM(

    debug=False,

    call_or_put='Put',

    search_fields=["ExchangeCode", "UnderlyingQuoteName"],

    include_weekly_opts=False,

    top_nu_srch_results=10)

IIVNGIPAC_exp_optn_AT_test3: IndxImpVolatNGreeksIPACalc = IIVNGIPAC_exp_optn_AT_test2.IPA_calc(

    debug=False,

    AT_opn_trade_only=True)

print(f"IIVNGIPAC_exp_optn_AT_test3.instrument: {IIVNGIPAC_exp_optn_AT_test3.instrument}")

print(f"IIVNGIPAC_exp_optn_AT_test3.ATM_opt: {IIVNGIPAC_exp_optn_AT_test3.ATM_opt}")

print(f"IIVNGIPAC_exp_optn_AT_test3.maturity: {IIVNGIPAC_exp_optn_AT_test3.maturity}")

if pd.to_datetime(IIVNGIPAC_exp_optn_AT_test3.maturity) > datetime.now():

    print(f"IIVNGIPAC_exp_optn_AT_test3.instrument_info:")

    display(IIVNGIPAC_exp_optn_AT_test3.instrument_info)

Index Implied Volatility aNd Greeks Instrument Pricing Analytics Calculation (IndxImpVolatNGreeksIPACalc) (IIVNGIPAC) expiered option test At Trade only:
IIVNGIPAC_exp_optn_AT_test3.instrument: STXE41500aD3.EX^D23
IIVNGIPAC_exp_optn_AT_test3.ATM_opt: STXE41500aD3.EX^D23
IIVNGIPAC_exp_optn_AT_test3.maturity: 2023-04-21 00:00:00

  STXE41500aD3.EX^D23 MarketValueInDealCcy RiskFreeRatePercent UnderlyingPrice Volatility DeltaPercent GammaPercent RhoPercent ThetaPercent VegaPercent
gmt Romance Summer Time                  
2022-09-30 15:10:00 2022-09-30 17:10:00+02:00 11.9 1.173 3321.43 19.676604 0.059823 0.000243 1.035673 -0.127868 2.92606
2022-11-24 09:20:00 2022-11-24 11:20:00+02:00 102.3 1.908 3968.03 18.397388 0.347736 0.000789 5.166494 -0.514168 9.243615
... ... ... ... ... ... ... ... ... ... ...
2023-04-20 09:20:00 2023-04-20 11:20:00+02:00 230.7 3.052 4380.89 45.251846 0.980195 0.000414 0.137612 -2.190143 0.121851
2023-04-20 14:10:00 2023-04-20 16:10:00+02:00 235.8 3.052 4380.89 72.055437 0.922719 0.000861 0.10791 -11.711597 0.337491
    	
            IIVNGIPAC_exp_optn_AT_test3.simple_graph()
        
        
    
    	
            IIVNGIPAC_exp_optn_AT_test3.graph(mrkt_exhng_open_time='9:00', mrkt_exhng_close_time='17:00').overlay().fig
        
        
    
    	
            IIVNGIPAC_exp_optn_AT_test3.graph(mrkt_exhng_open_time='9:00', mrkt_exhng_close_time='17:00').stack3().fig
        
        
    
    	
            rd.close_session() # close the RD session opend at the start
        
        
    
  • Login
  • Please Login
Contact Us MyRefinitiv