Back to Community
Help with a maximum Holding period XIV strategy

Hi everyone,
So I am doing a backtest on XIV based on the VIX.
I would like to set a holding period of maximum 21 days, however, the code seems not to work and I am not sure why.
In lines 55 to 58 I defined the maximum holding period and in lines 189 to 200 I want to sell the position if it has ben bought more than 21 days ago.

Could maybe anyone give me a hand?

Thank you so much

Clone Algorithm
16
Loading...
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 is a template algorithm on Quantopian for you to adapt and fill in.
"""
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 AverageDollarVolume
from quantopian.pipeline.filters.morningstar import Q500US
import talib
import numpy as np
import pandas as pd
 
   
def preview(roc):
    log.info('\n %s ' % roc.head())
    return roc


def initialize(context):
    """
    Called once at the start of the algorithm.
    """
    context.stocks = [sid(8554),
                      sid(38054),
                      sid(40516)]
    context.spy = sid(8554)
    context.xiv = sid(40516)
    
    set_benchmark(sid(40516))
    
    context.allocation = [0,0,0]
    
    # VIX ROC
    url = 'https://dl.dropboxusercontent.com/s/hq1qb2vndwzkfzs/01.%20XIVROC.csv?dl=0'

    # fetch the data
    roc = fetch_csv(url,
              date_column    = 'Date',
              date_format    = '%m/%d/%y',
              symbol_column  = 'Symbol',
              pre_func       = preview)


    
    # Rebalance every day, 1 hour after market open.
    schedule_function(my_rebalance, date_rules.every_day(), time_rules.market_open())
    
    # Record tracking variables at the end of each day.
    schedule_function(my_record_vars, date_rules.every_day(), time_rules.market_close())
    
    #Set slippage and commission
    set_slippage(slippage.VolumeShareSlippage(volume_limit=0.025, price_impact=0.1))
    set_commission(commission.PerShare(cost=0.0075, min_trade_cost=1))
     
    #: Declaring the days to hold, change this to what you want
    context.days_to_hold = 1
    #: Declares which stocks we currently held and how many days we've held them dict[stock:days_held]
    context.stocks_held = {}

def before_trading_start(context, data):
    """
    Called every day before market open. Variable definition
    """

    prices = data.history(context.spy, ['price','low'], 100, '1d')
    xivpri = data.history(context.xiv, ['price','low'], 100, '1d')

    previous=30
    after=0
    aftertop=1
    
        # Loop through our list of stocks
    for stock in context.stocks:
        # Get the StochRSI of this stock.
        rsik = talib.STOCHRSI(prices['price'], timeperiod=14, fastk_period=3, fastd_period=3)[0]
        rsid = talib.STOCHRSI(prices['price'], timeperiod=14, fastk_period=3, fastd_period=3)[1]
     
    WVF = (prices['price'].rolling(28).max() - prices['low'])/prices['price'].rolling(28).max() * 100
    
    shortma = pd.rolling_mean(WVF, 12)
    longma  = pd.rolling_mean(WVF, 26)
    macd    = shortma - longma
    signal  = pd.rolling_mean(macd, 9)
    trigger = macd - signal
    
    # Get the Moving average of WVF.
    Moving_WVF = WVF.rolling(3).mean()
    
    # Get the bollinger bands of WVF.
    WVFupper, WVFmiddle, WVFlower = talib.BBANDS(
        np.array(WVF), 
        timeperiod=28,
        # number of non-biased standard deviations from the mean
        nbdevup=2,
        nbdevdn=2,
        matype=0)
    
    ROC = data.current(context.xiv, 'Rate')

    
    if prices['price'][-after-1] < min(prices['price'][-previous-after-1:-after-1]):
        
        if after > 1 and prices['price'][-after-1] < min(prices['price'][-after:-1]):
            context.allocation[2] = 1.0
            context.allocation[0] = 0.0
            context.allocation[1] = 0.0
            
    if prices['price'][-after-1] < min(prices['price'][-previous-after-1:-after-1]):
        
        if after > 1 and prices['price'][-after-1] < min(prices['price'][-after:-1]):
            context.allocation[2] = 1.0
            context.allocation[0] = 0.0
            context.allocation[1] = 0.0
                      
        elif after == 1 and prices['price'][-after-1] < prices['price'][-1]:
            context.allocation[2] = 1.0
            context.allocation[0] = 0.0
            context.allocation[1] = 0.0
            
        elif after == 0:
            context.allocation[2] = 1.0
            context.allocation[0] = 0.0
            context.allocation[1] = 0.0
            
    else:
        # When WVF goes below the lower band, time to buy the stock.
        if (WVF[-2] > WVFlower[-2] and WVF[-1] < WVFlower[-1]):
            context.allocation[2] = 1.0
            context.allocation[0] = 0.0
            context.allocation[1] = 0.0

        if (rsik[-2]>rsid[-2] and rsik[-1]<=rsid[-1]):
            context.allocation[2] = 1.0
            context.allocation[0] = 0.0
            context.allocation[1] = 0.0

        if (trigger[-2]>0 and trigger[-1]<0): # MACD Signal for
                                              # the WVF.
            context.allocation[2] = 1.0
            context.allocation[0] = 0.0
            context.allocation[1] = 0.0
            
    if prices['price'][-after-1] > max(prices['price'][-previous-after-1:-after-1]):
        
        if after == 0:
            context.allocation[2] = 0.0
            context.allocation[0] = 0.0
            context.allocation[1] = 0.4
          
    else:            
    # When WVF goes above the upper band, time to buy.    
        if WVF[-2] < WVFupper[-2] and WVF[-1] > WVFupper[-1]:
            context.allocation[2] = 0.0
            context.allocation[1] = 1.0
            context.allocation[0] = 0.0 
            
        if (trigger[-2]<0 and trigger[-1]>0): # MACD Signal for
                                              # the WVF.
            context.allocation[2] = 0.0
            context.allocation[0] = 0.0
            context.allocation[1] = 0.0
            
        if (WVF[-1]>10):
            context.allocation[2] = 0.0
            context.allocation[0] = 0.0
            context.allocation[1] = 0.0
    
        if (ROC < -5):
            context.allocation[2] = 0.0
            context.allocation[0] = 0.0
            context.allocation[1] = 0.0
        

def my_rebalance(context,data):
    """
    Execute orders according to our schedule_function() timing. 
    """    
    #allocation = np.array(context.allocation)/sum(context.allocation)
    allocation = context.allocation
    for i,stock in enumerate(context.stocks):
        order_target_percent(stock, allocation[i])
    log.info (", ".join(["%s %0.3f" % (stock.symbol, allocation[i]) for i,stock in enumerate(context.stocks)]))
    
def order_positions(context, data):
        
    port = context.portfolio.positions
    
    # Close positions which have been held for 25 days.
    for security in port:
        if data.can_trade(security) and security != context.xiv:
            if context.stocks_held.get(security) is not None:
                context.stocks_held[security] += 1
                if context.stocks_held[security] >= context.days_to_hold:
                    context.allocation[2] = 0.0
                    context.allocation[0] = 1.0
                    context.allocation[1] = 0.0
                    del context.stocks_held[security]
            # if we've deleted it but still hasn't been exited. try exit
            else:
                log.info("Haven't yet exited %s, ordering again" % security.symbol)
    
    # Check my current positions
    current_longs = [pos for pos in port if (port[pos].amount > 0 and pos in context.stocks_held)]
    current_shorts = [pos for pos in port if (port[pos].amoung < 0 and pos in context.stocks_held)]
    
    # Rebalance shorts (equially weighted)
    for security in shorts:
        can_trade = context.stocks_held(security) <= context.days_to_hold or context.stocks_held.get(security) is None
        if data.can_trade(security) and can_trade:
            order_target_percent(security, -1.0 / len(shorts))
            if context.stocks_held.get(security) is None:
                context.stocks_held[security] = 0
                
    #Rebalance longs (equally weighted)
    for security in longs:
        can_trade = context.stocks_held.get(security) <= context.days_to_hold or context.stock_held.get(security) is None
        if data.can_trade(security) and can_trade:
            order_target_percent(security, 1.0/len(longs))
            if context.stocks_held.get(security) is None:
                context.stocks_held[security] = 0
    
def my_record_vars(context, data):
 # Check how many long and short positions we have.
    """
    This function is called at the end of each day and plots our leverage as well
    as the number of long and short positions we are holding.
    """

    longs = shorts = 0
    for position in context.portfolio.positions.itervalues():
        if position.amount > 0:
            longs += 1
        elif position.amount < 0:
            shorts += 1

    # Record our variables.
    record(leverage=context.account.leverage, long_count=longs, short_count=shorts)
    
def handle_data(context, data):
    

    record(ROC = data.current(context.xiv, 'Rate'))

    

There was a runtime error.
4 responses

Hi Sebastian,

Is there a typo at line 56? context.days_to_hold = 1 (21)

Hi Jack,
It's not, when I started to think that my holding period wasn't really working, I started trying different days to see if something changed, but nothing did. That's why I come to the conclusion that I must have another typo somewhere because the holding period isn't really working for me :(

I think the reason is that order_positions() is not called or scheduled to run.

It's not working because you rebalance daily and alter the position allocation without checking the days held

Also, this might work in a back test but in live trading you may lose track of your days held in the event your algo is restarted