Back to Community
UVXY Volatility Decay

This is my first attempt at an algorithm on Quantopian. For the last few months I've been fiddling around and paper trading with the strategy of shorting UVXY whenever it spikes. Basic idea is to initiate a trailing stop short order whenever UVXY has a daily gain over a certain threshold, in this case 15%. Then just let it fall back, plus an additional few percent and cover.

I'm a software developer by trade, but have very little python experience, so please excuse any bad code.

Clone Algorithm
20
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 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
 
class TrackingSecurity:
    def __init__(self, _security, _start_tracking_percent, _enter_stop_percent, _exit_stop_offset, _exit_stop_percent):
        self.security = _security
        
        self.start_tracking_percent = _start_tracking_percent
        self.start_tracking_value = 0.0
        
        self.enter_stop_percent = _enter_stop_percent
        self.enter_stop_value = 0.0
        self.enter_stop_high = 0.0
        
        self.exit_stop_percent = _exit_stop_percent
        self.exit_stop_offset = _exit_stop_offset
        self.exit_stop_value = 0.0
        self.exit_stop_low = 0.0
        self.exit_trigger = 0.0
        
        self.tracking_to_enter = False
        self.tracking_to_exit = False
        self.holding = False
        self.yesterday_close = 0.0
        
    def is_tracking(self):
        return self.tracking_to_enter or self.tracking_to_exit
    
    def set_close(self, data):
        hist = data.history(self.security, 'close', 1, '1d')
        self.yesterday_close = hist.values[0]
        self.start_tracking_value = self.yesterday_close + (self.yesterday_close * self.start_tracking_percent)
        self.exit_trigger = self.yesterday_close - (self.yesterday_close * self.exit_stop_offset)
        
    def handle_data(self, context, data):
        hist = data.history(self.security, ['high', 'low'], 1, '1m')
        history_item = hist.iloc[0]
        high = history_item["high"]
        low = history_item["low"]

        if not self.holding and not self.tracking_to_enter and high > self.start_tracking_value:
            self.tracking_to_enter = True
            
            self.enter_stop_high = high
            self.enter_stop_value = self.enter_stop_high - (self.enter_stop_high * self.enter_stop_percent)           
                
            """
            log.info("yesterday_close: %.2f" % self.yesterday_close)
            log.info("start_tracking_value: %.2f" % self.start_tracking_value)
            log.info("high: %.2f" % high)
            log.info("price: %.2f" % price)
            log.info("enter_stop_value: %.2f" % self.enter_stop_value)
            log.info("exit_trigger: %.2f" % self.exit_trigger)
            log.info("******************************")
            """
        elif not self.holding and self.tracking_to_enter and high > self.enter_stop_high:
            self.enter_stop_high = high
            self.enter_stop_value = self.enter_stop_high - (self.enter_stop_high * self.enter_stop_percent)
            
            """
            log.info("enter_stop_value: %.2f" % self.enter_stop_value)
            log.info("******************************")
            """
        elif not self.holding and self.tracking_to_enter and low < self.enter_stop_value:
            if data.can_trade(self.security):
                order_target_percent(self.security, -1)
                self.holding = True
                self.tracking_to_enter = False
            
            """
            log.info("enter: %.2f" % price)
            log.info("******************************")
            """
        elif self.holding and not self.tracking_to_exit and low < self.exit_trigger:
            self.tracking_to_exit = True
            self.exit_stop_low = low
            self.exit_stop_value = self.exit_stop_low + (self.exit_stop_low * self.exit_stop_percent)  
        
            """
            log.info("start tracking exit: %.2f" % price)
            log.info("exit_stop_value: %.2f" % self.exit_stop_value)
            log.info("******************************")
            """
        elif self.holding and self.tracking_to_exit and low < self.exit_stop_low:
            self.exit_stop_low = low
            self.exit_stop_value = self.exit_stop_low + (self.exit_stop_low * self.exit_stop_percent) 
            
            """
            log.info("exit_stop_value: %.2f" % self.exit_stop_value)
            log.info("******************************")
            """
        elif self.holding and self.tracking_to_exit and high > self.exit_stop_value:
            if data.can_trade(self.security):
                order_target_percent(self.security, 0)
                self.holding = False
                self.tracking_to_exit = False
                self.set_close(data)
            
            """
            log.info("exit: %.2f" % price)
            log.info("******************************")
            """
    
