Back

How to get Greeks inside of an Alpha Model

Is there an example on how to get Greeks Values from inside an Alpha Model?

I saw that the Option Universe Selection model inside the Wizard returns Option object, but Greeks are inside OptionContracts objects and they also need a Pricing Model and a little bit of WarmUp.

I'm, quite new to Python so I tried something, but I always get 0 as the value of Delta. It will be useful to have a simple example on how to get Greeks inside an Alpha Model

from Execution.ImmediateExecutionModel import ImmediateExecutionModel
from Portfolio.EqualWeightingPortfolioConstructionModel import EqualWeightingPortfolioConstructionModel
from OptionsUniverseSelectionModel import OptionsUniverseSelectionModel
from QuantConnect.Securities.Option import OptionPriceModels
from QuantConnect import Resolution
from QuantConnect import OptionRight
import numpy as np

tickers = ['SPY','QQQQ']
symbolList = []
weight = 1/len(tickers)

class CannaMaxLeverageAlphaModel(AlphaModel):
def __init__(self):
self.emitted = []
self.previousSymbols = []
pass


def OnSecuritiesChanged(self, algorithm, changes):
alloptions = changes.AddedSecurities
for ticker in symbolList:
alloptions = [x for x in alloptions if x.Symbol.ID!=ticker.ID.Underlying ]
#
myOptions = [x for x in alloptions if x.Right==OptionRight.Call]
maxExpiry = sorted(alloptions, key = lambda x: x.Expiry, reverse=True)[0]
myOptions = [x for x in alloptions if x.Expiry==maxExpiry.Expiry]

equities=[x for x in changes.AddedSecurities if x.Symbol.ID in [ticker.ID.Underlying for ticker in symbolList]]

for x in myOptions:
#pricing models: https://www.quantconnect.com/lean/documentation/topic27704.html
x.PriceModel = OptionPriceModels.CrankNicolsonFD()

#symbols = [x.Symbol for x in myOptions]
#history = algorithm.History(symbols, timedelta(days=7), Resolution.Minute)

def Update(self, algorithm, data):
insights = []
#https://www.quantconnect.com/lean/documentation/topic24200.html
chain = [x.Value for x in data.OptionChains]
contracts = [[]] * len(tickers)
for x in range(len(tickers)):
#https://www.quantconnect.com/lean/documentation/topic24233.html
contracts[x] = [y for y in chain[x] if y.Right==OptionRight.Call]
maxExpiry = sorted(contracts[x], key = lambda y: y.Expiry, reverse=True)[0]
contracts[x] = [y for y in contracts[x] if y.Expiry==maxExpiry.Expiry]

symbols = []
for x in range(len(contracts)):
qta = [0] * len(contracts[x])
capacity = [0] * len(contracts[x])
i = 0
for y in contracts[x]:
qta[i] = round(algorithm.CalculateOrderQuantity(y.Symbol, weight),0)
capacity[i] = qta[i] * y.Greeks.Delta
i = i+1
imax = capacity.index(np.max(capacity))
#emit insights once
if not (contracts[x][imax].Symbol in self.emitted):
self.emitted.append(contracts[x][imax].Symbol)
symbols.append(contracts[x][imax].Symbol)

insights=[]
if symbols != []:
for x in symbols:
insights.append(Insight.Price(x,timedelta(days=28), InsightDirection.Up))
if self.previousSymbols != []:
for y in self.previousSymbols:
insights.append(Insight.Price(y,timedelta(28), InsightDirection.Flat))
self.previousSymbols = symbols
return insights

 

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.


Hi Alessio,

This is difficult to debug as the algorithm class is not published above. However, the greeks can have a value of 0 when the pricing model is not set or hasn't been warmed up properly. See the attached backtest for an example algorithm which demonstrates how we can retrieve the greeks inside an alpha model.

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.


Thank You Derek for your insights!

I've used your code to get the greeks correctly

from StopTakeUnrealizedRiskModel import StopTakeUnrealizedRiskModel
from MaximumExpiryMarginRiskModel import MaximumExpiryMarginRiskModel
from Execution.ImmediateExecutionModel import ImmediateExecutionModel
from Portfolio.EqualWeightingPortfolioConstructionModel import EqualWeightingPortfolioConstructionModel
from QuantConnect.Securities.Option import OptionPriceModels
from QuantConnect import Resolution
from QuantConnect import OptionRight
from datetime import date, datetime, timedelta

class TachyonQuantumEngine(QCAlgorithm):

