Back to Community
NUGT/DUST Combo short - surprisingly interesting results


so following this article on NUGT/DUST slippage
what-every-trader-must-understand-before-they-trade-leveraged-etfs-like-nugt-and-dust I quickly coded a dozen of lines for an algo that would short NUGT and DUST every week to exploit slippage discrepancies and the results were surprisingly interesting (1.5 sharpe and max dd 10%)

I used 100k notional, not sure how well the algo would actually scale up ....

I'm not going to trade this, but I would be curious to hear about all the reason why this wouldn't actually work in practice or just hear people ideas on how to make it better!


Clone Algorithm
Total Returns
Max Drawdown
Benchmark Returns
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
based on short comments in

def initialize(context):
    Called once at the start of the algorithm.
    # Rebalance every day, 1 hour after market open.
    schedule_function(sod, date_rules.week_start(), time_rules.market_open())
    schedule_function(eod, date_rules.week_end(), time_rules.market_close(minutes=15))
    # schedule_function(sod, date_rules.every_day(), time_rules.market_close(hours=1))
    # schedule_function(eod, date_rules.every_day(), time_rules.market_open(hours=1))
    context.s1 = symbol('NUGT') 
    context.s2 = symbol('DUST')
    # VIX almost neutral
    # context.s1 = symbol('VXX') 
    # context.s2 = symbol('XIV')
    # does not work for QQQ
    # context.s1 = symbol('SQQQ')
    # context.s2 = symbol('TQQQ')
    context.week_age = 0
def before_trading_start(context, data):

def short(context, data):
    if context.week_age > 0:
    cash = context.portfolio.portfolio_value
    qt1 = -0.5 * round(cash/data.current(context.s1, 'price'))
    qt2 = -0.5 * round(cash/data.current(context.s2, 'price'))
    order_target(context.s1, qt1)
    order_target(context.s2, qt2)
    context.week_age = 0
def close_pos(context, data):
    if context.week_age < HOLDING_PERIOD:
        context.week_age += 1
    if len(context.portfolio.positions) == 0:
    for o in get_open_orders(context.s1):

    for o in get_open_orders(context.s2): cancel_order(o)
    order_target(context.s1, 0)
    order_target(context.s2, 0)
    context.week_age = 0

def sod(context, data):
    Assign weights to securities that we want to order.
    short(context, data)
def eod(context,data):
    Execute orders according to our schedule_function() timing. 
    close_pos(context, data)
def handle_data(context,data):
    Called every minute.
There was a runtime error.
4 responses

Hi Fabien, this might be of interest:

Short Selling Tutorial - Investopedia
Apr 28, 2017 - This “short covering” obligation gives rise to one of the biggest risks of ... In the
absence of the reality check provided by short sellers, stocks ...

The risk in this particular code is 285888 (short) with profit of 183175 or 64% not counting fees.

thanks I know the risks of short selling but in theory this stragegy is 100% market neutral (see beta almost 0) because both legs have a correlation of -1
This in theory is exploiting the ETF rebalancing inneficiencies, so the market risk is close to null

Also in the US (and most countries) you have to locate beforehand for that very purpose so to try and answer part of my own question, I think the risk is in the borrow rate that could go crazy every know and then and wipe out some of gains!
Not sure how quantopian simulate the shorting cost...

@Fabien, Quantopian doesn't model shorting or margin costs at all, which can give misleading returns for shorting/margin strategies. As I understand it, in practice the cost of borrowing leveraged ETFs already prices in the slippage discrepancy, so there's no profit to be made there... for you.

@Viridian makes sense... I figured I was missing some information somewhere!