def initialize(context):
    """
    chk, tza, nugt, spxu, uwti, sqqq, ugaz, faz, spx, dust, tmv, sdow, bis, uvxy
    
    yes: uvxy
    no: nugt, uwti
    """
    
    """
    uvxy: 558%
    context.ts = TrackingSecurity(_security = sid(41969), _start_tracking_percent = .15, _enter_stop_percent = .05, _exit_stop_offset = .05, _exit_stop_percent = .02)
    
    uvxy: 602%
    context.ts = TrackingSecurity(_security = sid(41969), _start_tracking_percent = .15, _enter_stop_percent = .05, _exit_stop_offset = .05, _exit_stop_percent = .01)
    
    uvxy: 318%
    context.ts = TrackingSecurity(_security = sid(41969), _start_tracking_percent = .15, _enter_stop_percent = .035, _exit_stop_offset = .075, _exit_stop_percent = .035)
    
    nugt: -311%
    context.ts = TrackingSecurity(_security = sid(40553), _start_tracking_percent = .15, _enter_stop_percent = .05, _exit_stop_offset = .05, _exit_stop_percent = .02)
    
    tza: 31%
    context.ts = TrackingSecurity(_security = sid(37133), _start_tracking_percent = .10, _enter_stop_percent = .025, _exit_stop_offset = .025, _exit_stop_percent = .02)
    
    spxu: 25%
    context.ts = TrackingSecurity(_security = sid(38532), _start_tracking_percent = .10, _enter_stop_percent = .025, _exit_stop_offset = .025, _exit_stop_percent = .02)
    
    spxu: 28%
    context.ts = TrackingSecurity(_security = sid(38532), _start_tracking_percent = .07, _enter_stop_percent = .025, _exit_stop_offset = .025, _exit_stop_percent = .02)
    
    dwti: 43%
    context.ts = TrackingSecurity(_security = sid(42472), _start_tracking_percent = .15, _enter_stop_percent = .05, _exit_stop_offset = .05, _exit_stop_percent = .02)
    
    tvix: 493%
    context.ts = TrackingSecurity(_security = sid(40515), _start_tracking_percent = .15, _enter_stop_percent = .05, _exit_stop_offset = .05, _exit_stop_percent = .02)
    
    spqq: 56%
    context.ts = TrackingSecurity(_security = sid(39211), _start_tracking_percent = .07, _enter_stop_percent = .025, _exit_stop_offset = .025, _exit_stop_percent = .02)
    """      
    
    context.ts = TrackingSecurity(_security = sid(41969), _start_tracking_percent = .15, _enter_stop_percent = .05, _exit_stop_offset = .05, _exit_stop_percent = 0.0)
    
    # Rebalance every day, 1 hour after market open.
    # schedule_function(my_rebalance, date_rules.every_day(), time_rules.market_open(hours=1))
     
    # Record tracking variables at the end of each day.
    # schedule_function(my_record_vars, date_rules.every_day(), time_rules.market_close())
     
    # Create our dynamic stock selector.
    # attach_pipeline(make_pipeline(), 'my_pipeline')
         
def make_pipeline():
    """
    A function to create our dynamic stock selector (pipeline). Documentation on
    pipeline can be found here: https://www.quantopian.com/help#pipeline-title
    """
    
    # Base universe set to the Q500US
    base_universe = Q500US()

    # Factor of yesterday's close price.
    yesterday_close = USEquityPricing.close.latest
     
    pipe = Pipeline(
        screen = base_universe,
        columns = {
            'close': yesterday_close,
        }
    )
    return pipe
   
