| 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