def Initialize(self):
self.SetStartDate(2019, 1, 1)
self.SetCash(10000)

self.alpha = CannaMaxLeverage()
self.AddAlpha(self.alpha)

self.risk = StopTakeUnrealizedRiskModel(0.20,0.40)
self.AddRiskManagement(self.risk)
self.risk2 = MaximumExpiryMarginRiskModel(45)
self.AddRiskManagement(self.risk2)

tickers = ['SPY','QQQQ'] #di quali ticker prendere le opzioni
for x in tickers:
self.option = self.AddOption(x)
self.option.SetFilter(-25, +25, 180, 900)
# set the pricing model for Greeks and volatility
# find more pricing models https://www.quantconnect.com/lean/documentation/topic27704.html
self.option.PriceModel = OptionPriceModels.CrankNicolsonFD()

self.SetExecution(ImmediateExecutionModel())
self.SetPortfolioConstruction(EqualWeightingPortfolioConstructionModel())
self.Settings.FreePortfolioValuePercentage = 0.10
# set the warm-up period for the pricing model
self.SetWarmUp(TimeSpan.FromDays(4))



class CannaMaxLeverage(AlphaModel):
def __init__(self):
pass

def Update(self, algorithm, data):
if algorithm.IsWarmingUp or algorithm.Portfolio.Invested:
return []


#https://www.quantconnect.com/lean/documentation/topic24200.html
chain = [x.Value for x in data.OptionChains]
if len(chain):
weight = 1/len(chain)
else:
return []

contracts = [[]] * len(chain)
for x in range(len(chain)):
#https://www.quantconnect.com/lean/documentation/topic24233.html
contracts[x] = [y for y in chain[x] if y.Right==OptionRight.Call]
maxExpiry = sorted(contracts[x], key = lambda y: y.Expiry, reverse=True)[0]
contracts[x] = [y for y in contracts[x] if y.Expiry==maxExpiry.Expiry]

symbols = []
for x in range(len(contracts)):
qta = [0] * len(contracts[x])
capacity = [0] * len(contracts[x])
i = 0
for y in contracts[x]:
qta[i] = round(algorithm.CalculateOrderQuantity(y.Symbol, weight),0)
capacity[i] = qta[i] * y.Greeks.Delta
i = i+1
imax = capacity.index(np.max(capacity))
symbols.append(contracts[x][imax].Symbol)

insights=[]
if symbols != []:
for x in symbols:
insights.append(Insight.Price(x,timedelta(days=900), InsightDirection.Up))
return insights

But now the problem is that it arrives at 75% and then never finish backtesting.

I also wonder if it's possible to use the Option Universe Selection Model instead of AddOption/SetFilter to get greeks inside the Alpha Model. I've tried to modify the stock code from LEAN to add the Pricing Model, but the Greeks stay at 0. Maybe it's because with this methodology they can't warmup properly?

#from Selection.OptionUniverseSelectionModel import OptionUniverseSelectionModel
from datetime import date, timedelta
# QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
# Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from clr import AddReference
from clr import GetClrType as typeof
AddReference("System")
AddReference("QuantConnect.Common")
AddReference("QuantConnect.Algorithm.Framework")

from QuantConnect import *
from QuantConnect.Securities import *
from QuantConnect.Data.Auxiliary import ZipEntryName
from QuantConnect.Data.UniverseSelection import OptionChainUniverse
from Selection.UniverseSelectionModel import UniverseSelectionModel
from datetime import datetime
from QuantConnect.Securities.Option import OptionPriceModels

class OptionUniverseSelectionModel(UniverseSelectionModel):
'''Provides an implementation of IUniverseSelectionMode that subscribes to option chains'''
def __init__(self,
refreshInterval,
optionChainSymbolSelector,
universeSettings = None,
securityInitializer = None):
'''Creates a new instance of OptionUniverseSelectionModel
Args:
refreshInterval: Time interval between universe refreshes</param>
optionChainSymbolSelector: Selects symbols from the provided option chain
universeSettings: Universe settings define attributes of created subscriptions, such as their resolution and the minimum time in universe before they can be removed
securityInitializer: [Obsolete, will not be used] Performs extra initialization (such as setting models) after we create a new security object'''
self.nextRefreshTimeUtc = datetime.min

self.refreshInterval = refreshInterval
self.optionChainSymbolSelector = optionChainSymbolSelector
self.universeSettings = universeSettings
self.securityInitializer = securityInitializer