def before_trading_start(context, data):
    """
    Called every day before market open.
    """  

    if not context.ts.is_tracking() and not context.ts.holding:
        context.ts.set_close(data)
    
    # log.info("From current: {0}, From Hist {1}".format(close, hist.iloc[0]['close']))
    # log.info("context.ts.yesterday_close = $%.2f" % context.ts.yesterday_close)
    #print "From current: " + close
     
def my_assign_weights(context, data):
    """
    Assign weights to securities that we want to order.
    """
    pass
 
def my_rebalance(context,data):
    """
    Execute orders according to our schedule_function() timing. 
    """
    pass
 
def my_record_vars(context, data):
    """
    Plot variables at the end of each day.
    """
    pass
 
def handle_data(context,data):
    """
    Called every minute.
    """
    # if not context.ts.is_tracking():
    context.ts.handle_data(context, data)
        
There was a runtime error.
3 responses

Welcome to Quantopian!

I'm sure you're a better software developer than me - I dabble in it, I'm not an expert. I'll need someone with more skills than me to help understand your code style.

What I can offer on this one is to look a bit more closely at how your algorithm's results pan out. I cloned your algorithm and added a custom record() function to track your leverage. In the beginning your leverage looks great, holding roughly at 1. But later on it points out is that your algo blew up financially in August of 2015. In the returns graph it looks like a little dip, but as your capital shrank your leverage went as higher than 250 and then as low as -1100. Boom!

Clone Algorithm
4
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 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
 
class TrackingSecurity:
    def __init__(self, _security, _start_tracking_percent, _enter_stop_percent, _exit_stop_offset, _exit_stop_percent):
        self.security = _security
        
        self.start_tracking_percent = _start_tracking_percent
        self.start_tracking_value = 0.0
        
        self.enter_stop_percent = _enter_stop_percent
        self.enter_stop_value = 0.0
        self.enter_stop_high = 0.0
        
        self.exit_stop_percent = _exit_stop_percent
        self.exit_stop_offset = _exit_stop_offset
        self.exit_stop_value = 0.0
        self.exit_stop_low = 0.0
        self.exit_trigger = 0.0
        
        self.tracking_to_enter = False
        self.tracking_to_exit = False
        self.holding = False
        self.yesterday_close = 0.0
        
    def is_tracking(self):
        return self.tracking_to_enter or self.tracking_to_exit
    
    def set_close(self, data):
        hist = data.history(self.security, 'close', 1, '1d')
        self.yesterday_close = hist.values[0]
        self.start_tracking_value = self.yesterday_close + (self.yesterday_close * self.start_tracking_percent)
        self.exit_trigger = self.yesterday_close - (self.yesterday_close * self.exit_stop_offset)
        
    def handle_data(self, context, data):
        hist = data.history(self.security, ['high', 'low'], 1, '1m')
        history_item = hist.iloc[0]
        high = history_item["high"]
        low = history_item["low"]

        if not self.holding and not self.tracking_to_enter and high > self.start_tracking_value:
            self.tracking_to_enter = True
            
            self.enter_stop_high = high
            self.enter_stop_value = self.enter_stop_high - (self.enter_stop_high * self.enter_stop_percent)           
                
            """
            log.info("yesterday_close: %.2f" % self.yesterday_close)
            log.info("start_tracking_value: %.2f" % self.start_tracking_value)
            log.info("high: %.2f" % high)
            log.info("price: %.2f" % price)
            log.info("enter_stop_value: %.2f" % self.enter_stop_value)
            log.info("exit_trigger: %.2f" % self.exit_trigger)
            log.info("******************************")
            """
        elif not self.holding and self.tracking_to_enter and high > self.enter_stop_high:
            self.enter_stop_high = high
            self.enter_stop_value = self.enter_stop_high - (self.enter_stop_high * self.enter_stop_percent)
            
            """
            log.info("enter_stop_value: %.2f" % self.enter_stop_value)
            log.info("******************************")
            """
        elif not self.holding and self.tracking_to_enter and low < self.enter_stop_value:
            if data.can_trade(self.security):
                order_target_percent(self.security, -1)
                self.holding = True
                self.tracking_to_enter = False
            
            """
            log.info("enter: %.2f" % price)
            log.info("******************************")
            """
        elif self.holding and not self.tracking_to_exit and low < self.exit_trigger:
            self.tracking_to_exit = True
            self.exit_stop_low = low
            self.exit_stop_value = self.exit_stop_low + (self.exit_stop_low * self.exit_stop_percent)  
        
            """
            log.info("start tracking exit: %.2f" % price)
            log.info("exit_stop_value: %.2f" % self.exit_stop_value)
            log.info("******************************")
            """
        elif self.holding and self.tracking_to_exit and low < self.exit_stop_low:
            self.exit_stop_low = low
            self.exit_stop_value = self.exit_stop_low + (self.exit_stop_low * self.exit_stop_percent) 
            
            """
            log.info("exit_stop_value: %.2f" % self.exit_stop_value)
            log.info("******************************")
            """
        elif self.holding and self.tracking_to_exit and high > self.exit_stop_value:
            if data.can_trade(self.security):
                order_target_percent(self.security, 0)
                self.holding = False
                self.tracking_to_exit = False
                self.set_close(data)
            
            """
            log.info("exit: %.2f" % price)
            log.info("******************************")
            """
    
