Overall Statistics |
Total Trades 57 Average Win 0.33% Average Loss -2.34% Compounding Annual Return 5.842% Drawdown 5.200% Expectancy 0.101 Net Profit 6.830% Sharpe Ratio 0.863 Probabilistic Sharpe Ratio 42.612% Loss Rate 4% Win Rate 96% Profit-Loss Ratio 0.14 Alpha 0 Beta 0 Annual Standard Deviation 0.048 Annual Variance 0.002 Information Ratio 0.863 Tracking Error 0.048 Treynor Ratio 0 Total Fees $56.00 Estimated Strategy Capacity $0 Lowest Capacity Asset TSLA XVXWE5AZKZ5Y|TSLA UNU3P8Y3WFAD |
#region imports from AlgorithmImports import * #endregion class SafeCoveredCallAlphaModel(AlphaModel): options = {} algorithm = None sliceData = None def __init__(self, ticker, option): self.ticker = ticker self.option = option # self.Schedule.On( # self.DateRules.EveryDay(self.symbol), \ # self.TimeRules.Every(timedelta(minutes=15)), \ # self.MonitorIronCondor # ) def MonitorCoveredCall(self): insights = [] # if already invested in this position then check if it expires today stockPrice = self.algorithm.Securities[self.ticker].Price for ecall in self.UnderlyingSoldOptions(OptionRight.Call): close = False if ecall.UnrealizedProfitPercent * 100 > 95: close = True elif stockPrice > ecall.Security.StrikePrice: close = True elif self.__ExpiresIn(ecall.Security) == 0: close = True if close: insights.append(Insight.Price( ecall.Symbol, Resolution.Minute, 1, InsightDirection.Flat )) return insights def Scanner(self, data): # for kvp in data.OptionChains: # underlyingSymbol = kvp.Key.Underlying # if not algorithm.IsMarketOpen(underlyingSymbol): # return # contracts = kvp.Value.Contracts.Values # zero_delta = 0 # for contract in contracts: # delta = contract.Greeks.Delta # if delta == 0: # zero_delta += 1 # if zero_delta > 0: # self.Debug(f'Zero delta in {100 * zero_delta / len(contracts)}% of contracts on {algorithm.Time}') insights = [] # IF RSI is > 40 -- NO # | YES # if 30 > self.RSIValue.Current.Value or self.RSIValue.Current.Value > 70: # return # 0 positions covered calls -- NO # | YES if len(self.UnderlyingSoldOptions(OptionRight.Call)) > 0: return insights # scannerTimes = [self.TimeRules.At(10, 0), self.TimeRules.At(10, 15), self.TimeRules.At(10, 45)] # dateRules = self.DateRules.Every(DayOfWeek.Tuesday, DayOfWeek.Thursday) # if self.options == {}: return insights chain = data.OptionChains.GetValue(self.option) if chain is None: return insights # The way we are defining expiration here is by taking an absolute value. So it might just be __ExpiresIn(x) > expiration contracts = [x for x in chain if self.__ExpiresIn(x) >= 14] # # only tradable contracts # # !!IMPORTANT!!: to escape the error `Backtest Handled Error: The security with symbol 'SPY 220216P00425000' is marked as non-tradable.` contracts = [x for x in contracts if self.algorithm.Securities[x.Symbol].IsTradable] if contracts is None: return insights call = min(contracts, key=lambda x: abs(x.Greeks.Delta - 0.02)) if call: insights.append(Insight.Price( call.Symbol, Resolution.Minute, 1, InsightDirection.Down )) return insights def Update(self, algorithm, data): if algorithm.IsWarmingUp: return [] self.algorithm = algorithm callsInsights = self.MonitorCoveredCall() if len(callsInsights) > 0: return callsInsights scanInsights = self.Scanner(data) if len(scanInsights) > 0: return scanInsights return [] # Returns all the covered calls of the specified underlying # @param optionType [OptionRight.Call | OptionRight.Put] # @param maxDays [Integer] number of days in the future that the contracts are filtered by def UnderlyingSoldOptions(self, optionType, maxDays = 60): contracts = [] for option in self.algorithm.Portfolio.Values: security = option.Security if (option.Type == SecurityType.Option and str(security.Underlying) == self.ticker and security.Right == optionType and option.Quantity < 0 and (security.Expiry.date() - self.algorithm.Time.date()).days < maxDays): contracts.append(option) return contracts # Method that returns a boolean if the security expires in the given days # @param security [Security] the option contract def __ExpiresIn(self, security): return (security.Expiry.date() - self.algorithm.Time.date()).days # def CoarseContractSelection(self, algorithm, security): # symbol = security.Symbol # price = security.Price # contracts = algorithm.OptionChainProvider.GetOptionContractList(symbol, algorithm.Time) # strikes = set([x.ID.StrikePrice for x in contracts]) # strikes = sorted(strikes, key=lambda x: abs(price-x))[:10] # return [x for x in contracts if (x.ID.Date - algorithm.Time).days < 15 and # x.ID.OptionRight == OptionRight.Put and # x.ID.StrikePrice in strikes] # def OnSecuritiesChanged(self, algorithm, changes): # # When the Underlying is added, add the contracts # for security in changes.AddedSecurities: # if security.Type != SecurityType.Equity: # continue # symbol = security.Symbol # contracts = self.CoarseContractSelection(algorithm, security) # if not contracts: # continue # msg = ', '.join([x.Value for x in contracts]) # algorithm.Log(f'Adding {len(contracts)} for {symbol} on {algorithm.Time}') # algorithm.optionsByEquity[symbol] = list() # for contract in contracts: # contractSymbol = algorithm.AddOptionContract(contract).Symbol # algorithm.optionsByEquity[symbol].append(contractSymbol) # # When the Underlying is removed, remove the contracts # for security in changes.RemovedSecurities: # symbol = security.Symbol # if security.Type == SecurityType.Equity: # contractSymbols = algorithm.optionsByEquity.pop(symbol, []) # for contractSymbol in contractSymbols: # algorithm.Log(f'Removing {contractSymbol} on {algorithm.Time}') # algorithm.RemoveOptionContract(contractSymbol) # def Update(self, algorithm, slice): # # Create insights for symbols up at least 10% on the day # for symbol in self.symbolData: # # If already invested, continue to next symbol # if algorithm.Securities[symbol].Invested or symbol not in slice.Bars or self.symbolData[symbol].max.Samples == 0: # continue # # Calculate return sign yesterday's close # yest_close = self.symbolData[symbol].yest_close # close = slice[symbol].Close # ret = (close - yest_close) / yest_close # high_of_day_break = close > self.symbolData[symbol].max.Current.Value # if ret >= 0.1 and high_of_day_break: # Up 10% on the day & breaks high of day # hours = algorithm.Securities[symbol].Exchange.Hours # # 1-minute before the close # closeTime = hours.GetNextMarketClose(algorithm.Time, False) - timedelta(minutes=10) # insights.append(Insight.Price(symbol, closeTime, InsightDirection.Up)) # # Update max indicator for all symbols # for symbol in self.symbolData: # if symbol in slice.Bars: # self.symbolData[symbol].max.Update(slice.Time, slice.Bars[symbol].High) # return Insight.Group(insights) # def OnSecuritiesChanged(self, algorithm, changes): # # if len(changes.AddedSecurities) > 0: # # # Get history of symbols over lookback window # # for added in changes.AddedSecurities: # # # We only care about options # # if added.Type == SecurityType.Option: # # self.options[added.Symbol] = added # for removed in changes.RemovedSecurities: # # Delete yesterday's close tracker # # self.options.pop(removed.Symbol, None) # algorithm.RemoveSecurity(removed.Symbol) # for symbol in algorithm.Securities.Keys: # if symbol.SecurityType == SecurityType.Option and symbol.Underlying == removed.Symbol: # algorithm.RemoveSecurity(symbol)
# 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 AlgorithmImports import * from Selection.OptionUniverseSelectionModel import OptionUniverseSelectionModel class TwoWeekSafeCallOptionUniverseSelectionModel(OptionUniverseSelectionModel): '''Creates option chain universes that select only the two week earliest expiry call contract and runs a user defined optionChainSymbolSelector every day to enable choosing different option chains''' def __init__(self, select_option_chain_symbols): super().__init__(timedelta(1), select_option_chain_symbols) def Filter(self, filter): '''Defines the option chain universe filter''' return (filter.Strikes(+30, +50) .Expiration(14, 25) .WeeklysOnly() .CallsOnly() .OnlyApplyFilterAtMarketOpen())
#region imports from AlgorithmImports import * #endregion 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 OptionUniverseSelectionModel2(UniverseSelectionModel): show = 0 '''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 ''' self.nextRefreshTimeUtc = datetime.min self.refreshInterval = refreshInterval self.optionChainSymbolSelector = optionChainSymbolSelector self.universeSettings = universeSettings 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) 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 if self.show < 10: self.show += 1 algorithm.Log(f"Called on {algorithm.Time}") #################### setting PricingModel ########################################## optionChain.PriceModel = OptionPriceModels.CrankNicolsonFD() #################################################################################### return OptionChainUniverse(optionChain, settings, algorithm.LiveMode) def CreateOptionChainSecurity(self, algorithm, symbol, settings): '''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 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(-1, +1) .Expiration(timedelta(10), timedelta(30))) return (filtered)
# region imports from AlgorithmImports import * # endregion from Alphas.ConstantAlphaModel import ConstantAlphaModel # from Selection.OptionUniverseSelectionModel import OptionUniverseSelectionModel from Execution.ImmediateExecutionModel import ImmediateExecutionModel from Risk.NullRiskManagementModel import NullRiskManagementModel # from UniverseSelection import OptionUniverseSelectionModel2 from SafeCoveredCallAlphaModel import SafeCoveredCallAlphaModel from TwoWeekSafeCallUniverseSelectionModel import TwoWeekSafeCallOptionUniverseSelectionModel class AddAlphaModelAlgorithm(QCAlgorithm): def Initialize(self): ''' Initialise the data and resolution required, as well as the cash and start-end dates for your algorithm. All algorithms must initialized.''' self.SetStartDate(2021, 1, 1) #Set Start Date self.SetEndDate(2022, 3, 1) #Set End Date self.SetCash(100000) #Set Strategy Cash self.ticker = self.GetParameter("ticker") self.UniverseSettings.Resolution = Resolution.Minute # equity = Symbol.Create(self.ticker, SecurityType.Equity, Market.USA) self.option = Symbol.Create(self.ticker, SecurityType.Option, Market.USA, f"?{self.ticker}") self.SetSecurityInitializer(self.CustomSecurityInitializer) # Set InteractiveBrokers Brokerage model self.SetBrokerageModel(BrokerageName.InteractiveBrokersBrokerage, AccountType.Margin) # set algorithm framework models # set framework models self.SetUniverseSelection(TwoWeekSafeCallOptionUniverseSelectionModel(self.SelectOptionChainSymbols)) # self.SetUniverseSelection(OptionUniverseSelectionModel2(timedelta(1), self.SelectOptionChainSymbols)) # self.SetAlpha(ConstantOptionContractAlphaModel(InsightType.Price, InsightDirection.Up, timedelta(hours = 0.5))) self.SetAlpha(SafeCoveredCallAlphaModel(self.ticker, self.option)) self.SetPortfolioConstruction(SingleSharePortfolioConstructionModel()) self.SetExecution(ImmediateExecutionModel()) self.SetRiskManagement(NullRiskManagementModel()) self.SetWarmUp(TimeSpan.FromDays(30)) def SelectOptionChainSymbols(self, utcTime): return [ self.option ] # https://www.quantconnect.com/forum/discussion/13199/greeks-with-optionchainprovider/p1/comment-38906 def OptionContractSecurityInitializer(self, security): if security.Type == SecurityType.Equity: symbol = security.Symbol security.VolatilityModel = StandardDeviationOfReturnsVolatilityModel(30, Resolution.Daily) for index, row in self.History(symbol, 30, Resolution.Daily).iterrows(): security.SetMarketPrice(IndicatorDataPoint(index[1], row.close)) if security.Type == SecurityType.Option: security.PriceModel = OptionPriceModels.CrankNicolsonFD() # https://www.quantconnect.com/forum/discussion/10236/options-delta-always-zero/p1/comment-29181 def CustomSecurityInitializer(self, security): '''Initialize the security with raw prices''' security.SetDataNormalizationMode(DataNormalizationMode.Raw) if security.Type == SecurityType.Equity: security.VolatilityModel = StandardDeviationOfReturnsVolatilityModel(30) history = self.History(security.Symbol, 31, Resolution.Daily) if history.empty or 'close' not in history.columns: return for time, row in history.loc[security.Symbol].iterrows(): trade_bar = TradeBar(time, security.Symbol, row.open, row.high, row.low, row.close, row.volume) security.VolatilityModel.Update(security, trade_bar) elif security.Type == SecurityType.Option: security.PriceModel = OptionPriceModels.CrankNicolsonFD() # BlackScholes() class SingleSharePortfolioConstructionModel(PortfolioConstructionModel): '''Portfolio construction model that sets target quantities to 1 for up insights and -1 for down insights''' def CreateTargets(self, algorithm, insights): targets = [] for insight in insights: targets.append(PortfolioTarget(insight.Symbol, insight.Direction)) return targets