Overall Statistics |
Total Trades 2 Average Win 2.00% Average Loss 0% Compounding Annual Return -96.799% Drawdown 15.100% Expectancy -1 Net Profit -12.916% Sharpe Ratio -2.571 Probabilistic Sharpe Ratio 1.809% Loss Rate 100% Win Rate 0% Profit-Loss Ratio 0 Alpha 0 Beta 0 Annual Standard Deviation 0.355 Annual Variance 0.126 Information Ratio -2.571 Tracking Error 0.355 Treynor Ratio 0 Total Fees $1.25 Estimated Strategy Capacity $30000000.00 Lowest Capacity Asset TSLA 323AA3YGQG0CM|TSLA UNU3P8Y3WFAD Portfolio Turnover 6.50% |
#region imports from AlgorithmImports import * #endregion # 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 QuantConnect.Securities.Option import OptionPriceModels from datetime import timedelta class OptionWheelAlgorithm(QCAlgorithm): def Initialize(self): self.SetBrokerageModel(BrokerageName.InteractiveBrokersBrokerage,AccountType.Margin) self._no_K = 20 # no of strikes around ATM => for uni selection self.MIN_EXPIRY = 3 # min num of days to expiration => for uni selection self.MAX_EXPIRY = 7 # max num of days to expiration => for uni selection self.MAX_DELTA_PUT = 0.4 self.MAX_DELTA_CALL = 0.4 self.TAKE_PROFIT = 0.01 self.MIN_PREMIUM = 0.3 self.benchmarkTicker = "SPX" self.SetStartDate(2022, 12, 1) self.SetEndDate(2022, 12, 15) self.SetCash(100000) self.OPTION_COUNT = 1 self.expiry = None self.symbols = [] for ticker in ["NVDA", "TSLA"]: option = self.AddOption(ticker) self.symbols.append(option.Symbol) self.resolution = Resolution.Minute self.call, self.put, self.takeProfitTicket = None, None, None self.symbol = option.Symbol # set our strike/expiry filter for this option chain option.SetFilter(self.UniverseFunc) # for greeks and pricer (needs some warmup) - https://github.com/QuantConnect/Lean/blob/21cd972e99f70f007ce689bdaeeafe3cb4ea9c77/Common/Securities/Option/OptionPriceModels.cs#L81 # both European & American, automatically option.PriceModel = OptionPriceModels.CrankNicolsonFD() # this is needed for Greeks calcs self.SetWarmUp(TimeSpan.FromDays(60)) # timedelta(7) # use the underlying equity as the benchmark self.SetBenchmark(self.benchmarkTicker) def OnData(self, slice): if (self.IsWarmingUp): return option_invested = [ x.Key for x in self.Portfolio if x.Value.Invested and x.Value.Type == SecurityType.Option] equity_invested = [ x.Key for x in self.Portfolio if x.Value.Invested and x.Value.Type == SecurityType.Equity] invested = [ x.Key for x in self.Portfolio if x.Value.Invested ] if len(option_invested) == 1: return # If we already have underlying - check if we need to sell covered call t=self.Portfolio[self.symbol] #self.Debug(self.Portfolio[self.symbol].ToString()) if self.Portfolio[self.symbol].Quantity > 0: self.Debug("TradeCallOption "+str(t)+" "+str(invested)+" ") self.TradeCallOption(slice) else: self.Debug("TradePutOption "+str(t)+" "+str(invested)+" "+str(self.Portfolio.Cash)) self.TradePutOption(slice) def TradePutOption(self, slice): for i in slice.OptionChains: if i.Key != self.symbol: continue chain = i.Value # filter the put options contracts puts = [x for x in chain if x.Right == OptionRight.Put and abs(x.Greeks.Delta) > 0 and abs( x.Greeks.Delta) < self.MAX_DELTA_PUT and x.BidPrice > self.MIN_PREMIUM] # sorted the contracts according to their expiration dates and choose the ATM options contracts = sorted(sorted(puts, key=lambda x: x.BidPrice, reverse=True), key=lambda x: x.Expiry) if len(contracts) == 0: continue self.put = contracts[0].Symbol self.expiry = contracts[0].Expiry c=self.Portfolio.Cash u=self.Portfolio.UnsettledCash strike = contracts[0].Strike self.OPTION_COUNT = int(c / 100 / strike) # short the put options ticket = self.MarketOrder( self.put, -self.OPTION_COUNT, asynchronous=False) # set Take Profit order self.takeProfitTicket = self.LimitOrder( self.put, self.OPTION_COUNT, round(ticket.AverageFillPrice * self.TAKE_PROFIT, 2)) def TradeCallOption(self, slice): for i in slice.OptionChains: if i.Key != self.symbol: continue chain = i.Value # filter the put options contracts calls = [x for x in chain if x.Right == OptionRight.Call and abs(x.Greeks.Delta) > 0 and abs( x.Greeks.Delta) < self.MAX_DELTA_CALL and x.BidPrice > self.MIN_PREMIUM] # sorted the contracts according to their expiration dates and choose the ATM options contracts = sorted(sorted(calls, key=lambda x: x.BidPrice, reverse=True), key=lambda x: x.Expiry) if len(contracts) == 0: continue self.call = contracts[0].Symbol self.expiry = contracts[0].Expiry # short the call options ticket = self.MarketOrder( self.call, -self.OPTION_COUNT, asynchronous=False) # set Take Profit order self.takeProfitTicket = self.LimitOrder( self.call, self.OPTION_COUNT, round(ticket.AverageFillPrice * self.TAKE_PROFIT, 2)) def OnOrderEvent(self, orderEvent): self.Log("OnOrderEvent "+str(self.Portfolio[self.symbol])+" "+str(orderEvent)) def OnAssignmentOrderEvent(self, assignmentEvent): self.Debug("OnAssignmentOrderEvent "+str(self.Portfolio[self.symbol])+" "+str(assignmentEvent)) if self.takeProfitTicket != None: self.takeProfitTicket.Cancel() self.takeProfitTicket = None def UniverseFunc(self, universe): return universe.IncludeWeeklys()\ .Strikes(-self._no_K, self._no_K)\ .Expiration(timedelta(self.MIN_EXPIRY), timedelta(self.MAX_EXPIRY)) def OnFrameworkData(self): return