def initialize(context):
    """
    chk, tza, nugt, spxu, uwti, sqqq, ugaz, faz, spx, dust, tmv, sdow, bis, uvxy
    
    yes: uvxy
    no: nugt, uwti
    """
    
    """
    uvxy: 558%
    context.ts = TrackingSecurity(_security = sid(41969), _start_tracking_percent = .15, _enter_stop_percent = .05, _exit_stop_offset = .05, _exit_stop_percent = .02)
    
    uvxy: 602%
    context.ts = TrackingSecurity(_security = sid(41969), _start_tracking_percent = .15, _enter_stop_percent = .05, _exit_stop_offset = .05, _exit_stop_percent = .01)
    
    uvxy: 318%
    context.ts = TrackingSecurity(_security = sid(41969), _start_tracking_percent = .15, _enter_stop_percent = .035, _exit_stop_offset = .075, _exit_stop_percent = .035)
    
    nugt: -311%
    context.ts = TrackingSecurity(_security = sid(40553), _start_tracking_percent = .15, _enter_stop_percent = .05, _exit_stop_offset = .05, _exit_stop_percent = .02)
    
    tza: 31%
    context.ts = TrackingSecurity(_security = sid(37133), _start_tracking_percent = .10, _enter_stop_percent = .025, _exit_stop_offset = .025, _exit_stop_percent = .02)
    
    spxu: 25%
    context.ts = TrackingSecurity(_security = sid(38532), _start_tracking_percent = .10, _enter_stop_percent = .025, _exit_stop_offset = .025, _exit_stop_percent = .02)
    
    spxu: 28%
    context.ts = TrackingSecurity(_security = sid(38532), _start_tracking_percent = .07, _enter_stop_percent = .025, _exit_stop_offset = .025, _exit_stop_percent = .02)
    
    dwti: 43%
    context.ts = TrackingSecurity(_security = sid(42472), _start_tracking_percent = .15, _enter_stop_percent = .05, _exit_stop_offset = .05, _exit_stop_percent = .02)
    
    tvix: 493%
    context.ts = TrackingSecurity(_security = sid(40515), _start_tracking_percent = .15, _enter_stop_percent = .05, _exit_stop_offset = .05, _exit_stop_percent = .02)
    
    spqq: 56%
    context.ts = TrackingSecurity(_security = sid(39211), _start_tracking_percent = .07, _enter_stop_percent = .025, _exit_stop_offset = .025, _exit_stop_percent = .02)
    """      
    
    context.ts = TrackingSecurity(_security = sid(41969), _start_tracking_percent = .15, _enter_stop_percent = .05, _exit_stop_offset = .05, _exit_stop_percent = 0.0)
    
    # Rebalance every day, 1 hour after market open.
    # schedule_function(my_rebalance, date_rules.every_day(), time_rules.market_open(hours=1))
     
    # Record tracking variables at the end of each day.
    schedule_function(my_record_vars, date_rules.every_day(), time_rules.market_close())
     
    # Create our dynamic stock selector.
    # attach_pipeline(make_pipeline(), 'my_pipeline')
         
