Overall Statistics |
Total Trades 16 Average Win 0.37% Average Loss -0.35% Compounding Annual Return 18.829% Drawdown 2.500% Expectancy 0.547 Net Profit 1.516% Sharpe Ratio 1.536 Probabilistic Sharpe Ratio 57.179% Loss Rate 25% Win Rate 75% Profit-Loss Ratio 1.06 Alpha -0.279 Beta 1.331 Annual Standard Deviation 0.094 Annual Variance 0.009 Information Ratio -2.856 Tracking Error 0.061 Treynor Ratio 0.109 Total Fees $23.00 |
""" refs # https://www.quantconnect.com/tutorials/strategy-library/volatility-risk-premium-effect # https://www.quantconnect.com/forum/discussion/2894/the-options-trading-strategy-based-on-macd-indicator/p1 # https://www.quantconnect.com/tutorials/tutorial-series/applied-options # https://www.quantconnect.com/forum/discussion/5709/optionchain-is-empty/p1 """ from datetime import timedelta import numpy as np import pandas as pd from scipy import stats np.random.seed(2020) # comment to make it a real roulette class OptionRouletteAlgorithm(QCAlgorithm): def Initialize(self): self.SetStartDate(2017, 1, 15) self.SetEndDate(2017,2, 15) #self.SetStartDate(2015, 1, 1) #self.SetEndDate(datetime.now().date() - timedelta(1)) self.SetCash(100000) equity = self.AddEquity("SPY", Resolution.Minute) option = self.AddOption("SPY", Resolution.Minute) self.symbol = equity.Symbol option.SetFilter(self.UniverseFunc) self.SetBenchmark(equity.Symbol) self.slice = None # Define the Schedules self.Schedule.On( self.DateRules.WeekStart(self.symbol), self.TimeRules.AfterMarketOpen(self.symbol, 5), Action(self.MyLiquidate) ) # Define the Schedules self.Schedule.On( self.DateRules.WeekStart(self.symbol), self.TimeRules.AfterMarketOpen(self.symbol, 10), Action(self.MyTrade) ) def OnData(self,slice): self.slice = slice if slice.OptionChains.Count > 0: pass def OnAssignmentOrderEvent(self, assignmentEvent): self.Log(str(assignmentEvent)) self.MyLiquidate() def OnOrderEvent(self, orderEvent): self.Log(str(orderEvent)) def UniverseFunc(self, universe): price = self.Securities[self.symbol].Price return universe.IncludeWeeklys()\ .Strikes(-50,50)\ .Expiration(timedelta(30), timedelta(50)) # TODO: read above api. def MyLiquidate(self): for x in self.Portfolio: if x.Value.Invested: self.Liquidate(x.Key) # redundant? if self.Portfolio[self.symbol].Invested: self.Liquidate(self.symbol) self.Log("MyLiquidate") def MyTrade(self): slice = self.slice if slice is None: return self.Log("MyTrade {} {}".format(self.Portfolio.Invested,slice.OptionChains.Count)) if slice.OptionChains.Count == 0: return for i in slice.OptionChains: chains = i.Value if not self.Portfolio.Invested: self.Log("trading!") # 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-40)) strikes = [i.Strike for i in puts] # determine at-the-money strike strike = min(strikes, key=lambda x: abs(x-underlying_price)) # compute probability hist = self.History([self.symbol], 252*5, Resolution.Daily) prct_changes = hist.loc[self.symbol]['close'].pct_change(40) # 68% = 1sd, 90% = 2sd. m2sd,m1sd,p1sd,p2sd = np.nanpercentile(prct_changes,[5,32,68,95]) # roulette logic optionStyle = np.random.choice(['short_strangle','short_iron_condor','long_strangle','synthetic_long'],1)[0] num = np.random.choice([2,5,10],1)[0] # long volatility strategies ******************************** # why would you ever? if optionStyle == 'synthetic_long': self.atm_put = [i for i in puts if i.Expiry == expiry and i.Strike == strike] self.atm_call = [i for i in calls if i.Expiry == expiry and i.Strike == strike] if self.atm_put and self.atm_call: mylist = [self.atm_put[0],self.atm_call[0]] self.Log('{}'.format([stats.percentileofscore(prct_changes,(x.Strike-underlying_price)/underlying_price) for x in mylist])) self.Sell(self.atm_put[0].Symbol, num) self.Buy(self.atm_call[0].Symbol, num) if optionStyle == 'long_strangle': self.atm_put = [i for i in puts if i.Expiry == expiry and i.Strike == strike] self.atm_call = [i for i in calls if i.Expiry == expiry and i.Strike == strike] if self.atm_put and self.atm_call: mylist = [self.atm_put[0],self.atm_call[0]] self.Log('{}'.format([stats.percentileofscore(prct_changes,(x.Strike-underlying_price)/underlying_price) for x in mylist])) self.Buy(self.atm_put[0].Symbol, num) self.Buy(self.atm_call[0].Symbol, num) # short volatility strategies ******************************** if optionStyle == 'short_iron_condor': otm_call_strike = min(strikes, key = lambda x:abs(x-underlying_price+p2sd*underlying_price)) atm_call_strike = min(strikes, key = lambda x:abs(x-underlying_price+p1sd*underlying_price)) # more like near atm atm_put_strike = min(strikes, key = lambda x:abs(x-underlying_price+m1sd*underlying_price)) otm_put_strike = min(strikes, key = lambda x:abs(x-underlying_price+m2sd*underlying_price)) self.otm_call = [i for i in calls if i.Expiry == expiry and i.Strike == otm_call_strike] self.atm_call = [i for i in calls if i.Expiry == expiry and i.Strike == atm_call_strike] self.atm_put = [i for i in puts if i.Expiry == expiry and i.Strike == atm_put_strike] self.otm_put = [i for i in puts if i.Expiry == expiry and i.Strike == otm_put_strike] if self.atm_call and self.atm_put and self.otm_put and self.otm_call: mylist = [self.otm_put[0],self.atm_call[0],self.atm_put[0],self.otm_call[0]] self.Log('{}'.format([stats.percentileofscore(prct_changes,(x.Strike-underlying_price)/underlying_price) for x in mylist])) # TODO: log net profit and potential max loss. # buy otm self.Buy(self.otm_call[0].Symbol, num) self.Buy(self.otm_put[0].Symbol, num) # sell near atm self.Sell(self.atm_call[0].Symbol, num) self.Sell(self.atm_put[0].Symbol, num) if optionStyle == 'short_strangle': otm_call_strike = min(strikes, key = lambda x:abs(x-underlying_price+p2sd*underlying_price)) otm_put_strike = min(strikes, key = lambda x:abs(x-underlying_price+m2sd*underlying_price)) self.otm_put = [i for i in puts if i.Expiry == expiry and i.Strike == otm_put_strike] self.otm_call = [i for i in calls if i.Expiry == expiry and i.Strike == otm_call_strike] if self.otm_put and self.otm_call: mylist = [self.otm_put[0],self.otm_call[0]] self.Log('{}'.format([stats.percentileofscore(prct_changes,(x.Strike-underlying_price)/underlying_price) for x in mylist])) self.Sell(self.otm_put[0].Symbol, num) self.Sell(self.otm_call[0].Symbol, num)