Back to Community
XIV Shotgun - Trading Inverse VIX with WVF

This is my latest incarnation of the original algo that inspired Pete Bakker's "The SPY Who Loves WVF" post.

Apply on daily interval. Buy orders are executed at EOD. Sell orders are executed next day at open.

Securities for Trading:
- XIV (risk-on asset)
- IEF (risk-off asset)

Securities for Data:
- VXX (used for calculating indicators)

Indicators:
- WVF = 100-day William's VIX Fix of VXX's daily close price
- Smoothed WVF 1 = 10-day EMA of WVF
- Smoothed WVF 2 = 30-day EMA of WVF
- RSI = 3-day RSI of VXX's daily close price

Buy Rules (buy XIV, sell IEF):
- If WVF crosses over Smoothed WVF 1 and WVF < Smoothed WVF 2, or
- If WVF crosses over both Smoothed WVF 1 and Smoothed WVF 2, or
- If Smoothed WVF 1 crosses over Smoothed WVF 2

Sell Rules (Sell XIV, buy IEF):
- If WVF crosses under Smoothed WVF 2, or
- If RSI crosses over 50

Take Profit/Stop Loss (XIV only)
- TP = +25%
- SL = -3%

Please feel free to comment and improve this algo. For collaboration request, please email: [email protected] and [email protected]

Clone Algorithm
619
Loading...
Backtest from to with initial capital
Total Returns
--
Alpha
--
Beta
--
Sharpe
--
Sortino
--
Max Drawdown
--
Benchmark Returns
--
Volatility
--
Returns 1 Month 3 Month 6 Month 12 Month
Alpha 1 Month 3 Month 6 Month 12 Month
Beta 1 Month 3 Month 6 Month 12 Month
Sharpe 1 Month 3 Month 6 Month 12 Month
Sortino 1 Month 3 Month 6 Month 12 Month
Volatility 1 Month 3 Month 6 Month 12 Month
Max Drawdown 1 Month 3 Month 6 Month 12 Month
#Author: Kory Hoang
#Developers: Jacob Lower & Mohammed Khalfan
#For collaboration requests, email: [email protected]

#Imports
from quantopian.pipeline.data.builtin import USEquityPricing
import statsmodels.api as sm 
import quantopian.pipeline.data 
import numpy as np
import talib
import scipy
import math

def initialize(context):
    #set_benchmark(symbol('XIV'))
    context.XIV = symbol('XIV') 
    context.VXX = symbol('VXX')
    context.BOND = symbol('IEF')
    context.num_trades = 0

    context.rsi_length = 3
    context.rsi_trigger = 50
    context.wvf_length = 100
    context.ema1 = 10
    context.ema2 = 30
    context.TakeProfitPct = 0.25
    context.StopLossPct = 0.03
    context.XIVpct = 1.0
    context.BONDpct = 1.0
    
    context.BuyPrice = 0
    context.SellLossPrice = 0
    context.SellProfitPrice = 0
    context.sell = False

    #Schedules
    schedule_function(check_rsi, date_rules.every_day(), time_rules.market_close(), False)
    schedule_function(market_open, date_rules.every_day(), time_rules.market_open(), False)
    schedule_function(my_record_vars, date_rules.every_day(), time_rules.market_close())
    
    #Set commission and slippage
    set_commission(commission.PerShare(cost=0.005, min_trade_cost=1.0)) 
    set_slippage(slippage.FixedSlippage(spread=0.01))
    set_long_only()
            
 
def market_open(context,data):
    if context.sell:
        context.sell = False
        if context.portfolio.positions[context.XIV].amount > 0 and len(get_open_orders()) == 0: 
            order_target_percent(context.XIV, 0)
            order_target_percent(context.BOND, context.BONDpct)
            
def check_rsi(context,data):    
    
    vxx_prices = data.history(context.VXX, "high", context.wvf_length*2, "1d")
    vxx_lows = data.history(context.VXX, "low", context.wvf_length*2, "1d")
    vxx_highest = vxx_prices.rolling(window = context.wvf_length, center=False).max()
    WVF = ((vxx_highest - vxx_lows)/(vxx_highest)) * 100

    rsi = talib.RSI(vxx_prices, timeperiod=context.rsi_length)
    
    context.SmoothedWVF1 = talib.EMA(WVF, timeperiod=context.ema1) 
    context.SmoothedWVF2 = talib.EMA(WVF, timeperiod=context.ema2)
    
    record(WVF1=WVF[-1])
    record(RSI=rsi[-1])
    record(EMA10=context.SmoothedWVF1[-1])
    record(EMA30=context.SmoothedWVF2[-1])
    
    print("vxx_low: ", vxx_lows[-1])
    print("vxx_highest: ", vxx_highest[-1])
    print("WVF: ", WVF[-1])
    print("context.SmoothedWVF1: ", context.SmoothedWVF1[-1])
    print("context.SmoothedWVF2: ", context.SmoothedWVF2[-1])
    
    ## BUY RULES
    #if WVF crosses over smoothwvf1 and wvf < smoothwvf2
    if ((WVF[-1] > context.SmoothedWVF1[-1] and WVF[-2] < context.SmoothedWVF1[-2] and WVF[-1] < context.SmoothedWVF2[-1]) or (context.SmoothedWVF1[-2] < context.SmoothedWVF2[-2] and context.SmoothedWVF1[-1] > context.SmoothedWVF2[-1]) or (WVF[-1] > context.SmoothedWVF1[-1] and WVF[-2] < context.SmoothedWVF1[-2] and WVF[-1] > context.SmoothedWVF2[-1] and WVF[-2] < context.SmoothedWVF2[-2])) and context.portfolio.positions[context.XIV].amount == 0:
        set_fixed_stop(context, data)
        order_target_percent(context.XIV, 1.0)
        order_target_percent(context.BOND, 0)
      
    ## SELL RULES
    if context.portfolio.positions[context.XIV].amount > 0 and len(get_open_orders()) == 0:
        #if rsi crosses over rsi_trigger
        if rsi[-2] < context.rsi_trigger and rsi[-1] > context.rsi_trigger:
            context.sell = True
            
        #if wvf crosses under smoothwvf2: sell
        elif WVF[-2] > context.SmoothedWVF2[-2] and WVF[-1] < context.SmoothedWVF2[-1]:
            context.sell = True