def GetNextRefreshTimeUtc(self):
'''Gets the next time the framework should invoke the `CreateUniverses` method to refresh the set of universes.'''
return self.nextRefreshTimeUtc

def CreateUniverses(self, algorithm):
'''Creates a new fundamental universe using this class's selection functions
Args:
algorithm: The algorithm instance to create universes for
Returns:
The universe defined by this model'''
self.nextRefreshTimeUtc = (algorithm.UtcTime + self.refreshInterval).date()

uniqueUnderlyingSymbols = set()
for optionSymbol in self.optionChainSymbolSelector(algorithm.UtcTime):
if optionSymbol.SecurityType != SecurityType.Option:
raise ValueError("optionChainSymbolSelector must return option symbols.")

# prevent creating duplicate option chains -- one per underlying
if optionSymbol.Underlying not in uniqueUnderlyingSymbols:
uniqueUnderlyingSymbols.add(optionSymbol.Underlying)
yield self.CreateOptionChain(algorithm, optionSymbol)

def CreateOptionChain(self, algorithm, symbol):
'''Creates a OptionChainUniverse for a given symbol
Args:
algorithm: The algorithm instance to create universes for
symbol: Symbol of the option
Returns:
OptionChainUniverse for the given symbol'''
if symbol.SecurityType != SecurityType.Option:
raise ValueError("CreateOptionChain requires an option symbol.")

# rewrite non-canonical symbols to be canonical
market = symbol.ID.Market
underlying = symbol.Underlying
if not symbol.IsCanonical():
alias = f"?{underlying.Value}"
symbol = Symbol.Create(underlying.Value, SecurityType.Option, market, alias)

# resolve defaults if not specified
settings = self.universeSettings if self.universeSettings is not None else algorithm.UniverseSettings
initializer = self.securityInitializer if self.securityInitializer is not None else algorithm.SecurityInitializer
# create canonical security object, but don't duplicate if it already exists
securities = [s for s in algorithm.Securities if s.Key == symbol]
if len(securities) == 0:
optionChain = self.CreateOptionChainSecurity(algorithm, symbol, settings, initializer)
else:
optionChain = securities[0]

# set the option chain contract filter function
optionChain.SetFilter(self.Filter)

# force option chain security to not be directly tradable AFTER it's configured to ensure it's not overwritten
optionChain.IsTradable = False

#################### setting PricingModel ##########################################
optionChain.PriceModel = OptionPriceModels.CrankNicolsonFD()
####################################################################################

return OptionChainUniverse(optionChain, settings, initializer, algorithm.LiveMode)

def CreateOptionChainSecurity(self, algorithm, symbol, settings, initializer):
'''Creates the canonical option chain security for a given symbol
Args:
algorithm: The algorithm instance to create universes for
symbol: Symbol of the option
settings: Universe settings define attributes of created subscriptions, such as their resolution and the minimum time in universe before they can be removed
initializer: [Obsolete, will not be used] Performs extra initialization (such as setting models) after we create a new security object
Returns
Option for the given symbol'''
config = algorithm.SubscriptionManager.SubscriptionDataConfigService.Add(typeof(ZipEntryName),
symbol,
settings.Resolution,
settings.FillForward,
settings.ExtendedMarketHours,
False)

return algorithm.Securities.CreateSecurity(symbol, config, settings.Leverage, False)

def Filter(self, filter):
## Cerco le opzioni tra +/- 10 strike, a partire da 6 mesi in avanti(180) +1 anno(540) o +2 anni(900)
# vorrei solo le Call LEAPS di Gennaio
#filter è un tipo particolare di oggetto:
#https://www.quantconnect.com/lean/documentation/topic26710.html
filtered = (filter.Strikes(-25, +25)
.Expiration(timedelta(180), timedelta(900)))

return (filtered)

 

0

Hi Alessio,

The TachyonQuantumEngine algorithm filters to such a large amount of contracts, the backtest takes too long for me to reproduce the issue of it stopping after 75% completion. Just backtesting over a couple of months, the algorithm was producing errors related to the available margin. I adjusted the FreePortfolioValuePercentage to 0.5 and it allowed an error-free backtest over this time period. Note that these alterations were made while disabling the custom risk management models since their definitions were not included in the code above. So I can further assist with debugging, can you publish the date of when the backtest fails and attach the code for the risk management models?

We can use the OptionUniverseSelectionModel. We just need to ensure we warm up the underlying volatility model. This is done in the alpha model's OnSecuritiesChanged method.

