Overall Statistics
Total Trades
119
Average Win
5.08%
Average Loss
-3.73%
Compounding Annual Return
15.850%
Drawdown
26.700%
Expectancy
0.321
Net Profit
80.272%
Sharpe Ratio
0.801
Probabilistic Sharpe Ratio
28.953%
Loss Rate
44%
Win Rate
56%
Profit-Loss Ratio
1.36
Alpha
0.032
Beta
0.862
Annual Standard Deviation
0.23
Annual Variance
0.053
Information Ratio
0.047
Tracking Error
0.157
Treynor Ratio
0.214
Total Fees
$297.50
Estimated Strategy Capacity
$2800000.00
from datetime import timedelta
from QuantConnect.Data.Custom.CBOE import *

class BasicTemplateAlgorithm(QCAlgorithm):

    '''Basic template algorithm simply initializes the date range and cash'''

    def Initialize(self):

        self.SetStartDate(2017,1, 1)  #Set Start Date
        self.SetEndDate(2020,12,31)    #Set End Date
        self.SetCash(100000)           #Set Strategy Cash
        self.equity = self.AddEquity("SPY", Resolution.Minute)
        self.equity.SetDataNormalizationMode(DataNormalizationMode.Raw)
        self.rsi = self.RSI("SPY", 14, MovingAverageType.Simple, Resolution.Minute)
        self.contract = str()
        self.contractsAdded = set()
        self.symbol = self.equity.Symbol

        self.DaysBeforeExp = 2 # number of days before expiry to exit
        self.DTE = 25 # target days till expiration
        self.OTM = 0.01 # target percentage OTM of put

        # schedule Plotting function 30 minutes after every market open
        self.Schedule.On(self.DateRules.EveryDay(self.symbol), \
                        self.TimeRules.AfterMarketOpen(self.symbol, 30), \
                        self.Plotting)
        self.SetWarmUp(14)

    

    def OnData(self, data):

        if self.IsWarmingUp:
            return

        if self.rsi.Current.Value < 15:
                self.Debug("RSI is less than 15")
                self.BuyCall(data)
                
        if self.contract:
            if (self.contract.ID.Date - self.Time) <= timedelta(self.DaysBeforeExp):
                self.Liquidate(self.contract)
                self.Log("Closed: too close to expiration")
                self.contract = str()

 

    def BuyCall(self, data):
        # get option data
        if self.contract == str():
            self.contract = self.OptionsFilter(data)
            return

# if not invested and option data added successfully, buy option
        elif not self.Portfolio[self.contract].Invested and data.ContainsKey(self.contract):
            self.Buy(self.contract, 10)


    def OptionsFilter(self, data):

        ''' OptionChainProvider gets a list of option contracts for an underlying symbol at requested date.
            Then you can manually filter the contract list returned by GetOptionContractList.
            The manual filtering will be limited to the information included in the Symbol
            (strike, expiration, type, style) and/or prices from a History call '''


        contracts = self.OptionChainProvider.GetOptionContractList(self.symbol, data.Time)
        self.underlyingPrice = self.Securities[self.symbol].Price
        # filter the out-of-money put options from the contract list which expire close to self.DTE num of days from now
        otm_puts = [i for i in contracts if i.ID.OptionRight == OptionRight.Call and
                                            self.underlyingPrice - i.ID.StrikePrice > self.OTM * self.underlyingPrice and
                                            self.DTE - 8 < (i.ID.Date - data.Time).days < self.DTE + 8]

        if len(otm_puts) > 0:
            # sort options by closest to self.DTE days from now and desired strike, and pick first
            contract = sorted(sorted(otm_puts, key = lambda x: abs((x.ID.Date - self.Time).days - self.DTE)),
                                                     key = lambda x: self.underlyingPrice - x.ID.StrikePrice)[0]
            if contract not in self.contractsAdded:
                self.contractsAdded.add(contract)
                # use AddOptionContract() to subscribe the data for specified contract
                self.AddOptionContract(contract, Resolution.Minute)
            return contract

        else:
            return str()


    def Plotting(self):
        # plot underlying's price
        self.Plot("Data Chart", self.symbol, self.Securities[self.symbol].Close)
        # plot strike of put option

        
        option_invested = [x.Key for x in self.Portfolio if x.Value.Invested and x.Value.Type==SecurityType.Option]
        if option_invested:
                self.Plot("Data Chart", "strike", option_invested[0].ID.StrikePrice)

        self.Plot("Indicators","RSI", self.rsi.Current.Value)

    def OnOrderEvent(self, orderEvent):
        # log order events
        self.Log(str(orderEvent))