Back

Help: Ranking Options by Normalized Implied Volatility

Hello everyone, I'm trying to implement an implied volatility rank which I can use for decision to trade the option. I've been unable to imlement the IV rank for multiple reasons, as the IV is often calculated to be 0, and an error for list index out of range often appears.

What I'm trying to do is to choose the 5 most liquid stocks and then create a dictionary for them that holds their indicators and other values which I might access later. The first indicator I want to hold is a historical volatility indicator that will be used to normalize the implied volatility. Then, I want to store the Normalized IV back into the dictionary so I can rank the dictionary by the stocks with the highest IV and make trades off them.

 

import pandas as pd

class ParticleCalibratedCoil(QCAlgorithm):

def Initialize(self):

'''
Parameters for adjusting
'''
self.numberOfLiquidStocks = 5 # Controls the number of stocks in play

'''
Backtesting variables
'''
self.SetWarmUp(1)
self.SetStartDate(2019, 11, 18)
self.SetEndDate(2019, 12, 18)
self.SetCash(1000000)

'''
Algorithm variables
'''
self.UniverseSettings.Resolution = Resolution.Minute
self.AddUniverse(self.CoarseSelectionFilter)
self.SetSecurityInitializer(lambda x: x.SetDataNormalizationMode(DataNormalizationMode.Raw))

self.dictOfUnderlyingIndicators = {}


def CoarseSelectionFilter(self, coarse):
'''
1. Sorts each element of the coarse object by dollar volume
2. Returns a list of coarse object, limited to top 100 highest volume
3. Returns a list of symbols we want to initialise into the algorithm
'''

self.sortedByDollarVolume = sorted(coarse, key=lambda c: c.DollarVolume, reverse=True)
self.topHundredMostLiquid = self.sortedByDollarVolume[:self.numberOfLiquidStocks]

return [stock.Symbol for stock in self.topHundredMostLiquid]

def OnSecuritiesChanged (self, changes):
'''
For any new securities added into the universe
If the security is an underlying
Subscribe to the option chains

For any securities we want removed from the universe
Remove the underlying and then remove the options

For each new secury added into the universe
If there is not yet one
Create a standard deviation indicator
'''
for underlying in changes.AddedSecurities:
if underlying.Symbol.SecurityType != SecurityType.Equity: continue
option = self.AddOption(underlying.Symbol.Value, Resolution.Minute)
option.SetFilter(-5, +2, timedelta(30), timedelta(60))

if not underlying.Symbol in self.dictOfUnderlyingIndicators:
self.dictOfUnderlyingIndicators[underlying.Symbol] = \
{"Volatility": self.RSI(underlying.Symbol, 1, Resolution.Minute)}

for underlying in changes.RemovedSecurities:
self.RemoveSecurity(underlying.Symbol)
for symbol in self.Securities.Keys:
if symbol.SecurityType == SecurityType.Option and symbol.Underlying == underlying.Symbol:
self.RemoveSecurity(symbol)


def OnData(self, slice):

'''
For each OptionChain, the key is the underlying symbol object, while the
value is the option chain.
For each chain in OptionChains, each chain represents the entire chain of option contracts
for the underlying security.
'''

for chain in slice.OptionChains.Values:

# Filter for the first ATM contract
atmContract = sorted(chain, key = lambda x: abs(x.UnderlyingLastPrice - x.Strike))[0]

impliedVolatility = atmContract.ImpliedVolatility
underlyingSymbol = atmContract.UnderlyingSymbol

if underlyingSymbol in self.dictOfUnderlyingIndicators and \
"Volatility" in self.dictOfUnderlyingIndicators[underlyingSymbol]:
self.dictOfUnderlyingIndicators[underlyingSymbol] = {'NormalizedIV':
impliedVolatility / self.dictOfUnderlyingIndicators[underlyingSymbol]["Volatility"].Current.Value
}