def make_pipeline():
    """
    A function to create our dynamic stock selector (pipeline). Documentation on
    pipeline can be found here: https://www.quantopian.com/help#pipeline-title
    """
    
    # Base universe set to the Q500US
    base_universe = Q500US()

    # Factor of yesterday's close price.
    yesterday_close = USEquityPricing.close.latest
     
    pipe = Pipeline(
        screen = base_universe,
        columns = {
            'close': yesterday_close,
        }
    )
    return pipe
   
def before_trading_start(context, data):
    """
    Called every day before market open.
    """  

    if not context.ts.is_tracking() and not context.ts.holding:
        context.ts.set_close(data)
    
    # log.info("From current: {0}, From Hist {1}".format(close, hist.iloc[0]['close']))
    # log.info("context.ts.yesterday_close = $%.2f" % context.ts.yesterday_close)
    #print "From current: " + close
     
def my_assign_weights(context, data):
    """
    Assign weights to securities that we want to order.
    """
    pass
 
def my_rebalance(context,data):
    """
    Execute orders according to our schedule_function() timing. 
    """
    pass
 
def my_record_vars(context, data):
    record(leverage=context.account.leverage)
   
 
def handle_data(context,data):
    """
    Called every minute.
    """
    # if not context.ts.is_tracking():
    context.ts.handle_data(context, data)
        
There was a runtime error.
Disclaimer

The material on this website is provided for informational purposes only and does not constitute an offer to sell, a solicitation to buy, or a recommendation or endorsement for any security or strategy, nor does it constitute an offer to provide investment advisory services by Quantopian. In addition, the material offers no opinion with respect to the suitability of any security or specific investment. No information contained herein should be regarded as a suggestion to engage in or refrain from any investment-related course of action as none of Quantopian nor any of its affiliates is undertaking to provide investment advice, act as an adviser to any plan or entity subject to the Employee Retirement Income Security Act of 1974, as amended, individual retirement account or individual retirement annuity, or give advice in a fiduciary capacity with respect to the materials presented herein. If you are an individual retirement or other investor, contact your financial advisor or other fiduciary unrelated to Quantopian about whether any given investment idea, strategy, product or service described herein may be appropriate for your circumstances. All investments involve risk, including loss of principal. Quantopian makes no guarantees as to the accuracy or completeness of the views expressed in the website. The views are subject to change, and may have become unreliable for various reasons, including changes in market conditions or economic circumstances.

Hi Dan

Thanks for the response. I'm a little curious as to how the leverage property works. Given the calculation of leverage:

(long value + abs(short_value)) / context.portfolio.portfolio_value

and that whenever I take a position it is a 100% portfolio value short position, how would the leverage ever be a value different than 1?

Your portfolio logic targets a leverage of 1.0, and presumably it gets pretty close to the target when it rebalances. But as soon as the price of your positions change, then the leverage changes.

Looking at the gross leverage formula you quoted: Your algorithm is taking short positions. When the price of those positions goes up, the numerator goes up (the absolute value of the position is going up), and the denominator goes down (your short position lost value, and your cash was constant). At the point that your portfolio value goes negative, your leverage also goes negative.