def set_fixed_stop(context, data):
    #Only call this once when the stock is bought
    if data.can_trade(context.XIV):
        price = data.current(context.XIV, 'price')
        context.BuyPrice = price
        context.SellLossPrice= price - (context.StopLossPct * price)
        context.SellProfitPrice= (price * context.TakeProfitPct) + price
        
def handle_data(context, data): 
    #If we have a position check sell conditions
    if context.portfolio.positions[context.XIV].amount > 0:
        price = data.current(context.XIV, 'price')
        if price > context.SellProfitPrice and len(get_open_orders()) == 0:
            order_target_percent(context.XIV, 0)
            order_target_percent(context.BOND, context.BONDpct)
            context.sell = False
        if price < context.SellLossPrice and len(get_open_orders()) == 0:
            order_target_percent(context.XIV, 0)
            order_target_percent(context.BOND, context.BONDpct)
            context.sell = False

def my_record_vars(context, data):    
    leverage = context.account.leverage
    record(leverage=leverage)

There was a runtime error.
35 responses

Tearsheet:

Loading notebook preview...
Notebook previews are currently unavailable.

Is it possible to test this algorithm prior to 2010? esp. around 2008. I keep getting NaN error....assuming no data?

I like it. Depending on what you are looking for, this is likely to be attractive to some.

Very cool. Would this algo actually be able to execute the orders right at market close? I suggest having it execute a few minutes before market close and also adding some code for retrying cancel orders just in case.

I noticed that XIV Shotgun purchased XIV at $85.66 at market close 2012-07-12. It has not sold yet. Pretty good cherry pick so far!

Very interesting algo!

  1. It would be very interesting to see, how the algo performed from 2004-2010 because the bullish market from 2011-2017 is ideal for the algo.
  2. Adding position sizing might give some additional safety - any off-market black swan event could wipe out the position, so using for example 33 % position size for the XIV and 100 % for the IEF might make the algo tradeable. Just an idea...

I've just noticed an error while running this. I have the issue of it ordering more XIV than allowed so I set it to only trade 90% then I was able to buy XIV when the next order came through, but still at only 90% it tried to order 1 more share of bonds than I am allowed to purchase. I ran a backtest and it showed that it should have ordered the right amount so I am confused about this issue. Would there be a way if rejected to then order 1 less share until purchase?

Here is a version that can use Robinhood margin. I think it may work for Interactive Brokers with margin. It is using, apparently, IB compatible commissions and slippage.

I think the TarPer function does a reasonable job keeping the orders small enough to not be rejected.

Clone Algorithm
37
Loading...
Backtest from to with initial capital
Total Returns
--
Alpha
--
Beta
--
Sharpe
--
Sortino
--
Max Drawdown
--
Benchmark Returns
--
Volatility
--
Returns 1 Month 3 Month 6 Month 12 Month
Alpha 1 Month 3 Month 6 Month 12 Month
Beta 1 Month 3 Month 6 Month 12 Month
Sharpe 1 Month 3 Month 6 Month 12 Month
Sortino 1 Month 3 Month 6 Month 12 Month
Volatility 1 Month 3 Month 6 Month 12 Month
Max Drawdown 1 Month 3 Month 6 Month 12 Month
import talib
from math import trunc  
from pytz import timezone      
import math
import time
import re
import functools
import itertools
from zipline.utils import tradingcalendar
History = 128
import pandas
import pandas as pd
import datetime
import numpy as np
import scipy
from scipy import stats
from quantopian.algorithm import attach_pipeline, pipeline_output
from quantopian.pipeline import Pipeline
from quantopian.pipeline.data.builtin import USEquityPricing
from quantopian.pipeline.factors import CustomFactor, Latest, SimpleMovingAverage
from quantopian.pipeline.data.quandl import cboe_vix, cboe_vxv, cboe_vxd, cboe_vvix
import statsmodels.api as sm 
import quantopian.pipeline.data 



def initialize(context):
    set_benchmark(symbol('XIV'))
    context.XIV = symbol('XIV') 
    context.VXX = symbol('VXX')
    context.BOND = symbol('TLT')
    context.num_trades = 0

    context.rsi_length  = 3
    context.rsi_trigger = 50
    context.wvf_length  = 100
    context.ema1 = 10
    context.ema2 = 30
    context.TakeProfitPct = 0.25
    context.StopLossPct   = 0.03
    context.XIVpct  = 1.00
    context.BONDpct = 1.00
    
    context.BuyPrice        = 0
    context.SellLossPrice   = 0
    context.SellProfitPrice = 0
    context.sell        = False
    context.ShowMaxLev  = True
    
    #Schedules
    schedule_function(check_rsi, date_rules.every_day(), time_rules.market_close(), False)
    schedule_function(market_open, date_rules.every_day(), time_rules.market_open(), False)
    schedule_function(my_record_vars, date_rules.every_day(), time_rules.market_close())
    
    #Set commission and slippage
    set_commission(commission.PerShare(cost=0.005, min_trade_cost=1.0)) 
    set_slippage(slippage.FixedSlippage(spread=0.01))
    set_long_only()
    
    # so that we can retry cancelled orders
    context.todays_orders = []
    
def before_trading_start(context, data):
    context.mx_lvrg  = 0 # daily max leverage
    pass

def market_open(context,data):
    if context.sell:
        context.sell = False
        if context.portfolio.positions[context.XIV].amount > 0 and len(get_open_orders()) == 0: 
            TarPer(context, data, context.XIV, 0.00)
            TarPer(context, data, context.BOND, context.BONDpct)
            
