Overall Statistics |
Total Trades 1442 Average Win 0.36% Average Loss -1.38% Compounding Annual Return 8.835% Drawdown 65.500% Expectancy 0.080 Net Profit 100.225% Sharpe Ratio 0.393 Probabilistic Sharpe Ratio 3.600% Loss Rate 14% Win Rate 86% Profit-Loss Ratio 0.26 Alpha -0.087 Beta 1.722 Annual Standard Deviation 0.307 Annual Variance 0.094 Information Ratio 0 Tracking Error 0.242 Treynor Ratio 0.07 Total Fees $1442.28 |
# 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 self.VXX = self.AddEquity("VXX", Resolution.Daily).Symbol 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"] df["cob"] = df["cob"].rolling(10).median() # "Strategy 3, Vratio10 by Tony Cooper" cob = df["cob"].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)) # 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]) # short vol (buy XIV) if cob < 0.95: 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) # long vol (buy VXX) elif cob > 1.05: 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) # 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.0, self.SHY: 0.0, 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.5, self.SHY: 0.0, self.XIV: 0.5, 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, }, } mydict = ihave_babies_and_30_year_home_mortgage # use braveheart param for wow factor. 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"