Back to Community
XIV/VXX Pair Trade

Sharing full 2011-01-01 to 2016-06-17 backtest.

Daily rebalance of XIV/VXX pair trade (60% long XIV, 40% short VXX) or (60% long VXX, short 40% XIV) based solely on PsychSignal's Twitter bull_minus_bear intensity sentiment on StockTwits of SPY.

Revert signal's long or short of the pair trade if drawdown exceeds 18% from peak and so on...

Clone Algorithm
313
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
''' 
    This algorithm defines a target long-only diversified portfolio and rebalances 
    it at a user-specified frequency. 
'''

import datetime
import pytz
from scipy import stats 
import pandas as pd
from zipline.utils.tradingcalendar import get_early_closes
from quantopian.pipeline.data.quandl import cboe_vix as vix
# Using the free sample in your pipeline algo
# Free dataset availability is 24 Aug 2009 - 31 Dec 2015
from quantopian.pipeline.data.psychsignal import aggregated_twitter_withretweets_stocktwits_free as psychsignal
from quantopian.algorithm import attach_pipeline, pipeline_output  
from quantopian.pipeline import Pipeline  
from quantopian.pipeline.factors import CustomFactor  
from quantopian.pipeline.data.builtin import USEquityPricing  
import numpy as np

class VIX(CustomFactor):
    inputs=[vix.vix_close]
    window_length = 1 
    
    def compute(self, today, assets, out, v_close):
        out[:] = v_close

class SidFactor(CustomFactor):  
    inputs = []  
    window_length = 1

    def compute(self, today, assets, out):  
        out[:] = assets
       
def initialize(context):
    
    pipe = Pipeline()  
    pipe_columns = {
        'bullish_intensity':psychsignal.bullish_intensity.latest,
        'bearish_intensity':psychsignal.bearish_intensity.latest,
        'bull_minus_bear':psychsignal.bull_minus_bear.latest,
        'vix':VIX()
    }
    
    pipe = Pipeline(columns = pipe_columns)
    pipe = attach_pipeline(pipe, name='psychsignal')
    
    # Screen out low liquidity securities.  
    mySidFilter = SidFactor()
    my_sid_filter = mySidFilter.eq(8554)
    pipe.set_screen(my_sid_filter)
    
    # Define the instruments in the portfolio:
    set_symbol_lookup_date('2012-1-1')
    context.stocks = {
        symbol('xiv'): -.40,
        symbol('vxx'): .60,
        #symbol('XIV'): .0,
        #symbol('VXX'): .60
    }
    
    context.bearish_stocks = {
        symbol('XIV'):.60, 
        symbol('VXX'):-.40
        #symbol('VXX'): .0,
        #symbol('XIV'): .60
    }
    
    # Define the benchmark (used to get early close dates for reference).
    context.spy = sid(8554)
    
    start_date = context.spy.security_start_date
    end_date   = context.spy.security_end_date
    
    # Initialize context variables the define rebalance logic:
    context.rebalance_date = None
    context.next_rebalance_Date = None
    context.rebalance_days = 60
    context.bull_minus_bear = 0
    context.portfolio_values = []
    context.portfolio_values2 = []
    context.n_window = 12
    context.n_window_2 = 27
    context.rolling_window_pl = 0
    context.rolling_window_pl2 = 0
    context.bull_minus_bear_window = []
    context.bmb_n_window = 30
    context.peak_value = context.portfolio.portfolio_value
    context.vix = 0
    
    # Get the dates when the market closes early:
    context.early_closes = get_early_closes(start_date, end_date).date
    context.liquidated = False
    
    # Scheduling a function to run everyday 30 minutes after market open to log
    # our sentiment scores.
    schedule_function(func=log_scores,
                      date_rule=date_rules.every_day(),
                      time_rule=time_rules.market_open(minutes=30))

    set_commission(commission.PerTrade(cost=1))

