Overall Statistics
Total Trades
26
Average Win
2.73%
Average Loss
-0.93%
Compounding Annual Return
8.373%
Drawdown
8.800%
Expectancy
0.210
Net Profit
2.310%
Sharpe Ratio
0.421
Probabilistic Sharpe Ratio
35.783%
Loss Rate
69%
Win Rate
31%
Profit-Loss Ratio
2.93
Alpha
0.262
Beta
-0.334
Annual Standard Deviation
0.233
Annual Variance
0.054
Information Ratio
-1.27
Tracking Error
0.309
Treynor Ratio
-0.294
Total Fees
$26.00
class LongStrangleAlgorithm(QCAlgorithm):

    def Initialize(self):
        self.SetStartDate(2020, 7, 1)
        # self.SetEndDate(2017, 5, 30)
        self.SetCash(10000)
        equity = self.AddEquity("SPY", Resolution.Minute)
        equity.SetDataNormalizationMode(DataNormalizationMode.Raw)
        self.underlyingsymbol = equity.Symbol
        self.SetBenchmark(equity.Symbol)
        
        tlt = self.AddEquity("TLT", Resolution.Hour)
        self.tlt_sma_long = self.SMA("TLT", 200, Resolution.Hour)
        self.tlt_sma_short = self.SMA("TLT", 50, Resolution.Hour)
        
        self.Schedule.On(
            self.DateRules.Every(DayOfWeek.Thursday), 
            self.TimeRules.BeforeMarketClose("SPY", 60), 
            self.ClosePositions
        ) 
        
        self.Schedule.On(
            self.DateRules.Every(DayOfWeek.Monday), 
            self.TimeRules.AfterMarketOpen("SPY", 120), 
            self.OpenPosition
        )
        
        self.Schedule.On(
            self.DateRules.Every(DayOfWeek.Monday), 
            self.TimeRules.AfterMarketOpen("SPY", 122), 
            self.Operate
        ) 
        
        # self.SetWarmUp(200)
    
    def Operate(self):
        
        if self.tlt_sma_long < self.tlt_sma_short:
            self.MarketOrder(self.call, 1)
        else:
            self.MarketOrder(self.put, 1)
        

    def OpenPosition(self):

        ''' OptionChainProvider gets the option chain provider,
            used to get the list of option contracts for an underlying symbol.
            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 '''

        if not self.Portfolio.Invested:
            filtered_contracts = self.InitialFilter(-10, 10, 3, 7)
            if len(filtered_contracts) == 0: return
                
            
            # sorted the optionchain by expiration date and choose the furthest date
            expiry = sorted(filtered_contracts,key = lambda x: x.ID.Date, reverse=False)[0].ID.Date
            # filter the call options from the contracts expires on that date
            call = [i for i in filtered_contracts if i.ID.Date == expiry and i.ID.OptionRight == OptionRight.Call]
            # sorted the contracts according to their strike prices 
            call_contracts = sorted(call,key = lambda x: x.ID.Date)    
            # choose the deep OTM call option
            self.call = call_contracts[-1]
                    
            self.AddOptionContract(self.call, Resolution.Minute)

                
            
            # sorted the optionchain by expiration date and choose the furthest date
            expiry = sorted(filtered_contracts,key = lambda x: x.ID.Date, reverse=False)[0].ID.Date
            # select the put options which have the same expiration date with the call option 
            # sort the put options by strike price
            put_contracts = sorted([i for i in filtered_contracts if i.ID.Date == expiry and i.ID.OptionRight == OptionRight.Put], key = lambda x: x.ID.Date)
            # choose the deep OTM put option
            self.put = put_contracts[0]
            self.AddOptionContract(self.put, Resolution.Minute)          
                
                
                         


    def InitialFilter(self, min_strike_rank, max_strike_rank, min_expiry, max_expiry):
        
        ''' This method is an initial filter of option contracts 
            according to the range of strike price and the expiration date '''
            
        contracts = self.OptionChainProvider.GetOptionContractList(self.underlyingsymbol, self.Time.date())
        if len(contracts) == 0 : return []
        # fitler the contracts based on the expiry range
        contract_list = [i for i in contracts if min_expiry < (i.ID.Date.date() - self.Time.date()).days < max_expiry]
        if len(contract_list) == 0: return []
        
        self.Log(contract_list)
        
        # find the strike price of ATM option
        atm_strike = sorted(contract_list,
                            key = lambda x: abs(x.ID.StrikePrice - self.Securities[self.underlyingsymbol].Price))[0].ID.StrikePrice
        strike_list = sorted(set([i.ID.StrikePrice for i in contract_list]))
        # find the index of ATM strike in the sorted strike list
        atm_strike_rank = strike_list.index(atm_strike)
        try: 
            strikes = strike_list[(atm_strike_rank + min_strike_rank):(atm_strike_rank + max_strike_rank)]
        except:
            strikes = strike_list
        filtered_contracts = [i for i in contract_list if i.ID.StrikePrice in strikes]
        
        self.Log(filtered_contracts)

        return filtered_contracts 

    def OnOrderEvent(self, orderEvent):
        self.Log(str(orderEvent))
        
    def ClosePositions(self):
        if self.Portfolio.Invested:
            self.Liquidate()