| Overall Statistics |
|
Total Orders 1850 Average Win 2.56% Average Loss -2.26% Compounding Annual Return 4.361% Drawdown 67.000% Expectancy 0.075 Start Equity 10000000 End Equity 17304322.84 Net Profit 73.043% Sharpe Ratio 0.216 Sortino Ratio 0.207 Probabilistic Sharpe Ratio 0.024% Loss Rate 50% Win Rate 50% Profit-Loss Ratio 1.13 Alpha 0.128 Beta -0.49 Annual Standard Deviation 0.388 Annual Variance 0.151 Information Ratio -0.015 Tracking Error 0.433 Treynor Ratio -0.171 Total Fees $3609727.16 Estimated Strategy Capacity $46000000.00 Lowest Capacity Asset VX YNNC8UBM7FMX Portfolio Turnover 31.44% |
#region imports
from AlgorithmImports import *
#endregion
class TermStructureOfVixAlgorithm(QCAlgorithm):
def initialize(self):
self.set_start_date(2012, 1, 1)
self.set_end_date(2024, 11, 1)
self.set_cash(10_000_000)
# Add the VIX Index and Futures.
self._vix_index = self.add_index("VIX")
self._vx_future = self.add_future(Futures.Indices.VIX)
self._vx_future.set_filter(14, 60)
# Create a Scheduled Event to update the portfolio.
self._get_liquid_price = False
self.schedule.on(
self.date_rules.every_day(self._vx_future.symbol),
self.time_rules.before_market_close(self._vx_future.symbol, 15),
lambda: setattr(self, '_get_liquid_price', True)
)
# Set some parameters.
self._daily_roll_entry_threshold = 0.05 # The paper uses +/-0.1
self._daily_roll_exit_threshold = 0.025 # The paper uses +/-0.05
self._min_days_to_expiry = 10
self._spread_threshold = 0.1
def on_data(self, data):
# For the last 15 minutes of the trading day, select the VIX Futures contract that has:
# - >=10 trading days until expiry
# - <=$0.1 bid-ask spread
vx_price = None
selected_contract = None
trading_days_until_expiry = None
if self._get_liquid_price:
chain = data.future_chains.get(self._vx_future.symbol)
# Get the VIX Future price.
if chain:
for contract in chain:
# Select the contract with at least 10 trading days until expiry.
trading_days_until_expiry = self._trading_days_until_expiry(contract)
if trading_days_until_expiry < self._min_days_to_expiry:
continue
# Wait for the selected contract to have a narrow spread.
if (contract.ask_price - contract.bid_price > self._spread_threshold and
# If the spread isn't narrow before market close, use the prices right before market close.
self._vx_future.exchange.hours.is_open(self.time + timedelta(minutes=1), extended_market_hours=False)):
break
vx_price = (contract.ask_price + contract.bid_price) / 2
selected_contract = contract
if not vx_price:
return
self._get_liquid_price = False
# Calculate the basis and daily roll.
vix_price = (self._vix_index.open + self._vix_index.close) / 2
basis = (vx_price - vix_price)
in_contango = basis > 0
daily_roll = basis / trading_days_until_expiry
self.plot('VIX', 'Future Price', vx_price)
self.plot('VIX', 'Spot Price', vix_price)
self.plot('In Contango?', 'Value', int(in_contango))
self.plot('Daily Roll', 'Value', daily_roll)
# Look for trade entries.
# "Short VIX futures positions are entered when the VIX futures basis is in
# contango and the daily roll exceeds .10 VIX futures points ($100 per day) and long VIX futures
# positions are entered when the VIX futures basis is in backwardation and the daily roll is less
# than -.10 VIX futures points" (p. 14)
if not self.portfolio.invested:
if in_contango and daily_roll > self._daily_roll_entry_threshold:
weight = -0.5 # Enter short VIX Future trade.
elif not in_contango and daily_roll < -self._daily_roll_entry_threshold:
weight = 0.5 # Enter long VIX Future trade.
else:
return
self.set_holdings(selected_contract.symbol, weight, tag=f"Entry: Expires on {selected_contract.expiry}")
self._vx_future.invested_contract = selected_contract
return
# Look for trade exits.
# Exit long VIX Future trade when daily_roll < self._daily_roll_exit_threshold or when the contract expires next day.
# Exit short VIX Future trade when daily_roll > -self._daily_roll_exit_threshold or when the contract expires next day.
trading_days_until_expiry = self._trading_days_until_expiry(self._vx_future.invested_contract)
holding = self.portfolio[self._vx_future.invested_contract.symbol]
tag = ""
if trading_days_until_expiry <= 1:
tag = f"Exit: Expires in {trading_days_until_expiry} trading day(s)"
elif holding.is_long and daily_roll < self._daily_roll_exit_threshold:
tag = f"Exit: daily roll ({daily_roll}) < threshold"
elif holding.is_short and daily_roll > -self._daily_roll_exit_threshold:
tag = f"Exit: daily roll ({daily_roll}) > -threshold"
if tag:
self.liquidate(tag=tag)
self._vx_future.invested_contract = None
def _trading_days_until_expiry(self, contract):
return len(list(self.trading_calendar.get_trading_days(self.time, contract.expiry - timedelta(1))))