def before_trading_start(context, data):
    
    if (context.portfolio.portfolio_value > context.peak_value):
        context.peak_value = context.portfolio.portfolio_value
     
    context.pl_from_peak = (context.portfolio.portfolio_value - context.peak_value)/context.peak_value
    
    context.portfolio_values.append(context.portfolio.portfolio_value)
    context.portfolio_values2.append(context.portfolio.portfolio_value)
    if len(context.portfolio_values) > context.n_window:
        old_value = context.portfolio_values.pop(0)
        context.rolling_window_pl = (context.portfolio.portfolio_value - old_value) / old_value
    
    if len(context.portfolio_values2) > context.n_window_2:
        old_value = context.portfolio_values2.pop(0)
        context.rolling_window_pl2 = (context.portfolio.portfolio_value - old_value) / old_value
    
    def swap_allocation():
        temp_arr = context.stocks
        context.stocks = context.bearish_stocks
        context.bearish_stocks = temp_arr
    
    def liquidated_allocation():
        context.old_stocks = context.bearish_stocks
        context.old_bearish_stocks = context.old_stocks
        
        context.stocks = {
            symbol('xiv'): .0,
            symbol('vxx'): .0,
        }
    
        context.bearish_stocks = {
            symbol('XIV'):.0, 
            symbol('VXX'):.0
        }
    
    #if context.vix > 23:
    #    if context.liquidated == False:
    #        log.info("Liquidate allocation when VIX is too high!")
    #        swap_allocation()
    #        context.liquidated = True
    if (context.rolling_window_pl < -0.15) or (context.rolling_window_pl2 < -0.22) or (context.pl_from_peak < -0.18):
        
        #if (context.liquidated == True):
        #    log.info("VIX falling back from 21 threshold, revert back to old allocation")
            #revert back to old allocation
         #   swap_allocation()
         #   context.rolling_window_pl = 0
         #   context.portfolio_values = []
        
         #   context.rolling_window_pl2 = 0
         #   context.portfolio_values2 = []
         #   context.peak_value = context.peak_value * 0.88
         #   context.liquidated = False
            
        #if (context.pl_from_peak < -0.20):
        log.info("regime change triggered, swapping allocation")
        swap_allocation()
        context.rolling_window_pl = 0
        context.portfolio_values = []
        
        context.rolling_window_pl2 = 0
        context.portfolio_values2 = []
        context.peak_value = context.peak_value * 0.88
    
    context.previous_bull_minus_bear = context.bull_minus_bear
    context.results = pipeline_output('psychsignal').dropna()[:2]
    context.bull_minus_bear = context.results["bull_minus_bear"][0]
    #context.bull_minus_bear_vxx = context.results["bull_minus_bear"][1]

    #context.bull_minus_bear = (context.bull_minus_bear + context.bull_minus_bear_vxx * 0.50) / 2
    
    context.vix = context.results['vix'][0]
    context.rebalanced = False
    #results = pipeline_output('sentiment_metrics').dropna()  
    #bull_ranks = results["bull_msgs"].rank().order()  
    #bear_ranks = results["bear_msgs"].rank().order()

def log_scores(context, data):
    #log.info(context.results)
    log.info("rolling_window_pl:")
    log.info(str(context.rolling_window_pl) + "," + str(context.rolling_window_pl2))
    
def handle_data(context, data):
    # Get the current exchange time, in local timezone: 
    exchange_time = pd.Timestamp(get_datetime()).tz_convert('US/Eastern')
    
    rebalance_flag = False
    if (context.previous_bull_minus_bear < 0 and context.bull_minus_bear > 0) or (context.previous_bull_minus_bear > 0 and context.bull_minus_bear < 0): 
        rebalance_flag = True
    
    # If it is rebalance day, rebalance:
    if (context.rebalanced == False and rebalance_flag) or context.rebalance_date == None or exchange_time >= context.next_rebalance_date:
       # If we are in rebalance window but there are open orders, wait til next minute
       if has_open_orders(data,context) == True:
            log.info('Has open orders, not rebalancing.')
       else:
           # If there are no open orders we can rebalance.
           rebalance(context, data, exchange_time) 
           context.rebalanced = True
           log.info('Rebalanced portfolio to target weights at %s' % exchange_time)

           # Update the current and next rebalance dates
           context.rebalance_date = exchange_time 
           context.next_rebalance_date = context.rebalance_date + datetime.timedelta(days=context.rebalance_days)      
           print(context.next_rebalance_date)

def rebalance(context,data,exchange_time):
    stocks_balance = {}
    if context.bull_minus_bear > 0:
        stocks_balance = context.stocks
    else:
        stocks_balance = context.bearish_stocks
    
    for stock in context.stocks:
        if stock in data:
            order_target_percent(stock, stocks_balance[stock])

def has_open_orders(data,context):               
    # Only rebalance when we have zero pending orders.
    has_orders = False
    for stk in data:
        orders = get_open_orders(stk)
        if orders:
            for oo in orders:                  
                message = 'Open order for {amount} shares in {stock}'  
                message = message.format(amount=oo.amount, stock=stk)  
            has_orders = True
    return has_orders           
There was a runtime error.