def check_rsi(context,data):    
    
    vxx_prices = data.history(context.VXX, "high", context.wvf_length*2, "1d")
    vxx_lows = data.history(context.VXX, "low", context.wvf_length*2, "1d")
    vxx_highest = vxx_prices.rolling(window = context.wvf_length, center=False).max()
    WVF = ((vxx_highest - vxx_lows)/(vxx_highest)) * 100

    rsi = talib.RSI(vxx_prices, timeperiod=context.rsi_length)
    
    context.SmoothedWVF1 = talib.EMA(WVF, timeperiod=context.ema1) 
    context.SmoothedWVF2 = talib.EMA(WVF, timeperiod=context.ema2)
    
    #record(WVF1=WVF[-1])
    #record(RSI=rsi[-1])
    #record(EMA10=context.SmoothedWVF1[-1])
    #record(EMA30=context.SmoothedWVF2[-1])
    
    print("vxx_low: ", vxx_lows[-1])
    print("vxx_highest: ", vxx_highest[-1])
    print("WVF: ", WVF[-1])
    print("context.SmoothedWVF1: ", context.SmoothedWVF1[-1])
    print("context.SmoothedWVF2: ", context.SmoothedWVF2[-1])
    
    ## BUY RULES
    #if WVF crosses over smoothwvf1 and wvf < smoothwvf2
    if ((WVF[-1] > context.SmoothedWVF1[-1] and WVF[-2] < context.SmoothedWVF1[-2] and WVF[-1] < context.SmoothedWVF2[-1]) or (context.SmoothedWVF1[-2] < context.SmoothedWVF2[-2] and context.SmoothedWVF1[-1] > context.SmoothedWVF2[-1]) or (WVF[-1] > context.SmoothedWVF1[-1] and WVF[-2] < context.SmoothedWVF1[-2] and WVF[-1] > context.SmoothedWVF2[-1] and WVF[-2] < context.SmoothedWVF2[-2])) and context.portfolio.positions[context.XIV].amount == 0:
        set_fixed_stop(context, data)
        TarPer(context, data, context.BOND, 0.00)
        TarPer(context, data, context.XIV, context.XIVpct)
      
    ## SELL RULES
    if context.portfolio.positions[context.XIV].amount > 0 and len(get_open_orders()) == 0:
        #if rsi crosses over rsi_trigger
        if rsi[-2] < context.rsi_trigger and rsi[-1] > context.rsi_trigger:
            context.sell = True
            
        #if wvf crosses under smoothwvf2: sell
        elif WVF[-2] > context.SmoothedWVF2[-2] and WVF[-1] < context.SmoothedWVF2[-1]:
            context.sell = True

def set_fixed_stop(context, data):
    #Only call this once when the stock is bought
    if data.can_trade(context.XIV):
        price = data.current(context.XIV, 'price')
        context.BuyPrice = price
        context.SellLossPrice= price - (context.StopLossPct * price)
        context.SellProfitPrice= (price * context.TakeProfitPct) + price
        
def TarPer(context, data, stock, TargetPercent):

    if DataCanTrade(context, data, stock):

        if 0 == TargetPercent:
            order_id = order_target_percent(stock, 0.00)
        else:
            # Always want money available to withdraw 
            # and also try to prevent margin related order rejections
            PV = context.portfolio.portfolio_value
            DoNotSpend = 1000 # 200 Cushion plus cash for withdrawals
            RhMargin = 3000.00 # Set to 0 if you do not have Robinhood Gold
            MaxLeverage = 1.33 - .12 # Hard Limit for leverage minus seemingly necessary cushion
            MaxLeverage = min(MaxLeverage, max(MaxLeverage, context.account.leverage))
            RhMargin = min(RhMargin, PV * (MaxLeverage - 1))
            RhPV = PV + RhMargin - DoNotSpend  
            RhCash = RhPV - context.portfolio.positions_value
            amount = context.portfolio.positions[stock].amount
            price = data.current(stock, 'price')
            PosValue   = float(amount * price)
            TarValue   = float(RhPV * TargetPercent)
            DiffValue  = float(TarValue - PosValue)
            DiffValue  = min(DiffValue, RhCash)
            DiffAmount = int(DiffValue / price)
            DiffAmount = 0 if 0 > DiffAmount and 0 == amount else DiffAmount
            order_id = order(stock, DiffAmount)
        # store the order id in case we need to retry the order
        context.todays_orders.append(order_id)

def DataCanTrade(context, data, stock):

    try:
        if data.can_trade(stock):
            return True
        else:
            return False
    except:
        return False


def my_record_vars(context, data):    
    # handling this in handle_data now to show mx_lvrg
    if not context.ShowMaxLev:
        record(Leverage=context.account.leverage)
        pass

def handle_data(context, data): 

    c = context
    if c.ShowMaxLev:
        if c.account.leverage > c.mx_lvrg:  
            c.mx_lvrg = c.account.leverage  
            record(mx_lvrg = c.mx_lvrg)    # Record maximum leverage encountered

    #If we have a position check sell conditions
    if context.portfolio.positions[context.XIV].amount > 0:
        price = data.current(context.XIV, 'price')
        if price > context.SellProfitPrice and len(get_open_orders()) == 0:
            TarPer(context, data, context.XIV, 0.00)
            TarPer(context, data, context.BOND, context.BONDpct)
            context.sell = False
            
        if price < context.SellLossPrice and len(get_open_orders()) == 0:
            TarPer(context, data, context.XIV, 0.00)
            TarPer(context, data, context.BOND, context.BONDpct)
            context.sell = False

    ##
    # Retry cancelled orders
    ##

    for order_id in context.todays_orders[:]:
        original_order = get_order(order_id)
        if original_order and original_order.status == 2 :
            # The order was somehow cancelled so retry
            retry_id = order(original_order.sid, original_order.amount)
            
            log.info('order for %i shares of %s cancelled - retrying' 
                         % (original_order.amount, original_order.sid))
            
            # Remove the original order (typically can't do but note the [:]) and store the new order
            context.todays_orders.remove(order_id)
            context.todays_orders.append(retry_id)

There was a runtime error.

Hi Kory,

I'm relatively new - still got a steep learning curve. General question: why do you use "Symbol(XIV)" in your code as opposed to using "sid(40516)" (as example - same for other symbols)?

Also @Charles Witt - learned a ton from you in other posts. Appreciate all the responses #QuantopianRockstar

The algorithm navigated this sell of brilliantly. Has anyone found a way to test against and '08 scenario?

Hi Tyler,

