Overall Statistics
Total Trades
70920
Average Win
0.11%
Average Loss
-0.03%
Compounding Annual Return
-100%
Drawdown
100%
Expectancy
-0.969
Net Profit
-100%
Sharpe Ratio
-0.142
Probabilistic Sharpe Ratio
0%
Loss Rate
99%
Win Rate
1%
Profit-Loss Ratio
3.28
Alpha
-1.314
Beta
9.581
Annual Standard Deviation
7.018
Annual Variance
49.256
Information Ratio
-0.148
Tracking Error
6.994
Treynor Ratio
-0.104
Total Fees
$70916.00
Estimated Strategy Capacity
$0
Lowest Capacity Asset
SDC 31RZMH42OKLOM|SDC X7SGNKRL4WV9
class SwimmingFluorescentOrangePanda(QCAlgorithm):

    def Initialize(self):
        self.SetStartDate(2021, 9, 20)  # Set Start Date
        self.SetCash(1000000)  # Set Strategy Cash
        self.AddEquity("SPY")
        
        self.rankFactor = {}
        self.selected = []
        self.selectedOptionSymbols = []
        
        self.AddUniverse(self.CustomCoarseUniverseSelection)
        
        self.SetSecurityInitializer(lambda x: x.SetDataNormalizationMode(DataNormalizationMode.Raw))
    
    def CustomCoarseUniverseSelection(self, coarse):
        # Assume only equities within the top 200 dollar volume have options
        sortedByDollarVolume = sorted(coarse, key=lambda x: x.DollarVolume, reverse=True)[:200]
        
        for c in sortedByDollarVolume:
            if c.Symbol not in self.rankFactor:
                self.rankFactor[c.Symbol] = SymbolData(self, c.Symbol)
                
            self.rankFactor[c.Symbol].Update(c.Price)
            
        sortedByRankFactor = sorted(self.rankFactor.items(), key=lambda x: x[1].factor, reverse=True)[:10]
        
        # Option selection
        self.selectedOptionSymbols = self.OptionSelection(sortedByRankFactor)
        
        return self.selectedOptionSymbols
        
    def OptionSelection(self, dict_):
        optionsList = []
        
        for symbol, symbolData in dict_:
            contracts = self.OptionChainProvider.GetOptionContractList(symbol, self.Time)
            if not contracts: continue
            
            # We only want close-expiratory options within 30 days
            contractList = [i for i in contracts if (i.ID.Date.date() - self.Time.date()).days <= 30]
            
            # find the strike price of ATM option
            atmStrike = sorted(contractList,
                                key = lambda x: abs(x.ID.StrikePrice - symbolData.price))[0].ID.StrikePrice
            
            # limit OTM and ITM range
            filteredContracts = [i for i in contractList if (i.ID.StrikePrice >= atmStrike * 0.99
                                                            and i.ID.StrikePrice <= atmStrike * 1.05
                                                            and i.ID.OptionRight == OptionRight.Call)
                                                            or
                                                            (i.ID.StrikePrice >= atmStrike * 0.95
                                                            and i.ID.StrikePrice <= atmStrike * 1.01
                                                            and i.ID.OptionRight == OptionRight.Put)]
    
            optionsList += filteredContracts
        
        if optionsList:
            history = self.History(optionsList, timedelta(days=3), Resolution.Minute)
            if history.empty: return []
        
            history = history.close
            history.index = pd.MultiIndex.from_tuples([x[-2:] for x in history.index], names=["symbol", "time"])
            history = history.unstack(0).resample("D").last()
            history = history.loc[~(history==0).all(axis=1)]
            pctChg = history.pct_change().iloc[-1]
            sortByPctChg = pctChg.sort_values(axis=0, ascending=False)
            # select top 20
            selectedOptionSymbols = sortByPctChg.iloc[:min(20, len(sortByPctChg))].index
            
            return [self.AddOptionContract(x, Resolution.Minute).Symbol for x in selectedOptionSymbols]
            
        return []
            
    def OnData(self, data):
        self.Liquidate()
                
        for symbol in self.selectedOptionSymbols:
            self.Buy(symbol, 1)

class SymbolData:
    
    def __init__(self, algorithm, symbol):
        self.window = RollingWindow[float](253)
        self.price = 0
        self.factor = 0
        
        history = algorithm.History(symbol, 253, Resolution.Daily)
        for bar in history.iloc[:-1].itertuples():
            self.window.Add(bar.close)
            
    def Update(self, close):
        self.price = close
        self.window.Add(close)
        
        if self.window.IsReady:
            returns = pd.Series(self.window).pct_change().iloc[1:]
            volatility = float(returns.std())
            priceChange = float(returns.iloc[-1])
            self.factor = volatility * abs(priceChange)