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