self.Debug(f"Implied volatility of {underlyingSymbol} is {impliedVolatility}")
self.Debug(f"The normalized implied volatility is {self.dictOfUnderlyingIndicators[underlyingSymbol]['NormalizedIV']}")

 

Update Backtest








0

The material on this website is provided for informational purposes only and does not constitute an offer to sell, a solicitation to buy, or a recommendation or endorsement for any security or strategy, nor does it constitute an offer to provide investment advisory services by QuantConnect. In addition, the material offers no opinion with respect to the suitability of any security or specific investment. QuantConnect makes no guarantees as to the accuracy or completeness of the views expressed in the website. The views are subject to change, and may have become unreliable for various reasons, including changes in market conditions or economic circumstances. All investments involve risk, including loss of principal. You should consult with an investment professional before making any investment decisions.


I managed to solve the implied volatiltiy problem using

option.PriceModel = OptionPriceModels.CrankNicolsonFD()

but still seem unable to rank them by IV.

0

Edit 2: I was using the wrong indicator, should have been STD instead of RSI. But I'm still getting the list index out of range error at for chain in slice.OptionChains.Values:

0

I'm trying a new way to create indicators for above, but I don't seem to be able to get the event handler to work. How do I create an indicator for each option that I'm subscribed to? Issue is at the last two lines.

 

import pandas as pd
from QuantConnect.Securities.Option import OptionPriceModels

class ParticleCalibratedCoil(QCAlgorithm):

def Initialize(self):

'''
Parameters for adjusting
'''
self.numberOfLiquidStocks = 5 # Controls the number of stocks in play


'''
Backtesting variables
'''
self.SetWarmUp(43800)
self.SetStartDate(2019, 10, 18)
self.SetEndDate(2019, 12, 18)
self.SetCash(1000000)

'''
Algorithm variables
'''
self.UniverseSettings.Resolution = Resolution.Minute
self.AddUniverse(self.CoarseSelectionFilter)
self.SetSecurityInitializer(lambda x: x.SetDataNormalizationMode(DataNormalizationMode.Raw))

self.indicators = {}


def CoarseSelectionFilter(self, coarse):
'''
1. Sorts each element of the coarse object by dollar volume
2. Returns a list of coarse object, limited to top 100 highest volume
3. Returns a list of symbols we want to initialise into the algorithm
'''

self.sortedByDollarVolume = sorted(coarse, key=lambda c: c.DollarVolume, reverse=True)
self.topHundredMostLiquid = self.sortedByDollarVolume[:self.numberOfLiquidStocks]

return [stock.Symbol for stock in self.topHundredMostLiquid]

def OnSecuritiesChanged (self,changes):

'''
For any new securities added into the universe
If the security is an underlying
Subscribe to the option chains

For any securities we want removed from the universe
Remove the underlying and then remove the options

For each new secury added into the universe
If there is not yet one
Create a standard deviation indicator
'''
for underlying in changes.AddedSecurities:
if underlying.Symbol.SecurityType != SecurityType.Equity: continue
option = self.AddOption(underlying.Symbol.Value, Resolution.Minute)
option.SetFilter(-5, +2, timedelta(30), timedelta(60))
option.PriceModel = OptionPriceModels.CrankNicolsonFD()


if not underlying.Symbol.Value in self.indicators:
self.indicators[underlying.Symbol.Value] = \
{"Volatility": self.STD(underlying.Symbol.Value, 30, Resolution.Daily)}
self.indicators[underlying.Symbol.Value]["AverageHV"] = \
SimpleMovingAverage(365)
self.indicators[underlying.Symbol.Value]["Volatility"].Updated += self.OnVolatility

for underlying in changes.RemovedSecurities:
self.RemoveSecurity(underlying.Symbol)
for symbol in self.Securities.Keys:
if symbol.SecurityType == SecurityType.Option and symbol.Underlying == underlying.Symbol:
self.RemoveSecurity(symbol)