I use a different platform called MultiCharts to test and execute my strategies. Quantopian is only one of my research tools. However, I did test this strategy on MultiCharts using synthetic XIV and VXX data found here. You should read this to understand the caveats of using synthetic data. With that said, the strategy survived 2008 but it would have had a 50-60% drawdown.

@Geoff,

No reason. Just preference.

I have an even more aggressive version of this algo that wouldn't have survived 2008 without a bruise neither but it would have grown back a hell of a lot faster and stronger. Here it is:

Clone Algorithm
146
Loading...
Backtest from to with initial capital
Total Returns
--
Alpha
--
Beta
--
Sharpe
--
Sortino
--
Max Drawdown
--
Benchmark Returns
--
Volatility
--
Returns 1 Month 3 Month 6 Month 12 Month
Alpha 1 Month 3 Month 6 Month 12 Month
Beta 1 Month 3 Month 6 Month 12 Month
Sharpe 1 Month 3 Month 6 Month 12 Month
Sortino 1 Month 3 Month 6 Month 12 Month
Volatility 1 Month 3 Month 6 Month 12 Month
Max Drawdown 1 Month 3 Month 6 Month 12 Month
#Author: Kory Hoang
#Developer: Mohammed Khalfan
#Email: [email protected]

#Imports
from quantopian.pipeline.data.builtin import USEquityPricing
import statsmodels.api as sm 
import quantopian.pipeline.data 
import numpy as np
import talib
import scipy
import math

def initialize(context):
    #set_benchmark(symbol('XIV'))
    context.XIV = symbol('XIV') 
    context.VXX = symbol('VXX')

    context.wvf_length = 50
    context.ema1 = 5
    context.ema2 = 20
    context.StopLossPct = 0.03
    
    context.BuyPrice = 0
    context.SellLossPrice = 0
    context.SellProfitPrice = 0
    context.sell = False

    #Schedules
    schedule_function(check_rsi, date_rules.every_day(), time_rules.market_close(), False)
    schedule_function(market_open, date_rules.every_day(), time_rules.market_open(), False)
    schedule_function(my_record_vars, date_rules.every_day(), time_rules.market_close())
    
    #Set commission and slippage
    set_commission(commission.PerShare(cost=0.005, min_trade_cost=1.0)) 
    set_slippage(slippage.FixedSlippage(spread=0.01))
    set_long_only()
            
 
def market_open(context,data):
    if context.sell:
        context.sell = False
        if context.portfolio.positions[context.XIV].amount > 0 and len(get_open_orders()) == 0: 
            order_target_percent(context.XIV, 0)
            
def check_rsi(context,data):    
    
    vxx_highs = data.history(context.VXX, "high", context.wvf_length*2, "1d")
    vxx_prices = data.history(context.VXX, "price", context.wvf_length*2, "1d")
    vxx_lows = data.history(context.VXX, "low", context.wvf_length*2, "1d")
    vxx_highest = vxx_highs.rolling(window = context.wvf_length, center=False).max()
    WVF = ((vxx_highest - vxx_lows)/(vxx_highest)) * 100
    
    context.SmoothedWVF1 = talib.EMA(WVF, timeperiod=context.ema1) 
    context.SmoothedWVF2 = talib.EMA(WVF, timeperiod=context.ema2)
    SMA30VXX = talib.SMA(vxx_prices, 30)
    SMA5VXX = talib.SMA(vxx_prices, 5)
    
    record(WVF1=WVF[-1])
    #record(SMA30=SMA30VXX[-1]) USELESS WITHOUT VXX PRICE!
    record(EMA5=context.SmoothedWVF1[-1])
    record(EMA20=context.SmoothedWVF2[-1])
    print("VXX[-2]: {} VXX[-1]: {} SMA5: {} SMA30[-2]: {} SMA30[-1]: {} WVF[-2]: {} WVF[-1]: {} SmoothedWVF1[-2]: {} SmoothedWVF1[-1]: {} SmoothedWVF2[-2]: {} SmoothedWVF2[-1]: {}".format(vxx_prices[-2], vxx_prices[-1], SMA5VXX[-1], SMA30VXX[-2], SMA30VXX[-1], WVF[-2], WVF[-1], context.SmoothedWVF1[-2], context.SmoothedWVF1[-1], context.SmoothedWVF2[-2], context.SmoothedWVF2[-1]))
    
    ## BUY RULES
    if context.portfolio.positions[context.XIV].amount == 0:
        if ((WVF[-1] > context.SmoothedWVF1[-1] and WVF[-2] < context.SmoothedWVF1[-2] and WVF[-1] < context.SmoothedWVF2[-1]) or (WVF[-1] > context.SmoothedWVF1[-1] and WVF[-2] < context.SmoothedWVF1[-2] and WVF[-1] > context.SmoothedWVF2[-1] and WVF[-2] < context.SmoothedWVF2[-2])) and vxx_prices[-1] < SMA5VXX[-1]:
            set_fixed_stop(context, data)
            order_target_percent(context.XIV, 1.0)
            print("bought")
 
    ## SELL RULES
    if context.portfolio.positions[context.XIV].amount > 0 and len(get_open_orders()) == 0:
        #if vxx crosses above SMA30: sell
        if vxx_prices[-2] < SMA30VXX[-2] and vxx_prices[-1] > SMA30VXX[-1]:
            context.sell = True
            print("vxx crosses above SMA30: sell")
            
        #if wvf crosses under smoothwvf2: sell
        if WVF[-2] > context.SmoothedWVF2[-2] and WVF[-1] < context.SmoothedWVF2[-1]:
            context.sell = True
            print("wvf crosses under smoothwvf2: sell")

def set_fixed_stop(context, data):
    #Only call this once when the stock is bought
    if data.can_trade(context.XIV):
        price = data.current(context.XIV, 'price')
        context.BuyPrice = price
        context.SellLossPrice= price - (context.StopLossPct * price)
        
def handle_data(context, data): 
    #If we have a position check sell conditions
    if context.portfolio.positions[context.XIV].amount > 0:
        price = data.current(context.XIV, 'price')
        if price < context.SellLossPrice and len(get_open_orders()) == 0:
            order_target_percent(context.XIV, 0)
            context.sell = False
            print("SL hit")

