Overall Statistics
Total Trades
68
Average Win
6.93%
Average Loss
-0.83%
Compounding Annual Return
383.712%
Drawdown
29.500%
Expectancy
5.328
Net Profit
449.830%
Sharpe Ratio
7.486
Probabilistic Sharpe Ratio
94.664%
Loss Rate
32%
Win Rate
68%
Profit-Loss Ratio
8.38
Alpha
3.302
Beta
2.702
Annual Standard Deviation
0.689
Annual Variance
0.475
Information Ratio
8.128
Tracking Error
0.55
Treynor Ratio
1.91
Total Fees
$331.48
Estimated Strategy Capacity
$70000000.00
class StatisticalArbitrage(QCAlgorithm):

    def Initialize(self):
        self.SetStartDate(2020, 4, 1)
        self.SetCash(100000)
        
        data = self.AddEquity("SPY", Resolution.Minute)
        data.SetLeverage(20)
        self.symbol = data.Symbol
        
        option = self.AddOption("SPY", Resolution.Minute)
        option.SetFilter(-20, 20, 25, 35)
        
        self.last_day = -1
        
    def OnData(self,slice):
        # Check once a day.
        if self.Time.day == self.last_day:
            return
        self.last_day = self.Time.day
            
        for i in slice.OptionChains:
            chains = i.Value

            if not self.Portfolio.Invested:
                # divide option chains into call and put options 
                calls = list(filter(lambda x: x.Right == OptionRight.Call, chains))
                puts = list(filter(lambda x: x.Right == OptionRight.Put, chains))
                
                # if lists are empty return
                if not calls or not puts: return
            
                underlying_price = self.Securities[self.symbol].Price
                expiries = [i.Expiry for i in puts]
                
                # determine expiration date nearly one month
                expiry = min(expiries, key=lambda x: abs((x.date()-self.Time.date()).days-30))
                strikes = [i.Strike for i in puts]
                
                # determine at-the-money strike
                strike = min(strikes, key=lambda x: abs(x-underlying_price))
                
                # determine 15% out-of-the-money strike
                otm_strike = min(strikes, key = lambda x:abs(x - float(0.85) * underlying_price))
        
                atm_call = [i for i in calls if i.Expiry == expiry and i.Strike == strike]
                atm_put = [i for i in puts if i.Expiry == expiry and i.Strike == strike]
                otm_put = [i for i in puts if i.Expiry == expiry and i.Strike == otm_strike]
        
                if atm_call and atm_put and otm_put:
                    options_q = int(self.Portfolio.MarginRemaining / (underlying_price * 100))

                    # Set max leverage.
                    self.Securities[atm_call[0].Symbol].MarginModel = BuyingPowerModel(20)
                    self.Securities[atm_put[0].Symbol].MarginModel = BuyingPowerModel(20)
                    self.Securities[otm_put[0].Symbol].MarginModel = BuyingPowerModel(20)
                    
                    # sell at-the-money straddle
                    self.Sell(atm_call[0].Symbol, options_q)
                    self.Sell(atm_put[0].Symbol, options_q)
                    
                    # buy 15% out-of-the-money put
                    self.Buy(otm_put[0].Symbol, options_q)
                    
                    # buy index.
                    self.SetHoldings(self.symbol, 5)
        
            invested = [x.Key for x in self.Portfolio if x.Value.Invested]
            if len(invested) == 1:
                self.Liquidate(self.symbol)
class StatisticalArbitrage(QCAlgorithm):

    def Initialize(self):
        self.SetStartDate(2020, 4, 1)
        self.SetCash(100000)
        
        data = self.AddEquity("SPY", Resolution.Minute)
        data.SetLeverage(20)
        self.symbol = data.Symbol
        
        option = self.AddOption("SPY", Resolution.Minute)
        option.SetFilter(-20, 20, 25, 35)
        
        self.last_day = -1
        
    def OnData(self,slice):
        # Check once a day.
        if self.Time.day == self.last_day:
            return
        self.last_day = self.Time.day
            
        for i in slice.OptionChains:
            chains = i.Value

            if not self.Portfolio.Invested:
                # divide option chains into call and put options 
                calls = list(filter(lambda x: x.Right == OptionRight.Call, chains))
                puts = list(filter(lambda x: x.Right == OptionRight.Put, chains))
                
                # if lists are empty return
                if not calls or not puts: return
            
                underlying_price = self.Securities[self.symbol].Price
                expiries = [i.Expiry for i in puts]
                
                # determine expiration date nearly one month
                expiry = min(expiries, key=lambda x: abs((x.date()-self.Time.date()).days-30))
                strikes = [i.Strike for i in puts]
                
                # determine at-the-money strike
                strike = min(strikes, key=lambda x: abs(x-underlying_price))
                
                # determine 15% out-of-the-money strike
                otm_strike = min(strikes, key = lambda x:abs(x - float(0.85) * underlying_price))
        
                atm_call = [i for i in calls if i.Expiry == expiry and i.Strike == strike]
                atm_put = [i for i in puts if i.Expiry == expiry and i.Strike == strike]
                otm_put = [i for i in puts if i.Expiry == expiry and i.Strike == otm_strike]
        
                if atm_call and atm_put and otm_put:
                    options_q = int(self.Portfolio.MarginRemaining / (underlying_price * 100))

                    # Set max leverage.
                    self.Securities[atm_call[0].Symbol].MarginModel = BuyingPowerModel(20)
                    self.Securities[atm_put[0].Symbol].MarginModel = BuyingPowerModel(20)
                    self.Securities[otm_put[0].Symbol].MarginModel = BuyingPowerModel(20)
                    
                    # sell at-the-money straddle
                    self.Sell(atm_call[0].Symbol, options_q)
                    self.Sell(atm_put[0].Symbol, options_q)
                    
                    # buy 15% out-of-the-money put
                    self.Buy(otm_put[0].Symbol, options_q)
                    
                    # buy index.
                    self.SetHoldings(self.symbol, 5)
        
            invested = [x.Key for x in self.Portfolio if x.Value.Invested]
            if len(invested) == 1:
                self.Liquidate(self.symbol)