| Overall Statistics |
|
Total Orders 163 Average Win 0.02% Average Loss 0.00% Compounding Annual Return 0.134% Drawdown 0.200% Expectancy 2.387 Start Equity 10000000 End Equity 10067322.22 Net Profit 0.673% Sharpe Ratio -5.444 Sortino Ratio -5.613 Probabilistic Sharpe Ratio 21.799% Loss Rate 61% Win Rate 39% Profit-Loss Ratio 7.77 Alpha -0.008 Beta 0.01 Annual Standard Deviation 0.001 Annual Variance 0 Information Ratio -0.979 Tracking Error 0.106 Treynor Ratio -0.723 Total Fees $446.72 Estimated Strategy Capacity $11000000000.00 Lowest Capacity Asset VX VE3CV045CRU1 Portfolio Turnover 0.04% |
#region imports
from AlgorithmImports import *
from collections import deque
import statsmodels.api as sm
#endregion
class TermStructureOfVixAlgorithm(QCAlgorithm):
def initialize(self):
self.set_start_date(2012, 1, 1) # Set Start Date
self.set_end_date(2017, 1, 1) # Set End Date
self.set_cash(10000000) # Set Strategy Cash
self.settings.minimum_order_margin_portfolio_percentage = 0
vix = self.add_index("VIX", Resolution.DAILY)
self.vix = vix.symbol
self.vix_multiplier = vix.symbol_properties.contract_multiplier
self.vx1 = self.add_future(Futures.Indices.VIX,
resolution=Resolution.DAILY,
extended_market_hours=True,
data_normalization_mode=DataNormalizationMode.BACKWARDS_RATIO,
data_mapping_mode=DataMappingMode.OPEN_INTEREST,
contract_depth_offset=0
)
self.vx1_multiplier = self.vx1.symbol_properties.contract_multiplier
self.es1 = self.add_future(Futures.Indices.SP_500_E_MINI,
resolution=Resolution.DAILY,
extended_market_hours=True,
data_normalization_mode=DataNormalizationMode.BACKWARDS_RATIO,
data_mapping_mode=DataMappingMode.OPEN_INTEREST,
contract_depth_offset=0
)
self.es1_multiplier = self.es1.symbol_properties.contract_multiplier
# the rolling window to save the front month VX future price
self.price_vx = RollingWindow[float](252)
# the rolling window to save the front month ES future price
self.price_es = RollingWindow[float](252)
# the rolling window to save the time-to-maturity of the contract
self.days_to_maturity = RollingWindow[float](252)
stock_plot = Chart("Trade")
stock_plot.add_series(Series("VIX", SeriesType.LINE, 0))
stock_plot.add_series(Series("VIX Futures", SeriesType.LINE, 0))
stock_plot.add_series(Series("Buy", SeriesType.SCATTER, 0))
stock_plot.add_series(Series("Sell", SeriesType.SCATTER, 0))
stock_plot.add_series(Series("Daily Roll", SeriesType.SCATTER, 0))
stock_plot.add_series(Series("Hedge Ratio", SeriesType.SCATTER, 0))
self.set_warm_up(253, Resolution.DAILY)
def on_data(self, data):
if data.bars.contains_key(self.vx1.symbol) and data.bars.contains_key(self.es1.symbol):
# update the rolling window price and time-to-maturity series every day
vx1_price = data.bars[self.vx1.symbol].close
self.price_vx.add(float(data.bars[self.vx1.symbol].close))
self.price_es.add(float(data.bars[self.es1.symbol].close))
self.days_to_maturity.add((self.vx1.mapped.id.date-self.time).days)
if (self.is_warming_up or not self.price_vx.is_ready or
not self.price_es.is_ready or not self.days_to_maturity.is_ready or
(self.vx1.mapped.id.date - self.time).days == 0):
return
if data.bars.contains_key(self.vx1.symbol) and data.bars.contains_key(self.es1.symbol):
# calculate the daily roll
daily_roll = (
(vx1_price*self.vx1_multiplier - self.securities[self.vix].price*self.vix_multiplier)
/ (self.vx1.mapped.id.date - self.time).days
)
self.plot("Trade", "VIX", vx1_price)
self.plot("Trade", "VIX Futures", vx1_price)
self.plot("Trade", "Daily Roll", daily_roll)
if not self.portfolio[self.vx1.mapped].invested:
# Short if the contract is in contango with adaily roll greater than 0.10
if daily_roll > 0.1:
hedge_ratio = self._calculate_hedge_ratio(data.bars[self.es1.symbol].close)
self.plot("Trade", "Sell", vx1_price)
qty = self.calculate_order_quantity(self.vx1.mapped, -1) // self.vx1.symbol_properties.contract_multiplier
if qty:
self.market_order(self.vx1.mapped, qty)
qty = self.calculate_order_quantity(self.es1.mapped, -1*hedge_ratio) // self.es1.symbol_properties.contract_multiplier
if qty:
self.market_order(self.es1.mapped, qty)
# Long if the contract is in backwardation with adaily roll less than -0.10
elif daily_roll < -0.1:
hedge_ratio = self._calculate_hedge_ratio(data.bars[self.es1.symbol].close)
self.plot("Trade", "Buy", vx1_price)
qty = self.calculate_order_quantity(self.vx1.mapped, 1) // self.vx1.symbol_properties.contract_multiplier
if qty:
self.market_order(self.vx1.mapped, qty)
qty = self.calculate_order_quantity(self.es1.mapped, 1*hedge_ratio) // self.es1.symbol_properties.contract_multiplier
if qty:
self.market_order(self.es1.mapped, qty)
# exit if the daily roll being less than 0.05 if holding short positions
if self.portfolio[self.vx1.mapped].is_short and daily_roll < 0.05:
self.liquidate()
return
# exit if the daily roll being greater than -0.05 if holding long positions
if self.portfolio[self.vx1.mapped].is_long and daily_roll > -0.05:
self.liquidate()
return
if self.vx1.mapped and self.es1.mapped:
# if these exit conditions are not triggered, trades are exited two days before it expires
if self.portfolio[self.vx1.mapped].invested and self.portfolio[self.es1.mapped].invested:
if (self.vx1.mapped.id.date-self.time).days <= 2 or (self.es1.mapped.id.date-self.time).days <= 2:
self.liquidate()
def _calculate_hedge_ratio(self, es1_price):
price_vx = np.array(list(self.price_vx))[::-1]/self.vx1_multiplier
price_es = np.array(list(self.price_es))[::-1]/self.es1_multiplier
delta__v_x = np.diff(price_vx)
res__e_s = np.diff(price_es)/price_es[:-1]*100
tts = np.array(list(self.days_to_maturity))[::-1][1:]
df = pd.DataFrame({"delta__v_x":delta__v_x, "SPRET":res__e_s, "product":res__e_s*tts}).dropna()
# remove rows with zero value
df = df[(df != 0).all(1)]
y = df['delta__v_x'].astype(float)
X = df[['SPRET', "product"]].astype(float)
X = sm.add_constant(X)
model = sm.OLS(y, X).fit()
beta_1 = model.params[1]
beta_2 = model.params[2]
hedge_ratio = (beta_1 + beta_2*((self.vx1.mapped.id.date-self.time).days))/float(es1_price)
self.plot("Trade", "Hedge Ratio", hedge_ratio)
return hedge_ratio