def my_record_vars(context, data):    
    leverage = context.account.leverage
    record(leverage=leverage*10)

There was a runtime error.

Is there a way to reduce drawdown by swing trading XIV, and holding a conservative/moderate portfolio in between? Or swing trade XIV as a percentage of your portfolio rather than going all-in?

I might try and get some quick and dirty code together later to see if that is effective.

@Delman Check this out, you might like it. 50% is allocated to a version of XIV Shotgun and the other 50% is allocated to a high yield bond momentum strategy.

Clone Algorithm
115
Loading...
Backtest from to with initial capital
Total Returns
--
Alpha
--
Beta
--
Sharpe
--
Sortino
--
Max Drawdown
--
Benchmark Returns
--
Volatility
--
Returns 1 Month 3 Month 6 Month 12 Month
Alpha 1 Month 3 Month 6 Month 12 Month
Beta 1 Month 3 Month 6 Month 12 Month
Sharpe 1 Month 3 Month 6 Month 12 Month
Sortino 1 Month 3 Month 6 Month 12 Month
Volatility 1 Month 3 Month 6 Month 12 Month
Max Drawdown 1 Month 3 Month 6 Month 12 Month
import numpy as np
import scipy
import pandas as pd
import talib

def initialize(context):
    
    set_slippage(slippage.FixedSlippage(spread=0.01))
    set_commission(commission.PerShare(cost=0.005, min_trade_cost=1.00))
    set_benchmark(sid(8554))
    
    #high yield variables 
    context.highYieldTargetPercent = 0.50
    #XIV Shotgun
    context.shotgunPercent = 0.50
    
    context.quarter_flag = False
    context.HYG = sid(33655) # HYG, Junk Bonds
    context.IEF = sid(23870) # IEF, Intermediate-Term Treasuries
    context.HighYieldBuy = 0
    context.HighYieldCurrent = 0
    context.BuyAlert = False
    context.firstBuy = True
    
    #VXX used for strategy to buy XIV
    context.vxx = sid(38054) # VXX, Short-Term VIX
    context.xiv = sid(40516) # XIV, Short-Term Inverse VIX
    context.safe = sid(38984) # VGIT, Intermediate-Term Treasuries
    
    context.StopLossPct = 0.03
    context.AvgLength = 20
    context.LengthWVF = 100
    context.LengthEMA1 = 10
    context.LengthEMA2 = 30
    
    #internal variables to store data
    context.SellAlert = False 
    context.vxxAvg = 0 
    context.SmoothedWVF1 = 0
    context.SmoothedWVF2 = 0
    context.vxxLow = 0
    context.vxxHigh = 0
    context.StopPrice = 0
    context.BuyPrice = 0
    context.SellPrice = 0
    
    #High Yield Schedules
    schedule_function(end_of_month, date_rule=date_rules.month_end())
    schedule_function(rebalance, date_rules.month_start(), time_rules.market_close(minutes=10))
    
    #XIV Shotgun Schedules
    #Buy stocks 10 mins before market closes.
    schedule_function(openFunction, date_rules.every_day(), time_rules.market_close(minutes=10), False) 
    #Sell once market opens if sell alert was triggered on prior day before. 
    schedule_function(closeFunction, date_rules.every_day(), time_rules.market_open(minutes=150), False) 

def rebalance(context, data):
    if context.BuyAlert:
        if context.HighYieldCurrent != 0:
            order_target_percent(context.HighYieldCurrent,  0)
        order_target_percent(context.HighYieldBuy,  context.highYieldTargetPercent)
        context.HighYieldCurrent = context.HighYieldBuy
        context.BuyAlert = False

def end_of_month(context, data):
    if get_datetime().month % 3 == 0  or context.firstBuy: 
        n = 81
        context.quarter_flag = True
        maxPercent = 0
        
        HYG_prices = data.history(context.HYG, "price", n, "1d")
        HYP_PercentChange = (HYG_prices[-1] - HYG_prices[-81]) / HYG_prices[-1]
        context.HighYieldBuy = context.HYG
        maxPercent = HYP_PercentChange
        
        IEF_prices = data.history(context.IEF, "price", n, "1d")
        IEF_PercentChange = (IEF_prices[-1] - IEF_prices[-81]) / IEF_prices[-1]
        if IEF_PercentChange > maxPercent:
            context.HighYieldBuy = context.IEF
            maxPercent = IEF_PercentChange
        
        if context.HighYieldBuy != context.HighYieldCurrent:
            context.BuyAlert = True
        context.firstBuy = False

def openFunction(context, data):
    
    #Added to buy high yield as soon as algo starts
    if context.firstBuy:
        end_of_month(context, data)
        rebalance(context, data)
    
    DateTime = get_datetime()
    #Gets Moving Average of VXX
    price_hist = data.history(context.vxx, 'price', context.AvgLength, '1d')
    context.vxxAvg = price_hist.mean()
    
    #get data for calculations
    n = 200
    vxx_prices = data.history(context.vxx, "price", n + 2, "1d")
    vxx_lows = data.history(context.vxx, "low", n + 2, "1d")
    vxx_highest = vxx_prices.rolling(window = context.LengthWVF,center=False).max()
    
    #William's VIX Fix indicator a.k.a. the Synthetic VIX
    WVF = ((vxx_highest - vxx_lows)/(vxx_highest)) * 100
    
    # Calculates smoothed WVF
    context.SmoothedWVF1 = talib.EMA(WVF, timeperiod=context.LengthEMA1) 
    context.SmoothedWVF2 = talib.EMA(WVF, timeperiod=context.LengthEMA2)
    
    #Do some checks for crossovers
    if WVF[-1] > context.SmoothedWVF1[-1] and WVF[-2] < context.SmoothedWVF1[-1]:
        wvf_crossedSmoothedWVF1 = True
    else: 
        wvf_crossedSmoothedWVF1 = False
    #Same except for smoothed2 
    if WVF[-1] > context.SmoothedWVF2[-1] and WVF[-2] < context.SmoothedWVF2[-1]:
        wvf_crossedSmoothedWVF2 = True
    else: 
        wvf_crossedSmoothedWVF2 = False
    
    #Current price of vxx and xiv
    price = data.current(context.vxx, 'price')
    pricexiv = data.current(context.xiv, 'price')
    
    #Do buy conditions. 
    if ( wvf_crossedSmoothedWVF1 and WVF[-1] < context.SmoothedWVF2[-1]) or (wvf_crossedSmoothedWVF2 and wvf_crossedSmoothedWVF1):
        #if we don't have a position set fixed stop
        if context.portfolio.positions[context.xiv].amount == 0:
            set_fixed_stop(context, data)
            context.BuyPrice = pricexiv
            order_target_percent(context.safe,  0)
            order_target_percent(context.xiv,  context.shotgunPercent)
        
    #if len(context.portfolio.positions) > 0:
    #Test to see if we need to set a sell alert. 
    if ((price > context.vxxAvg and vxx_prices[-2] < context.vxxAvg) or (WVF[-1] < context.SmoothedWVF2[-1] and WVF[-2] > context.SmoothedWVF2[-1])):
        #Sell Tomorrow morning 
        context.SellAlert = True
  
    #Graph some values
    record(VXX_MAVG = context.vxxAvg)
    record(wvf_vxx = WVF[-1])
    record(SmoothWVF1 = context.SmoothedWVF1[-1])
    record(SmoothWVF2 = context.SmoothedWVF2[-1])

