Overall Statistics |
Total Trades 1622 Average Win 0.63% Average Loss -0.45% Compounding Annual Return 16.350% Drawdown 17.800% Expectancy 0.359 Net Profit 246.172% Sharpe Ratio 0.867 Probabilistic Sharpe Ratio 34.038% Loss Rate 43% Win Rate 57% Profit-Loss Ratio 1.39 Alpha 0.022 Beta 0.952 Annual Standard Deviation 0.158 Annual Variance 0.025 Information Ratio 0.151 Tracking Error 0.109 Treynor Ratio 0.144 Total Fees $1652.80 |
# ref # Alexi Muci, A simple VIX Strategy, https://www.quantconnect.com/forum/discussion/2657/a-simple-vix-strategy # Tony Cooper, Easy Volatility Investing, https://www.ssrn.com/abstract=2255327 from QuantConnect import * from QuantConnect.Algorithm import * import pandas as pd import numpy as np from my_custom_data import CboeVix, CboeVxV class VIXStrategyByRatio(QCAlgorithm): def Initialize(self): # SVXY inception date 10/3/2011 # VXX inception date 1/29/2009 self.SetStartDate(2011, 10, 15) #self.SetStartDate(2019, 1, 15) self.SetEndDate(datetime.now().date() - timedelta(1)) self.SetCash(10000) #self.SetBrokerageModel(BrokerageName.InteractiveBrokersBrokerage, AccountType.Margin) # ^^ with a low alpha model that trades daily, you get eaten alive by brokerage fees if you close positions everyday with starting capital of 10k. self.SetBrokerageModel(BrokerageName.AlphaStreams) # add the #2 ETFs (short and long VIX futures) # swap xiv with svxy, since xiv is wiped out. self.XIV = self.AddEquity("SVXY", Resolution.Daily).Symbol # switched to svxy self.VXX = self.AddEquity("ZIV", Resolution.Daily).Symbol # switched to ziv self.SPY = self.AddEquity("SPY", Resolution.Daily).Symbol self.SHY = self.AddEquity("SHY", Resolution.Daily).Symbol self.SetBenchmark("SPY") # Define symbol and "type" of custom data: used for signal ratio self.VIX = self.AddData(CboeVix, "VIX").Symbol self.VXV = self.AddData(CboeVxV, "VXV").Symbol self.window_len = 252 hist = self.History([self.VIX], self.window_len, Resolution.Daily) self.window_vix = RollingWindow[float](self.window_len) for close in hist.loc[self.VIX]['close']: self.window_vix.Add(close) self.window_vix_date = RollingWindow[str](self.window_len) for item in hist.index: self.window_vix_date.Add(item[-1].strftime('%Y-%m-%d')) hist = self.History([self.VXV], self.window_len, Resolution.Daily) self.window_vxv = RollingWindow[float](self.window_len) for close in hist.loc[self.VXV]['close']: self.window_vxv.Add(close) self.window_vxv_date = RollingWindow[str](self.window_len) for item in hist.index: self.window_vxv_date.Add(item[-1].strftime('%Y-%m-%d')) # Define the Schedules self.Schedule.On( self.DateRules.EveryDay(), self.TimeRules.AfterMarketOpen(self.XIV, 0), Action(self.Balance) ) #self.Schedule.On( # self.DateRules.EveryDay(), # self.TimeRules.BeforeMarketClose(self.XIV, 5), # Action(self.Close) #) self.SetWarmUp(timedelta(self.window_len)) # TODO: verify data correctness. # TODO: is there a better way to collect data? def OnData(self, data): if data.ContainsKey(self.VIX): self.window_vix.Add(data[self.VIX].Price) self.window_vix_date.Add(data.Time.strftime('%Y-%m-%d')) if data.ContainsKey(self.VXV): self.window_vxv.Add(data[self.VXV].Price) self.window_vxv_date.Add(data.Time.strftime('%Y-%m-%d')) def Close(self): for x in [self.SPY,self.SHY,self.XIV,self.VXX]: self.SetHoldings(x, 0.0) def Balance(self): if not self.window_vxv.IsReady: return if not self.window_vix.IsReady: return if not self.window_vxv_date.IsReady: return if not self.window_vix_date.IsReady: return # TODO: compute needs to be moved to ??? alpha compute method. # then alpha can be used to aid in construct portfolio. df = pd.DataFrame() # flip the data, so last item is the most current. vix_date_array = [i for i in self.window_vix_date][::-1] vxv_date_array = [i for i in self.window_vxv_date][::-1] vix_price = np.array([i for i in self.window_vix])[::-1] vxv_price = np.array([i for i in self.window_vxv])[::-1] df['vix_date'] = vix_date_array df['vxv_date'] = vxv_date_array df['VIX'] = vix_price df['VXV'] = vxv_price # avoid look ahead bias. df['VIX'] = df['VIX'].shift(1) df['VXV'] = df['VXV'].shift(1) df["cob"] = df["VIX"]/df["VXV"] # compute momentum of cob. # get z score of cob. if z score increases # it is likely that backwardation would happen df['mean'] = df['cob'].rolling(126).mean() df['sd'] = df['cob'].rolling(126).std() df['z'] = (df['cob']-df['mean'])/df['sd'] df['dz'] = df['z'].pct_change() df['dzma'] = df['dz'].rolling(5).mean() df['cobma'] = df['cob'].rolling(10).median() # "Strategy 3, Vratio10 by Tony Cooper" cob = df["cobma"].iloc[-1] dzma = df['dzma'].iloc[-1] XIV_qnty = self.Portfolio[self.XIV].Quantity VXX_qnty = self.Portfolio[self.VXX].Quantity self.Log("{},{},{},{},{},{}".format( df['vix_date'].iloc[-1], df['vxv_date'].iloc[-1], df['VIX'].iloc[-1], df['VXV'].iloc[-1], cob,dzma)) # cob = vix/vxv # cob < 1 , vix < vxv: contago # cob > 1 , vix > vxv: backwardation (1 mnth more expensive than 3 mnth future) # https://en.wikipedia.org/wiki/Contango # https://en.wikipedia.org/wiki/Normal_backwardation # # np.nanpercentile(df['VIX/VXV'],[30,40,50,60,70,80,90]) # >>> array([0.86649373, 0.88454818, 0.9025271 , 0.92344436, 0.94629521, 0.97491226, 1.01362785]) # # ___o .--. # /___| |OO| # /' |_| |_ # (_ _) # | | \ # | |oo_/sjw # # Don't say Tony Cooper didn't warn you about the Grim Reaper. # !prioritize fear # long volatility if trending towards backwardation at any time #if cob > 0.88 and dzma > 0: if cob > np.nanpercentile(df['cob'],[20])[0] and dzma > 0: situation = 'long_volatility' self.Log("long VOL") #self.Notify.Email("XXXX@gmail.com", "IB Algo Execution", "long VXX"); self.Log("long VOL") Insight(self.XIV, timedelta(days=1), InsightType.Price, InsightDirection.Down, confidence=1.0) Insight(self.VXX, timedelta(days=1), InsightType.Price, InsightDirection.Up, confidence=1.0) # short volatility if trending towards contago plus a threshold for absolute cob value. #elif cob < 0.97 and dzma < 0: elif cob < np.nanpercentile(df['cob'],[80])[0] or dzma < 0: # or gets better PSR than and... ? why? situation = 'short_volatility' self.Log("short VOL") #self.Notify.Email("XXXXX@gmail.com", "IB Algo Execution", "long XIV"); self.Log("short VOL") Insight(self.VXX, timedelta(days=1), InsightType.Price, InsightDirection.Down, confidence=1.0) Insight(self.XIV, timedelta(days=1), InsightType.Price, InsightDirection.Up, confidence=1.0) # flat ? - not really sure what to do - chicken mode else: situation = 'flat' self.Log("Flat") #self.Notify.Email("xxxxxxxxxx@gmail.com", "IB Algo Execution", "Flat position"); self.Log("Flat") Insight(self.XIV, timedelta(days=1), InsightType.Price, InsightDirection.Flat, confidence=0.3) Insight(self.VXX, timedelta(days=1), InsightType.Price, InsightDirection.Flat, confidence=0.3) iama_braveheart = { 'short_volatility':{ self.SPY: 0.0, self.SHY: 0.0, self.XIV: 1.0, self.VXX: 0.0, }, 'flat': { self.SPY: 0.3, self.SHY: 0.7, self.XIV: 0.0, self.VXX: 0.0, }, 'long_volatility': { self.SPY: 0.0, self.SHY: 0.0, self.XIV: 0.0, self.VXX: 1.0, }, } ihave_babies_and_30_year_home_mortgage = { # blend in some SHY and SPY to reduce drawdowns. 'short_volatility':{ self.SPY: 0.7, self.SHY: 0.0, self.XIV: 0.3, self.VXX: 0.0, }, 'flat': { self.SPY: 0.5, self.SHY: 0.5, self.XIV: 0.0, self.VXX: 0.0, }, 'long_volatility': { self.SPY: 0.3, self.SHY: 0.7, self.XIV: 0.0, self.VXX: 0.0, }, } # braveheart param: PSR 41%, win/loss rate 59/41, max Drawdown 49% # alternative param: PSR 34%, win/loss rate 57/43, max Drawdown 18% mydict = ihave_babies_and_30_year_home_mortgage for k,v in mydict[situation].items(): self.SetHoldings(k,v)
from QuantConnect.Python import PythonQuandl # quandl data not CLOSE from QuantConnect.Python import PythonData # custom data from QuantConnect.Data import SubscriptionDataSource from datetime import datetime, timedelta import decimal class CboeVix(PythonData): '''CBOE Vix Download Custom Data Class''' def GetSource(self, config, date, isLiveMode): url_vix = "http://www.cboe.com/publish/scheduledtask/mktdata/datahouse/vixcurrent.csv" return SubscriptionDataSource(url_vix, SubscriptionTransportMedium.RemoteFile) def Reader(self, config, line, date, isLiveMode): if not (line.strip() and line[0].isdigit()): return None # New CboeVix object index = CboeVix(); index.Symbol = config.Symbol try: # Example File Format: # Date VIX Open VIX High VIX Low VIX Close # 01/02/2004 17.96 18.68 17.54 18.22 #print line data = line.split(',') date = data[0].split('/') index.Time = datetime(int(date[2]), int(date[0]), int(date[1])) index.Value = decimal.Decimal(data[4]) index["Open"] = float(data[1]) index["High"] = float(data[2]) index["Low"] = float(data[3]) index["Close"] = float(data[4]) except ValueError: # Do nothing return None # except KeyError, e: # print 'I got a KeyError - reason "%s"' % str(e) return index # NB: CboeVxV class == CboeVix class, except for the URL class CboeVxV(PythonData): '''CBOE VXV Download Custom Data Class''' def GetSource(self, config, date, isLiveMode): url_vxv = "http://www.cboe.com/publish/scheduledtask/mktdata/datahouse/vix3mdailyprices.csv" return SubscriptionDataSource(url_vxv, SubscriptionTransportMedium.RemoteFile) def Reader(self, config, line, date, isLiveMode): if not (line.strip() and line[0].isdigit()): return None index = CboeVxV(); index.Symbol = config.Symbol try: # Example File Format: # OPEN HIGH LOW CLOSE # 12/04/2007 24.8 25.01 24.15 24.65 data = line.split(',') date = data[0].split('/') index.Time = datetime(int(date[2]), int(date[0]), int(date[1])) index.Value = decimal.Decimal(data[4]) index["Open"] = float(data[1]) index["High"] = float(data[2]) index["Low"] = float(data[3]) index["Close"] = float(data[4]) except ValueError: # Do nothing return None return index # for using VIX futures settle in calc. ratios like VIX/VIX1 class QuandlFuture(PythonQuandl): '''Custom quandl data type for setting customized value column name. Value column is used for the primary trading calculations and charting.''' def __init__(self): # Define ValueColumnName: cannot be None, Empty or non-existant column name # If ValueColumnName is "Close", do not use PythonQuandl, use Quandl: # self.AddData[QuandlFuture](self.VIX1, Resolution.Daily) self.ValueColumnName = "Settle"