def OnData(self, slice):

'''
For each OptionChain, the key is the underlying symbol object, while the
value is the option chain.
For each chain in OptionChains, each chain represents the entire chain of option contracts
for the underlying security.
'''

for chain in slice.OptionChains.Values:

# Filter for the first ATM contract
if chain != None:
atmContract = sorted(chain, key = lambda x: abs(x.UnderlyingLastPrice - x.Strike))[0]

impliedVolatility = atmContract.ImpliedVolatility
underlyingSymbol = atmContract.UnderlyingSymbol

def OnVolatility(self, sender, updated):
if self.indicators[underlying.Symbol.Value]["Volatility"].IsReady:
self.indicators[underlying.Symbol.Value]["AverageHV"].Update(self.Time, updated.Value)

 

0

This is my latest attempt but still doesn't work. Would appreciate if anyone has a good solution!

 

import pandas as pd
from functools import partial
from QuantConnect.Securities.Option import OptionPriceModels

class ParticleCalibratedCoil(QCAlgorithm):

def Initialize(self):

'''
Parameters for adjusting
'''
self.numberOfLiquidStocks = 5 # Controls the number of stocks in play


'''
Backtesting variables
'''
self.SetWarmUp(43800)
self.SetStartDate(2019, 10, 18)
self.SetEndDate(2019, 12, 18)
self.SetCash(1000000)

'''
Algorithm variables
'''
self.UniverseSettings.Resolution = Resolution.Minute
self.AddUniverse(self.CoarseSelectionFilter)
self.SetSecurityInitializer(lambda x: x.SetDataNormalizationMode(DataNormalizationMode.Raw))

self.indicators = {}


def CoarseSelectionFilter(self, coarse):
'''
1. Sorts each element of the coarse object by dollar volume
2. Returns a list of coarse object, limited to top 100 highest volume
3. Returns a list of symbols we want to initialise into the algorithm
'''

self.sortedByDollarVolume = sorted(coarse, key=lambda c: c.DollarVolume, reverse=True)
self.topHundredMostLiquid = self.sortedByDollarVolume[:self.numberOfLiquidStocks]

return [stock.Symbol for stock in self.topHundredMostLiquid]

def OnSecuritiesChanged (self,changes):

'''
For any new securities added into the universe
If the security is an underlying
Subscribe to the option chains

For any securities we want removed from the universe
Remove the underlying and then remove the options

For each new secury added into the universe
If there is not yet one
Create a standard deviation indicator
'''
for underlying in changes.AddedSecurities:
if underlying.Symbol.SecurityType != SecurityType.Equity: continue
option = self.AddOption(underlying.Symbol.Value, Resolution.Minute)
option.SetFilter(-5, +2, timedelta(30), timedelta(60))
option.PriceModel = OptionPriceModels.CrankNicolsonFD()


if not underlying.Symbol.Value in self.indicators:
self.indicators[underlying.Symbol.Value] = \
{"Volatility": self.STD(underlying.Symbol.Value, 30, Resolution.Daily)}
self.indicators[underlying.Symbol.Value]["AverageHV"] = \
SimpleMovingAverage(365)

symbol = underlying.Symbol.Value
self.indicators[underlying.Symbol.Value]["Volatility"].Updated += partial(self.OnVolatility, symbol=symbol)

for underlying in changes.RemovedSecurities:
self.RemoveSecurity(underlying.Symbol)
for symbol in self.Securities.Keys:
if symbol.SecurityType == SecurityType.Option and symbol.Underlying == underlying.Symbol:
self.RemoveSecurity(symbol)


def OnData(self, slice):

'''
For each OptionChain, the key is the underlying symbol object, while the
value is the option chain.
For each chain in OptionChains, each chain represents the entire chain of option contracts
for the underlying security.
'''

for chain in slice.OptionChains.Values:

# Filter for the first ATM contract
if chain != None:
atmContract = sorted(chain, key = lambda x: abs(x.UnderlyingLastPrice - x.Strike))[0]

impliedVolatility = atmContract.ImpliedVolatility
underlyingSymbol = atmContract.UnderlyingSymbol.Value
if self.indicators[underlyingSymbol]["AverageHV"].IsReady:
historicalVolatility = self.indicators[underlyingSymbol]["AverageHV"].Current.Value
self.Debug(f"underlyingSymbol has a implied volatility of {impliedVolatility}")
self.Debug(f"underlyingSymbol has a historical volatility of {historicalVolatility}")




def OnVolatility(self, sender, updated, symbol):
if self.indicators[symbol]["Volatility"].IsReady:
self.indicators[symbol]["AverageHV"].Update(self.Time, updated.Value)

 

0

Runtime Error: IndexError : list index out of range
at OnData in main.py:line 95
IndexError : list index out of range (Open Stacktrace)

This error is very common across all my attempts, despite checking if chain is not none.

0

Hi Oscar,

The event handler is getting called properly. See the attached backtest below which prints a log message on each call. An issue with the code above is that the STD indicator is not warmed up so the handler receives a value of 0 for the first 30 calls. In the attached algorithm, we warm up the indicator after creating it. This process of warming up the indicator can also be extended to warmup the SMA of the STD as well if desired. However, I leave this to be implemented.

To resolve the IndexError issue, we just need to change line 94 in the file above from `if chain != None:` to `if chain.Contracts.Count < 1: continue`. This too is implemented in the attached algorithm.

Best,
Derek Melchin

1

The material on this website is provided for informational purposes only and does not constitute an offer to sell, a solicitation to buy, or a recommendation or endorsement for any security or strategy, nor does it constitute an offer to provide investment advisory services by QuantConnect. In addition, the material offers no opinion with respect to the suitability of any security or specific investment. QuantConnect makes no guarantees as to the accuracy or completeness of the views expressed in the website. The views are subject to change, and may have become unreliable for various reasons, including changes in market conditions or economic circumstances. All investments involve risk, including loss of principal. You should consult with an investment professional before making any investment decisions.


This process of warming up the indicator can also be extended to warmup the SMA of the STD as well if desired. However, I leave this to be implemented.

Apologies if it sounds like a dumb question, but will it work if I just change the history to 365 instead of 30? Since it should provide the SD indicator 30 days to warm up trigger the SMA updates as well?

0

Hi Oscar,

365 days of history would not be quite enough. Instead, we would need 365 + 30 = 395 days of history. The first 30 days warm up the STD indicator, then the following 365 days will update the STD indicator and warm up the SMA. It's important we only warm up the SMA when the STD IsReady.

Best,
Derek Melchin

0

The material on this website is provided for informational purposes only and does not constitute an offer to sell, a solicitation to buy, or a recommendation or endorsement for any security or strategy, nor does it constitute an offer to provide investment advisory services by QuantConnect. In addition, the material offers no opinion with respect to the suitability of any security or specific investment. QuantConnect makes no guarantees as to the accuracy or completeness of the views expressed in the website. The views are subject to change, and may have become unreliable for various reasons, including changes in market conditions or economic circumstances. All investments involve risk, including loss of principal. You should consult with an investment professional before making any investment decisions.


Update Backtest





0

The material on this website is provided for informational purposes only and does not constitute an offer to sell, a solicitation to buy, or a recommendation or endorsement for any security or strategy, nor does it constitute an offer to provide investment advisory services by QuantConnect. In addition, the material offers no opinion with respect to the suitability of any security or specific investment. QuantConnect makes no guarantees as to the accuracy or completeness of the views expressed in the website. The views are subject to change, and may have become unreliable for various reasons, including changes in market conditions or economic circumstances. All investments involve risk, including loss of principal. You should consult with an investment professional before making any investment decisions.


Loading...

This discussion is closed