def handle_data(context, data):
    pricexiv = data.current(context.xiv, 'price')
    if pricexiv < context.StopPrice and len(get_open_orders()) == 0 and context.portfolio.positions[context.xiv].amount > 0:
        #logging_sale(context, data, "Short Sale: ")
        order_target_percent(context.xiv,  0)
        context.StopPrice = 0
        context.BuyPrice = 0
    if len(get_open_orders()) == 0 and context.portfolio.positions[context.xiv].amount == 0:
        order_target_percent(context.safe,  context.shotgunPercent)

    if 'mx_lvrg' not in context:             # Max leverage  
        context.mx_lvrg = 0                  # Init this instead in initialize() for better efficiency  
    if context.account.leverage > context.mx_lvrg:  
        context.mx_lvrg = context.account.leverage  
        record(mx_lvrg = context.mx_lvrg)    # Record maximum leverage encountered          
        
def closeFunction(context, data): 
    if context.SellAlert and len(get_open_orders()) == 0 and context.portfolio.positions[context.xiv].amount > 0:
        #logging_sale(context, data, "Condition sale: ")
        order_target_percent(context.xiv,  0)
        order_target_percent(context.safe,  context.shotgunPercent)
        context.BuyPrice = 0
        context.StopPrice = 0
    context.SellAlert = False
    
def set_fixed_stop(context, data):
    #Only want to call this once when the stock is bought. 
    if data.can_trade(context.xiv):
        price = data.current(context.xiv, 'price')
        context.StopPrice= max(context.StopPrice, price - (context.StopLossPct * price)) 
       
def before_trading_start(context, data):
    if context.quarter_flag:
        print(get_datetime())
        context.quarter_flag = False
    if context.portfolio.positions[context.xiv].amount == 0:
        context.StopPrice = 0
        
def logging_sale(context, data, reason):
    DateTime = get_datetime()
    if context.portfolio.positions[context.xiv].amount > 0:
        pricexiv = data.current(context.xiv, 'price')
        stopPercentFinal = (context.BuyPrice - pricexiv) / context.BuyPrice
        stopPercentTarget = (context.BuyPrice - context.StopPrice) / context.BuyPrice
        log.info(reason + str(get_datetime()))
        log.info("Sell Price:" + str(pricexiv))
        log.info("Buy Price: " + str(context.BuyPrice))
        log.info("Stop Price: " + str(context.StopPrice))
        log.info("Stop Actual%: " + str(stopPercentFinal))
        log.info("Stop Target%: " + str(stopPercentTarget))
        log.info("***************************")
There was a runtime error.

@ Kory Hoang it would be very helpful to many users on here if you could backtest Charles Witt strategy back to the 08-09 crisis.

If you can help figure out how to load my own custom synthetic XIV & VXX data into Quantopian then I'll do it

Hey Kory, great algo.. I was just backtesting this yesterday and noticed quite a bit of downtime (ie where leverage is 0). Would it be possible to stick all available cash during these periods into the bond funds, and then sell when the time is right to buy XIV/VXX?

Edit: Also, I was getting slightly better returns with half-day set to True.

Happy to report the original algo has margin average of only $14.28 per day until near the end, almost nothing. (https://www.quantopian.com/posts/margin). Is there some reason one would not want to trade this with real money? (volatility or drawdown or or ?)

@Kern sure. With low trading frequency algos like these, I usually make them park in IEF or TLT when not activated.

@Seahawk I've been trading XIV Shotgun since March along with 6 other algos for our startup's charity fund (www.quantprophet.com/charity)

Congrats. With 7 algos, different start dates and possibly transferring profits out and/or adding to them occasionally it might take a team of accountants to arrive at a percentage in the real market but if there is any way to gage a backtest comparison that'd be cool.

I've had a few hiccups along the way. The biggest problems for me so far are human error and system failure, not actual problems with the strategies themselves. They have been performing very well as expected and slippages have been in line with my assumptions.

My broker's (Interactive Broker) portfolio analytics feature actually calculates my return net of deposits/withdrawals for me so I don't have to worry about that (but I don't often deposit/withdraw from our startup's charity fund anyway).

@Kory, I've played around with your version of the algorithm a little bit, and the only thing I cannot figure out is how to get the leverage at or equal to 1. Even turning down the XIV to Bond ratio, it jumps unexpectedly. Any thoughts?

Will Quantopian allow me to trade different strategies in the same account? Say I could set the cash to 50% and trade 2 strategies this way?

@Tyler, no. You'd have to combine them into one algorithm or open a second brokerage account.

@tyler. If you use IB you can create linked accounts and they consolidate in the master account. The linked account needs its own login. I have 6 Algos like that

I love this algorithm, so I've decided to improve it, simplify it, make it more efficient, and more profitable.
I may have a bug because it was just a quick adjustment, but some bugs could actually make it better :-)

