├── EightVolSignals.py ├── Hedged_PIG_BM_Rally_Scenario.py ├── VolQ_Pig_ETF_Proxy.py ├── VolQ_vs_UPAR.py └── VolQvsRPAR.py /EightVolSignals.py: -------------------------------------------------------------------------------- 1 | # region imports 2 | from AlgorithmImports import * 3 | from datetime import timedelta 4 | import datetime 5 | import numpy as np 6 | import pandas as pd 7 | # endregion 8 | 9 | class EightVolSignals(QCAlgorithm): 10 | 11 | def Initialize(self): 12 | self.SetStartDate(2015, 1, 1) # Set Start Date 13 | self.SetCash(100000) # Set Strategy Cash 14 | # 8 Signals Derived from VIX and its forward curve. 15 | # https://medium.datadriveninvestor.com/how-to-harness-the-power-of-vix-to-protect-your-portfolio-part-3-bbfa12ba7c38 16 | self.VRatio = self.Contango = self.ContangoRoll = self.VRP = self.FVRP = self.VolMomo = self.VIXMR = self.VIX3MMR = 0.0 17 | self.Signals = {"VRatio": 0, "Contango": 0, "ContangoRoll": 0, "VRP": 0, "FVRP": 0, "VolMomo": 0, "VIXMR": 0, "VIX3MMR": 0} 18 | self.RiskOn = 0.0 19 | self.Allocated = 0.0 20 | # Data 21 | self.spy = self.AddEquity("SPY", Resolution.Hour) 22 | self.vix = self.AddIndex("VIX", Resolution.Hour) 23 | self.spx = self.AddIndex("SPX", Resolution.Hour) 24 | self.cgo = self.AddData(VIXCentralContango, "VX", Resolution.Daily).Symbol 25 | self.SetBenchmark(SecurityType.Equity, "SPY") 26 | # Indicators 27 | self.vixEMA7 = self.EMA("VIX", 7, Resolution.Hour) 28 | self.vixSMA50 = self.SMA("VIX", 50, Resolution.Hour) 29 | self.spxLOGR = self.LOGR("SPX", 1, Resolution.Daily) 30 | self.spxSlowSTD = IndicatorExtensions.Of(StandardDeviation(10), self.spxLOGR) 31 | self.spxFastSTD = IndicatorExtensions.Of(StandardDeviation(5), self.spxLOGR) 32 | self.spxSlowHV = self.spxFastHV = 0.0 33 | # VIX History 34 | self.vixWindow = RollingWindow[TradeBar](66) 35 | self.vixHistory = self.History(self.vix.Symbol, 66, Resolution.Daily) 36 | for time, row in self.vixHistory.loc[self.vix.Symbol].iterrows(): 37 | self.vixWindow.Add(TradeBar(time.date(), self.vix.Symbol, row.open, row.high, row.low, row.close, 0.0)) 38 | self.vix3M = self.vixWindow[self.vixWindow.Count-1].Close 39 | self.vixLast = self.vixWindow[0].Close 40 | self.contLast = None 41 | # Warm Up 42 | self.SetWarmUp(66) 43 | 44 | def OnData(self, data: Slice): 45 | if self.IsWarmingUp: return 46 | 47 | if data.ContainsKey("VX.VIXCentralContango"): 48 | self.contLast = data["VX.VIXCentralContango"] 49 | 50 | if self.Time.hour != 15: return 51 | 52 | if data.ContainsKey("VIX"): 53 | self.vixWindow.Add(data["VIX"]) 54 | self.vix3M = self.vixWindow[self.vixWindow.Count-1].Close 55 | self.vixLast = data["VIX"].Close 56 | self.spxSlowHV = float(self.spxSlowSTD.Current.Value * np.sqrt(365))*100 57 | self.spxFastHV = float(self.spxFastSTD.Current.Value * np.sqrt(365))*100 58 | # Calculate 8 Signals 59 | self.VRatio = self.vix3M / self.vixLast #VIX3M/VIX, VRatio > 1 = Risk ON 60 | self.Contango = self.contLast.Contango_F2_Minus_F1 #VX2/VX1-1, Contanto > -5%, Risk ON 61 | self.ContangoRoll = (self.contLast.F2 / self.vixLast) - 1 #VX2/VIX-1, Contango Roll > 10%, Risk ON 62 | self.VRP = self.vixLast - self.spxSlowHV #VIX-HV10(SPX), Volatility Risk Premium (VRP) > 0, Risk ON 63 | self.FVRP = self.vixEMA7.Current.Value - self.spxFastHV #EMA(VIX,7)-HV5(SPX), FVRP > 0, Risk ON 64 | self.VolMomo = self.vixSMA50.Current.Value - self.vixLast #SMA(VIX,50)-VIX, Volatility Momentum > 0, Risk ON 65 | self.VIXMR = 1.0 if self.vixLast > 12 and self.vixLast < 20 else 0.0 #VIX Mean Reversion, VIX > 12 and VIX < 20, Risk ON 66 | self.VIX3MMR = 1.0 if self.vix3M > 12 and self.vix3M < 20 else 0.0 #VIX3M Mean Reversion, VIX3M > 12 and VIX3M < 20, Risk ON 67 | # Set Signals Risk On or Risk Off 68 | if sum(self.Signals.values()) < 4: self.RiskOn = 0.0 69 | self.Signals["VRatio"] = 1 if self.VRatio > 1 else 0 70 | self.Signals["Contango"] = 1 if self.Contango > -0.05 else 0 71 | self.Signals["ContangoRoll"] = 1 if self.ContangoRoll > 0.1 else 0 72 | self.Signals["VRP"] = 1 if self.VRP > 0 else 0 73 | self.Signals["FVRP"] = 1 if self.FVRP > 0 else 0 74 | self.Signals["VolMomo"] = 1 if self.VolMomo > 0 else 0 75 | self.Signals["VIXMR"] = 1 if self.VIXMR == 1.0 else 0 76 | self.Signals["VIX3MMR"] = 1 if self.VIX3MMR == 1.0 else 0 77 | #Set Risk On/Off 78 | if sum(self.Signals.values()) >= 4: self.RiskOn += 0.5 79 | else: self.RiskOn = 0.0 80 | if self.RiskOn > 1.0: self.RiskOn = 1.0 81 | # Debug Print Out 82 | self.Debug(f"{self.Time.date()} Risk:{self.RiskOn} {self.Signals} {data['VIX'].Close:.2f} {round(data['SPX'].Close):.0f}") 83 | #Trade 84 | if self.RiskOn == 0.0: 85 | if self.Allocated >= 0: 86 | self.Allocated = -0.9 87 | self.SetHoldings("SPY", self.Allocated) 88 | elif self.RiskOn == 1.0: 89 | if self.Allocated <= 0: 90 | self.Allocated = 0.9 91 | self.SetHoldings("SPY", self.Allocated) 92 | -------------------------------------------------------------------------------- /Hedged_PIG_BM_Rally_Scenario.py: -------------------------------------------------------------------------------- 1 | import bt 2 | import matplotlib 3 | import matplotlib.pyplot as plt 4 | import pandas as pd 5 | from datetime import date, timedelta 6 | matplotlib.use('MacOSX') #Use 'TkAgg' if not MacOSX 7 | 8 | pos = {} #Set Up Core Positions for Each Rebalance Date 9 | pos[pd.to_datetime('2021-11-09')] = dict.fromkeys(['TLT','EMB','IWM','DIA','SPY','QQQ','EEM','VGK','REM','VNQ','BITO','LQD','HYG','IGSB','AAPL','AMZN','FB','GOOGL','MSFT','NVDA','GDX','CPER','GSG'], -1.0) | dict.fromkeys(['IEI','SHY','SHV','BIL','GLD','UUP','FXF','FXY','VXX'], 1.0) 10 | pos[pd.to_datetime('2022-02-24')] = dict.fromkeys(['TLT','EMB','IWM','DIA','SPY','QQQ','EEM','VGK','REM','VNQ','BITO','LQD','HYG','IGSB','AAPL','AMZN','FB','GOOGL','MSFT','NVDA'], -1.0) | dict.fromkeys(['IEI','SHY','SHV','BIL','UUP','FXF','FXY','VXX','GLD'], 1.0) 11 | pos[pd.to_datetime('2022-03-31')] = dict.fromkeys(['TLT','EMB','IWM','DIA','SPY','QQQ','EFA','EEM','VGK','REM','VNQ','SMH','JNK','IJH','BITO','LQD','HYG','IGSB','XLF','EUFN','AAPL','AMZN','FB','GOOGL','MSFT','NVDA'], -0.75) | dict.fromkeys(['BIL','SHV','SHY','IEI','STIP','UUP','FXF','FXY','GLD','SLV'], 0.75) 12 | pos[pd.to_datetime('2022-05-20')] = dict.fromkeys(['TLT','EMB','IWM','DIA','SPY','QQQ','EFA','EEM','VGK','REM','VNQ','SMH','JNK','IJH','BITO','LQD','HYG','IGSB','XLF','EUFN','AAPL','AMZN','FB','GOOGL','MSFT','NVDA'], -0.50) | dict.fromkeys(['BIL','SHV','SHY','IEI','STIP','UUP','FXF','FXY','GLD','SLV'], 0.50) 13 | for k in pos: pos[k].update((x, y*1/len(pos[k])) for x, y in pos[k].items()) #Equal Weight Core Positions 14 | pos[pd.to_datetime('2022-05-20')] = pos[pd.to_datetime('2022-05-20')] | dict.fromkeys(['ES=F','NQ=F','RTY=F'], 0.05) #Add Hedges Where Applicable 15 | pig = pd.DataFrame.from_dict(pos,orient='index').fillna(0.0) 16 | pig.index.name = 'Date' 17 | prices = bt.get(list(pig), clean_tickers=False, start=pig.first_valid_index(), end=date.today()) 18 | for i in range(1,45): #Add Future Data for What If Scenario: Bear Market Rally in Short Positions and Hedges, Longs Flat 19 | bmr = pd.DataFrame(prices[-1:].values, index=[prices.index[-1] + timedelta(days=1)], columns=prices.columns) 20 | for c in bmr.columns: bmr[c] = bmr[c]*(1.02-(i%2)*0.03) if pig[c].values[-1]<0 or pig[c].values[-1]==pig['ES=F'].values[-1] else bmr[c] 21 | prices = pd.concat([prices, bmr]) 22 | report = bt.run(bt.Backtest(bt.Strategy('Pig ETF Proxy (Hedged)', algos=[bt.algos.SelectAll(), bt.algos.WeighTarget(pig), bt.algos.Rebalance()]), prices)) 23 | report.display() 24 | report.plot() 25 | plt.title('The Power of the Hedgehog PIG Portfolio\nWhat If Scenario: 24% Bear Market Rally') 26 | plt.show() 27 | -------------------------------------------------------------------------------- /VolQ_Pig_ETF_Proxy.py: -------------------------------------------------------------------------------- 1 | import bt 2 | import matplotlib 3 | import matplotlib.pyplot as plt 4 | import pandas as pd 5 | from datetime import date, timedelta 6 | matplotlib.use('MacOSX') #Use 'TkAgg' if not on MacOSX 7 | 8 | pos = {} #Set Up Core Positions for Each Rebalance Date 9 | pos[pd.to_datetime('2021-11-09')] = dict.fromkeys(['TLT','EMB','IWM','DIA','SPY','QQQ','EEM','VGK','REM','VNQ','BITO','LQD','HYG','IGSB','AAPL','AMZN','FB','GOOGL','MSFT','NVDA','GDX','CPER','GSG'], -1.0) | dict.fromkeys(['IEI','SHY','SHV','BIL','GLD','UUP','FXF','FXY','VXX'], 1.0) 10 | pos[pd.to_datetime('2022-02-24')] = dict.fromkeys(['TLT','EMB','IWM','DIA','SPY','QQQ','EEM','VGK','REM','VNQ','BITO','LQD','HYG','IGSB','AAPL','AMZN','FB','GOOGL','MSFT','NVDA'], -1.0) | dict.fromkeys(['IEI','SHY','SHV','BIL','UUP','FXF','FXY','VXX','GLD'], 1.0) 11 | pos[pd.to_datetime('2022-03-31')] = dict.fromkeys(['TLT','EMB','IWM','DIA','SPY','QQQ','EFA','EEM','VGK','REM','VNQ','SMH','JNK','IJH','BITO','LQD','HYG','IGSB','XLF','EUFN','AAPL','AMZN','FB','GOOGL','MSFT','NVDA'], -0.75) | dict.fromkeys(['BIL','SHV','SHY','IEI','STIP','UUP','FXF','FXY','GLD','SLV'], 0.75) 12 | pos[pd.to_datetime('2022-05-20')] = dict.fromkeys(['TLT','EMB','IWM','DIA','SPY','QQQ','EFA','EEM','VGK','REM','VNQ','SMH','JNK','IJH','BITO','LQD','HYG','IGSB','XLF','EUFN','AAPL','AMZN','FB','GOOGL','MSFT','NVDA'], -0.50) | dict.fromkeys(['BIL','SHV','SHY','IEI','STIP','UUP','FXF','FXY','GLD','SLV'], 0.50) 13 | for k in pos: pos[k].update((x, y*1/len(pos[k])) for x, y in pos[k].items()) #Equal Weight Core Positions 14 | pos[pd.to_datetime('2022-05-20')] = pos[pd.to_datetime('2022-05-20')] | dict.fromkeys(['ES=F','NQ=F','RTY=F'], 0.05) #Add Hedges Where Applicable 15 | pig = pd.DataFrame.from_dict(pos,orient='index').fillna(0.0) 16 | pig.index.name = 'Date' 17 | report = bt.run(bt.Backtest(data=bt.get(list(pig), clean_tickers=False, start=pig.first_valid_index(), end=date.today()), strategy=bt.Strategy('Pig ETF Proxy', algos=[bt.algos.SelectAll(), bt.algos.WeighTarget(pig), bt.algos.Rebalance()]))) 18 | report.display() 19 | report.plot() 20 | plt.title('VolQ Pig Portfolio ETF Proxy\nPerformance Since Inception (9 Nov 2021)') 21 | plt.show() 22 | -------------------------------------------------------------------------------- /VolQ_vs_UPAR.py: -------------------------------------------------------------------------------- 1 | import bt 2 | import matplotlib 3 | import matplotlib.pyplot as plt 4 | import pandas as pd 5 | from datetime import date, timedelta 6 | matplotlib.use('MacOSX') #Use 'TkAgg' if not on MacOSX 7 | #%matplotlib inline 8 | 9 | pos = {} #Set Up Core Positions for Each Rebalance Date 10 | pos[pd.to_datetime('2021-11-09')] = dict.fromkeys(['TLT','EMB','IWM','DIA','SPY','QQQ','EEM','VGK','REM','VNQ','BITO','LQD','HYG','IGSB','AAPL','AMZN','FB','GOOGL','MSFT','NVDA','GDX','CPER','GSG'], -1.0) | dict.fromkeys(['IEI','SHY','SHV','BIL','GLD','UUP','FXF','FXY','VXX'], 1.0) 11 | pos[pd.to_datetime('2022-02-24')] = dict.fromkeys(['TLT','EMB','IWM','DIA','SPY','QQQ','EEM','VGK','REM','VNQ','BITO','LQD','HYG','IGSB','AAPL','AMZN','FB','GOOGL','MSFT','NVDA'], -1.0) | dict.fromkeys(['IEI','SHY','SHV','BIL','UUP','FXF','FXY','VXX','GLD'], 1.0) 12 | pos[pd.to_datetime('2022-03-31')] = dict.fromkeys(['TLT','EMB','IWM','DIA','SPY','QQQ','EFA','EEM','VGK','REM','VNQ','SMH','JNK','IJH','BITO','LQD','HYG','IGSB','XLF','EUFN','AAPL','AMZN','FB','GOOGL','MSFT','NVDA'], -0.75) | dict.fromkeys(['BIL','SHV','SHY','IEI','STIP','UUP','FXF','FXY','GLD','SLV'], 0.75) 13 | pos[pd.to_datetime('2022-05-20')] = dict.fromkeys(['TLT','EMB','IWM','DIA','SPY','QQQ','EFA','EEM','VGK','REM','VNQ','SMH','JNK','IJH','BITO','LQD','HYG','IGSB','XLF','EUFN','AAPL','AMZN','FB','GOOGL','MSFT','NVDA'], -0.50) | dict.fromkeys(['BIL','SHV','SHY','IEI','STIP','UUP','FXF','FXY','GLD','SLV'], 0.50) 14 | for k in pos: pos[k].update((x, y*1/len(pos[k])) for x, y in pos[k].items()) #Equal Weight Core Positions 15 | pos[pd.to_datetime('2022-05-20')] = pos[pd.to_datetime('2022-05-20')] | dict.fromkeys(['ES=F','NQ=F','RTY=F'], 0.05) #Add Hedges Where Applicable 16 | pig = pd.DataFrame.from_dict(pos,orient='index').fillna(0.0) 17 | pig.index.name = 'Date' 18 | pigprices = bt.get(list(pig), clean_tickers=False, start=pig.first_valid_index(), end=date.today()) 19 | parpos = {} #Set up Risk Parity Positions for Each Rebalance Date 20 | parpos[pd.to_datetime('2021-11-09')] = {'RPAR':1.0} 21 | parpos[pd.to_datetime('2022-01-04')] = {'UPAR':1.0} 22 | par = pd.DataFrame.from_dict(parpos,orient='index').fillna(0.0) 23 | par.index.name = 'Date' 24 | rparprices = bt.get(['RPAR'], clean_tickers=False, start=pig.first_valid_index(), end=date.today()) 25 | uparprices = bt.get(['UPAR'], clean_tickers=False, start=pig.first_valid_index(), end=date.today()) 26 | parprices = rparprices.merge(uparprices, left_index=True, right_index=True,how='outer').fillna(0.0) 27 | report = bt.run(bt.Backtest(data=pigprices, strategy=bt.Strategy('Pig ETF Proxy', algos=[bt.algos.SelectAll(), bt.algos.WeighTarget(pig), bt.algos.Rebalance()])), 28 | bt.Backtest(data=parprices, strategy=bt.Strategy('RPAR->UPAR', algos=[bt.algos.SelectAll(), bt.algos.WeighTarget(par), bt.algos.Rebalance()]))) 29 | report.display() 30 | report.plot() 31 | plt.title('VolQ Pig Portfolio ETF Proxy\nPerformance Since Inception (9 Nov 2021)\nNo 3x Futures, Options, or Intras') 32 | plt.show() 33 | -------------------------------------------------------------------------------- /VolQvsRPAR.py: -------------------------------------------------------------------------------- 1 | import bt 2 | import matplotlib 3 | import matplotlib.pyplot as plt 4 | import pandas as pd 5 | from datetime import date, timedelta 6 | matplotlib.use('MacOSX') #Use 'TkAgg' if not on MacOSX 7 | #%matplotlib inline 8 | 9 | pos = {} #Set Up Core Positions for Each Rebalance Date 10 | pos[pd.to_datetime('2021-11-09')] = dict.fromkeys(['TLT','EMB','IWM','DIA','SPY','QQQ','EEM','VGK','REM','VNQ','BITO','LQD','HYG','IGSB','AAPL','AMZN','FB','GOOGL','MSFT','NVDA','GDX','CPER','GSG'], -1.0) | dict.fromkeys(['IEI','SHY','SHV','BIL','GLD','UUP','FXF','FXY','VXX'], 1.0) 11 | pos[pd.to_datetime('2022-02-24')] = dict.fromkeys(['TLT','EMB','IWM','DIA','SPY','QQQ','EEM','VGK','REM','VNQ','BITO','LQD','HYG','IGSB','AAPL','AMZN','FB','GOOGL','MSFT','NVDA'], -1.0) | dict.fromkeys(['IEI','SHY','SHV','BIL','UUP','FXF','FXY','VXX','GLD'], 1.0) 12 | pos[pd.to_datetime('2022-03-31')] = dict.fromkeys(['TLT','EMB','IWM','DIA','SPY','QQQ','EFA','EEM','VGK','REM','VNQ','SMH','JNK','IJH','BITO','LQD','HYG','IGSB','XLF','EUFN','AAPL','AMZN','FB','GOOGL','MSFT','NVDA'], -0.75) | dict.fromkeys(['BIL','SHV','SHY','IEI','STIP','UUP','FXF','FXY','GLD','SLV'], 0.75) 13 | pos[pd.to_datetime('2022-05-20')] = dict.fromkeys(['TLT','EMB','IWM','DIA','SPY','QQQ','EFA','EEM','VGK','REM','VNQ','SMH','JNK','IJH','BITO','LQD','HYG','IGSB','XLF','EUFN','AAPL','AMZN','FB','GOOGL','MSFT','NVDA'], -0.50) | dict.fromkeys(['BIL','SHV','SHY','IEI','STIP','UUP','FXF','FXY','GLD','SLV'], 0.50) 14 | for k in pos: pos[k].update((x, y*1/len(pos[k])) for x, y in pos[k].items()) #Equal Weight Core Positions 15 | pos[pd.to_datetime('2022-05-20')] = pos[pd.to_datetime('2022-05-20')] | dict.fromkeys(['ES=F','NQ=F','RTY=F'], 0.05) #Add Hedges Where Applicable 16 | pig = pd.DataFrame.from_dict(pos,orient='index').fillna(0.0) 17 | pig.index.name = 'Date' 18 | pigprices = bt.get(list(pig), clean_tickers=False, start=pig.first_valid_index(), end=date.today()) 19 | parpos = {} #Set up Risk Parity Positions for Each Rebalance Date 20 | parpos[pd.to_datetime('2021-11-09')] = {'RPAR':1.0} 21 | parpos[pd.to_datetime('2022-01-04')] = {'UPAR':1.0} 22 | par = pd.DataFrame.from_dict(parpos,orient='index').fillna(0.0) 23 | par.index.name = 'Date' 24 | rparprices = bt.get(['RPAR'], clean_tickers=False, start=pig.first_valid_index(), end=date.today()) 25 | uparprices = bt.get(['UPAR'], clean_tickers=False, start=pig.first_valid_index(), end=date.today()) 26 | parprices = rparprices.merge(uparprices, left_index=True, right_index=True,how='outer').fillna(0.0) 27 | report = bt.run(bt.Backtest(data=pigprices, strategy=bt.Strategy('Pig ETF Proxy', algos=[bt.algos.SelectAll(), bt.algos.WeighTarget(pig), bt.algos.Rebalance()])), 28 | bt.Backtest(data=parprices, strategy=bt.Strategy('RPAR->UPAR', algos=[bt.algos.SelectAll(), bt.algos.WeighTarget(par), bt.algos.Rebalance()]))) 29 | report.display() 30 | report.plot() 31 | plt.title('VolQ Pig Portfolio ETF Proxy\nPerformance Since Inception (9 Nov 2021)') 32 | plt.show() 33 | --------------------------------------------------------------------------------