#region imports
from AlgorithmImports import *
#endregion
class IronCondorAlgorithm(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2010, 1, 1)
self.SetEndDate(2010, 12, 31)
self.SetCash(10000)
#option = self.AddOption("SPY")
self.equity = self.AddEquity('SPY', Resolution.Minute)
self.symbol = self.equity.Symbol
self.expiry = datetime(2300, 5, 17)
self.InitOptionsAndGreeks(self.equity)
#option.SetOptionAssignmentModel(NullOptionAssignmentModel())
# use the underlying equity GOOG as the benchmark
self.SetBenchmark(self.symbol)
self.long_call_delta = 0.1
self.short_call_delta = 0.25
self.short_put_delta = 0.25
self.long_put_delta = 0.1
self.DTE = 45
self.strikeCount = 500 # no of strikes around underyling price => for universe selection
self.minExpiryDTE = 40 # min num of days to expiration => for uni selection
self.maxExpiryDTE = 50 # max num of days to expiration => for uni selection
def SelectContractByDelta(self, symbolArg, strikeDeltaArg, expiryDTE, optionRightArg):
canonicalSymbol = self.AddOption(symbolArg)
self.canonicalSymbol = canonicalSymbol
if (self.CurrentSlice.OptionChains.values().__len__()>0):
theOptionChain = self.CurrentSlice.OptionChains[canonicalSymbol.Symbol]
theExpiryDate = self.Time + timedelta(days=expiryDTE)
## Filter the Call/Put options contracts
filteredContracts = [x for x in theOptionChain if x.Right == optionRightArg]
## Sort the contracts according to their closeness to our desired expiry
contractsSortedByExpiration = sorted(filteredContracts, key=lambda p: abs(p.Expiry - theExpiryDate), reverse=False)
closestExpirationDate = contractsSortedByExpiration[0].Expiry
## Get all contracts for selected expiration
contractsMatchingExpiryDTE = [contract for contract in contractsSortedByExpiration if contract.Expiry == closestExpirationDate]
if not contractsMatchingExpiryDTE:
self.Debug("No contracts found matching the closest expiration date.")
return None
## Get the contract with the contract with the closest delta
closestContract = min(contractsMatchingExpiryDTE, key=lambda x: abs(abs(x.Greeks.Delta)-strikeDeltaArg))
return closestContract
def OptionsFilterFunction(self, optionsContractsChain):
strikeCount = self.strikeCount # no of strikes around underyling price => for universe selection
minExpiryDTE = self.minExpiryDTE # min num of days to expiration => for uni selection
maxExpiryDTE = self.maxExpiryDTE # max num of days to expiration => for uni selection
return optionsContractsChain.IncludeWeeklys()\
.Strikes(-strikeCount, strikeCount)\
.Expiration(timedelta(minExpiryDTE), timedelta(maxExpiryDTE))
## Initialize Options settings, chain filters, pricing models, etc
## ====================================================================
def InitOptionsAndGreeks(self, theEquity ):
## 1. Specify the data normalization mode (must be 'Raw' for options)
theEquity.SetDataNormalizationMode(DataNormalizationMode.Raw)
## 2. Set Warmup period of at leasr 30 days
self.SetWarmup(30, Resolution.Daily)
## 3. Set the security initializer to call SetMarketPrice
self.SetSecurityInitializer(lambda x: x.SetMarketPrice(self.GetLastKnownPrice(x)))
## 4. Subscribe to the option feed for the symbol
theOptionSubscription = self.AddOption(theEquity.Symbol)
## 5. set the pricing model, to calculate Greeks and volatility
theOptionSubscription.PriceModel = OptionPriceModels.CrankNicolsonFD() # both European & American, automatically
## 6. Set the function to filter out strikes and expiry dates from the option chain
theOptionSubscription.SetFilter(self.OptionsFilterFunction)
def OnData(self,slice):
#for security in self.Securities.Values:
# if security.Invested and security.Symbol.SecurityType == SecurityType.Option:
# days_till_expiry = (security.Expiry - self.Time).days
# #self.Log(f'Days till expiry: {days_till_expiry}')
#
# trading_days_till_expiry = list(self.TradingCalendar.GetDaysByType(TradingDayType.BusinessDay, self.Time, self.Time + (security.Expiry - self.Time)))
# #self.Log(f'Trading Days till expiry: {len(trading_days_till_expiry)}')
#
# # do buying/selling logic here
# if days_till_expiry < 0: # 0 = disable
# self.Liquidate()
days_till_expiry = (self.expiry - self.Time).days
if days_till_expiry < 10: # 0 = disable
self.Liquidate()
# If there is underlying assets in portfolio at expiration, liquidate the stocks in order to roll into new contracts
if self.Portfolio[self.symbol].Invested:
self.Liquidate()
if self.Portfolio.Invested or not self.IsMarketOpen(self.symbol):
return
# Select the strikes in the strategy legs
far_put = self.SelectContractByDelta(self.equity.Symbol,self.long_put_delta,self.DTE,OptionRight.Put)
near_put = self.SelectContractByDelta(self.equity.Symbol,self.short_put_delta,self.DTE,OptionRight.Put)
near_call = self.SelectContractByDelta(self.equity.Symbol,self.short_call_delta,self.DTE,OptionRight.Call)
far_call = self.SelectContractByDelta(self.equity.Symbol,self.long_call_delta,self.DTE,OptionRight.Call)
# expiry = self.SelectContractByDelta(self.equity.Symbol,self.long_call_delta,self.DTE,OptionRight.Call).Expiry
self.expiry = datetime(2300, 5, 17)
if not far_call is None:
self.expiry = far_call.Expiry
try:
self.iron_condor = OptionStrategies.IronCondor(
self.canonicalSymbol.Symbol,
far_put.Strike,
near_put.Strike,
near_call.Strike,
far_call.Strike,
far_call.Expiry)
self.Sell(self.iron_condor, 1)
self.Debug(f"{self.Time}: Selling IC {far_put.Strike} - {near_put.Strike} | {near_put.UnderlyingLastPrice} | {near_call.Strike} - {far_call.Strike}, for {near_put.BidPrice + near_call.BidPrice - far_call.AskPrice - far_put.AskPrice}")
except:
#self.Debug(f"{self.Time}: Error with the following strikes:{far_put}, {near_put}, {near_call}, {far_call}. Skipping. ")
return
#def OnOrderEvent(self, orderEvent):
# if orderEvent.Status == 3:
# self.Debug(orderEvent)