Clone Algorithm
124
Loading...
Backtest from to with initial capital
Total Returns
--
Alpha
--
Beta
--
Sharpe
--
Sortino
--
Max Drawdown
--
Benchmark Returns
--
Volatility
--
Returns 1 Month 3 Month 6 Month 12 Month
Alpha 1 Month 3 Month 6 Month 12 Month
Beta 1 Month 3 Month 6 Month 12 Month
Sharpe 1 Month 3 Month 6 Month 12 Month
Sortino 1 Month 3 Month 6 Month 12 Month
Volatility 1 Month 3 Month 6 Month 12 Month
Max Drawdown 1 Month 3 Month 6 Month 12 Month
#Author: Kory Hoang
#Developers: Jacob Lower & Mohammed Khalfan
#For collaboration requests, email: [email protected]

#Imports
from quantopian.pipeline.data.builtin import USEquityPricing
import statsmodels.api as sm 
import quantopian.pipeline.data 
import numpy as np
import talib
import scipy
import math
import random

def initialize(context):
    #set_benchmark(symbol('XIV'))
    context.XIV = symbol('XIV') 
    context.VXX = symbol('VXX')
    context.BOND = symbol('IEF')
    context.num_trades = 0

    context.rsi_length = 3
    context.rsi_trigger = 50
    context.wvf_length = 100
    context.ema1 = 10
    context.ema2 = 30
    context.TakeProfitPct = 0.25
    context.StopLossPct = 0.03
    context.XIVpct = 1.0
    context.BONDpct = 1.0
    
    context.BuyPrice = 0
    context.SellLossPrice = 0
    context.SellProfitPrice = 0
    context.sell = False

    #Schedules
    schedule_function(check_rsi, date_rules.every_day(), time_rules.market_close(), False)
    schedule_function(market_open, date_rules.every_day(), time_rules.market_open(), False)
    schedule_function(my_record_vars, date_rules.every_day(), time_rules.market_close())
    
    #Set commission and slippage
    set_commission(commission.PerShare(cost=0.005, min_trade_cost=1.0)) 
    set_slippage(slippage.FixedSlippage(spread=0.01))
    set_long_only()
    
    random.seed(0)
            
 
def market_open(context,data):
    if context.sell:
        context.sell = False
        if context.portfolio.positions[context.XIV].amount > 0 and len(get_open_orders()) == 0: 
            order_target_percent(context.XIV, 0)
            order_target_percent(context.BOND, context.BONDpct)
            
def check_rsi(context,data):    
    
    monkey_pick = random.randrange(0,100)
    
    ## BUY RULES
    if (monkey_pick >= 60):
        set_fixed_stop(context, data)
        order_target_percent(context.XIV, 1.0)
        order_target_percent(context.BOND, 0)
      
    ## SELL RULES
    if context.portfolio.positions[context.XIV].amount > 0 and len(get_open_orders()) == 0:
        if (monkey_pick <= 5):
            context.sell = True
            
def set_fixed_stop(context, data):
    #Only call this once when the stock is bought
    if data.can_trade(context.XIV):
        price = data.current(context.XIV, 'price')
        context.BuyPrice = price
        context.SellLossPrice= price - (context.StopLossPct * price)
        context.SellProfitPrice= (price * context.TakeProfitPct) + price
        
def handle_data(context, data): 
    #If we have a position check sell conditions
    if context.portfolio.positions[context.XIV].amount > 0:
        price = data.current(context.XIV, 'price')
        if price > context.SellProfitPrice and len(get_open_orders()) == 0:
            order_target_percent(context.XIV, 0)
            order_target_percent(context.BOND, context.BONDpct)
            context.sell = False
        if price < context.SellLossPrice and len(get_open_orders()) == 0:
            order_target_percent(context.XIV, 0)
            order_target_percent(context.BOND, context.BONDpct)
            context.sell = False

def my_record_vars(context, data):    
    leverage = context.account.leverage
    record(leverage=leverage)

There was a runtime error.

@Kon Rad, yes your monkeypick (haha) outperforms but you are missing a key component which is volatility, your sharpe is much lower with a DD @ 50%, so I think you are just proving that Kory algo is actually quite efficient... or did I miss your point?

I am happy to announce that this algo, along with all of my other volatility algos, have survived Black Monday 2018!

Clone Algorithm
619
Loading...
Backtest from to with initial capital
Total Returns
--
Alpha
--
Beta
--
Sharpe
--
Sortino
--
Max Drawdown
--
Benchmark Returns
--
Volatility
--
Returns 1 Month 3 Month 6 Month 12 Month
Alpha 1 Month 3 Month 6 Month 12 Month
Beta 1 Month 3 Month 6 Month 12 Month
Sharpe 1 Month 3 Month 6 Month 12 Month
Sortino 1 Month 3 Month 6 Month 12 Month
Volatility 1 Month 3 Month 6 Month 12 Month
Max Drawdown 1 Month 3 Month 6 Month 12 Month
#Author: Kory Hoang
#Developers: Jacob Lower & Mohammed Khalfan
#For collaboration requests, email: [email protected]

#Imports
from quantopian.pipeline.data.builtin import USEquityPricing
import statsmodels.api as sm 
import quantopian.pipeline.data 
import numpy as np
import talib
import scipy
import math

def initialize(context):
    #set_benchmark(symbol('XIV'))
    context.XIV = symbol('XIV') 
    context.VXX = symbol('VXX')
    context.BOND = symbol('IEF')
    context.num_trades = 0

    context.rsi_length = 3
    context.rsi_trigger = 50
    context.wvf_length = 100
    context.ema1 = 10
    context.ema2 = 30
    context.TakeProfitPct = 0.25
    context.StopLossPct = 0.03
    context.XIVpct = 1.0
    context.BONDpct = 1.0
    
    context.BuyPrice = 0
    context.SellLossPrice = 0
    context.SellProfitPrice = 0
    context.sell = False

    #Schedules
    schedule_function(check_rsi, date_rules.every_day(), time_rules.market_close(), False)
    schedule_function(market_open, date_rules.every_day(), time_rules.market_open(), False)
    schedule_function(my_record_vars, date_rules.every_day(), time_rules.market_close())
    
    #Set commission and slippage
    set_commission(commission.PerShare(cost=0.005, min_trade_cost=1.0)) 
    set_slippage(slippage.FixedSlippage(spread=0.01))
    set_long_only()
            
 