def OnSecuritiesChanged(self, algorithm, changes):
equities = [x for x in changes.AddedSecurities if x.Type == SecurityType.Equity]
if equities:
symbols = [x.Symbol for x in equities]
history = algorithm.History(symbols, 30, Resolution.Daily)
for equity in equities:
df = history.loc[equity.Symbol]
for idx, row in df.iterrows():
tradebar = TradeBar(idx, equity.Symbol, row.open, row.high, row.low, row.close, row.volume)
equity.VolatilityModel.Update(equity, tradebar)

See the attached backtest for reference.

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.


Thank You Derek!

Here is the code of the custom Risk Models

# QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
# Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from clr import AddReference
AddReference("System")
AddReference("QuantConnect.Common")
AddReference("QuantConnect.Algorithm")
AddReference("QuantConnect.Algorithm.Framework")

from QuantConnect import *
from QuantConnect.Algorithm import *
from QuantConnect.Algorithm.Framework import *
from QuantConnect.Algorithm.Framework.Portfolio import PortfolioTarget
from QuantConnect.Algorithm.Framework.Risk import RiskManagementModel
from datetime import timedelta

class MaximumExpiryMarginRiskModel(RiskManagementModel):
'''Provides an implementation of IRiskManagementModel that limits the unrealized profit per holding to the specified percentage'''

def __init__(self, maximumExpiry = 30):
'''Initializes a new instance of the MaximumUnrealizedProfitPercentPerSecurity class
Args:
maximumExpiry: The maximum number of days allowed till expiration. Defaults 30 days per security'''
self.maximumExpiry = abs(maximumExpiry)

def ManageRisk(self, algorithm, targets):
'''Manages the algorithm's risk at each time step
Args:
algorithm: The algorithm instance
targets: The current portfolio targets to be assessed for risk'''
targets = []
for kvp in algorithm.Securities:
security = kvp.Value

if not security.Invested:
continue

expiry = security.Holdings.Symbol.ID.Date
if timedelta(days=self.maximumExpiry) >= expiry - algorithm.Time:
# liquidate
targets.append(PortfolioTarget(security.Symbol, 0))

return targets


# QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
# Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from clr import AddReference
AddReference("System")
AddReference("QuantConnect.Common")
AddReference("QuantConnect.Algorithm")
AddReference("QuantConnect.Algorithm.Framework")

from QuantConnect import *
from QuantConnect.Algorithm import *
from QuantConnect.Algorithm.Framework import *
from QuantConnect.Algorithm.Framework.Portfolio import PortfolioTarget
from QuantConnect.Algorithm.Framework.Risk import RiskManagementModel

class StopTakeUnrealizedRiskModel(RiskManagementModel):
'''Provides an implementation of IRiskManagementModel that limits the unrealized profit per holding to the specified percentage'''

def __init__(self, takeUnrealized = 0.05, stopUnrealized = 0.05):
'''Initializes a new instance of the MaximumUnrealizedProfitPercentPerSecurity class
Args:
takeUnrealized: The maximum percentage unrealized profit allowed for any single security holding, defaults to 5% gain per security
stopUnrealized: The maximum percentage unrealized Loss allowed for any single security holding, defaults to 5% drawdown per security'''
self.takeUnrealized = abs(takeUnrealized)
self.stopUnrealized = -1 * abs(stopUnrealized)

def ManageRisk(self, algorithm, targets):
'''Manages the algorithm's risk at each time step
Args:
algorithm: The algorithm instance
targets: The current portfolio targets to be assessed for risk'''
targets = []
for kvp in algorithm.Securities:
security = kvp.Value

if not security.Invested:
continue

pnl = security.Holdings.UnrealizedProfitPercent
if (pnl > self.takeUnrealized) or (pnl < self.stopUnrealized):
# liquidate
targets.append(PortfolioTarget(security.Symbol, 0))

return targets

I think the problem is between February and March, maybe the strong depreciation of that period pushes too many contracts in the chain?

Also by removing some contracts, in that period the algorithm is too slow. Used this code to remove some Options:

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

Best Regards.

0

Hi Alessio,

Thank you for providing the code for the risk models. After backtesting this algorithm over the February and March periods, it seems the algorithm tries to invest in option contracts deemed non-tradable. To fix this, we can extend our contract filtering process to check if the security is tradeable.

contracts[x] = [y for y in chain[x] if y.Right==OptionRight.Call and algorithm.Securities[y.Symbol].IsTradable]

See the attached backtest for reference.

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