def market_open(context,data):
    if context.sell:
        context.sell = False
        if context.portfolio.positions[context.XIV].amount > 0 and len(get_open_orders()) == 0: 
            order_target_percent(context.XIV, 0)
            order_target_percent(context.BOND, context.BONDpct)
            
def check_rsi(context,data):    
    
    vxx_prices = data.history(context.VXX, "high", context.wvf_length*2, "1d")
    vxx_lows = data.history(context.VXX, "low", context.wvf_length*2, "1d")
    vxx_highest = vxx_prices.rolling(window = context.wvf_length, center=False).max()
    WVF = ((vxx_highest - vxx_lows)/(vxx_highest)) * 100

    rsi = talib.RSI(vxx_prices, timeperiod=context.rsi_length)
    
    context.SmoothedWVF1 = talib.EMA(WVF, timeperiod=context.ema1) 
    context.SmoothedWVF2 = talib.EMA(WVF, timeperiod=context.ema2)
    
    record(WVF1=WVF[-1])
    record(RSI=rsi[-1])
    record(EMA10=context.SmoothedWVF1[-1])
    record(EMA30=context.SmoothedWVF2[-1])
    
    print("vxx_low: ", vxx_lows[-1])
    print("vxx_highest: ", vxx_highest[-1])
    print("WVF: ", WVF[-1])
    print("context.SmoothedWVF1: ", context.SmoothedWVF1[-1])
    print("context.SmoothedWVF2: ", context.SmoothedWVF2[-1])
    
    ## BUY RULES
    #if WVF crosses over smoothwvf1 and wvf < smoothwvf2
    if ((WVF[-1] > context.SmoothedWVF1[-1] and WVF[-2] < context.SmoothedWVF1[-2] and WVF[-1] < context.SmoothedWVF2[-1]) or (context.SmoothedWVF1[-2] < context.SmoothedWVF2[-2] and context.SmoothedWVF1[-1] > context.SmoothedWVF2[-1]) or (WVF[-1] > context.SmoothedWVF1[-1] and WVF[-2] < context.SmoothedWVF1[-2] and WVF[-1] > context.SmoothedWVF2[-1] and WVF[-2] < context.SmoothedWVF2[-2])) and context.portfolio.positions[context.XIV].amount == 0:
        set_fixed_stop(context, data)
        order_target_percent(context.XIV, 1.0)
        order_target_percent(context.BOND, 0)
      
    ## SELL RULES
    if context.portfolio.positions[context.XIV].amount > 0 and len(get_open_orders()) == 0:
        #if rsi crosses over rsi_trigger
        if rsi[-2] < context.rsi_trigger and rsi[-1] > context.rsi_trigger:
            context.sell = True
            
        #if wvf crosses under smoothwvf2: sell
        elif WVF[-2] > context.SmoothedWVF2[-2] and WVF[-1] < context.SmoothedWVF2[-1]:
            context.sell = True

def set_fixed_stop(context, data):
    #Only call this once when the stock is bought
    if data.can_trade(context.XIV):
        price = data.current(context.XIV, 'price')
        context.BuyPrice = price
        context.SellLossPrice= price - (context.StopLossPct * price)
        context.SellProfitPrice= (price * context.TakeProfitPct) + price
        
def handle_data(context, data): 
    #If we have a position check sell conditions
    if context.portfolio.positions[context.XIV].amount > 0:
        price = data.current(context.XIV, 'price')
        if price > context.SellProfitPrice and len(get_open_orders()) == 0:
            order_target_percent(context.XIV, 0)
            order_target_percent(context.BOND, context.BONDpct)
            context.sell = False
        if price < context.SellLossPrice and len(get_open_orders()) == 0:
            order_target_percent(context.XIV, 0)
            order_target_percent(context.BOND, context.BONDpct)
            context.sell = False

def my_record_vars(context, data):    
    leverage = context.account.leverage
    record(leverage=leverage)
There was a runtime error.

Here's a screenshot of all my volatility algos taken after the market closed on February 5, 2018. They are set up on MultiCharts. Note the +50% VXX trade on the top right chart.

Looks like the others all lost ~33%? Were you trading these with real money?

Reflecting on what happened, do you think the algos were prepared for this first of its kind event or do you think it was luck? What would have happened had the VIX spike been even more out-of-the-blue (e.g. caused some overnight black swan event)?

They did not lose 33%, I'm not sure where you're seeing that. They all went to cash long before the crash took place. Check out the latest backtests in my other posts for reference. And yes I've been trading all of them with real money for 2 years.

The algos are trend-following by design and were well suited for the scenario that unfolded, i.e., a gradual crash that took place over several days and not a flash crash.

Also, my hedging strategy of buying SVXY puts mentioned here also resulted in over 100x return for the hedge, which would have effectively canceled out your XIV loss on Feb 5.

How did you real-trading? As Quantopian cancels its support.

Nice work!

How about using leveraged ETFs like UPRO/TQQQ/SOXL as trading securities?
We can remain VXX and WVF as indicators.
This could reduce the systematic risk or "black swan" as XIV/SVXY can suddenly drop to bottom (could be during overnight or weekends).
(If the auto-trading system has a problem and we hold XIV/SVXY for a long time, it could possibly be a disaster, too.)

Other thoughts:
Maybe we can replace VXX with UVXY to increase the indicator's sensitivity, or use Money Flow Index (MFI) to replace RSI.
Or use MACD's derivative as another indicator.
These naive thoughts may also work for the "Ballistic XIV/VXX" strategy, or be used for inefficiency detection of other assets.

Anyway, simplicity is always better than complexity. I just propose these ideas for discussion.

Hi,

Beginner question here : how do you live trade ? Do you manually buy or sell according to the signals generated by the algo or is it automatic ? I cannot see any command in the algo which would execute the trade on a trading platform.

Thanks in advance for the reply.