Back to Community
Robin Hood VIX Asylum

Update: slightly cleaned up code and buy mechanism

This is the one I am using now. It is still a bit rough and it is definitely a work in progress.

Credit goes to many people I reckon.
Thanks Peter Bakker for apparently resurrecting noko.csv and my Robin Hood VIX Mix Rogue Trader.
See https://www.quantopian.com/posts/robin-hood-vix-mix-rogue-trader#5a842f0d29805d177db7c877
Huge thanks to everyone especially Kory Hoang who contributed to Ballistic XIV.
See https://www.quantopian.com/posts/ballistic-xiv-slash-vxx-my-best-long-xiv-slash-vxx-strategy-to-date#5a7fc95d80c659001165d057
Huge thanks to those that contributed to the VIX MoreSignals aka BigSignal. That is what I call it. It is probably known by something else in other threads. I do not remember who did the most work on that, but it is much appreciated.

If you get a chance, take a look at Kory Hoang algo noted above. It did very well!

Kory's algo already is comfortable to sit in cash while still making nice profits. I think combining it with MoreSignals makes a slightly improved buying, selling, and sitting in cash mechanism.

Clone Algorithm
332
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
#Imports
from datetime import datetime  
from zipline.utils import tradingcalendar
from quantopian.pipeline.data.builtin import USEquityPricing
import statsmodels.api as sm 
import numpy as np
import pandas as pd
import functools
import quantopian.pipeline.data 
import re
import talib
import scipy

History = 128

def initialize(context):

    vixUrl = 'http://www.cboe.com/publish/scheduledtask/mktdata/datahouse/vixcurrent.csv'
    vxvUrl = 'http://www.cboe.com/publish/scheduledtask/mktdata/datahouse/vix3mdailyprices.csv'
    nokoUrl = 'http://173.212.203.121/noko.csv'  
    
    fetch_csv(nokoUrl, 
              symbol='v1', 
              date_column='Date', 
              date_format='%Y-%m-%d', 
              pre_func=addFieldsVX1, 
              post_func=shift_data)
    
    fetch_csv(nokoUrl, 
              symbol='v2', 
              date_column='Date', 
              date_format='%Y-%m-%d', 
              pre_func=addFieldsVX2, 
              post_func=shift_data)

    fetch_csv(vixUrl, 
              symbol='v', 
              skiprows=1,
              date_column='Date', 
              pre_func=addFieldsVIX,
              post_func=shift_data)

    fetch_csv(vxvUrl, 
              symbol='vxv', 
              skiprows=2,
              date_column='Date', 
              pre_func=addFieldsVXV,
              post_func=shift_data)

    
    # ---- START ADJUSTED FOR ROBIN HOOD ----
    context.dayTradeDetector = [0] * 5 # array of 5 days, assuming there are no day trades on the account.
    context.SameDayTradesAllowed = 3 # Same day trades each 5 days, maximum, the algo wills top same day trading until that
    # if you wish to not execute same day trade at all, just place a 0 instead.
    # it is important to know that if you reset the algorithm after a few day trades, when turning it back on, the algorithm will forget
    # the count of same day trades you already have so you COULD be marked as a pattern day trader since 
    # the algorithm just assume you have no day trades when starting, 
    # if thats the case, you can directly declare the array with the same day trades you have and the algorithm will start from there
    # for instance if you had a day trade 4 days ago you will initialize the array like this: 
    # context.dayTradeDetector =  [0, 2, 0, 0, 0]
    # if you had 3 day trades, two of them 3 days ago, and other one yesterday you will initialize the array like this: 
    # context.dayTradeDetector =  [0, 0, 4, 0, 2] and so on... today is the first field of the array.
    
    # if you are using robin hood gold you can set how much gold available you have here. If not leave a 0
    context.RobinHoodMargin = 7000 # real 8000 minus 1000 for withdrawals
    context.PercentageToBuySVXY = 0.99 
    context.PercentageToBuyTQQQ = 0.99 
    context.PercentageToBuyUVXY = 0.99 
    # PercentageToBuy in some cases Robin Hood failed to invest the 100%, is not happening all the time but when it does is annoying
    # I reduced it to 0.99 and I dont have any trouble anymore, if that is your case try to reduce it.
    # ---- END ADJUSTED FOR ROBIN HOOD ----
    context.tradeIndex = 0
    
    set_benchmark(symbol('SVXY'))
    context.SVXY = symbol('SVXY')
    context.TQQQ = symbol('TQQQ')
    context.UVXY = symbol('UVXY')
    context.SPYG = symbol('SPYG')

    context.vix = -1
    
    #Editable parameters
    context.StopLossPct = 0.25
    
    #Used for logging
    context.StopPrice = 0
    context.BuyPrice = 0
    context.SellLossPrice = 0
    context.SellProfitPrice = 0
    context.last_bar = False
    context.sell = False
    context.buy = False

    # apparently highly successful VIX signals
    schedule_function(MoreSignals, date_rules.every_day(), time_rules.market_open(minutes = 2))

    # On Bar Close Functions
    for x in [1,3,5]:
        schedule_function(my_rebalance, date_rules.every_day(), time_rules.market_open(hours=x, minutes=59))
    schedule_function(my_rebalance, date_rules.every_day(), time_rules.market_close())
    
    # On Bar Open Functions
    schedule_function(bar_open, date_rules.every_day(), time_rules.market_open())
    for x in [2,4,6]:
        schedule_function(bar_open, date_rules.every_day(), time_rules.market_open(hours=x))
        
    schedule_function(my_record_vars, date_rules.every_day(), time_rules.market_close())

    #Set commission and slippage
    set_commission(commission.PerShare(cost=0.000, min_trade_cost=0.0)) 
    set_slippage(slippage.FixedSlippage(spread=0.01))
    
def bar_open(context,data): 
    cancel_open_orders(context, data)
    if context.buy is True and len(get_open_orders()) == 0 and data.can_trade(context.SVXY):
        set_fixed_stop(context, data)
        if BuySVXY(context, data):
          context.buy = False
          context.sell = False
          context.newsl = 0
            
    if context.sell is True and context.portfolio.positions[context.SVXY].amount > 0: 
        if SellSVXY(context, data):
          context.sell = False
          context.buy = False 
 
def my_rebalance(context,data):
    cancel_open_orders(context, data)
    xiv_prices = data.history(context.SVXY, "price", 1600, "1m").resample('30T',  closed='right', label='right').last().dropna()

    #convert to 2 hour:
    xiv2hr = []
    if '20:00:00+00:00' in str(xiv_prices.index[-1]) or '21:00:00+00:00' in str(xiv_prices.index[-1]):
        xiv2hr.append([xiv_prices.index[-28], xiv_prices[-28]])
        xiv2hr.append([xiv_prices.index[-27], xiv_prices[-27]])        
        xiv2hr.append([xiv_prices.index[-23], xiv_prices[-23]])
        xiv2hr.append([xiv_prices.index[-19], xiv_prices[-19]])
        xiv2hr.append([xiv_prices.index[-15], xiv_prices[-15]])
        xiv2hr.append([xiv_prices.index[-14], xiv_prices[-14]])
        xiv2hr.append([xiv_prices.index[-10], xiv_prices[-10]])
        xiv2hr.append([xiv_prices.index[-6], xiv_prices[-6]])
        xiv2hr.append([xiv_prices.index[-2], xiv_prices[-2]])
        xiv2hr.append([xiv_prices.index[-1], xiv_prices[-1]])
        context.last_bar = True
    elif '19:30:00+00:00' in str(xiv_prices.index[-1]) or '20:30:00+00:00' in str(xiv_prices.index[-1]):
        xiv2hr.append([xiv_prices.index[-31], xiv_prices[-31]])
        xiv2hr.append([xiv_prices.index[-27], xiv_prices[-27]]) 
        xiv2hr.append([xiv_prices.index[-26], xiv_prices[-26]])
        xiv2hr.append([xiv_prices.index[-22], xiv_prices[-22]])
        xiv2hr.append([xiv_prices.index[-18], xiv_prices[-18]])
        xiv2hr.append([xiv_prices.index[-14], xiv_prices[-14]]) 
        xiv2hr.append([xiv_prices.index[-13], xiv_prices[-13]])
        xiv2hr.append([xiv_prices.index[-9], xiv_prices[-9]])
        xiv2hr.append([xiv_prices.index[-5], xiv_prices[-5]])
        xiv2hr.append([xiv_prices.index[-1], xiv_prices[-1]])
        context.last_bar = False
    elif '17:30:00+00:00' in str(xiv_prices.index[-1]) or '18:30:00+00:00' in str(xiv_prices.index[-1]):
        xiv2hr.append([xiv_prices.index[-31], xiv_prices[-31]])
        xiv2hr.append([xiv_prices.index[-27], xiv_prices[-27]]) 
        xiv2hr.append([xiv_prices.index[-23], xiv_prices[-23]])
        xiv2hr.append([xiv_prices.index[-22], xiv_prices[-22]])
        xiv2hr.append([xiv_prices.index[-18], xiv_prices[-18]])
        xiv2hr.append([xiv_prices.index[-14], xiv_prices[-14]])
        xiv2hr.append([xiv_prices.index[-10], xiv_prices[-10]])
        xiv2hr.append([xiv_prices.index[-9], xiv_prices[-9]])
        xiv2hr.append([xiv_prices.index[-5], xiv_prices[-5]])
        xiv2hr.append([xiv_prices.index[-1], xiv_prices[-1]])
        context.last_bar = False
    elif '15:30:00+00:00' in str(xiv_prices.index[-1]) or '16:30:00+00:00' in str(xiv_prices.index[-1]):
        xiv2hr.append([xiv_prices.index[-31], xiv_prices[-31]])
        xiv2hr.append([xiv_prices.index[-27], xiv_prices[-27]]) 
        xiv2hr.append([xiv_prices.index[-23], xiv_prices[-23]])
        xiv2hr.append([xiv_prices.index[-19], xiv_prices[-19]])
        xiv2hr.append([xiv_prices.index[-18], xiv_prices[-18]])
        xiv2hr.append([xiv_prices.index[-14], xiv_prices[-14]])        
        xiv2hr.append([xiv_prices.index[-10], xiv_prices[-10]])
        xiv2hr.append([xiv_prices.index[-6], xiv_prices[-6]])
        xiv2hr.append([xiv_prices.index[-5], xiv_prices[-5]])
        xiv2hr.append([xiv_prices.index[-1], xiv_prices[-1]])
        context.last_bar = False
    else:
        log.error("2 HOUR CONVERSION FAILURE")
        return
    dates, vals = zip(*xiv2hr)
    s = pd.Series(vals, index=dates)
    
    rsi = talib.RSI(s,2)

    # BUY RULE
    if context.buy is False and rsi[-2] < 70 and rsi[-1] >= 70 and context.portfolio.positions[context.SVXY].amount == 0:
        context.buy = True

    # SELL RULE
    if rsi[-2] > 85 and rsi[-1] <= 85 and context.portfolio.positions[context.SVXY].amount > 0 and len(get_open_orders()) == 0:
        #context.sell = True 
        if SellSVXY(context, data):
          context.buy = False
        
    # panic button
    high = (data.history(context.SVXY, "high", 119, "1m")).max()
    if context.last_bar:
        high = (data.history(context.SVXY, "high", 29, "1m")).max()
    price = data.current(context.SVXY, 'price')
    if ((high/price) - 1) > .1:
        if SellSVXY(context, data):
          context.sell = False
          context.buy = False

def set_fixed_stop(context, data):
    #Only call this once when the stock is bought
    price = data.current(context.SVXY, 'price')
    context.BuyPrice = price
    context.SellLossPrice= max(context.StopPrice, price - (context.StopLossPct * price)) 

def handle_data(context, data):
    if context.portfolio.positions[context.SVXY].amount > 0:     
        price = data.current(context.SVXY, 'price')
        # set break even
        if price - context.BuyPrice >= 1:
            if context.BuyPrice > context.SellLossPrice:
                context.SellLossPrice = context.BuyPrice
        
        # 50% trailing stop
        #if price - context.BuyPrice >= 1:
        #    p = price - context.BuyPrice
        #    newsl = context.BuyPrice + (p * 0.5)
        #    if newsl > context.SellLossPrice:
        #        context.SellLossPrice= max(context.StopPrice, newsl) 
    
        #If we have a position check sell conditions
        if price <= context.SellLossPrice and len(get_open_orders()) == 0:
            # Here you want to consider if you want to sell anyway, and receive the penalty of same day trader. 
            # since it is an emergency anyway and could be worth to just perform the sell directly. 
            # If thats the case here is where you place the code
            if SellSVXY(context, data): 
             context.sell = False
             context.buy = False

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

def before_trading_start(context, data):
    #record(Cash = context.portfolio.cash)
    context.tradeIndex += 1
    if context.tradeIndex == 5: # restart the counter
        context.tradeIndex = 0
    context.dayTradeDetector[context.tradeIndex] = 0 # restart the day
    
def BuySVXY(context, data):
    if context.portfolio.positions[context.UVXY].amount > 0: SellUVXY(context, data)
    if IsDayTradeLimitHit(context):
        log.warn("Did NOT buy SVXY ... avoiding day trading restriction.")
        return False
    
    if context.BigSignalShortVIX:
        if context.portfolio.positions[context.TQQQ].amount > 0: SellTQQQ(context, data)
        context.dayTradeDetector[context.tradeIndex] += 1
        if ( 
            context.portfolio.positions[context.TQQQ].amount == 0 
            and 
            context.portfolio.positions[context.UVXY].amount == 0 
        ):
            TarPer(context, data, context.SVXY, context.PercentageToBuySVXY)
            log.warn("BuySVXY "+str(context.PercentageToBuySVXY));
    else: TarPer(context, data, context.SVXY, 0)
    if ( 
        context.BigSignalTQQQ 
        and 
        context.portfolio.positions[context.SVXY].amount == 0 
        and 
        context.portfolio.positions[context.UVXY].amount == 0 
    ):
        BuyTQQQ(context, data)
    return True

def BuyTQQQ(context, data):
    if IsDayTradeLimitHit(context):
        log.warn("Did NOT buy TQQQ ... avoiding day trading restriction.")
        return False
    
    if context.BigSignalTQQQ:
        context.dayTradeDetector[context.tradeIndex] += 1
        TarPer(context, data, context.TQQQ, context.PercentageToBuyTQQQ)
        log.warn("BuyTQQQ "+str(context.PercentageToBuyTQQQ));
    return True

def BuyUVXY(context, data):
    if IsDayTradeLimitHit(context):
        log.warn("Did NOT buy UVXY ... avoiding day trading restriction.")
        return False

    if ( 
        context.BigSignalLongVIX 
        and 
        context.portfolio.positions[context.SVXY].amount == 0 
        and 
        context.portfolio.positions[context.TQQQ].amount == 0 
    ):
        context.dayTradeDetector[context.tradeIndex] += 1
        TarPer(context, data, context.UVXY, context.PercentageToBuyUVXY)
        log.warn("BuyUVXY "+str(context.PercentageToBuyUVXY));
    return True

def SellSVXY(context, data):
    if  IsDayTradeLimitHit(context):
        log.warn("Did NOT sell SVXY ... avoiding day trading restriction")
        return False
    
    context.dayTradeDetector[context.tradeIndex] += 1
    order_target_percent(context.SVXY, 0)
    log.warn("SellSVXY");
    if context.portfolio.positions[context.TQQQ].amount > 0: SellTQQQ(context, data)
    BuyUVXY(context, data)
    return True
    
def SellTQQQ(context, data):
    if  IsDayTradeLimitHit(context):
        log.warn("Did NOT sell TQQQ ... avoiding day trading restriction")
        return False
    
    context.dayTradeDetector[context.tradeIndex] += 1
    order_target_percent(context.TQQQ, 0)
    log.warn("SellTQQQ");
    return True
    
def SellUVXY(context, data):
    if  IsDayTradeLimitHit(context):
        log.warn("Did NOT sell UVXY ... avoiding day trading restriction")
        return False
    
    context.dayTradeDetector[context.tradeIndex] += 1
    order_target_percent(context.UVXY, 0)
    log.warn("SellUVXY");
    return True
    
    
def IsDayTradeLimitHit(context):
    # we dont need to worry about same day trading if we are over 25k, as far as I know
    if context.portfolio.portfolio_value +  context.portfolio.cash >= 25000 :
        return False
        
    recentDayTrades = 0
    for day in context.dayTradeDetector:
      if day > 1:
         recentDayTrades += (day-1) #(day // 2) # in case we have more than a day trade the same day, this still needs to be debuged

    if recentDayTrades > 0:
       log.warn("Day trades detected: "+str(recentDayTrades))
    
    #if recentDayTrades >= 3:
    #   raise Exception("Patter day trading hit")
    
    return recentDayTrades >= context.SameDayTradesAllowed

def TarPer(context, data, stock, TargetPercent):

    if DataCanTrade(context, data, stock):

        if 0 == TargetPercent:
            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 = 2000 # 200 Cushion plus cash for withdrawals
            RhMargin = context.RobinHoodMargin # 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(stock, DiffAmount)

        #if stock.symbol in context.stops: del context.stops[stock.symbol]
            
            
def DataCanTrade(context, data, stock):

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

def cancel_open_orders(context, data):
    oo = get_open_orders()
    if len(oo) == 0:
        return
    for stock, orders in oo.iteritems():
        for order in orders:
            cancel_order(order)
            #message = 'Canceling order of {amount} shares in {stock}'
            #log.info(message.format(amount=order.amount, stock=stock))
    
def cancel_open_orders_for(context, data, security):
    oo = get_open_orders()
    if len(oo) == 0:
        return
    for stock, orders in oo.iteritems():
        for order in orders:
            if stock is security:
                if order.amount < context.portfolio.positions[stock].amount:
                    cancel_order(order)
                    #message = 'Canceling order of {amount} shares in {stock}'
                    #log.info(message.format(amount=order.amount, stock=stock))
                else: # Do NOT want to cancel stop loss order
                    #message = 'NOT Canceling stop loss order of {amount} shares in {stock}'
                    #log.info(message.format(amount=order.amount, stock=stock))
                    pass

def MoreSignals(context, data):  

    update_indices(context, data)     
    last_vix = context.VIXprice = data.current('v', 'Close')
    last_vx1 = data.current('v1','Close')  
    last_vx2 = data.current('v2','Close')      
    last_vxv = data.current('vxv', 'Close')
    last_vix_200ma_ratio = data.current('v', '200ma Ratio')
    dispersion = data.current('v','20dstd')
    
    record(dispersion=dispersion)
    record(last_vix=last_vix/10)
    # Calculating the gap between spot vix and the first month vix future
    last_ratio_v_v1 = last_vix/last_vx1

    # Calculating the contango ratio of the front and second month VIX Futures 
    last_ratio_v1_v2 = last_vx1/last_vx2

    # Blending the previous two ratios together using a weighted average
    ratio_weight = 0.7
    last_ratio = (ratio_weight*last_ratio_v_v1) + ((1-ratio_weight)*last_ratio_v1_v2) - 1
    
    vix_vxv_ratio = last_vix/last_vxv
    
    # Retrieve SPY prices for technical indicators
    prices = data.history(context.SPYG, 'open', 40, '1d')
    
    # Retrieve SPY MACD data
    macda, signal, hist = talib.MACD(prices, fastperiod=12,slowperiod=26,signalperiod=9)
    macd = macda[-1] - signal[-1]
    
    # Calculate how much vix moved the previous day
    if (context.vix <> -1) : 
        vix_ratio = last_vix/context.vix -1
    else :
        vix_ratio = 0
    context.vix = last_vix
    
    SVXY_history = data.history(context.SVXY, 'price', 2, '1d')  
    
    SVXY_ratio = SVXY_history[1]/SVXY_history[0] - 1
    
    # Setting thresholds
    threshold_vix_too_low = 10.76   # 0 
    threshold_vix_200ma_ratio_low = 0.79  # 0 
    threshold_SVXY = -0.049          # 1
    threshold_vxv_SVXY = 0.87        # 2
    threshold_uvxy = 0.049          # 3
    threshold_macd = -0.55          # 3
    threshold_vxv_uvxy = 1.3        # 4
    threshold_vix_high = 19.9       # 5
    threshold_vc_low = -0.148       # 6
    threshold_vc_high = 0.046       # 8
    threshold_vc_high_2 = -0.06     # 8
    threshold_SVXY_ratio = -0.053    # 10
    threshold_uvxy_ratio = 0.08     # 11
    # 0
    if last_vix < threshold_vix_too_low \
            and last_vix_200ma_ratio < threshold_vix_200ma_ratio_low :
        log.info('! if VIX is too low, invest in UVXY with the hope of a spike')
        target_sid = context.UVXY
    # 1        
    elif last_ratio < threshold_SVXY: # 
        log.info('! if contango is high, invest in SVXY to gain from decay')
        target_sid = context.SVXY
    
    # 2
    elif vix_vxv_ratio < threshold_vxv_SVXY: # 
        log.info('! if short term vol is low compared to mid term, invest in SVXY to gain from decay')
        target_sid = context.SVXY

    # 3
    elif last_ratio > threshold_uvxy and macd > threshold_macd: # 
        log.info('! if backwardation is high, invest in UVXY to gain from decay')
        target_sid = context.UVXY

    # 4
    elif vix_vxv_ratio > threshold_vxv_uvxy: # 
        log.info('! if short term vol is high compared to mid term, invest in UVXY to gain from growth')
        target_sid = context.UVXY

    # 5
    elif last_vix > threshold_vix_high: # 
        log.info('! if VIX is too high, invest in SVXY expecting that VIX will drop')
        target_sid = context.SVXY

    # 6
    elif vix_ratio < threshold_vc_low: # 
        log.info('! Vix down sharply, invest in SVXY expecting that futures curve gets pulled down')
        target_sid = context.SVXY

    # 7
    elif vix_ratio > threshold_vc_high: # 
        log.info('! Vix up sharply, invest in UVXY expecting that futures curve gets pulled up')
        target_sid = context.UVXY

    # 8
    elif vix_ratio > threshold_vc_high_2: #
        log.info('! have to think')
        target_sid = context.SVXY

    # 9
    else:
        target_sid = context.UVXY
        log.info('! Going for UVXY...')

    # 10
    if (target_sid == context.SVXY and SVXY_ratio < threshold_SVXY_ratio) : 
        log.info('! indicators say SVXY but it just dropped overnight, so go for TQQQ')
        target_sid = context.TQQQ 

    # 11
    elif (target_sid == context.UVXY and SVXY_ratio > threshold_uvxy_ratio) :
        log.info('! indicators say UVXY but it just dropped overnight, so go for TQQQ')
        target_sid = context.TQQQ
    
    context.BigSignalShortVIX = True if context.SVXY is target_sid else False
    context.BigSignalLongVIX  = True if context.UVXY is target_sid else False
    context.BigSignalTQQQ     = True if context.TQQQ is target_sid else False
    
def update_indices(context, data):
    context.fetch_failed = False
    context.vix_vals = unpack_from_data(context, data, 'v')    
    context.vxv_vals = unpack_from_data(context, data, 'vxv')  
    context.vx1_vals = unpack_from_data(context, data, 'v1')
    context.vx2_vals = unpack_from_data(context, data, 'v2')

def fix_close(df,closeField):
    df = df.rename(columns={closeField:'Close'})
    # remove spurious asterisks
    df['Date'] = df['Date'].apply(lambda dt: re.sub('\*','',dt))
    # convert date column to timestamps
    df['Date'] = df['Date'].apply(lambda dt: pd.Timestamp(datetime.strptime(dt,'%m/%d/%Y')))
    df = df.sort_values(by='Date', ascending=True)
    return df

def fix_closeVX(df,closeField):
    df = df.rename(columns={closeField:'Close'})
    # remove spurious asterisks
    df['Date'] = df['Date'].apply(lambda dt: re.sub('\*','',dt))
    # convert date column to timestamps
    df['Date'] = df['Date'].apply(lambda dt: pd.Timestamp(datetime.strptime(dt,'%Y-%m-%d')))
    df = df.sort_values(by='Date', ascending=True)
    return df


def subsequent_trading_date(date):
    tdays = tradingcalendar.trading_days
    last_date = pd.to_datetime(date)
    last_dt = tradingcalendar.canonicalize_datetime(last_date)
    next_dt = tdays[tdays.searchsorted(last_dt) + 1]
    return next_dt

def add_last_bar(df):
    last_date = df.index[-1]
    subsequent_date = subsequent_trading_date(last_date)
    blank_row = pd.Series({}, index=df.columns, name=subsequent_date)
    # add today, and shift all previous data up to today. This 
    # should result in the same data frames as in backtest
    df = df.append(blank_row).shift(1).dropna(how='all')
    return df

def shift_data(df):
    df = add_last_bar(df)
    df.fillna(method='ffill') 
    df['PrevCloses'] = my_rolling_apply_series(df['Close'], to_csv_str, History)
    dates = pd.Series(df.index)
    dates.index = df.index
    df['PrevDates'] = my_rolling_apply_series(dates, to_csv_str, History)
    return df

def unpack_from_data(context, data, sym):
    try:
        v = data.current(sym, 'PrevCloses')
        i = data.current(sym, 'PrevDates')
        return from_csv_strs(i,v,True).apply(float)
    except:
        log.warn("Unable to unpack historical {s} data.".format(s=sym))
        context.fetch_failed = True

def addFieldsVIX(df):
    df = fix_close(df,'VIX Close')
    df['200ma'] = df['Close'].rolling(200).mean()
    df['200ma Ratio'] = df['Close'] / df['200ma']
    df['20dstd'] =  df['Close'].rolling(20).std()

    return df

def addFieldsVXV(df):
    df.rename(columns={'Unnamed: 0': 'Date'}, inplace=True)
    df = fix_close(df,'CLOSE')
    return df

def addFieldsVX1(df):
    df = fix_closeVX(df,'F1')
    return df

def addFieldsVX2(df):
    df = fix_closeVX(df,'F2')
    return df

# convert a series of values to a comma-separated string of said values
def to_csv_str(s):
    return functools.reduce(lambda x,y: x+','+y, pd.Series(s).apply(str))

# a specific instance of rolling apply, for Series of any type (not just numeric,
# ala pandas.rolling_apply), where the index of the series is set to the indices
# of the last elements of each subset
def my_rolling_apply_series(s_in, f, n):
    s_out = pd.Series([f(s_in[i:i+n]) for i in range(0,len(s_in)-(n-1))]) 
    s_out.index = s_in.index[n-1:]
    return s_out

# reconstitutes a Series from two csv-encoded strings, one of the index, one of the values
def from_csv_strs(x, y, idx_is_date):
    s = pd.Series(y.split(','),index=x.split(','))
    if (idx_is_date):
        s.index = s.index.map(lambda x: pd.Timestamp(x))
    return s
There was a runtime error.
15 responses

Charles, do you have this hooked up live to Robinhood or IB?

@Tyler
Short answer: Robinhood
Long answer: Both the algo and my Robinhood integration are still very much a work in progress.

Hi Charles, I see a lot of the sells going through at 4:00PM - are you manually trading this? Won't you miss the signal due to the 15 minute delay?

To simulate what the result would be if the whole period SVXY is 50% and UVXY is 75% of the performance see here! The metrics stay OK

Clone Algorithm
9
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
#Imports
from datetime import datetime  
from pytz import timezone      
from zipline.utils import tradingcalendar
from quantopian.pipeline.data.builtin import USEquityPricing
import statsmodels.api as sm 
import numpy as np
import pandas as pd
import functools
import quantopian.pipeline.data 
import re
import scipy
import talib
import time

History = 128

def initialize(context):

    vixUrl = 'http://www.cboe.com/publish/scheduledtask/mktdata/datahouse/vixcurrent.csv'
    vxvUrl = 'http://www.cboe.com/publish/scheduledtask/mktdata/datahouse/vix3mdailyprices.csv'
    nokoUrl = 'http://173.212.203.121/noko.csv'  
    
    fetch_csv(nokoUrl, 
              symbol='v1', 
              date_column='Date', 
              date_format='%Y-%m-%d', 
              pre_func=addFieldsVX1, 
              post_func=shift_data)
    
    fetch_csv(nokoUrl, 
              symbol='v2', 
              date_column='Date', 
              date_format='%Y-%m-%d', 
              pre_func=addFieldsVX2, 
              post_func=shift_data)

    fetch_csv(vixUrl, 
              symbol='v', 
              skiprows=1,
              date_column='Date', 
              pre_func=addFieldsVIX,
              post_func=shift_data)

    fetch_csv(vxvUrl, 
              symbol='vxv', 
              skiprows=2,
              date_column='Date', 
              pre_func=addFieldsVXV,
              post_func=shift_data)

    context.RobinHoodMargin = 7000 # real 8000 minus 1000 for withdrawals
    
    set_benchmark(symbol('SVXY'))
    context.SVXY = symbol('SVXY')
    context.TQQQ = symbol('TQQQ')
    context.UVXY = symbol('UVXY')
    context.SPYG = symbol('SPYG')

    context.ShowMaxLev = True
    context.vix = -1
    context.BigSignalShortVIX = context.BigSignalTQQQ = context.BigSignalLongVIX = False
    context.PriceSVXY = context.PriceTQQQ = context.PriceUVXY = 0.00
    context.BoughtShortVIX = context.BoughtLongVIX = False

    #Internal stop variables
    # context.stoploss is set in handle_data
    context.stops         = {}
    context.stoppedout    = []
    context.SellAndWait   = 0
    context.OkToAct = context.TooLateToAct = False
    
    #Used for logging
    context.last_bar = False
    context.SignalSellSVXY = False
    context.SignalBuySVXY  = False
    context.SignalBuyUVXY  = False

    # wait after market open before doing any possible orders
    schedule_function(OkToActTrigger, date_rules.every_day(), time_rules.market_open(minutes = 3))

    # too late to act because of 15 minute delay plus 10 minute cushion
    schedule_function(TooLateToActTrigger, date_rules.every_day(), time_rules.market_close(minutes = 25))

    # apparently highly successful VIX signals
    schedule_function(MoreSignals, date_rules.every_day(), time_rules.market_open(minutes = 2))

    # On Bar Close Functions
    for x in [1,3,5]:
        schedule_function(my_rebalance, date_rules.every_day(), time_rules.market_open(hours=x, minutes=59))
    schedule_function(my_rebalance, date_rules.every_day(), time_rules.market_close())
    
    schedule_function(my_record_vars, date_rules.every_day(), time_rules.market_close())

    #Set commission and slippage
    set_commission(commission.PerShare(cost=0.000, min_trade_cost=0.0)) 
    set_slippage(slippage.FixedSlippage(spread=0.01))
    
def OkToActTrigger(context,data): 
    context.OkToAct = True

def TooLateToActTrigger(context,data): 
    context.TooLateToAct = True

def my_rebalance(context,data):
    cancel_open_orders(context, data)
    #looking at 4 days of minutely price history
    svxy_prices = data.history(context.SVXY, "price", 1600, "1m").resample('30T',  closed='right', label='right').last().dropna()

    #convert to 2 hour:
    svxy2hr = []
    if '20:00:00+00:00' in str(svxy_prices.index[-1]) or '21:00:00+00:00' in str(svxy_prices.index[-1]):
        svxy2hr.append([svxy_prices.index[-28], svxy_prices[-28]])
        svxy2hr.append([svxy_prices.index[-27], svxy_prices[-27]])        
        svxy2hr.append([svxy_prices.index[-23], svxy_prices[-23]])
        svxy2hr.append([svxy_prices.index[-19], svxy_prices[-19]])
        svxy2hr.append([svxy_prices.index[-15], svxy_prices[-15]])
        svxy2hr.append([svxy_prices.index[-14], svxy_prices[-14]])
        svxy2hr.append([svxy_prices.index[-10], svxy_prices[-10]])
        svxy2hr.append([svxy_prices.index[-6], svxy_prices[-6]])
        svxy2hr.append([svxy_prices.index[-2], svxy_prices[-2]])
        svxy2hr.append([svxy_prices.index[-1], svxy_prices[-1]])
        context.last_bar = True
    elif '19:30:00+00:00' in str(svxy_prices.index[-1]) or '20:30:00+00:00' in str(svxy_prices.index[-1]):
        svxy2hr.append([svxy_prices.index[-31], svxy_prices[-31]])
        svxy2hr.append([svxy_prices.index[-27], svxy_prices[-27]]) 
        svxy2hr.append([svxy_prices.index[-26], svxy_prices[-26]])
        svxy2hr.append([svxy_prices.index[-22], svxy_prices[-22]])
        svxy2hr.append([svxy_prices.index[-18], svxy_prices[-18]])
        svxy2hr.append([svxy_prices.index[-14], svxy_prices[-14]]) 
        svxy2hr.append([svxy_prices.index[-13], svxy_prices[-13]])
        svxy2hr.append([svxy_prices.index[-9], svxy_prices[-9]])
        svxy2hr.append([svxy_prices.index[-5], svxy_prices[-5]])
        svxy2hr.append([svxy_prices.index[-1], svxy_prices[-1]])
        context.last_bar = False
    elif '17:30:00+00:00' in str(svxy_prices.index[-1]) or '18:30:00+00:00' in str(svxy_prices.index[-1]):
        svxy2hr.append([svxy_prices.index[-31], svxy_prices[-31]])
        svxy2hr.append([svxy_prices.index[-27], svxy_prices[-27]]) 
        svxy2hr.append([svxy_prices.index[-23], svxy_prices[-23]])
        svxy2hr.append([svxy_prices.index[-22], svxy_prices[-22]])
        svxy2hr.append([svxy_prices.index[-18], svxy_prices[-18]])
        svxy2hr.append([svxy_prices.index[-14], svxy_prices[-14]])
        svxy2hr.append([svxy_prices.index[-10], svxy_prices[-10]])
        svxy2hr.append([svxy_prices.index[-9], svxy_prices[-9]])
        svxy2hr.append([svxy_prices.index[-5], svxy_prices[-5]])
        svxy2hr.append([svxy_prices.index[-1], svxy_prices[-1]])
        context.last_bar = False
    elif '15:30:00+00:00' in str(svxy_prices.index[-1]) or '16:30:00+00:00' in str(svxy_prices.index[-1]):
        svxy2hr.append([svxy_prices.index[-31], svxy_prices[-31]])
        svxy2hr.append([svxy_prices.index[-27], svxy_prices[-27]]) 
        svxy2hr.append([svxy_prices.index[-23], svxy_prices[-23]])
        svxy2hr.append([svxy_prices.index[-19], svxy_prices[-19]])
        svxy2hr.append([svxy_prices.index[-18], svxy_prices[-18]])
        svxy2hr.append([svxy_prices.index[-14], svxy_prices[-14]])        
        svxy2hr.append([svxy_prices.index[-10], svxy_prices[-10]])
        svxy2hr.append([svxy_prices.index[-6], svxy_prices[-6]])
        svxy2hr.append([svxy_prices.index[-5], svxy_prices[-5]])
        svxy2hr.append([svxy_prices.index[-1], svxy_prices[-1]])
        context.last_bar = False
    else:
        log.error("2 HOUR CONVERSION FAILURE")
        return
    dates, vals = zip(*svxy2hr)
    s = pd.Series(vals, index=dates)
    
    rsi = talib.RSI(s,2)
    RSI5 = talib.RSI(s, 5) 

    # BUY SVXY RULE
    if rsi[-2] < 70 and rsi[-1] >= 70:
        context.SignalBuySVXY = True

    # SELL SVXY RULE or maybe BUY UVXY
    if rsi[-2] > 85 and rsi[-1] <= 85:
        context.SignalSellSVXY = True
        if (
            0 == context.portfolio.positions[context.SVXY].amount
            and
            RSI5[-1] < 70
        ):
            context.SignalBuyUVXY = True
        else: context.SignalBuyUVXY = False

def handle_data(context, data):
    pvr(context, data)
    c = context

    c.SellAndWait -= 1 # 1 unit is 1 minute
    c.SellAndWait = 0 if 0 > c.SellAndWait else c.SellAndWait

    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

    """
    ##
    # Buy SVXY
    ##
    if c.ShortVIX and not c.BoughtShortVIX and not c.SellAndWait:
        PriceSVXY = data.current(c.SVXY, 'price')
        if not c.PriceSVXY: c.PriceSVXY = PriceSVXY
        if PriceSVXY < c.PriceSVXY:
            c.PriceSVXY = PriceSVXY
        elif PriceSVXY > 1.0025 * c.PriceSVXY:
            c.BoughtShortVIX = True
            TarPer(context, data, c.SVXY, c.VIX_GrowthLeverage)

    ##
    # Buy UVXY
    ##
    if c.LongVIX and not c.BoughtLongVIX and not c.SellAndWait:
        PriceUVXY = data.current(c.UVXY, 'price')
        if not c.PriceUVXY: c.PriceUVXY = PriceUVXY
        if PriceUVXY < c.PriceUVXY:
            c.PriceUVXY = PriceUVXY
        elif PriceUVXY > 1.04 * c.PriceUVXY:
            c.BoughtLongVIX = True
            TarPer(context, data, c.UVXY, c.VIX_HedgeLeverage)
    """

    if not c.SellAndWait and c.OkToAct and not c.TooLateToAct:
        for position in c.portfolio.positions.itervalues():

            # Variable stop loss based on signals
            stoploss = 0.25 # default
            if position.sid is c.UVXY:
                stoploss = 0.21 if c.BigSignalLongVIX else 0.005
            else:
                if c.BigSignalTQQQ:     stoploss = 0.035
                if c.BigSignalShortVIX: stoploss = 0.105

            if position.amount == 0:
                if position.asset.symbol in c.stops: del c.stops[position.asset.symbol]
                continue
            elif position.asset.symbol not in c.stops:
                stoploss= stoploss if position.amount > 0 else -stoploss
                c.stops[position.asset.symbol]=position.last_sale_price*(1-stoploss)
                #log.info(' ! I have added '+str(position.asset.symbol)+' to Stops @ '+str((position.last_sale_price)*(1-stoploss)))
            elif c.stops[position.asset.symbol] < position.last_sale_price*(1- stoploss) and position.amount > 0:
                c.stops[position.asset.symbol]=position.last_sale_price*(1- stoploss)
                #log.info(' ! I have updated '+str(position.asset.symbol)+'- (Long) to stop @ '+str((position.last_sale_price)*(1- stoploss)))
                # set break even
                if position.last_sale_price > position.cost_basis * 1.075:
                    if position.cost_basis > c.stops[position.asset.symbol]:
                        c.stops[position.asset.symbol] = position.cost_basis
            elif c.stops[position.asset.symbol] > position.last_sale_price and position.amount > 0:
                #sell
                log.info(' ! '+str(position.asset.symbol)+'- (Long) has hit stoploss @ '+str(position.last_sale_price))
                if get_open_orders(position.sid): cancel_open_orders_for(context, data, position.sid)
                c.stoppedout.append(position.asset.symbol)
                TarPer(context, data, position.sid, 0.00)
 
            ##
            # Sell and Wait
            ##
            if (
                    (position.sid is not c.UVXY and 0 < position.amount)
                    and
                    (position.last_sale_price > 1.03 * position.cost_basis)
            ):
                c.SellAndWait = int(390 * 1)
                log.warn(str(position.sid.symbol) + ' 3 percent reached SellAndWait triggered: ' + str(c.SellAndWait) + ' minutes')
            elif (
                    (position.sid is c.UVXY and 0 < position.amount)
                    and
                    (position.last_sale_price > 1.05 * position.cost_basis)
            ):
                c.SellAndWait = int(390 * 1)
                log.warn(str(position.sid.symbol) + ' 5 percent reached SellAndWait triggered: ' + str(c.SellAndWait) + ' minutes')

        ##
        # Sell and Wait
        ##
        if 0 < c.SellAndWait:
            for stock in c.portfolio.positions:
                cancel_open_orders_for(context, data, stock)
                TarPer(context, data, stock, 0.00)
        else:
            if c.SignalBuySVXY: BuySVXY(context, data)
            if c.SignalSellSVXY: SellSVXY(context, data)
            else:
                if c.SignalBuyUVXY: BuyUVXY(context, data)

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 before_trading_start(context, data):
    context.OkToAct = context.TooLateToAct = False
    context.mx_lvrg  = 0 # daily max leverage
    # To help determine previous signal when start algo in middle of trading session
    if not context.BigSignalShortVIX and not context.BigSignalTQQQ and not context.BigSignalLongVIX: # Maybe we can figure it out
        context.BigSignalShortVIX = True if 0 < context.portfolio.positions[context.SVXY].amount else False
        context.BigSignalTQQQ = True if 0 < context.portfolio.positions[context.TQQQ].amount else False
        context.BigSignalLongVIX = True if 0 < context.portfolio.positions[context.UVXY].amount else False
        if context.BigSignalLongVIX and ( context.BigSignalShortVIX or context.BigSignalTQQQ ): # Do no harm
            context.BigSignalShortVIX = context.BigSignalTQQQ = context.BigSignalLongVIX = False

def BuySVXY(context, data):
    context.SignalBuySVXY = False
    TarPer(context, data, context.UVXY, 0.000)
    TarPer(context, data, context.TQQQ, 0.000)
    TarPer(context, data, context.SVXY, 0.999/2)
    log.warn("BuySVXY 0.999");

def BuyUVXY(context, data):
    context.SignalBuyUVXY = False
    TarPer(context, data, context.TQQQ, 0.000)
    TarPer(context, data, context.SVXY, 0.000)
    TarPer(context, data, context.UVXY, 0.999*4/3)
    log.warn("BuyUVXY 0.999");

def SellSVXY(context, data):
    context.SignalSellSVXY = False
    if context.portfolio.positions[context.SVXY].amount > 0:
        TarPer(context, data, context.SVXY, 0)
        log.warn("SellSVXY at SellSVXY signal");
    if context.portfolio.positions[context.TQQQ].amount > 0:
        TarPer(context, data, context.TQQQ, 0)
        log.warn("SellTQQQ at SellSVXY signal");

def TarPer(context, data, stock, TargetPercent):

    if DataCanTrade(context, data, stock):

        if 0 == TargetPercent:
            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 = 2000 # 200 Cushion plus cash for withdrawals
            RhMargin = context.RobinHoodMargin # 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
            if stock is context.UVXY:
                LimitPrice = price * 1.00
                order(stock, DiffAmount, style=LimitOrder(LimitPrice))
            else:
                order(stock, DiffAmount)

        if stock.symbol in context.stops: del context.stops[stock.symbol]
            
            
def DataCanTrade(context, data, stock):

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

def cancel_open_orders(context, data):
    oo = get_open_orders()
    if len(oo) == 0:
        return
    for stock, orders in oo.iteritems():
        for order in orders:
            cancel_order(order)
            #message = 'Canceling order of {amount} shares in {stock}'
            #log.info(message.format(amount=order.amount, stock=stock))
    
def cancel_open_orders_for(context, data, security):
    oo = get_open_orders()
    if len(oo) == 0:
        return
    for stock, orders in oo.iteritems():
        for order in orders:
            if stock is security:
                if order.amount < context.portfolio.positions[stock].amount:
                    cancel_order(order)
                    #message = 'Canceling order of {amount} shares in {stock}'
                    #log.info(message.format(amount=order.amount, stock=stock))
                else: # Do NOT want to cancel stop loss order
                    #message = 'NOT Canceling stop loss order of {amount} shares in {stock}'
                    #log.info(message.format(amount=order.amount, stock=stock))
                    pass

def MoreSignals(context, data):  

    update_indices(context, data)
    target_sid = context.SVXY # default if no conditions are met
    last_vix = context.VIXprice = data.current('v', 'Close')
    last_vx1 = data.current('v1','Close')  
    last_vx2 = data.current('v2','Close')      
    last_vxv = data.current('vxv', 'Close')
    last_vix_200ma_ratio = data.current('v', '200ma Ratio')
    # Maybe use dispersion aka vix standard deviation in buying mechanism
    context.dispersion = data.current('v','20dstd')
    #context.threshold_dispersion = 0.55

    
    record(dispersion=context.dispersion)
    record(last_vixDivBy10=last_vix/10)
    # Calculating the gap between spot vix and the first month vix future
    last_ratio_v_v1 = last_vix/last_vx1

    # Calculating the contango ratio of the front and second month VIX Futures 
    last_ratio_v1_v2 = last_vx1/last_vx2

    # Blending the previous two ratios together using a weighted average
    ratio_weight = 0.7
    last_ratio = (ratio_weight*last_ratio_v_v1) + ((1-ratio_weight)*last_ratio_v1_v2) - 1
    
    vix_vxv_ratio = last_vix/last_vxv
    
    # Retrieve SPY prices for technical indicators
    prices = data.history(context.SPYG, 'open', 40, '1d')
    
    # Retrieve SPY MACD data
    macda, signal, hist = talib.MACD(prices, fastperiod=12,slowperiod=26,signalperiod=9)
    macd = macda[-1] - signal[-1]
    
    # Calculate how much vix moved the previous day
    if (context.vix <> -1) : 
        vix_ratio = last_vix/context.vix -1
    else :
        vix_ratio = 0
    context.vix = last_vix
    
    SVXY_history = data.history(context.SVXY, 'price', 2, '1d')  
    
    SVXY_ratio = SVXY_history[1]/SVXY_history[0] - 1
    
    # Setting thresholds
    #if
    threshold_vix_too_low = 10.76   # 0 
    threshold_vix_200ma_ratio_low = 0.79  # 0 
    threshold_SVXY = -0.049          # 1
    threshold_vxv_SVXY = 0.87        # 2
    threshold_uvxy = 0.049          # 3
    threshold_macd = -0.55          # 3
    threshold_vxv_uvxy = 1.3        # 4
    threshold_vix_high = 19.9       # 5
    threshold_vc_low = -0.148       # 6
    threshold_vc_high = 0.046       # 8
    threshold_vc_high_2 = -0.06     # 8
    #if
    threshold_SVXY_ratio = -0.053    # 10
    threshold_uvxy_ratio = 0.08     # 11
    # 0
    if last_vix < threshold_vix_too_low \
            and last_vix_200ma_ratio < threshold_vix_200ma_ratio_low :
        log.info('! if VIX is too low, invest in UVXY with the hope of a spike')
        target_sid = context.UVXY
    # 1        
    elif last_ratio < threshold_SVXY: # 
        log.info('! if contango is high, invest in SVXY to gain from decay')
        target_sid = context.SVXY
    
    # 2
    elif vix_vxv_ratio < threshold_vxv_SVXY: # 
        log.info('! if short term vol is low compared to mid term, invest in SVXY to gain from decay')
        target_sid = context.SVXY

    # 3
    elif last_ratio > threshold_uvxy and macd > threshold_macd: # 
        log.info('! if backwardation is high, invest in UVXY to gain from decay')
        target_sid = context.UVXY

    # 4
    elif vix_vxv_ratio > threshold_vxv_uvxy: # 
        log.info('! if short term vol is high compared to mid term, invest in UVXY to gain from growth')
        target_sid = context.UVXY

    # 5
    elif last_vix > threshold_vix_high: # 
        log.info('! if VIX is too high, invest in SVXY expecting that VIX will drop')
        target_sid = context.SVXY

    # 6
    elif vix_ratio < threshold_vc_low: # 
        log.info('! Vix down sharply, invest in SVXY expecting that futures curve gets pulled down')
        target_sid = context.SVXY

    # 7
    elif vix_ratio > threshold_vc_high: # 
        log.info('! Vix up sharply, invest in UVXY expecting that futures curve gets pulled up')
        target_sid = context.UVXY

    # 8
    elif vix_ratio > threshold_vc_high_2: #
        log.info('! have to think')
        target_sid = context.SVXY

    # 10
    if (target_sid == context.SVXY and SVXY_ratio < threshold_SVXY_ratio) : 
        log.info('! indicators say SVXY but it just dropped overnight, so go for TQQQ')
        target_sid = context.TQQQ 

    # 11
    elif (target_sid == context.UVXY and SVXY_ratio > threshold_uvxy_ratio) :
        log.info('! indicators say UVXY but it just dropped overnight, so go for TQQQ')
        target_sid = context.TQQQ
    
    context.BigSignalShortVIX = True if context.SVXY is target_sid else False
    context.BigSignalLongVIX  = True if context.UVXY is target_sid else False
    context.BigSignalTQQQ     = True if context.TQQQ is target_sid else False
    
def update_indices(context, data):
    context.fetch_failed = False
    context.vix_vals = unpack_from_data(context, data, 'v')    
    context.vxv_vals = unpack_from_data(context, data, 'vxv')  
    context.vx1_vals = unpack_from_data(context, data, 'v1')
    context.vx2_vals = unpack_from_data(context, data, 'v2')

def fix_close(df,closeField):
    df = df.rename(columns={closeField:'Close'})
    # remove spurious asterisks
    df['Date'] = df['Date'].apply(lambda dt: re.sub('\*','',dt))
    # convert date column to timestamps
    df['Date'] = df['Date'].apply(lambda dt: pd.Timestamp(datetime.strptime(dt,'%m/%d/%Y')))
    df = df.sort_values(by='Date', ascending=True)
    return df

def fix_closeVX(df,closeField):
    df = df.rename(columns={closeField:'Close'})
    # remove spurious asterisks
    df['Date'] = df['Date'].apply(lambda dt: re.sub('\*','',dt))
    # convert date column to timestamps
    df['Date'] = df['Date'].apply(lambda dt: pd.Timestamp(datetime.strptime(dt,'%Y-%m-%d')))
    df = df.sort_values(by='Date', ascending=True)
    return df


def subsequent_trading_date(date):
    tdays = tradingcalendar.trading_days
    last_date = pd.to_datetime(date)
    last_dt = tradingcalendar.canonicalize_datetime(last_date)
    next_dt = tdays[tdays.searchsorted(last_dt) + 1]
    return next_dt

def add_last_bar(df):
    last_date = df.index[-1]
    subsequent_date = subsequent_trading_date(last_date)
    blank_row = pd.Series({}, index=df.columns, name=subsequent_date)
    # add today, and shift all previous data up to today. This 
    # should result in the same data frames as in backtest
    df = df.append(blank_row).shift(1).dropna(how='all')
    return df

def shift_data(df):
    df = add_last_bar(df)
    df.fillna(method='ffill') 
    df['PrevCloses'] = my_rolling_apply_series(df['Close'], to_csv_str, History)
    dates = pd.Series(df.index)
    dates.index = df.index
    df['PrevDates'] = my_rolling_apply_series(dates, to_csv_str, History)
    return df

def unpack_from_data(context, data, sym):
    try:
        v = data.current(sym, 'PrevCloses')
        i = data.current(sym, 'PrevDates')
        return from_csv_strs(i,v,True).apply(float)
    except:
        log.warn("Unable to unpack historical {s} data.".format(s=sym))
        context.fetch_failed = True

def addFieldsVIX(df):
    df = fix_close(df,'VIX Close')
    df['200ma'] = df['Close'].rolling(200).mean()
    df['200ma Ratio'] = df['Close'] / df['200ma']
    df['20dstd'] =  df['Close'].rolling(20).std()

    return df

def addFieldsVXV(df):
    df.rename(columns={'Unnamed: 0': 'Date'}, inplace=True)
    df = fix_close(df,'CLOSE')
    return df

def addFieldsVX1(df):
    df = fix_closeVX(df,'F1')
    return df

def addFieldsVX2(df):
    df = fix_closeVX(df,'F2')
    return df

# convert a series of values to a comma-separated string of said values
def to_csv_str(s):
    return functools.reduce(lambda x,y: x+','+y, pd.Series(s).apply(str))

# a specific instance of rolling apply, for Series of any type (not just numeric,
# ala pandas.rolling_apply), where the index of the series is set to the indices
# of the last elements of each subset
def my_rolling_apply_series(s_in, f, n):
    s_out = pd.Series([f(s_in[i:i+n]) for i in range(0,len(s_in)-(n-1))]) 
    s_out.index = s_in.index[n-1:]
    return s_out

# reconstitutes a Series from two csv-encoded strings, one of the index, one of the values
def from_csv_strs(x, y, idx_is_date):
    s = pd.Series(y.split(','),index=x.split(','))
    if (idx_is_date):
        s.index = s.index.map(lambda x: pd.Timestamp(x))
    return s

def pvr(context, data):
    ''' Custom chart and/or logging of profit_vs_risk returns and related information from https://www.quantopian.com/posts/pvr#569784bda73e9bf2b7000180
    '''  
    #import time  
    #from datetime import datetime  
    #from pytz import timezone      # Python will only do once, makes this portable.  
                                   #   Move to top of algo for better efficiency.  
    c = context  # Brevity is the soul of wit -- Shakespeare [for readability]  
    if 'pvr' not in c:

        # For real money, you can modify this to total cash input minus any withdrawals  
        manual_cash = c.portfolio.starting_cash  
        time_zone   = 'US/Central'   # Optionally change to your own time zone for wall clock time

        c.pvr = {  
            'options': {  
                # # # # # # # # # #  Options  # # # # # # # # # #  
                'logging'         : 0,    # Info to logging window with some new maximums  
                'log_summary'     : 126,  # Summary every x days. 252/yr

                'record_pvr'      : 0,    # Profit vs Risk returns (percentage)  
                'record_pvrp'     : 0,    # PvR (p)roportional neg cash vs portfolio value  
                'record_cash'     : 1,    # Cash available  
                'record_max_lvrg' : 0,    # Maximum leverage encountered  
                'record_risk_hi'  : 0,    # Highest risk overall  
                'record_shorting' : 0,    # Total value of any shorts  
                'record_max_shrt' : 0,    # Max value of shorting total  
                'record_cash_low' : 0,    # Any new lowest cash level  
                'record_q_return' : 0,    # Quantopian returns (percentage)  
                'record_pnl'      : 0,    # Profit-n-Loss  
                'record_risk'     : 1,    # Risked, max cash spent or shorts beyond longs+cash  
                'record_leverage' : 0,    # End of day leverage (context.account.leverage)  
                # All records are end-of-day or the last data sent to chart during any day.  
                # The way the chart operates, only the last value of the day will be seen.  
                # # # # # # # # #  End options  # # # # # # # # #  
            },  
            'pvr'        : 0,      # Profit vs Risk returns based on maximum spent  
            'cagr'       : 0,  
            'max_lvrg'   : 0,  
            'max_shrt'   : 0,  
            'risk_hi'    : 0,  
            'days'       : 0.0,  
            'date_prv'   : '',  
            'date_end'   : get_environment('end').date(),  
            'cash_low'   : manual_cash,  
            'cash'       : manual_cash,  
            'start'      : manual_cash,  
            'tz'         : time_zone,  
            'begin'      : time.time(),  # For run time  
            'run_str'    : '{} to {}  ${}  {} {}'.format(get_environment('start').date(), get_environment('end').date(), int(manual_cash), datetime.now(timezone(time_zone)).strftime("%Y-%m-%d %H:%M"), time_zone)  
        }  
        if c.pvr['options']['record_pvrp']: c.pvr['options']['record_pvr'] = 0 # if pvrp is active, straight pvr is off  
        if get_environment('arena') not in ['backtest', 'live']: c.pvr['options']['log_summary'] = 1 # Every day when real money  
        log.info(c.pvr['run_str'])  
    p = c.pvr ; o = c.pvr['options'] ; pf = c.portfolio ; pnl = pf.portfolio_value - p['start']  
    def _pvr(c):  
        p['cagr'] = ((pf.portfolio_value / p['start']) ** (1 / (p['days'] / 252.))) - 1  
        ptype = 'PvR' if o['record_pvr'] else 'PvRp'  
        log.info('{} {} %/day   cagr {}   Portfolio value {}   PnL {}'.format(ptype, '%.4f' % (p['pvr'] / p['days']), '%.3f' % p['cagr'], '%.0f' % pf.portfolio_value, '%.0f' % pnl))  
        log.info('  Profited {} on {} activated/transacted for PvR of {}%'.format('%.0f' % pnl, '%.0f' % p['risk_hi'], '%.1f' % p['pvr']))  
        log.info('  QRet {} PvR {} CshLw {} MxLv {} RskHi {} MxShrt {}'.format('%.2f' % (100 * pf.returns), '%.2f' % p['pvr'], '%.0f' % p['cash_low'], '%.2f' % p['max_lvrg'], '%.0f' % p['risk_hi'], '%.0f' % p['max_shrt']))  
    def _minut():  
        dt = get_datetime().astimezone(timezone(p['tz']))  
        return str((dt.hour * 60) + dt.minute - 570).rjust(3)  # (-570 = 9:31a)  
    date = get_datetime().date()  
    if p['date_prv'] != date:  
        p['date_prv'] = date  
        p['days'] += 1.0  
    do_summary = 0  
    if o['log_summary'] and p['days'] % o['log_summary'] == 0 and _minut() == '100':  
        do_summary = 1              # Log summary every x days  
    if do_summary or date == p['date_end']:  
        p['cash'] = pf.cash  
    elif p['cash'] == pf.cash and not o['logging']: return  # for speed

    shorts = sum([z.amount * z.last_sale_price for s, z in pf.positions.items() if z.amount < 0])  
    new_key_hi = 0                  # To trigger logging if on.  
    cash       = pf.cash  
    cash_dip   = int(max(0, p['start'] - cash))  
    risk       = int(max(cash_dip, -shorts))

    if o['record_pvrp'] and cash < 0:   # Let negative cash ding less when portfolio is up.  
        cash_dip = int(max(0, cash_dip * p['start'] / pf.portfolio_value))  
        # Imagine: Start with 10, grows to 1000, goes negative to -10, should not be 200% risk.

    if int(cash) < p['cash_low']:             # New cash low  
        new_key_hi = 1  
        p['cash_low'] = int(cash)             # Lowest cash level hit  
        if o['record_cash_low']: record(CashLow = p['cash_low'])

    if c.account.leverage > p['max_lvrg']:  
        new_key_hi = 1  
        p['max_lvrg'] = c.account.leverage    # Maximum intraday leverage  
        if o['record_max_lvrg']: record(MaxLv   = p['max_lvrg'])

    if shorts < p['max_shrt']:  
        new_key_hi = 1  
        p['max_shrt'] = shorts                # Maximum shorts value  
        if o['record_max_shrt']: record(MxShrt  = p['max_shrt'])

    if risk > p['risk_hi']:  
        new_key_hi = 1  
        p['risk_hi'] = risk                   # Highest risk overall  
        if o['record_risk_hi']:  record(RiskHi  = p['risk_hi'])

    # Profit_vs_Risk returns based on max amount actually invested, long or short  
    if p['risk_hi'] != 0: # Avoid zero-divide  
        p['pvr'] = 100 * pnl / p['risk_hi']  
        ptype = 'PvRp' if o['record_pvrp'] else 'PvR'  
        if o['record_pvr'] or o['record_pvrp']: record(**{ptype: p['pvr']})

    if o['record_shorting']: record(Shorts = shorts)             # Shorts value as a positve  
    if o['record_leverage']: record(Lvrg   = c.account.leverage) # Leverage  
    if o['record_cash']    : record(Cash   = cash)               # Cash  
    if o['record_risk']    : record(Risk   = risk)  # Amount in play, maximum of shorts or cash used  
    if o['record_q_return']: record(QRet   = 100 * pf.returns)  
    if o['record_pnl']     : record(PnL    = pnl)                # Profit|Loss

    if o['logging'] and new_key_hi:  
        log.info('{}{}{}{}{}{}{}{}{}{}{}{}'.format(_minut(),  
            ' Lv '     + '%.1f' % c.account.leverage,  
            ' MxLv '   + '%.2f' % p['max_lvrg'],  
            ' QRet '   + '%.1f' % (100 * pf.returns),  
            ' PvR '    + '%.1f' % p['pvr'],  
            ' PnL '    + '%.0f' % pnl,  
            ' Cash '   + '%.0f' % cash,  
            ' CshLw '  + '%.0f' % p['cash_low'],  
            ' Shrt '   + '%.0f' % shorts,  
            ' MxShrt ' + '%.0f' % p['max_shrt'],  
            ' Risk '   + '%.0f' % risk,  
            ' RskHi '  + '%.0f' % p['risk_hi']  
        ))  
    if do_summary: _pvr(c)  
    if get_datetime() == get_environment('end'):   # Summary at end of run  
        _pvr(c) ; elapsed = (time.time() - p['begin']) / 60  # minutes  
        log.info( '{}\nRuntime {} hr {} min'.format(p['run_str'], int(elapsed / 60), '%.1f' % (elapsed % 60)))
There was a runtime error.

Update: I deleted the previous post because I found some obvious logic errors. I tweaked the parameters and also added additional UVXY buy mechanism. I also made it run MoreSignals if not exist already. That could help if you start in middle of trading session.

@Thomas
Yes, sometimes trades are manual and on days that I have all my automation running correctly, then trades are automated. You are right. I noticed that potential problem too. In this version, I added a TooLateToAct trigger. So, if the condition for sale exists at 3 minutes after market open the next morning, then it will sell at that time. Of course you will not know until 18 or so minutes after market open. Obviously not a perfect situation. It is our reality as Quantopian paper trade users though. I very much appreciate Q for providing this platform.

Update: There was an obvious error in the post that I just now deleted. It had default to buy UVXY at market open. I also made some additional changes that are helpful. I made the stop loss to be variable according to signals.

Using this version now. It is unlikely to grab huge gains, like what was possible around Feb 5th, 2018. It does however seem to get steady returns if given enough time. It tends to be cash overnight if it had a good profit in trading session. That lowers overall return but also avoids overnight reversals. Still a work in progress.

Clone Algorithm
332
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
#Imports
from datetime import datetime  
from pytz import timezone      
from zipline.utils import tradingcalendar
from quantopian.pipeline.data.builtin import USEquityPricing
import statsmodels.api as sm 
import numpy as np
import pandas as pd
import functools
import quantopian.pipeline.data 
import re
import scipy
import talib
import time

History = 128

def initialize(context):

    vixUrl = 'http://www.cboe.com/publish/scheduledtask/mktdata/datahouse/vixcurrent.csv'
    vxvUrl = 'http://www.cboe.com/publish/scheduledtask/mktdata/datahouse/vix3mdailyprices.csv'
    nokoUrl = 'http://173.212.203.121/noko.csv'  
    
    fetch_csv(nokoUrl, 
              symbol='v1', 
              date_column='Date', 
              date_format='%Y-%m-%d', 
              pre_func=addFieldsVX1, 
              post_func=shift_data)
    
    fetch_csv(nokoUrl, 
              symbol='v2', 
              date_column='Date', 
              date_format='%Y-%m-%d', 
              pre_func=addFieldsVX2, 
              post_func=shift_data)

    fetch_csv(vixUrl, 
              symbol='v', 
              skiprows=1,
              date_column='Date', 
              pre_func=addFieldsVIX,
              post_func=shift_data)

    fetch_csv(vxvUrl, 
              symbol='vxv', 
              skiprows=2,
              date_column='Date', 
              pre_func=addFieldsVXV,
              post_func=shift_data)

    context.RobinHoodMargin = 7000 # real 8000 minus 1000 for withdrawals
    
    set_benchmark(symbol('SVXY'))
    context.SVXY = symbol('SVXY')
    context.TQQQ = symbol('TQQQ')
    context.UVXY = symbol('UVXY')
    context.SPYG = symbol('SPYG')

    context.ShowMaxLev = True
    context.vix = -1
    context.BigSignalShortVIX = context.BigSignalTQQQ = context.BigSignalLongVIX = False
    context.PriceSVXY = context.PriceUVXY = 0.00
    context.BoughtShortVIX = context.BoughtLongVIX = False

    #Internal stop variables
    # context.stoploss is set in handle_data
    context.stops         = {}
    context.stoppedout    = []
    context.SellAndWait   = 0
    context.OkToAct = context.TooLateToAct = False
    
    #Used for logging
    context.last_bar = False
    context.SignalSellSVXY = False
    context.SignalBuySVXY  = False
    context.SignalBuyUVXY  = False

    # wait after market open before doing any possible orders
    schedule_function(OkToActTrigger, date_rules.every_day(), time_rules.market_open(minutes = 3))

    # too late to act because of 15 minute delay plus 10 minute cushion
    schedule_function(TooLateToActTrigger, date_rules.every_day(), time_rules.market_close(minutes = 25))

    # apparently highly successful VIX signals
    schedule_function(MoreSignals, date_rules.every_day(), time_rules.market_open(minutes = 2))

    # On Bar Close Functions
    for x in [1,3,5]:
        schedule_function(my_rebalance, date_rules.every_day(), time_rules.market_open(hours=x, minutes=59))
    schedule_function(my_rebalance, date_rules.every_day(), time_rules.market_close())
    
    schedule_function(my_record_vars, date_rules.every_day(), time_rules.market_close())

    #Set commission and slippage
    set_commission(commission.PerShare(cost=0.000, min_trade_cost=0.0)) 
    set_slippage(slippage.FixedSlippage(spread=0.01))
    
def OkToActTrigger(context,data): 
    context.OkToAct = True

def TooLateToActTrigger(context,data): 
    context.TooLateToAct = True

def before_trading_start(context, data):
    context.OkToAct = context.TooLateToAct = False
    context.mx_lvrg  = 0 # daily max leverage

def TarPer(context, data, stock, TargetPercent):

    cancel_open_orders_for(context, data, stock)
    if DataCanTrade(context, data, stock):

        if 0 == TargetPercent:
            amount = context.portfolio.positions[stock].amount
            price = data.current(stock, 'price')
            order_target_percent(stock, 0.00)
            log.warn("SOLD ALL " + stock.symbol + " " + str(amount) + \
            " Shares @ MarketPrice " + str(price))
        else:
            # Always want money available to withdraw 
            # and also try to prevent margin related order rejections
            PV = context.portfolio.portfolio_value
            DoNotSpend = 2000 # 200 Cushion plus cash for withdrawals
            RhMargin = context.RobinHoodMargin # 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
            Action = "Bought " if 0 < DiffAmount else "Sold "
            if stock is context.UVXY:
                LimitPrice = price * 1.00
                order(stock, DiffAmount, style=LimitOrder(LimitPrice))
                log.warn(Action + stock.symbol + " " + str(DiffAmount) + \
                " Shares @ LimitPrice " + str(LimitPrice))
            else:
                order(stock, DiffAmount)
                log.warn(Action + stock.symbol + " " + str(DiffAmount) + \
                " Shares @ MarketPrice " + str(price))

        if stock.symbol in context.stops: del context.stops[stock.symbol]

def my_rebalance(context,data):
    c = context
    if not c.BigSignalShortVIX and not c.BigSignalTQQQ and not c.BigSignalLongVIX: MoreSignals(context, data)
    cancel_open_orders(context, data)
    #looking at 4 days of minutely price history
    svxy_prices = data.history(context.SVXY, "price", 1600, "1m").resample('30T',  closed='right', label='right').last().dropna()

    #convert to 2 hour:
    svxy2hr = []
    if '20:00:00+00:00' in str(svxy_prices.index[-1]) or '21:00:00+00:00' in str(svxy_prices.index[-1]):
        svxy2hr.append([svxy_prices.index[-28], svxy_prices[-28]])
        svxy2hr.append([svxy_prices.index[-27], svxy_prices[-27]])        
        svxy2hr.append([svxy_prices.index[-23], svxy_prices[-23]])
        svxy2hr.append([svxy_prices.index[-19], svxy_prices[-19]])
        svxy2hr.append([svxy_prices.index[-15], svxy_prices[-15]])
        svxy2hr.append([svxy_prices.index[-14], svxy_prices[-14]])
        svxy2hr.append([svxy_prices.index[-10], svxy_prices[-10]])
        svxy2hr.append([svxy_prices.index[-6], svxy_prices[-6]])
        svxy2hr.append([svxy_prices.index[-2], svxy_prices[-2]])
        svxy2hr.append([svxy_prices.index[-1], svxy_prices[-1]])
        context.last_bar = True
    elif '19:30:00+00:00' in str(svxy_prices.index[-1]) or '20:30:00+00:00' in str(svxy_prices.index[-1]):
        svxy2hr.append([svxy_prices.index[-31], svxy_prices[-31]])
        svxy2hr.append([svxy_prices.index[-27], svxy_prices[-27]]) 
        svxy2hr.append([svxy_prices.index[-26], svxy_prices[-26]])
        svxy2hr.append([svxy_prices.index[-22], svxy_prices[-22]])
        svxy2hr.append([svxy_prices.index[-18], svxy_prices[-18]])
        svxy2hr.append([svxy_prices.index[-14], svxy_prices[-14]]) 
        svxy2hr.append([svxy_prices.index[-13], svxy_prices[-13]])
        svxy2hr.append([svxy_prices.index[-9], svxy_prices[-9]])
        svxy2hr.append([svxy_prices.index[-5], svxy_prices[-5]])
        svxy2hr.append([svxy_prices.index[-1], svxy_prices[-1]])
        context.last_bar = False
    elif '17:30:00+00:00' in str(svxy_prices.index[-1]) or '18:30:00+00:00' in str(svxy_prices.index[-1]):
        svxy2hr.append([svxy_prices.index[-31], svxy_prices[-31]])
        svxy2hr.append([svxy_prices.index[-27], svxy_prices[-27]]) 
        svxy2hr.append([svxy_prices.index[-23], svxy_prices[-23]])
        svxy2hr.append([svxy_prices.index[-22], svxy_prices[-22]])
        svxy2hr.append([svxy_prices.index[-18], svxy_prices[-18]])
        svxy2hr.append([svxy_prices.index[-14], svxy_prices[-14]])
        svxy2hr.append([svxy_prices.index[-10], svxy_prices[-10]])
        svxy2hr.append([svxy_prices.index[-9], svxy_prices[-9]])
        svxy2hr.append([svxy_prices.index[-5], svxy_prices[-5]])
        svxy2hr.append([svxy_prices.index[-1], svxy_prices[-1]])
        context.last_bar = False
    elif '15:30:00+00:00' in str(svxy_prices.index[-1]) or '16:30:00+00:00' in str(svxy_prices.index[-1]):
        svxy2hr.append([svxy_prices.index[-31], svxy_prices[-31]])
        svxy2hr.append([svxy_prices.index[-27], svxy_prices[-27]]) 
        svxy2hr.append([svxy_prices.index[-23], svxy_prices[-23]])
        svxy2hr.append([svxy_prices.index[-19], svxy_prices[-19]])
        svxy2hr.append([svxy_prices.index[-18], svxy_prices[-18]])
        svxy2hr.append([svxy_prices.index[-14], svxy_prices[-14]])        
        svxy2hr.append([svxy_prices.index[-10], svxy_prices[-10]])
        svxy2hr.append([svxy_prices.index[-6], svxy_prices[-6]])
        svxy2hr.append([svxy_prices.index[-5], svxy_prices[-5]])
        svxy2hr.append([svxy_prices.index[-1], svxy_prices[-1]])
        context.last_bar = False
    else:
        log.error("2 HOUR CONVERSION FAILURE")
        return
    dates, vals = zip(*svxy2hr)
    s = pd.Series(vals, index=dates)
    
    rsi = talib.RSI(s,2)
    RSI5 = talib.RSI(s, 5) 

    # BUY SVXY RULE
    if rsi[-2] < 70 and rsi[-1] >= 70:
        context.SignalBuySVXY  = True
        context.BoughtShortVIX = False

    # SELL SVXY RULE or maybe BUY UVXY
    if rsi[-2] > 85 and rsi[-1] <= 85:
        context.SignalSellSVXY = True
        if RSI5[-1] < 70:
            context.SignalBuyUVXY = True
            context.BoughtLongVIX = False
    # maybe BUY UVXY
    if (
        not context.SignalBuySVXY
        and
        0 == context.portfolio.positions[context.SVXY].amount
        and
        context.BigSignalLongVIX
        and
        2.00 <= context.dispersion 
    ):
        context.SignalBuyUVXY = True
        context.BoughtLongVIX = False

def handle_data(context, data):
    pvr(context, data)
    c = context

    c.SellAndWait -= 1 # 1 unit is 1 minute
    c.SellAndWait = 0 if 0 > c.SellAndWait else c.SellAndWait

    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 not c.SellAndWait and c.OkToAct and not c.TooLateToAct:
        for position in c.portfolio.positions.itervalues():

            ##
            # Variable stop loss based on signals
            ##
            SVXY_Dispersion = 3.00 # SVXY 3.00 may be best overall
            UVXY_Dispersion = 3.00 # UVXY no idea what is best yet
            stoploss = 0.25 # default - probably not used
            if position.sid is c.UVXY:
                if UVXY_Dispersion <= c.dispersion:
                    stoploss = 0.50 # 0.21 may be best
                else:
                    stoploss = 0.03 if c.BigSignalLongVIX else 0.01
            else:
                if SVXY_Dispersion <= c.dispersion:
                    stoploss = 0.25
                else:
                    if c.BigSignalTQQQ:     stoploss = 0.02
                    if c.BigSignalShortVIX: stoploss = 0.03

            if position.amount == 0:
                if position.asset.symbol in c.stops: del c.stops[position.asset.symbol]
                continue
            elif position.asset.symbol not in c.stops:
                stoploss= stoploss if position.amount > 0 else -stoploss
                c.stops[position.asset.symbol]=position.last_sale_price*(1-stoploss)
                #log.info(' ! I have added '+str(position.asset.symbol)+' to Stops @ '+str((position.last_sale_price)*(1-stoploss)))
            elif c.stops[position.asset.symbol] < position.last_sale_price*(1- stoploss) and position.amount > 0:
                c.stops[position.asset.symbol]=position.last_sale_price*(1- stoploss)
                #log.info(' ! I have updated '+str(position.asset.symbol)+'- (Long) to stop @ '+str((position.last_sale_price)*(1- stoploss)))
                # set break even
                if position.last_sale_price > position.cost_basis * 1.075:
                    if position.cost_basis > c.stops[position.asset.symbol]:
                        c.stops[position.asset.symbol] = position.cost_basis
            elif c.stops[position.asset.symbol] > position.last_sale_price and position.amount > 0:
                #sell
                log.info(' ! '+str(position.asset.symbol)+'- (Long) has hit stoploss @ '+str(position.last_sale_price))
                if get_open_orders(position.sid): cancel_open_orders_for(context, data, position.sid)
                c.stoppedout.append(position.asset.symbol)
                TarPer(context, data, position.sid, 0.00)
 
            ##
            # Sell and Wait
            ##
            SVXY_Up = 1.03 # 1.03 seems to be the sweet spot
            UVXY_Up = 2.00 # probably stop loss way before this
            if (
                    (position.sid is not c.UVXY and 0 < position.amount)
                    and
                    (position.last_sale_price > SVXY_Up * position.cost_basis)
            ):
                c.SellAndWait = int(390 * 0.03)
                log.warn(str(position.sid.symbol) + ' 3 percent reached SellAndWait triggered: ' + str(c.SellAndWait) + ' minutes')
            elif (
                    (position.sid is c.UVXY and 0 < position.amount)
                    and
                    (position.last_sale_price > UVXY_Up * position.cost_basis)
            ):
                c.SellAndWait = int(390 * 0.03)
                log.warn(str(position.sid.symbol) + ' 5 percent reached SellAndWait triggered: ' + str(c.SellAndWait) + ' minutes')

        ##
        # Sell and Wait
        ##
        if 0 < c.SellAndWait:
            c.PriceSVXY = c.PriceUVXY = 0.00
            c.SignalBuySVXY = c.SignalSellSVXY = c.SignalBuyUVXY = False
            for stock in c.portfolio.positions:
                cancel_open_orders_for(context, data, stock)
                TarPer(context, data, stock, 0.00)
        ##
        # Slippery Buy allows for some drop before buy
        ##
        else:
            SVXY_UpAfterSlip = 1.0025
            UVXY_UpAfterSlip = 1.005
            if c.SignalBuySVXY:
                if 0 < c.portfolio.positions[context.UVXY].amount:
                    c.PriceUVXY = 0.00
                    c.SignalBuyUVXY = False
                    TarPer(context, data, context.UVXY, 0.000)
                elif 0 == c.portfolio.positions[context.SVXY].amount:
                    if not c.BoughtShortVIX:
                        PriceSVXY = data.current(c.SVXY, 'price')
                        if not c.PriceSVXY: c.PriceSVXY = PriceSVXY
                        if PriceSVXY < c.PriceSVXY:
                            c.PriceSVXY = PriceSVXY
                        elif PriceSVXY > SVXY_UpAfterSlip * c.PriceSVXY:
                            c.PriceSVXY = c.PriceUVXY = 0.00
                            c.SignalBuySVXY = c.SignalSellSVXY = c.SignalBuyUVXY = False
                            c.BoughtShortVIX = True
                            TarPer(context, data, context.SVXY, 0.999)
            elif c.SignalSellSVXY:
                if c.portfolio.positions[context.SVXY].amount > 0:
                    c.PriceSVXY = c.PriceUVXY = 0.00
                    c.SignalBuySVXY = c.SignalSellSVXY = c.SignalBuyUVXY = False
                    TarPer(context, data, context.SVXY, 0)
            elif c.SignalBuyUVXY:
                if 0 < c.portfolio.positions[context.SVXY].amount:
                    c.PriceSVXY = c.PriceUVXY = 0.00
                    c.SignalBuySVXY = c.SignalSellSVXY = False
                    TarPer(context, data, context.SVXY, 0.000)
                elif 0 == c.portfolio.positions[context.UVXY].amount:
                    if not c.BoughtLongVIX:
                        PriceUVXY = data.current(c.UVXY, 'price')
                        if not c.PriceUVXY: c.PriceUVXY = PriceUVXY
                        if PriceUVXY < c.PriceUVXY:
                            c.PriceUVXY = PriceUVXY
                        elif PriceUVXY > UVXY_UpAfterSlip * c.PriceUVXY:
                            c.PriceSVXY = c.PriceUVXY = 0.00
                            c.SignalBuySVXY = c.SignalSellSVXY = c.SignalBuyUVXY = False
                            c.BoughtLongVIX = True
                            TarPer(context, data, context.UVXY, 0.999)

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 DataCanTrade(context, data, stock):

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

def cancel_open_orders(context, data):
    oo = get_open_orders()
    if len(oo) == 0:
        return
    for stock, orders in oo.iteritems():
        for order in orders:
            cancel_order(order)
            #message = 'Canceling order of {amount} shares in {stock}'
            #log.info(message.format(amount=order.amount, stock=stock))
    
def cancel_open_orders_for(context, data, security):
    oo = get_open_orders()
    if len(oo) == 0:
        return
    for stock, orders in oo.iteritems():
        for order in orders:
            if stock is security:
                if order.amount < context.portfolio.positions[stock].amount:
                    cancel_order(order)
                    #message = 'Canceling order of {amount} shares in {stock}'
                    #log.info(message.format(amount=order.amount, stock=stock))
                else: # Do NOT want to cancel stop loss order
                    #message = 'NOT Canceling stop loss order of {amount} shares in {stock}'
                    #log.info(message.format(amount=order.amount, stock=stock))
                    pass

def MoreSignals(context, data):  

    update_indices(context, data)
    target_sid = context.SVXY # default if no conditions are met
    last_vix = context.VIXprice = data.current('v', 'Close')
    last_vx1 = data.current('v1','Close')  
    last_vx2 = data.current('v2','Close')      
    last_vxv = data.current('vxv', 'Close')
    last_vix_200ma_ratio = data.current('v', '200ma Ratio')
    # Maybe use dispersion aka vix standard deviation in buying mechanism
    context.dispersion = data.current('v','20dstd')
    #context.threshold_dispersion = 0.55

    
    record(dispersion=context.dispersion)
    record(last_vixDivBy10=last_vix/10)
    # Calculating the gap between spot vix and the first month vix future
    last_ratio_v_v1 = last_vix/last_vx1

    # Calculating the contango ratio of the front and second month VIX Futures 
    last_ratio_v1_v2 = last_vx1/last_vx2

    # Blending the previous two ratios together using a weighted average
    ratio_weight = 0.7
    last_ratio = (ratio_weight*last_ratio_v_v1) + ((1-ratio_weight)*last_ratio_v1_v2) - 1
    
    vix_vxv_ratio = last_vix/last_vxv
    
    # Retrieve SPY prices for technical indicators
    prices = data.history(context.SPYG, 'open', 40, '1d')
    
    # Retrieve SPY MACD data
    macda, signal, hist = talib.MACD(prices, fastperiod=12,slowperiod=26,signalperiod=9)
    macd = macda[-1] - signal[-1]
    
    # Calculate how much vix moved the previous day
    if (context.vix <> -1) : 
        vix_ratio = last_vix/context.vix -1
    else :
        vix_ratio = 0
    context.vix = last_vix
    
    SVXY_history = data.history(context.SVXY, 'price', 2, '1d')  
    
    SVXY_ratio = SVXY_history[1]/SVXY_history[0] - 1
    
    # Setting thresholds
    #if
    threshold_vix_too_low = 10.76   # 0 
    threshold_vix_200ma_ratio_low = 0.79  # 0 
    threshold_SVXY = -0.049          # 1
    threshold_vxv_SVXY = 0.87        # 2
    threshold_uvxy = 0.049          # 3
    threshold_macd = -0.55          # 3
    threshold_vxv_uvxy = 1.3        # 4
    threshold_vix_high = 19.9       # 5
    threshold_vc_low = -0.148       # 6
    threshold_vc_high = 0.046       # 8
    threshold_vc_high_2 = -0.06     # 8
    #if
    threshold_SVXY_ratio = -0.053    # 10
    threshold_uvxy_ratio = 0.08     # 11
    # 0
    if last_vix < threshold_vix_too_low \
            and last_vix_200ma_ratio < threshold_vix_200ma_ratio_low :
        log.info('! if VIX is too low, invest in UVXY with the hope of a spike')
        target_sid = context.UVXY
    # 1        
    elif last_ratio < threshold_SVXY: # 
        log.info('! if contango is high, invest in SVXY to gain from decay')
        target_sid = context.SVXY
    
    # 2
    elif vix_vxv_ratio < threshold_vxv_SVXY: # 
        log.info('! if short term vol is low compared to mid term, invest in SVXY to gain from decay')
        target_sid = context.SVXY

    # 3
    elif last_ratio > threshold_uvxy and macd > threshold_macd: # 
        log.info('! if backwardation is high, invest in UVXY to gain from decay')
        target_sid = context.UVXY

    # 4
    elif vix_vxv_ratio > threshold_vxv_uvxy: # 
        log.info('! if short term vol is high compared to mid term, invest in UVXY to gain from growth')
        target_sid = context.UVXY

    # 5
    elif last_vix > threshold_vix_high: # 
        log.info('! if VIX is too high, invest in SVXY expecting that VIX will drop')
        target_sid = context.SVXY

    # 6
    elif vix_ratio < threshold_vc_low: # 
        log.info('! Vix down sharply, invest in SVXY expecting that futures curve gets pulled down')
        target_sid = context.SVXY

    # 7
    elif vix_ratio > threshold_vc_high: # 
        log.info('! Vix up sharply, invest in UVXY expecting that futures curve gets pulled up')
        target_sid = context.UVXY

    # 8
    elif vix_ratio > threshold_vc_high_2: #
        log.info('! have to think')
        target_sid = context.SVXY

    # 10
    if (target_sid == context.SVXY and SVXY_ratio < threshold_SVXY_ratio) : 
        log.info('! indicators say SVXY but it just dropped overnight, so go for TQQQ')
        target_sid = context.TQQQ 

    # 11
    elif (target_sid == context.UVXY and SVXY_ratio > threshold_uvxy_ratio) :
        log.info('! indicators say UVXY but it just dropped overnight, so go for TQQQ')
        target_sid = context.TQQQ
    
    context.BigSignalShortVIX = True if context.SVXY is target_sid else False
    context.BigSignalLongVIX  = True if context.UVXY is target_sid else False
    context.BigSignalTQQQ     = True if context.TQQQ is target_sid else False
    
def update_indices(context, data):
    context.fetch_failed = False
    context.vix_vals = unpack_from_data(context, data, 'v')    
    context.vxv_vals = unpack_from_data(context, data, 'vxv')  
    context.vx1_vals = unpack_from_data(context, data, 'v1')
    context.vx2_vals = unpack_from_data(context, data, 'v2')

def fix_close(df,closeField):
    df = df.rename(columns={closeField:'Close'})
    # remove spurious asterisks
    df['Date'] = df['Date'].apply(lambda dt: re.sub('\*','',dt))
    # convert date column to timestamps
    df['Date'] = df['Date'].apply(lambda dt: pd.Timestamp(datetime.strptime(dt,'%m/%d/%Y')))
    df = df.sort_values(by='Date', ascending=True)
    return df

def fix_closeVX(df,closeField):
    df = df.rename(columns={closeField:'Close'})
    # remove spurious asterisks
    df['Date'] = df['Date'].apply(lambda dt: re.sub('\*','',dt))
    # convert date column to timestamps
    df['Date'] = df['Date'].apply(lambda dt: pd.Timestamp(datetime.strptime(dt,'%Y-%m-%d')))
    df = df.sort_values(by='Date', ascending=True)
    return df


def subsequent_trading_date(date):
    tdays = tradingcalendar.trading_days
    last_date = pd.to_datetime(date)
    last_dt = tradingcalendar.canonicalize_datetime(last_date)
    next_dt = tdays[tdays.searchsorted(last_dt) + 1]
    return next_dt

def add_last_bar(df):
    last_date = df.index[-1]
    subsequent_date = subsequent_trading_date(last_date)
    blank_row = pd.Series({}, index=df.columns, name=subsequent_date)
    # add today, and shift all previous data up to today. This 
    # should result in the same data frames as in backtest
    df = df.append(blank_row).shift(1).dropna(how='all')
    return df

def shift_data(df):
    df = add_last_bar(df)
    df.fillna(method='ffill') 
    df['PrevCloses'] = my_rolling_apply_series(df['Close'], to_csv_str, History)
    dates = pd.Series(df.index)
    dates.index = df.index
    df['PrevDates'] = my_rolling_apply_series(dates, to_csv_str, History)
    return df

def unpack_from_data(context, data, sym):
    try:
        v = data.current(sym, 'PrevCloses')
        i = data.current(sym, 'PrevDates')
        return from_csv_strs(i,v,True).apply(float)
    except:
        log.warn("Unable to unpack historical {s} data.".format(s=sym))
        context.fetch_failed = True

def addFieldsVIX(df):
    df = fix_close(df,'VIX Close')
    df['200ma'] = df['Close'].rolling(200).mean()
    df['200ma Ratio'] = df['Close'] / df['200ma']
    df['20dstd'] =  df['Close'].rolling(20).std()

    return df

def addFieldsVXV(df):
    df.rename(columns={'Unnamed: 0': 'Date'}, inplace=True)
    df = fix_close(df,'CLOSE')
    return df

def addFieldsVX1(df):
    df = fix_closeVX(df,'F1')
    return df

def addFieldsVX2(df):
    df = fix_closeVX(df,'F2')
    return df

# convert a series of values to a comma-separated string of said values
def to_csv_str(s):
    return functools.reduce(lambda x,y: x+','+y, pd.Series(s).apply(str))

# a specific instance of rolling apply, for Series of any type (not just numeric,
# ala pandas.rolling_apply), where the index of the series is set to the indices
# of the last elements of each subset
def my_rolling_apply_series(s_in, f, n):
    s_out = pd.Series([f(s_in[i:i+n]) for i in range(0,len(s_in)-(n-1))]) 
    s_out.index = s_in.index[n-1:]
    return s_out

# reconstitutes a Series from two csv-encoded strings, one of the index, one of the values
def from_csv_strs(x, y, idx_is_date):
    s = pd.Series(y.split(','),index=x.split(','))
    if (idx_is_date):
        s.index = s.index.map(lambda x: pd.Timestamp(x))
    return s

def pvr(context, data):
    ''' Custom chart and/or logging of profit_vs_risk returns and related information from https://www.quantopian.com/posts/pvr#569784bda73e9bf2b7000180
    '''  
    #import time  
    #from datetime import datetime  
    #from pytz import timezone      # Python will only do once, makes this portable.  
                                   #   Move to top of algo for better efficiency.  
    c = context  # Brevity is the soul of wit -- Shakespeare [for readability]  
    if 'pvr' not in c:

        # For real money, you can modify this to total cash input minus any withdrawals  
        manual_cash = c.portfolio.starting_cash  
        time_zone   = 'US/Central'   # Optionally change to your own time zone for wall clock time

        c.pvr = {  
            'options': {  
                # # # # # # # # # #  Options  # # # # # # # # # #  
                'logging'         : 0,    # Info to logging window with some new maximums  
                'log_summary'     : 126,  # Summary every x days. 252/yr

                'record_pvr'      : 0,    # Profit vs Risk returns (percentage)  
                'record_pvrp'     : 0,    # PvR (p)roportional neg cash vs portfolio value  
                'record_cash'     : 1,    # Cash available  
                'record_max_lvrg' : 0,    # Maximum leverage encountered  
                'record_risk_hi'  : 0,    # Highest risk overall  
                'record_shorting' : 0,    # Total value of any shorts  
                'record_max_shrt' : 0,    # Max value of shorting total  
                'record_cash_low' : 0,    # Any new lowest cash level  
                'record_q_return' : 0,    # Quantopian returns (percentage)  
                'record_pnl'      : 0,    # Profit-n-Loss  
                'record_risk'     : 1,    # Risked, max cash spent or shorts beyond longs+cash  
                'record_leverage' : 0,    # End of day leverage (context.account.leverage)  
                # All records are end-of-day or the last data sent to chart during any day.  
                # The way the chart operates, only the last value of the day will be seen.  
                # # # # # # # # #  End options  # # # # # # # # #  
            },  
            'pvr'        : 0,      # Profit vs Risk returns based on maximum spent  
            'cagr'       : 0,  
            'max_lvrg'   : 0,  
            'max_shrt'   : 0,  
            'risk_hi'    : 0,  
            'days'       : 0.0,  
            'date_prv'   : '',  
            'date_end'   : get_environment('end').date(),  
            'cash_low'   : manual_cash,  
            'cash'       : manual_cash,  
            'start'      : manual_cash,  
            'tz'         : time_zone,  
            'begin'      : time.time(),  # For run time  
            'run_str'    : '{} to {}  ${}  {} {}'.format(get_environment('start').date(), get_environment('end').date(), int(manual_cash), datetime.now(timezone(time_zone)).strftime("%Y-%m-%d %H:%M"), time_zone)  
        }  
        if c.pvr['options']['record_pvrp']: c.pvr['options']['record_pvr'] = 0 # if pvrp is active, straight pvr is off  
        if get_environment('arena') not in ['backtest', 'live']: c.pvr['options']['log_summary'] = 1 # Every day when real money  
        log.info(c.pvr['run_str'])  
    p = c.pvr ; o = c.pvr['options'] ; pf = c.portfolio ; pnl = pf.portfolio_value - p['start']  
    def _pvr(c):  
        p['cagr'] = ((pf.portfolio_value / p['start']) ** (1 / (p['days'] / 252.))) - 1  
        ptype = 'PvR' if o['record_pvr'] else 'PvRp'  
        log.info('{} {} %/day   cagr {}   Portfolio value {}   PnL {}'.format(ptype, '%.4f' % (p['pvr'] / p['days']), '%.3f' % p['cagr'], '%.0f' % pf.portfolio_value, '%.0f' % pnl))  
        log.info('  Profited {} on {} activated/transacted for PvR of {}%'.format('%.0f' % pnl, '%.0f' % p['risk_hi'], '%.1f' % p['pvr']))  
        log.info('  QRet {} PvR {} CshLw {} MxLv {} RskHi {} MxShrt {}'.format('%.2f' % (100 * pf.returns), '%.2f' % p['pvr'], '%.0f' % p['cash_low'], '%.2f' % p['max_lvrg'], '%.0f' % p['risk_hi'], '%.0f' % p['max_shrt']))  
    def _minut():  
        dt = get_datetime().astimezone(timezone(p['tz']))  
        return str((dt.hour * 60) + dt.minute - 570).rjust(3)  # (-570 = 9:31a)  
    date = get_datetime().date()  
    if p['date_prv'] != date:  
        p['date_prv'] = date  
        p['days'] += 1.0  
    do_summary = 0  
    if o['log_summary'] and p['days'] % o['log_summary'] == 0 and _minut() == '100':  
        do_summary = 1              # Log summary every x days  
    if do_summary or date == p['date_end']:  
        p['cash'] = pf.cash  
    elif p['cash'] == pf.cash and not o['logging']: return  # for speed

    shorts = sum([z.amount * z.last_sale_price for s, z in pf.positions.items() if z.amount < 0])  
    new_key_hi = 0                  # To trigger logging if on.  
    cash       = pf.cash  
    cash_dip   = int(max(0, p['start'] - cash))  
    risk       = int(max(cash_dip, -shorts))

    if o['record_pvrp'] and cash < 0:   # Let negative cash ding less when portfolio is up.  
        cash_dip = int(max(0, cash_dip * p['start'] / pf.portfolio_value))  
        # Imagine: Start with 10, grows to 1000, goes negative to -10, should not be 200% risk.

    if int(cash) < p['cash_low']:             # New cash low  
        new_key_hi = 1  
        p['cash_low'] = int(cash)             # Lowest cash level hit  
        if o['record_cash_low']: record(CashLow = p['cash_low'])

    if c.account.leverage > p['max_lvrg']:  
        new_key_hi = 1  
        p['max_lvrg'] = c.account.leverage    # Maximum intraday leverage  
        if o['record_max_lvrg']: record(MaxLv   = p['max_lvrg'])

    if shorts < p['max_shrt']:  
        new_key_hi = 1  
        p['max_shrt'] = shorts                # Maximum shorts value  
        if o['record_max_shrt']: record(MxShrt  = p['max_shrt'])

    if risk > p['risk_hi']:  
        new_key_hi = 1  
        p['risk_hi'] = risk                   # Highest risk overall  
        if o['record_risk_hi']:  record(RiskHi  = p['risk_hi'])

    # Profit_vs_Risk returns based on max amount actually invested, long or short  
    if p['risk_hi'] != 0: # Avoid zero-divide  
        p['pvr'] = 100 * pnl / p['risk_hi']  
        ptype = 'PvRp' if o['record_pvrp'] else 'PvR'  
        if o['record_pvr'] or o['record_pvrp']: record(**{ptype: p['pvr']})

    if o['record_shorting']: record(Shorts = shorts)             # Shorts value as a positve  
    if o['record_leverage']: record(Lvrg   = c.account.leverage) # Leverage  
    if o['record_cash']    : record(Cash   = cash)               # Cash  
    if o['record_risk']    : record(Risk   = risk)  # Amount in play, maximum of shorts or cash used  
    if o['record_q_return']: record(QRet   = 100 * pf.returns)  
    if o['record_pnl']     : record(PnL    = pnl)                # Profit|Loss

    if o['logging'] and new_key_hi:  
        log.info('{}{}{}{}{}{}{}{}{}{}{}{}'.format(_minut(),  
            ' Lv '     + '%.1f' % c.account.leverage,  
            ' MxLv '   + '%.2f' % p['max_lvrg'],  
            ' QRet '   + '%.1f' % (100 * pf.returns),  
            ' PvR '    + '%.1f' % p['pvr'],  
            ' PnL '    + '%.0f' % pnl,  
            ' Cash '   + '%.0f' % cash,  
            ' CshLw '  + '%.0f' % p['cash_low'],  
            ' Shrt '   + '%.0f' % shorts,  
            ' MxShrt ' + '%.0f' % p['max_shrt'],  
            ' Risk '   + '%.0f' % risk,  
            ' RskHi '  + '%.0f' % p['risk_hi']  
        ))  
    if do_summary: _pvr(c)  
    if get_datetime() == get_environment('end'):   # Summary at end of run  
        _pvr(c) ; elapsed = (time.time() - p['begin']) / 60  # minutes  
        log.info( '{}\nRuntime {} hr {} min'.format(p['run_str'], int(elapsed / 60), '%.1f' % (elapsed % 60)))
There was a runtime error.

Update: Changed the format of price logging to 2 decimal places.

Improved I think. Fixed a minor logging error. Restricted and added to buying and selling mechanisms. It struggles the most with the last six months and 2013 through early 2014.

Clone Algorithm
332
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
#Imports
from datetime import datetime  
from pytz import timezone      
from zipline.utils import tradingcalendar
from quantopian.pipeline.data.builtin import USEquityPricing
import statsmodels.api as sm 
import numpy as np
import pandas as pd
import functools
import quantopian.pipeline.data 
import re
import scipy
import talib
import time

History = 128

def initialize(context):
    c = context

    vixUrl = 'http://www.cboe.com/publish/scheduledtask/mktdata/datahouse/vixcurrent.csv'
    vxvUrl = 'http://www.cboe.com/publish/scheduledtask/mktdata/datahouse/vix3mdailyprices.csv'
    nokoUrl = 'http://173.212.203.121/noko.csv'  
    
    fetch_csv(nokoUrl, 
              symbol='v1', 
              date_column='Date', 
              date_format='%Y-%m-%d', 
              pre_func=addFieldsVX1, 
              post_func=shift_data)
    
    fetch_csv(nokoUrl, 
              symbol='v2', 
              date_column='Date', 
              date_format='%Y-%m-%d', 
              pre_func=addFieldsVX2, 
              post_func=shift_data)

    fetch_csv(vixUrl, 
              symbol='v', 
              skiprows=1,
              date_column='Date', 
              pre_func=addFieldsVIX,
              post_func=shift_data)

    fetch_csv(vxvUrl, 
              symbol='vxv', 
              skiprows=2,
              date_column='Date', 
              pre_func=addFieldsVXV,
              post_func=shift_data)

    context.RobinHoodMargin = 7000 # real 8000 minus 1000 for withdrawals
    
    set_benchmark(symbol('SVXY'))
    context.SVXY = symbol('SVXY')
    context.TQQQ = symbol('TQQQ')
    context.UVXY = symbol('UVXY')
    context.SPYG = symbol('SPYG')

    context.ShowMaxLev = True
    context.vix = -1
    context.BigSignalShortVIX = context.BigSignalTQQQ = context.BigSignalLongVIX = False
    context.PriceSVXY = context.PriceUVXY = 0.00
    context.BoughtShortVIX = context.BoughtLongVIX = False

    #Internal stop variables
    # context.stoploss is set in handle_data
    context.stops         = {}
    context.stoppedout    = []
    c.SellAndWait = c.SVXY_DaysNotOwned = c.UVXY_DaysOwned = 0

    context.OkToAct = context.TooLateToAct = False
    
    #Used for logging
    context.last_bar = False
    context.SignalSellSVXY = False
    context.SignalBuySVXY  = False
    context.SignalBuyUVXY  = False

    # wait after market open before doing any possible orders
    schedule_function(OkToActTrigger, date_rules.every_day(), time_rules.market_open(minutes = 3))

    # too late to act because of 15 minute delay plus 10 minute cushion
    schedule_function(TooLateToActTrigger, date_rules.every_day(), time_rules.market_close(minutes = 25))

    # apparently highly successful VIX signals
    schedule_function(MoreSignals, date_rules.every_day(), time_rules.market_open(minutes = 2))

    # On Bar Close Functions
    for x in [1,3,5]:
        schedule_function(Rebalance, date_rules.every_day(), time_rules.market_open(hours=x, minutes=59))
    schedule_function(Rebalance, date_rules.every_day(), time_rules.market_close())
    
    schedule_function(my_record_vars, date_rules.every_day(), time_rules.market_close())

    #Set commission and slippage
    set_commission(commission.PerShare(cost=0.000, min_trade_cost=0.0)) 
    set_slippage(slippage.FixedSlippage(spread=0.01))
    
def OkToActTrigger(context,data): 
    context.OkToAct = True

def TooLateToActTrigger(context,data): 
    context.TooLateToAct = True

def before_trading_start(context, data):
    c = context
    c.OkToAct = c.TooLateToAct = False
    c.mx_lvrg  = 0 # daily max leverage

    if 0 == c.portfolio.positions[c.SVXY].amount:
        c.SVXY_DaysNotOwned += 1
    else:
        c.SVXY_DaysNotOwned  = 0

    if 0 < c.portfolio.positions[c.UVXY].amount:
        c.UVXY_DaysOwned += 1
    else:
        c.UVXY_DaysOwned  = 0


def TarPer(context, data, stock, TargetPercent):

    cancel_open_orders_for(context, data, stock)
    if DataCanTrade(context, data, stock):

        if 0 == TargetPercent:
            amount = context.portfolio.positions[stock].amount
            price = data.current(stock, 'price')
            order_target_percent(stock, 0.00)
            log.info('SOLD ALL {} {} Shares @ MarketPrice {:.2f}'\
            .format(stock.symbol, amount, price))
        else:
            # Always want money available to withdraw 
            # and also try to prevent margin related order rejections
            PV = context.portfolio.portfolio_value
            DoNotSpend = 2000 # 200 Cushion plus cash for withdrawals
            RhMargin = context.RobinHoodMargin # 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
            Action = "Bought" if 0 < DiffAmount else "Sold"
            if stock is context.UVXY:
                LimitPrice = price * 1.00
                order(stock, DiffAmount, style=LimitOrder(LimitPrice))
                log.info('{} {} {} Shares @ LimitPrice {:.2f}'\
                .format(Action, stock.symbol, DiffAmount, price))
            else:
                order(stock, DiffAmount)
                log.info('{} {} {} Shares @ MarketPrice {:.2f}'\
                .format(Action, stock.symbol, DiffAmount, price))

        if stock.symbol in context.stops: del context.stops[stock.symbol]

def Rebalance(context,data):
    c = context
    if not c.BigSignalShortVIX and not c.BigSignalTQQQ and not c.BigSignalLongVIX: MoreSignals(context, data)
    cancel_open_orders(context, data)
    #looking at 4 days of minutely price history
    svxy_prices = data.history(c.SVXY, "price", 1600, "1m").resample('30T',  closed='right', label='right').last().dropna()

    #convert to 2 hour:
    svxy2hr = []
    if '20:00:00+00:00' in str(svxy_prices.index[-1]) or '21:00:00+00:00' in str(svxy_prices.index[-1]):
        svxy2hr.append([svxy_prices.index[-28], svxy_prices[-28]])
        svxy2hr.append([svxy_prices.index[-27], svxy_prices[-27]])        
        svxy2hr.append([svxy_prices.index[-23], svxy_prices[-23]])
        svxy2hr.append([svxy_prices.index[-19], svxy_prices[-19]])
        svxy2hr.append([svxy_prices.index[-15], svxy_prices[-15]])
        svxy2hr.append([svxy_prices.index[-14], svxy_prices[-14]])
        svxy2hr.append([svxy_prices.index[-10], svxy_prices[-10]])
        svxy2hr.append([svxy_prices.index[-6], svxy_prices[-6]])
        svxy2hr.append([svxy_prices.index[-2], svxy_prices[-2]])
        svxy2hr.append([svxy_prices.index[-1], svxy_prices[-1]])
        c.last_bar = True
    elif '19:30:00+00:00' in str(svxy_prices.index[-1]) or '20:30:00+00:00' in str(svxy_prices.index[-1]):
        svxy2hr.append([svxy_prices.index[-31], svxy_prices[-31]])
        svxy2hr.append([svxy_prices.index[-27], svxy_prices[-27]]) 
        svxy2hr.append([svxy_prices.index[-26], svxy_prices[-26]])
        svxy2hr.append([svxy_prices.index[-22], svxy_prices[-22]])
        svxy2hr.append([svxy_prices.index[-18], svxy_prices[-18]])
        svxy2hr.append([svxy_prices.index[-14], svxy_prices[-14]]) 
        svxy2hr.append([svxy_prices.index[-13], svxy_prices[-13]])
        svxy2hr.append([svxy_prices.index[-9], svxy_prices[-9]])
        svxy2hr.append([svxy_prices.index[-5], svxy_prices[-5]])
        svxy2hr.append([svxy_prices.index[-1], svxy_prices[-1]])
        c.last_bar = False
    elif '17:30:00+00:00' in str(svxy_prices.index[-1]) or '18:30:00+00:00' in str(svxy_prices.index[-1]):
        svxy2hr.append([svxy_prices.index[-31], svxy_prices[-31]])
        svxy2hr.append([svxy_prices.index[-27], svxy_prices[-27]]) 
        svxy2hr.append([svxy_prices.index[-23], svxy_prices[-23]])
        svxy2hr.append([svxy_prices.index[-22], svxy_prices[-22]])
        svxy2hr.append([svxy_prices.index[-18], svxy_prices[-18]])
        svxy2hr.append([svxy_prices.index[-14], svxy_prices[-14]])
        svxy2hr.append([svxy_prices.index[-10], svxy_prices[-10]])
        svxy2hr.append([svxy_prices.index[-9], svxy_prices[-9]])
        svxy2hr.append([svxy_prices.index[-5], svxy_prices[-5]])
        svxy2hr.append([svxy_prices.index[-1], svxy_prices[-1]])
        c.last_bar = False
    elif '15:30:00+00:00' in str(svxy_prices.index[-1]) or '16:30:00+00:00' in str(svxy_prices.index[-1]):
        svxy2hr.append([svxy_prices.index[-31], svxy_prices[-31]])
        svxy2hr.append([svxy_prices.index[-27], svxy_prices[-27]]) 
        svxy2hr.append([svxy_prices.index[-23], svxy_prices[-23]])
        svxy2hr.append([svxy_prices.index[-19], svxy_prices[-19]])
        svxy2hr.append([svxy_prices.index[-18], svxy_prices[-18]])
        svxy2hr.append([svxy_prices.index[-14], svxy_prices[-14]])        
        svxy2hr.append([svxy_prices.index[-10], svxy_prices[-10]])
        svxy2hr.append([svxy_prices.index[-6], svxy_prices[-6]])
        svxy2hr.append([svxy_prices.index[-5], svxy_prices[-5]])
        svxy2hr.append([svxy_prices.index[-1], svxy_prices[-1]])
        c.last_bar = False
    else:
        log.error("2 HOUR CONVERSION FAILURE")
        return
    dates, vals = zip(*svxy2hr)
    s = pd.Series(vals, index=dates)
    
    rsi  = talib.RSI(s,2)
    RSI5 = talib.RSI(s,5) 

    # BUY SVXY RULE
    if rsi[-2] < 70 and rsi[-1] >= 70 and (
        0 == c.portfolio.positions[c.UVXY].amount
        or
        0 < c.UVXY_DaysOwned
    ):
        c.SignalBuySVXY  = True
        c.BoughtShortVIX = False

    # SELL SVXY RULE or maybe BUY UVXY
    if rsi[-2] > 85 and rsi[-1] <= 85:
        c.SignalSellSVXY = True
        if RSI5[-1] < 70:
            c.SignalBuyUVXY = True
            c.BoughtLongVIX = False
    # maybe BUY UVXY
    if (
        not c.SignalBuySVXY
        and
        0 == c.portfolio.positions[c.SVXY].amount
        and
        c.BigSignalLongVIX
        and
        ( 2.00 <= c.dispersion or 0 < c.SVXY_DaysNotOwned )
    ):
        c.SignalBuyUVXY = True
        c.BoughtLongVIX = False

def handle_data(context, data):
    pvr(context, data)
    c = context

    c.SellAndWait -= 1 # 1 unit is 1 minute
    c.SellAndWait = 0 if 0 > c.SellAndWait else c.SellAndWait

    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 not c.SellAndWait and c.OkToAct and not c.TooLateToAct:
        for position in c.portfolio.positions.itervalues():

            ##
            # Variable stop loss based on signals
            ##
            SVXY_Dispersion = 3.00 # SVXY 3.00 may be best overall
            UVXY_Dispersion = 3.00 # UVXY no idea what is best yet
            stoploss = 0.25 # default - probably not used
            if position.sid is c.UVXY:
                if (
                    UVXY_Dispersion <= c.dispersion
                    or
                    1 <= c.SVXY_DaysNotOwned
                ):
                    stoploss = 0.21 # 0.21 may be best
                else:
                    stoploss = 0.03 if c.BigSignalLongVIX else 0.01
            else:
                if SVXY_Dispersion <= c.dispersion:
                    stoploss = 0.25
                else:
                    if c.BigSignalTQQQ:     stoploss = 0.02
                    if c.BigSignalShortVIX: stoploss = 0.03

            if position.amount == 0:
                if position.asset.symbol in c.stops: del c.stops[position.asset.symbol]
                continue
            elif position.asset.symbol not in c.stops:
                stoploss= stoploss if position.amount > 0 else -stoploss
                c.stops[position.asset.symbol]=position.last_sale_price*(1-stoploss)
                #log.info(' ! I have added '+str(position.asset.symbol)+' to Stops @ '+str((position.last_sale_price)*(1-stoploss)))
            elif c.stops[position.asset.symbol] < position.last_sale_price*(1- stoploss) and position.amount > 0:
                c.stops[position.asset.symbol]=position.last_sale_price*(1- stoploss)
                #log.info(' ! I have updated '+str(position.asset.symbol)+'- (Long) to stop @ '+str((position.last_sale_price)*(1- stoploss)))
                # set break even
                if position.last_sale_price > position.cost_basis * 1.075:
                    if position.cost_basis > c.stops[position.asset.symbol]:
                        c.stops[position.asset.symbol] = position.cost_basis
            elif c.stops[position.asset.symbol] > position.last_sale_price and position.amount > 0:
                #sell
                log.info(' ! '+str(position.asset.symbol)+'- (Long) has hit stoploss @ '+str(position.last_sale_price))
                if get_open_orders(position.sid): cancel_open_orders_for(context, data, position.sid)
                c.stoppedout.append(position.asset.symbol)
                c.PriceSVXY = c.PriceUVXY = 0.00
                c.SignalBuySVXY = c.SignalSellSVXY = c.SignalBuyUVXY = False
                if position.sid is c.UVXY: c.SellAndWait = int(390 * 1.00)
                TarPer(context, data, position.sid, 0.00)
 
            ##
            # Sell and Wait
            ##
            SVXY_Up = 1.07 # 1.07 good 1.03 too
            UVXY_Up = 1.10 # 1.10 good 1.25 more common than 1.50
            if (
                    (position.sid is not c.UVXY and 0 < position.amount)
                    and
                    (position.last_sale_price > SVXY_Up * position.cost_basis)
            ):
                c.SellAndWait = int(390 * 1.33)
                log.warn(str(position.sid.symbol) + ' ' + str(SVXY_Up) + \
                ' growth reached SellAndWait triggered: ' + \
                str(c.SellAndWait) + ' minutes')
            elif (
                    (position.sid is c.UVXY and 0 < position.amount)
                    and
                    (position.last_sale_price > UVXY_Up * position.cost_basis)
            ):
                c.SellAndWait = int(390 * 1.66)
                log.warn(str(position.sid.symbol) + ' ' + str(UVXY_Up) + \
                ' growth reached SellAndWait triggered: ' + \
                str(c.SellAndWait) + ' minutes')

        ##
        # Sell and Wait
        ##
        if 0 < c.SellAndWait:
            c.PriceSVXY = c.PriceUVXY = 0.00
            c.SignalBuySVXY = c.SignalSellSVXY = c.SignalBuyUVXY = False
            for stock in c.portfolio.positions:
                cancel_open_orders_for(context, data, stock)
                TarPer(context, data, stock, 0.00)
        ##
        # Slippery Buy allows for some drop before buy
        ##
        else:
            SVXY_UpAfterSlip = 1.0025
            UVXY_UpAfterSlip = 1.005
            if c.SignalBuySVXY:
                if 0 < c.portfolio.positions[c.UVXY].amount:
                    c.PriceUVXY = 0.00
                    c.SignalBuyUVXY = False
                    TarPer(context, data, c.UVXY, 0.000)
                elif 0 == c.portfolio.positions[c.SVXY].amount:
                    if not c.BoughtShortVIX:
                        PriceSVXY = data.current(c.SVXY, 'price')
                        if not c.PriceSVXY: c.PriceSVXY = PriceSVXY
                        if PriceSVXY < c.PriceSVXY:
                            c.PriceSVXY = PriceSVXY
                        elif PriceSVXY > SVXY_UpAfterSlip * c.PriceSVXY:
                            c.PriceSVXY = c.PriceUVXY = 0.00
                            c.SignalBuySVXY = c.SignalSellSVXY = c.SignalBuyUVXY = False
                            c.BoughtShortVIX = True
                            TarPer(context, data, c.SVXY, 0.999)
            elif c.SignalSellSVXY:
                if c.portfolio.positions[c.SVXY].amount > 0:
                    c.PriceSVXY = c.PriceUVXY = 0.00
                    c.SignalBuySVXY = c.SignalSellSVXY = c.SignalBuyUVXY = False
                    TarPer(context, data, c.SVXY, 0)
            elif c.SignalBuyUVXY:
                if 0 < c.portfolio.positions[c.SVXY].amount:
                    c.PriceSVXY = c.PriceUVXY = 0.00
                    c.SignalBuySVXY = c.SignalSellSVXY = False
                    TarPer(context, data, c.SVXY, 0.000)
                elif 0 == c.portfolio.positions[c.UVXY].amount:
                    if not c.BoughtLongVIX:
                        PriceUVXY = data.current(c.UVXY, 'price')
                        if not c.PriceUVXY: c.PriceUVXY = PriceUVXY
                        if PriceUVXY < c.PriceUVXY:
                            c.PriceUVXY = PriceUVXY
                        elif PriceUVXY > UVXY_UpAfterSlip * c.PriceUVXY:
                            c.PriceSVXY = c.PriceUVXY = 0.00
                            c.SignalBuySVXY = c.SignalSellSVXY = c.SignalBuyUVXY = False
                            c.BoughtLongVIX = True
                            TarPer(context, data, c.UVXY, 0.999)

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 DataCanTrade(context, data, stock):

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

def cancel_open_orders(context, data):
    oo = get_open_orders()
    if len(oo) == 0:
        return
    for stock, orders in oo.iteritems():
        for order in orders:
            cancel_order(order)
            #message = 'Canceling order of {amount} shares in {stock}'
            #log.info(message.format(amount=order.amount, stock=stock))
    
def cancel_open_orders_for(context, data, security):
    oo = get_open_orders()
    if len(oo) == 0:
        return
    for stock, orders in oo.iteritems():
        for order in orders:
            if stock is security:
                if order.amount < context.portfolio.positions[stock].amount:
                    cancel_order(order)
                    #message = 'Canceling order of {amount} shares in {stock}'
                    #log.info(message.format(amount=order.amount, stock=stock))
                else: # Do NOT want to cancel stop loss order
                    #message = 'NOT Canceling stop loss order of {amount} shares in {stock}'
                    #log.info(message.format(amount=order.amount, stock=stock))
                    pass

def MoreSignals(context, data):  

    update_indices(context, data)
    target_sid = context.SVXY # default if no conditions are met
    last_vix = context.VIXprice = data.current('v', 'Close')
    last_vx1 = data.current('v1','Close')  
    last_vx2 = data.current('v2','Close')      
    last_vxv = data.current('vxv', 'Close')
    last_vix_200ma_ratio = data.current('v', '200ma Ratio')
    # Maybe use dispersion aka vix standard deviation in buying mechanism
    context.dispersion = data.current('v','20dstd')
    #context.threshold_dispersion = 0.55

    
    record(dispersion=context.dispersion)
    record(last_vixDivBy10=last_vix/10)
    # Calculating the gap between spot vix and the first month vix future
    last_ratio_v_v1 = last_vix/last_vx1

    # Calculating the contango ratio of the front and second month VIX Futures 
    last_ratio_v1_v2 = last_vx1/last_vx2

    # Blending the previous two ratios together using a weighted average
    ratio_weight = 0.7
    last_ratio = (ratio_weight*last_ratio_v_v1) + ((1-ratio_weight)*last_ratio_v1_v2) - 1
    
    vix_vxv_ratio = last_vix/last_vxv
    
    # Retrieve SPY prices for technical indicators
    prices = data.history(context.SPYG, 'open', 40, '1d')
    
    # Retrieve SPY MACD data
    macda, signal, hist = talib.MACD(prices, fastperiod=12,slowperiod=26,signalperiod=9)
    macd = macda[-1] - signal[-1]
    
    # Calculate how much vix moved the previous day
    if (context.vix <> -1) : 
        vix_ratio = last_vix/context.vix -1
    else :
        vix_ratio = 0
    context.vix = last_vix
    
    SVXY_history = data.history(context.SVXY, 'price', 2, '1d')  
    
    SVXY_ratio = SVXY_history[1]/SVXY_history[0] - 1
    
    # Setting thresholds
    #if
    threshold_vix_too_low = 10.76   # 0 
    threshold_vix_200ma_ratio_low = 0.79  # 0 
    threshold_SVXY = -0.049          # 1
    threshold_vxv_SVXY = 0.87        # 2
    threshold_uvxy = 0.049          # 3
    threshold_macd = -0.55          # 3
    threshold_vxv_uvxy = 1.3        # 4
    threshold_vix_high = 19.9       # 5
    threshold_vc_low = -0.148       # 6
    threshold_vc_high = 0.046       # 8
    threshold_vc_high_2 = -0.06     # 8
    #if
    threshold_SVXY_ratio = -0.053    # 10
    threshold_uvxy_ratio = 0.08     # 11
    # 0
    if last_vix < threshold_vix_too_low \
            and last_vix_200ma_ratio < threshold_vix_200ma_ratio_low :
        log.info('! if VIX is too low, invest in UVXY with the hope of a spike')
        target_sid = context.UVXY
    # 1        
    elif last_ratio < threshold_SVXY: # 
        log.info('! if contango is high, invest in SVXY to gain from decay')
        target_sid = context.SVXY
    
    # 2
    elif vix_vxv_ratio < threshold_vxv_SVXY: # 
        log.info('! if short term vol is low compared to mid term, invest in SVXY to gain from decay')
        target_sid = context.SVXY

    # 3
    elif last_ratio > threshold_uvxy and macd > threshold_macd: # 
        log.info('! if backwardation is high, invest in UVXY to gain from decay')
        target_sid = context.UVXY

    # 4
    elif vix_vxv_ratio > threshold_vxv_uvxy: # 
        log.info('! if short term vol is high compared to mid term, invest in UVXY to gain from growth')
        target_sid = context.UVXY

    # 5
    elif last_vix > threshold_vix_high: # 
        log.info('! if VIX is too high, invest in SVXY expecting that VIX will drop')
        target_sid = context.SVXY

    # 6
    elif vix_ratio < threshold_vc_low: # 
        log.info('! Vix down sharply, invest in SVXY expecting that futures curve gets pulled down')
        target_sid = context.SVXY

    # 7
    elif vix_ratio > threshold_vc_high: # 
        log.info('! Vix up sharply, invest in UVXY expecting that futures curve gets pulled up')
        target_sid = context.UVXY

    # 8
    elif vix_ratio > threshold_vc_high_2: #
        log.info('! have to think')
        target_sid = context.SVXY

    # 10
    if (target_sid == context.SVXY and SVXY_ratio < threshold_SVXY_ratio) : 
        log.info('! indicators say SVXY but it just dropped overnight, so go for TQQQ')
        target_sid = context.TQQQ 

    # 11
    elif (target_sid == context.UVXY and SVXY_ratio > threshold_uvxy_ratio) :
        log.info('! indicators say UVXY but it just dropped overnight, so go for TQQQ')
        target_sid = context.TQQQ
    
    context.BigSignalShortVIX = True if context.SVXY is target_sid else False
    context.BigSignalLongVIX  = True if context.UVXY is target_sid else False
    context.BigSignalTQQQ     = True if context.TQQQ is target_sid else False
    
def update_indices(context, data):
    context.fetch_failed = False
    context.vix_vals = unpack_from_data(context, data, 'v')    
    context.vxv_vals = unpack_from_data(context, data, 'vxv')  
    context.vx1_vals = unpack_from_data(context, data, 'v1')
    context.vx2_vals = unpack_from_data(context, data, 'v2')

def fix_close(df,closeField):
    df = df.rename(columns={closeField:'Close'})
    # remove spurious asterisks
    df['Date'] = df['Date'].apply(lambda dt: re.sub('\*','',dt))
    # convert date column to timestamps
    df['Date'] = df['Date'].apply(lambda dt: pd.Timestamp(datetime.strptime(dt,'%m/%d/%Y')))
    df = df.sort_values(by='Date', ascending=True)
    return df

def fix_closeVX(df,closeField):
    df = df.rename(columns={closeField:'Close'})
    # remove spurious asterisks
    df['Date'] = df['Date'].apply(lambda dt: re.sub('\*','',dt))
    # convert date column to timestamps
    df['Date'] = df['Date'].apply(lambda dt: pd.Timestamp(datetime.strptime(dt,'%Y-%m-%d')))
    df = df.sort_values(by='Date', ascending=True)
    return df


def subsequent_trading_date(date):
    tdays = tradingcalendar.trading_days
    last_date = pd.to_datetime(date)
    last_dt = tradingcalendar.canonicalize_datetime(last_date)
    next_dt = tdays[tdays.searchsorted(last_dt) + 1]
    return next_dt

def add_last_bar(df):
    last_date = df.index[-1]
    subsequent_date = subsequent_trading_date(last_date)
    blank_row = pd.Series({}, index=df.columns, name=subsequent_date)
    # add today, and shift all previous data up to today. This 
    # should result in the same data frames as in backtest
    df = df.append(blank_row).shift(1).dropna(how='all')
    return df

def shift_data(df):
    df = add_last_bar(df)
    df.fillna(method='ffill') 
    df['PrevCloses'] = my_rolling_apply_series(df['Close'], to_csv_str, History)
    dates = pd.Series(df.index)
    dates.index = df.index
    df['PrevDates'] = my_rolling_apply_series(dates, to_csv_str, History)
    return df

def unpack_from_data(context, data, sym):
    try:
        v = data.current(sym, 'PrevCloses')
        i = data.current(sym, 'PrevDates')
        return from_csv_strs(i,v,True).apply(float)
    except:
        log.warn("Unable to unpack historical {s} data.".format(s=sym))
        context.fetch_failed = True

def addFieldsVIX(df):
    df = fix_close(df,'VIX Close')
    df['200ma'] = df['Close'].rolling(200).mean()
    df['200ma Ratio'] = df['Close'] / df['200ma']
    df['20dstd'] =  df['Close'].rolling(20).std()

    return df

def addFieldsVXV(df):
    df.rename(columns={'Unnamed: 0': 'Date'}, inplace=True)
    df = fix_close(df,'CLOSE')
    return df

def addFieldsVX1(df):
    df = fix_closeVX(df,'F1')
    return df

def addFieldsVX2(df):
    df = fix_closeVX(df,'F2')
    return df

# convert a series of values to a comma-separated string of said values
def to_csv_str(s):
    return functools.reduce(lambda x,y: x+','+y, pd.Series(s).apply(str))

# a specific instance of rolling apply, for Series of any type (not just numeric,
# ala pandas.rolling_apply), where the index of the series is set to the indices
# of the last elements of each subset
def my_rolling_apply_series(s_in, f, n):
    s_out = pd.Series([f(s_in[i:i+n]) for i in range(0,len(s_in)-(n-1))]) 
    s_out.index = s_in.index[n-1:]
    return s_out

# reconstitutes a Series from two csv-encoded strings, one of the index, one of the values
def from_csv_strs(x, y, idx_is_date):
    s = pd.Series(y.split(','),index=x.split(','))
    if (idx_is_date):
        s.index = s.index.map(lambda x: pd.Timestamp(x))
    return s

def pvr(context, data):
    ''' Custom chart and/or logging of profit_vs_risk returns and related information from https://www.quantopian.com/posts/pvr#569784bda73e9bf2b7000180
    '''  
    #import time  
    #from datetime import datetime  
    #from pytz import timezone      # Python will only do once, makes this portable.  
                                   #   Move to top of algo for better efficiency.  
    c = context  # Brevity is the soul of wit -- Shakespeare [for readability]  
    if 'pvr' not in c:

        # For real money, you can modify this to total cash input minus any withdrawals  
        manual_cash = c.portfolio.starting_cash  
        time_zone   = 'US/Central'   # Optionally change to your own time zone for wall clock time

        c.pvr = {  
            'options': {  
                # # # # # # # # # #  Options  # # # # # # # # # #  
                'logging'         : 0,    # Info to logging window with some new maximums  
                'log_summary'     : 126,  # Summary every x days. 252/yr

                'record_pvr'      : 0,    # Profit vs Risk returns (percentage)  
                'record_pvrp'     : 0,    # PvR (p)roportional neg cash vs portfolio value  
                'record_cash'     : 1,    # Cash available  
                'record_max_lvrg' : 0,    # Maximum leverage encountered  
                'record_risk_hi'  : 0,    # Highest risk overall  
                'record_shorting' : 0,    # Total value of any shorts  
                'record_max_shrt' : 0,    # Max value of shorting total  
                'record_cash_low' : 0,    # Any new lowest cash level  
                'record_q_return' : 0,    # Quantopian returns (percentage)  
                'record_pnl'      : 0,    # Profit-n-Loss  
                'record_risk'     : 1,    # Risked, max cash spent or shorts beyond longs+cash  
                'record_leverage' : 0,    # End of day leverage (context.account.leverage)  
                # All records are end-of-day or the last data sent to chart during any day.  
                # The way the chart operates, only the last value of the day will be seen.  
                # # # # # # # # #  End options  # # # # # # # # #  
            },  
            'pvr'        : 0,      # Profit vs Risk returns based on maximum spent  
            'cagr'       : 0,  
            'max_lvrg'   : 0,  
            'max_shrt'   : 0,  
            'risk_hi'    : 0,  
            'days'       : 0.0,  
            'date_prv'   : '',  
            'date_end'   : get_environment('end').date(),  
            'cash_low'   : manual_cash,  
            'cash'       : manual_cash,  
            'start'      : manual_cash,  
            'tz'         : time_zone,  
            'begin'      : time.time(),  # For run time  
            'run_str'    : '{} to {}  ${}  {} {}'.format(get_environment('start').date(), get_environment('end').date(), int(manual_cash), datetime.now(timezone(time_zone)).strftime("%Y-%m-%d %H:%M"), time_zone)  
        }  
        if c.pvr['options']['record_pvrp']: c.pvr['options']['record_pvr'] = 0 # if pvrp is active, straight pvr is off  
        if get_environment('arena') not in ['backtest', 'live']: c.pvr['options']['log_summary'] = 1 # Every day when real money  
        log.info(c.pvr['run_str'])  
    p = c.pvr ; o = c.pvr['options'] ; pf = c.portfolio ; pnl = pf.portfolio_value - p['start']  
    def _pvr(c):  
        p['cagr'] = ((pf.portfolio_value / p['start']) ** (1 / (p['days'] / 252.))) - 1  
        ptype = 'PvR' if o['record_pvr'] else 'PvRp'  
        log.info('{} {} %/day   cagr {}   Portfolio value {}   PnL {}'.format(ptype, '%.4f' % (p['pvr'] / p['days']), '%.3f' % p['cagr'], '%.0f' % pf.portfolio_value, '%.0f' % pnl))  
        log.info('  Profited {} on {} activated/transacted for PvR of {}%'.format('%.0f' % pnl, '%.0f' % p['risk_hi'], '%.1f' % p['pvr']))  
        log.info('  QRet {} PvR {} CshLw {} MxLv {} RskHi {} MxShrt {}'.format('%.2f' % (100 * pf.returns), '%.2f' % p['pvr'], '%.0f' % p['cash_low'], '%.2f' % p['max_lvrg'], '%.0f' % p['risk_hi'], '%.0f' % p['max_shrt']))  
    def _minut():  
        dt = get_datetime().astimezone(timezone(p['tz']))  
        return str((dt.hour * 60) + dt.minute - 570).rjust(3)  # (-570 = 9:31a)  
    date = get_datetime().date()  
    if p['date_prv'] != date:  
        p['date_prv'] = date  
        p['days'] += 1.0  
    do_summary = 0  
    if o['log_summary'] and p['days'] % o['log_summary'] == 0 and _minut() == '100':  
        do_summary = 1              # Log summary every x days  
    if do_summary or date == p['date_end']:  
        p['cash'] = pf.cash  
    elif p['cash'] == pf.cash and not o['logging']: return  # for speed

    shorts = sum([z.amount * z.last_sale_price for s, z in pf.positions.items() if z.amount < 0])  
    new_key_hi = 0                  # To trigger logging if on.  
    cash       = pf.cash  
    cash_dip   = int(max(0, p['start'] - cash))  
    risk       = int(max(cash_dip, -shorts))

    if o['record_pvrp'] and cash < 0:   # Let negative cash ding less when portfolio is up.  
        cash_dip = int(max(0, cash_dip * p['start'] / pf.portfolio_value))  
        # Imagine: Start with 10, grows to 1000, goes negative to -10, should not be 200% risk.

    if int(cash) < p['cash_low']:             # New cash low  
        new_key_hi = 1  
        p['cash_low'] = int(cash)             # Lowest cash level hit  
        if o['record_cash_low']: record(CashLow = p['cash_low'])

    if c.account.leverage > p['max_lvrg']:  
        new_key_hi = 1  
        p['max_lvrg'] = c.account.leverage    # Maximum intraday leverage  
        if o['record_max_lvrg']: record(MaxLv   = p['max_lvrg'])

    if shorts < p['max_shrt']:  
        new_key_hi = 1  
        p['max_shrt'] = shorts                # Maximum shorts value  
        if o['record_max_shrt']: record(MxShrt  = p['max_shrt'])

    if risk > p['risk_hi']:  
        new_key_hi = 1  
        p['risk_hi'] = risk                   # Highest risk overall  
        if o['record_risk_hi']:  record(RiskHi  = p['risk_hi'])

    # Profit_vs_Risk returns based on max amount actually invested, long or short  
    if p['risk_hi'] != 0: # Avoid zero-divide  
        p['pvr'] = 100 * pnl / p['risk_hi']  
        ptype = 'PvRp' if o['record_pvrp'] else 'PvR'  
        if o['record_pvr'] or o['record_pvrp']: record(**{ptype: p['pvr']})

    if o['record_shorting']: record(Shorts = shorts)             # Shorts value as a positve  
    if o['record_leverage']: record(Lvrg   = c.account.leverage) # Leverage  
    if o['record_cash']    : record(Cash   = cash)               # Cash  
    if o['record_risk']    : record(Risk   = risk)  # Amount in play, maximum of shorts or cash used  
    if o['record_q_return']: record(QRet   = 100 * pf.returns)  
    if o['record_pnl']     : record(PnL    = pnl)                # Profit|Loss

    if o['logging'] and new_key_hi:  
        log.info('{}{}{}{}{}{}{}{}{}{}{}{}'.format(_minut(),  
            ' Lv '     + '%.1f' % c.account.leverage,  
            ' MxLv '   + '%.2f' % p['max_lvrg'],  
            ' QRet '   + '%.1f' % (100 * pf.returns),  
            ' PvR '    + '%.1f' % p['pvr'],  
            ' PnL '    + '%.0f' % pnl,  
            ' Cash '   + '%.0f' % cash,  
            ' CshLw '  + '%.0f' % p['cash_low'],  
            ' Shrt '   + '%.0f' % shorts,  
            ' MxShrt ' + '%.0f' % p['max_shrt'],  
            ' Risk '   + '%.0f' % risk,  
            ' RskHi '  + '%.0f' % p['risk_hi']  
        ))  
    if do_summary: _pvr(c)  
    if get_datetime() == get_environment('end'):   # Summary at end of run  
        _pvr(c) ; elapsed = (time.time() - p['begin']) / 60  # minutes  
        log.info( '{}\nRuntime {} hr {} min'.format(p['run_str'], int(elapsed / 60), '%.1f' % (elapsed % 60)))
There was a runtime error.

Ever so slight improvement. Maybe you like. I tried to extract more profit from the "HaveToThink" #8 signal in MoreSignals by adding a conditional UVXY buy. Normally it only suggests SVXY for #8.

Clone Algorithm
332
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
#Imports
from datetime import datetime  
from pytz import timezone      
from zipline.utils import tradingcalendar
from quantopian.pipeline.data.builtin import USEquityPricing
import statsmodels.api as sm 
import numpy as np
import pandas as pd
import functools
import quantopian.pipeline.data 
import re
import scipy
import talib
import time

History = 128

def initialize(context):
    c = context

    vixUrl = 'http://www.cboe.com/publish/scheduledtask/mktdata/datahouse/vixcurrent.csv'
    vxvUrl = 'http://www.cboe.com/publish/scheduledtask/mktdata/datahouse/vix3mdailyprices.csv'
    nokoUrl = 'http://173.212.203.121/noko.csv'  
    
    fetch_csv(nokoUrl, 
              symbol='v1', 
              date_column='Date', 
              date_format='%Y-%m-%d', 
              pre_func=addFieldsVX1, 
              post_func=shift_data)
    
    fetch_csv(nokoUrl, 
              symbol='v2', 
              date_column='Date', 
              date_format='%Y-%m-%d', 
              pre_func=addFieldsVX2, 
              post_func=shift_data)

    fetch_csv(vixUrl, 
              symbol='v', 
              skiprows=1,
              date_column='Date', 
              pre_func=addFieldsVIX,
              post_func=shift_data)

    fetch_csv(vxvUrl, 
              symbol='vxv', 
              skiprows=2,
              date_column='Date', 
              pre_func=addFieldsVXV,
              post_func=shift_data)

    context.RobinHoodMargin = 7000 # real 8000 minus 1000 for withdrawals
    
    set_benchmark(symbol('SVXY'))
    context.SVXY = symbol('SVXY')
    context.TQQQ = symbol('TQQQ')
    context.UVXY = symbol('UVXY')
    context.SPYG = symbol('SPYG')

    context.ShowMaxLev = True
    context.vix = -1
    context.BigSignalShortVIX = context.BigSignalTQQQ = context.BigSignalLongVIX = False
    context.PriceSVXY = context.PriceUVXY = 0.00
    context.BoughtShortVIX = context.BoughtLongVIX = False

    #Internal stop variables
    # context.stoploss is set in handle_data
    context.stops         = {}
    context.stoppedout    = []
    c.SellAndWait = c.SVXY_DaysNotOwned = c.UVXY_DaysOwned = 0
    c.HaveToThink = False
    c.DaysHaveToThink = 0

    context.OkToAct = context.TooLateToAct = False
    
    #Used for logging
    context.last_bar = False
    context.SignalSellSVXY = False
    context.SignalBuySVXY  = False
    context.SignalBuyUVXY  = False

    # wait after market open before doing any possible orders
    schedule_function(OkToActTrigger, date_rules.every_day(), time_rules.market_open(minutes = 3))

    # too late to act because of 15 minute delay plus 10 minute cushion
    schedule_function(TooLateToActTrigger, date_rules.every_day(), time_rules.market_close(minutes = 25))

    # apparently highly successful VIX signals
    schedule_function(MoreSignals, date_rules.every_day(), time_rules.market_open(minutes = 2))

    # On Bar Close Functions
    for x in [1,3,5]:
        schedule_function(Rebalance, date_rules.every_day(), time_rules.market_open(hours=x, minutes=59))
    schedule_function(Rebalance, date_rules.every_day(), time_rules.market_close())
    
    schedule_function(my_record_vars, date_rules.every_day(), time_rules.market_close())

    #Set commission and slippage
    set_commission(commission.PerShare(cost=0.000, min_trade_cost=0.0)) 
    set_slippage(slippage.FixedSlippage(spread=0.01))
    
def OkToActTrigger(context,data): 
    context.OkToAct = True

def TooLateToActTrigger(context,data): 
    context.TooLateToAct = True

def before_trading_start(context, data):
    c = context
    c.OkToAct = c.TooLateToAct = False
    c.mx_lvrg  = 0 # daily max leverage

    if 0 == c.portfolio.positions[c.SVXY].amount:
        c.SVXY_DaysNotOwned += 1
    else:
        c.SVXY_DaysNotOwned  = 0

    if 0 < c.portfolio.positions[c.UVXY].amount:
        c.UVXY_DaysOwned += 1
    else:
        c.UVXY_DaysOwned  = 0

    if c.HaveToThink:
        c.DaysHaveToThink += 1
    else:
        c.DaysHaveToThink  = 0
    c.HaveToThink = False

def TarPer(context, data, stock, TargetPercent):

    cancel_open_orders_for(context, data, stock)
    if DataCanTrade(context, data, stock):

        if 0 == TargetPercent:
            amount = context.portfolio.positions[stock].amount
            price = data.current(stock, 'price')
            order_target_percent(stock, 0.00)
            log.info('SOLD ALL {} {} Shares @ MarketPrice {:.2f}'\
            .format(stock.symbol, amount, price))
        else:
            # Always want money available to withdraw 
            # and also try to prevent margin related order rejections
            PV = context.portfolio.portfolio_value
            DoNotSpend = 2000 # 200 Cushion plus cash for withdrawals
            RhMargin = context.RobinHoodMargin # 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
            Action = "Bought" if 0 < DiffAmount else "Sold"
            if stock is context.UVXY:
                LimitPrice = price * 1.00
                order(stock, DiffAmount, style=LimitOrder(LimitPrice))
                log.info('{} {} {} Shares @ LimitPrice {:.2f}'\
                .format(Action, stock.symbol, DiffAmount, LimitPrice))
            else:
                order(stock, DiffAmount)
                log.info('{} {} {} Shares @ MarketPrice {:.2f}'\
                .format(Action, stock.symbol, DiffAmount, price))

        if stock.symbol in context.stops: del context.stops[stock.symbol]

def Rebalance(context,data):
    c = context
    if not c.BigSignalShortVIX and not c.BigSignalTQQQ and not c.BigSignalLongVIX: MoreSignals(context, data)
    cancel_open_orders(context, data)
    #looking at 4 days of minutely price history
    svxy_prices = data.history(c.SVXY, "price", 1600, "1m").resample('30T',  closed='right', label='right').last().dropna()

    #convert to 2 hour:
    svxy2hr = []
    if '20:00:00+00:00' in str(svxy_prices.index[-1]) or '21:00:00+00:00' in str(svxy_prices.index[-1]):
        svxy2hr.append([svxy_prices.index[-28], svxy_prices[-28]])
        svxy2hr.append([svxy_prices.index[-27], svxy_prices[-27]])        
        svxy2hr.append([svxy_prices.index[-23], svxy_prices[-23]])
        svxy2hr.append([svxy_prices.index[-19], svxy_prices[-19]])
        svxy2hr.append([svxy_prices.index[-15], svxy_prices[-15]])
        svxy2hr.append([svxy_prices.index[-14], svxy_prices[-14]])
        svxy2hr.append([svxy_prices.index[-10], svxy_prices[-10]])
        svxy2hr.append([svxy_prices.index[-6], svxy_prices[-6]])
        svxy2hr.append([svxy_prices.index[-2], svxy_prices[-2]])
        svxy2hr.append([svxy_prices.index[-1], svxy_prices[-1]])
        c.last_bar = True
    elif '19:30:00+00:00' in str(svxy_prices.index[-1]) or '20:30:00+00:00' in str(svxy_prices.index[-1]):
        svxy2hr.append([svxy_prices.index[-31], svxy_prices[-31]])
        svxy2hr.append([svxy_prices.index[-27], svxy_prices[-27]]) 
        svxy2hr.append([svxy_prices.index[-26], svxy_prices[-26]])
        svxy2hr.append([svxy_prices.index[-22], svxy_prices[-22]])
        svxy2hr.append([svxy_prices.index[-18], svxy_prices[-18]])
        svxy2hr.append([svxy_prices.index[-14], svxy_prices[-14]]) 
        svxy2hr.append([svxy_prices.index[-13], svxy_prices[-13]])
        svxy2hr.append([svxy_prices.index[-9], svxy_prices[-9]])
        svxy2hr.append([svxy_prices.index[-5], svxy_prices[-5]])
        svxy2hr.append([svxy_prices.index[-1], svxy_prices[-1]])
        c.last_bar = False
    elif '17:30:00+00:00' in str(svxy_prices.index[-1]) or '18:30:00+00:00' in str(svxy_prices.index[-1]):
        svxy2hr.append([svxy_prices.index[-31], svxy_prices[-31]])
        svxy2hr.append([svxy_prices.index[-27], svxy_prices[-27]]) 
        svxy2hr.append([svxy_prices.index[-23], svxy_prices[-23]])
        svxy2hr.append([svxy_prices.index[-22], svxy_prices[-22]])
        svxy2hr.append([svxy_prices.index[-18], svxy_prices[-18]])
        svxy2hr.append([svxy_prices.index[-14], svxy_prices[-14]])
        svxy2hr.append([svxy_prices.index[-10], svxy_prices[-10]])
        svxy2hr.append([svxy_prices.index[-9], svxy_prices[-9]])
        svxy2hr.append([svxy_prices.index[-5], svxy_prices[-5]])
        svxy2hr.append([svxy_prices.index[-1], svxy_prices[-1]])
        c.last_bar = False
    elif '15:30:00+00:00' in str(svxy_prices.index[-1]) or '16:30:00+00:00' in str(svxy_prices.index[-1]):
        svxy2hr.append([svxy_prices.index[-31], svxy_prices[-31]])
        svxy2hr.append([svxy_prices.index[-27], svxy_prices[-27]]) 
        svxy2hr.append([svxy_prices.index[-23], svxy_prices[-23]])
        svxy2hr.append([svxy_prices.index[-19], svxy_prices[-19]])
        svxy2hr.append([svxy_prices.index[-18], svxy_prices[-18]])
        svxy2hr.append([svxy_prices.index[-14], svxy_prices[-14]])        
        svxy2hr.append([svxy_prices.index[-10], svxy_prices[-10]])
        svxy2hr.append([svxy_prices.index[-6], svxy_prices[-6]])
        svxy2hr.append([svxy_prices.index[-5], svxy_prices[-5]])
        svxy2hr.append([svxy_prices.index[-1], svxy_prices[-1]])
        c.last_bar = False
    else:
        log.error("2 HOUR CONVERSION FAILURE")
        return
    dates, vals = zip(*svxy2hr)
    s = pd.Series(vals, index=dates)
    
    rsi  = talib.RSI(s,2)
    RSI5 = talib.RSI(s,5) 

    # BUY SVXY RULE
    if rsi[-2] < 70 and rsi[-1] >= 70 and (
        0 == c.portfolio.positions[c.UVXY].amount
        or
        0 < c.UVXY_DaysOwned
    ):
        c.SignalBuySVXY  = True
        c.BoughtShortVIX = False

    # SELL SVXY RULE or maybe BUY UVXY
    if rsi[-2] > 85 and rsi[-1] <= 85:
        c.SignalSellSVXY = True
        if RSI5[-1] < 70:
            c.SignalBuyUVXY = True
            c.BoughtLongVIX = False
    # maybe BUY UVXY
    if (
        not c.SignalBuySVXY
        and
        0 == c.portfolio.positions[c.SVXY].amount
        and
        ( c.BigSignalLongVIX or 1 < c.DaysHaveToThink )
        and
        ( 2.00 <= c.dispersion or 0 < c.SVXY_DaysNotOwned )
    ):
        c.SignalBuyUVXY = True
        c.BoughtLongVIX = False

def handle_data(context, data):
    pvr(context, data)
    c = context

    c.SellAndWait -= 1 # 1 unit is 1 minute
    c.SellAndWait = 0 if 0 > c.SellAndWait else c.SellAndWait

    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 not c.SellAndWait and c.OkToAct and not c.TooLateToAct:
        for position in c.portfolio.positions.itervalues():

            ##
            # Variable stop loss based on signals
            ##
            SVXY_Dispersion = 3.00 # SVXY 3.00 may be best overall
            UVXY_Dispersion = 3.00 # UVXY no idea what is best yet
            stoploss = 0.25 # default - probably not used
            if position.sid is c.UVXY:
                if (
                    UVXY_Dispersion <= c.dispersion
                    or
                    1 <= c.SVXY_DaysNotOwned
                ):
                    stoploss = 0.21 # 0.21 may be best
                else:
                    stoploss = 0.03 if c.BigSignalLongVIX else 0.01
            else:
                if SVXY_Dispersion <= c.dispersion:
                    stoploss = 0.25
                else:
                    if c.BigSignalTQQQ:     stoploss = 0.02
                    if c.BigSignalShortVIX: stoploss = 0.03

            if position.amount == 0:
                if position.asset.symbol in c.stops: del c.stops[position.asset.symbol]
                continue
            elif position.asset.symbol not in c.stops:
                stoploss= stoploss if position.amount > 0 else -stoploss
                c.stops[position.asset.symbol]=position.last_sale_price*(1-stoploss)
                #log.info(' ! I have added '+str(position.asset.symbol)+' to Stops @ '+str((position.last_sale_price)*(1-stoploss)))
            elif c.stops[position.asset.symbol] < position.last_sale_price*(1- stoploss) and position.amount > 0:
                c.stops[position.asset.symbol]=position.last_sale_price*(1- stoploss)
                #log.info(' ! I have updated '+str(position.asset.symbol)+'- (Long) to stop @ '+str((position.last_sale_price)*(1- stoploss)))
                # set break even
                if position.last_sale_price > position.cost_basis * 1.075:
                    if position.cost_basis > c.stops[position.asset.symbol]:
                        c.stops[position.asset.symbol] = position.cost_basis
            elif c.stops[position.asset.symbol] > position.last_sale_price and position.amount > 0:
                #sell
                log.info(' ! '+str(position.asset.symbol)+'- (Long) has hit stoploss @ '+str(position.last_sale_price))
                if get_open_orders(position.sid): cancel_open_orders_for(context, data, position.sid)
                c.stoppedout.append(position.asset.symbol)
                c.PriceSVXY = c.PriceUVXY = 0.00
                c.SignalBuySVXY = c.SignalSellSVXY = c.SignalBuyUVXY = False
                if position.sid is c.UVXY: c.SellAndWait = int(390 * 1.00)
                TarPer(context, data, position.sid, 0.00)
 
            ##
            # Sell and Wait
            ##
            SVXY_Up = 1.07 # 1.07 good 1.03 too
            UVXY_Up = 1.10 # 1.10 good 1.25 more common than 1.50
            if (
                    (position.sid is not c.UVXY and 0 < position.amount)
                    and
                    (position.last_sale_price > SVXY_Up * position.cost_basis)
            ):
                c.SellAndWait = int(390 * 1.33)
                log.warn(str(position.sid.symbol) + ' ' + str(SVXY_Up) + \
                ' growth reached SellAndWait triggered: ' + \
                str(c.SellAndWait) + ' minutes')
            elif (
                    (position.sid is c.UVXY and 0 < position.amount)
                    and
                    (position.last_sale_price > UVXY_Up * position.cost_basis)
            ):
                c.SellAndWait = int(390 * 1.66)
                log.warn(str(position.sid.symbol) + ' ' + str(UVXY_Up) + \
                ' growth reached SellAndWait triggered: ' + \
                str(c.SellAndWait) + ' minutes')

        ##
        # Sell and Wait
        ##
        if 0 < c.SellAndWait:
            c.PriceSVXY = c.PriceUVXY = 0.00
            c.SignalBuySVXY = c.SignalSellSVXY = c.SignalBuyUVXY = False
            for stock in c.portfolio.positions:
                cancel_open_orders_for(context, data, stock)
                TarPer(context, data, stock, 0.00)
        ##
        # Slippery Buy allows for some drop before buy
        ##
        else:
            SVXY_UpAfterSlip = 1.0025
            UVXY_UpAfterSlip = 1.005
            if c.SignalBuySVXY:
                if 0 < c.portfolio.positions[c.UVXY].amount:
                    c.PriceUVXY = 0.00
                    c.SignalBuyUVXY = False
                    TarPer(context, data, c.UVXY, 0.000)
                elif 0 == c.portfolio.positions[c.SVXY].amount:
                    if not c.BoughtShortVIX:
                        PriceSVXY = data.current(c.SVXY, 'price')
                        if not c.PriceSVXY: c.PriceSVXY = PriceSVXY
                        if PriceSVXY < c.PriceSVXY:
                            c.PriceSVXY = PriceSVXY
                        elif PriceSVXY > SVXY_UpAfterSlip * c.PriceSVXY:
                            c.PriceSVXY = c.PriceUVXY = 0.00
                            c.SignalBuySVXY = c.SignalSellSVXY = c.SignalBuyUVXY = False
                            c.BoughtShortVIX = True
                            TarPer(context, data, c.SVXY, 0.999)
            elif c.SignalSellSVXY:
                if c.portfolio.positions[c.SVXY].amount > 0:
                    c.PriceSVXY = c.PriceUVXY = 0.00
                    c.SignalBuySVXY = c.SignalSellSVXY = c.SignalBuyUVXY = False
                    TarPer(context, data, c.SVXY, 0)
            elif c.SignalBuyUVXY:
                if 0 < c.portfolio.positions[c.SVXY].amount:
                    c.PriceSVXY = c.PriceUVXY = 0.00
                    c.SignalBuySVXY = c.SignalSellSVXY = False
                    TarPer(context, data, c.SVXY, 0.000)
                elif 0 == c.portfolio.positions[c.UVXY].amount:
                    if not c.BoughtLongVIX:
                        PriceUVXY = data.current(c.UVXY, 'price')
                        if not c.PriceUVXY: c.PriceUVXY = PriceUVXY
                        if PriceUVXY < c.PriceUVXY:
                            c.PriceUVXY = PriceUVXY
                        elif PriceUVXY > UVXY_UpAfterSlip * c.PriceUVXY:
                            c.PriceSVXY = c.PriceUVXY = 0.00
                            c.SignalBuySVXY = c.SignalSellSVXY = c.SignalBuyUVXY = False
                            c.BoughtLongVIX = True
                            TarPer(context, data, c.UVXY, 0.999)

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 DataCanTrade(context, data, stock):

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

def cancel_open_orders(context, data):
    oo = get_open_orders()
    if len(oo) == 0:
        return
    for stock, orders in oo.iteritems():
        for order in orders:
            cancel_order(order)
            #message = 'Canceling order of {amount} shares in {stock}'
            #log.info(message.format(amount=order.amount, stock=stock))
    
def cancel_open_orders_for(context, data, security):
    oo = get_open_orders()
    if len(oo) == 0:
        return
    for stock, orders in oo.iteritems():
        for order in orders:
            if stock is security:
                if order.amount < context.portfolio.positions[stock].amount:
                    cancel_order(order)
                    #message = 'Canceling order of {amount} shares in {stock}'
                    #log.info(message.format(amount=order.amount, stock=stock))
                else: # Do NOT want to cancel stop loss order
                    #message = 'NOT Canceling stop loss order of {amount} shares in {stock}'
                    #log.info(message.format(amount=order.amount, stock=stock))
                    pass

def MoreSignals(context, data):
    c = context

    update_indices(context, data)
    target_sid = context.SVXY # default if no conditions are met
    last_vix = context.VIXprice = data.current('v', 'Close')
    last_vx1 = data.current('v1','Close')  
    last_vx2 = data.current('v2','Close')      
    last_vxv = data.current('vxv', 'Close')
    last_vix_200ma_ratio = data.current('v', '200ma Ratio')
    # Maybe use dispersion aka vix standard deviation in buying mechanism
    context.dispersion = data.current('v','20dstd')
    #context.threshold_dispersion = 0.55

    
    record(dispersion=context.dispersion)
    record(last_vixDivBy10=last_vix/10)
    # Calculating the gap between spot vix and the first month vix future
    last_ratio_v_v1 = last_vix/last_vx1

    # Calculating the contango ratio of the front and second month VIX Futures 
    last_ratio_v1_v2 = last_vx1/last_vx2

    # Blending the previous two ratios together using a weighted average
    ratio_weight = 0.7
    last_ratio = (ratio_weight*last_ratio_v_v1) + ((1-ratio_weight)*last_ratio_v1_v2) - 1
    
    vix_vxv_ratio = last_vix/last_vxv
    
    # Retrieve SPY prices for technical indicators
    prices = data.history(context.SPYG, 'open', 40, '1d')
    
    # Retrieve SPY MACD data
    macda, signal, hist = talib.MACD(prices, fastperiod=12,slowperiod=26,signalperiod=9)
    macd = macda[-1] - signal[-1]
    
    # Calculate how much vix moved the previous day
    if (context.vix <> -1) : 
        vix_ratio = last_vix/context.vix -1
    else :
        vix_ratio = 0
    context.vix = last_vix
    
    SVXY_history = data.history(context.SVXY, 'price', 2, '1d')  
    
    SVXY_ratio = SVXY_history[1]/SVXY_history[0] - 1
    
    # Setting thresholds
    #if
    threshold_vix_too_low = 10.76   # 0 
    threshold_vix_200ma_ratio_low = 0.79  # 0 
    threshold_SVXY = -0.049          # 1
    threshold_vxv_SVXY = 0.87        # 2
    threshold_uvxy = 0.049          # 3
    threshold_macd = -0.55          # 3
    threshold_vxv_uvxy = 1.3        # 4
    threshold_vix_high = 19.9       # 5
    threshold_vc_low = -0.148       # 6
    threshold_vc_high = 0.046       # 8
    threshold_vc_high_2 = -0.06     # 8 HaveToThink
    #if
    threshold_SVXY_ratio = -0.053    # 10
    threshold_uvxy_ratio = 0.08     # 11
    # 0
    if last_vix < threshold_vix_too_low \
            and last_vix_200ma_ratio < threshold_vix_200ma_ratio_low :
        log.info('! if VIX is too low, invest in UVXY with the hope of a spike')
        target_sid = context.UVXY
    # 1        
    elif last_ratio < threshold_SVXY: # 
        log.info('! if contango is high, invest in SVXY to gain from decay')
        target_sid = context.SVXY
    
    # 2
    elif vix_vxv_ratio < threshold_vxv_SVXY: # 
        log.info('! if short term vol is low compared to mid term, invest in SVXY to gain from decay')
        target_sid = context.SVXY

    # 3
    elif last_ratio > threshold_uvxy and macd > threshold_macd: # 
        log.info('! if backwardation is high, invest in UVXY to gain from decay')
        target_sid = context.UVXY

    # 4
    elif vix_vxv_ratio > threshold_vxv_uvxy: # 
        log.info('! if short term vol is high compared to mid term, invest in UVXY to gain from growth')
        target_sid = context.UVXY

    # 5
    elif last_vix > threshold_vix_high: # 
        log.info('! if VIX is too high, invest in SVXY expecting that VIX will drop')
        target_sid = context.SVXY

    # 6
    elif vix_ratio < threshold_vc_low: # 
        log.info('! Vix down sharply, invest in SVXY expecting that futures curve gets pulled down')
        target_sid = context.SVXY

    # 7
    elif vix_ratio > threshold_vc_high: # 
        log.info('! Vix up sharply, invest in UVXY expecting that futures curve gets pulled up')
        target_sid = context.UVXY

    # 8
    elif vix_ratio > threshold_vc_high_2: #
        log.info('! have to think')
        target_sid = context.SVXY
        c.HaveToThink = True

    # 10
    if (target_sid == context.SVXY and SVXY_ratio < threshold_SVXY_ratio) : 
        log.info('! indicators say SVXY but it just dropped overnight, so go for TQQQ')
        target_sid = context.TQQQ 

    # 11
    elif (target_sid == context.UVXY and SVXY_ratio > threshold_uvxy_ratio) :
        log.info('! indicators say UVXY but it just dropped overnight, so go for TQQQ')
        target_sid = context.TQQQ
    
    context.BigSignalShortVIX = True if context.SVXY is target_sid else False
    context.BigSignalLongVIX  = True if context.UVXY is target_sid else False
    context.BigSignalTQQQ     = True if context.TQQQ is target_sid else False
    
def update_indices(context, data):
    context.fetch_failed = False
    context.vix_vals = unpack_from_data(context, data, 'v')    
    context.vxv_vals = unpack_from_data(context, data, 'vxv')  
    context.vx1_vals = unpack_from_data(context, data, 'v1')
    context.vx2_vals = unpack_from_data(context, data, 'v2')

def fix_close(df,closeField):
    df = df.rename(columns={closeField:'Close'})
    # remove spurious asterisks
    df['Date'] = df['Date'].apply(lambda dt: re.sub('\*','',dt))
    # convert date column to timestamps
    df['Date'] = df['Date'].apply(lambda dt: pd.Timestamp(datetime.strptime(dt,'%m/%d/%Y')))
    df = df.sort_values(by='Date', ascending=True)
    return df

def fix_closeVX(df,closeField):
    df = df.rename(columns={closeField:'Close'})
    # remove spurious asterisks
    df['Date'] = df['Date'].apply(lambda dt: re.sub('\*','',dt))
    # convert date column to timestamps
    df['Date'] = df['Date'].apply(lambda dt: pd.Timestamp(datetime.strptime(dt,'%Y-%m-%d')))
    df = df.sort_values(by='Date', ascending=True)
    return df


def subsequent_trading_date(date):
    tdays = tradingcalendar.trading_days
    last_date = pd.to_datetime(date)
    last_dt = tradingcalendar.canonicalize_datetime(last_date)
    next_dt = tdays[tdays.searchsorted(last_dt) + 1]
    return next_dt

def add_last_bar(df):
    last_date = df.index[-1]
    subsequent_date = subsequent_trading_date(last_date)
    blank_row = pd.Series({}, index=df.columns, name=subsequent_date)
    # add today, and shift all previous data up to today. This 
    # should result in the same data frames as in backtest
    df = df.append(blank_row).shift(1).dropna(how='all')
    return df

def shift_data(df):
    df = add_last_bar(df)
    df.fillna(method='ffill') 
    df['PrevCloses'] = my_rolling_apply_series(df['Close'], to_csv_str, History)
    dates = pd.Series(df.index)
    dates.index = df.index
    df['PrevDates'] = my_rolling_apply_series(dates, to_csv_str, History)
    return df

def unpack_from_data(context, data, sym):
    try:
        v = data.current(sym, 'PrevCloses')
        i = data.current(sym, 'PrevDates')
        return from_csv_strs(i,v,True).apply(float)
    except:
        log.warn("Unable to unpack historical {s} data.".format(s=sym))
        context.fetch_failed = True

def addFieldsVIX(df):
    df = fix_close(df,'VIX Close')
    df['200ma'] = df['Close'].rolling(200).mean()
    df['200ma Ratio'] = df['Close'] / df['200ma']
    df['20dstd'] =  df['Close'].rolling(20).std()

    return df

def addFieldsVXV(df):
    df.rename(columns={'Unnamed: 0': 'Date'}, inplace=True)
    df = fix_close(df,'CLOSE')
    return df

def addFieldsVX1(df):
    df = fix_closeVX(df,'F1')
    return df

def addFieldsVX2(df):
    df = fix_closeVX(df,'F2')
    return df

# convert a series of values to a comma-separated string of said values
def to_csv_str(s):
    return functools.reduce(lambda x,y: x+','+y, pd.Series(s).apply(str))

# a specific instance of rolling apply, for Series of any type (not just numeric,
# ala pandas.rolling_apply), where the index of the series is set to the indices
# of the last elements of each subset
def my_rolling_apply_series(s_in, f, n):
    s_out = pd.Series([f(s_in[i:i+n]) for i in range(0,len(s_in)-(n-1))]) 
    s_out.index = s_in.index[n-1:]
    return s_out

# reconstitutes a Series from two csv-encoded strings, one of the index, one of the values
def from_csv_strs(x, y, idx_is_date):
    s = pd.Series(y.split(','),index=x.split(','))
    if (idx_is_date):
        s.index = s.index.map(lambda x: pd.Timestamp(x))
    return s

def pvr(context, data):
    ''' Custom chart and/or logging of profit_vs_risk returns and related information from https://www.quantopian.com/posts/pvr#569784bda73e9bf2b7000180
    '''  
    #import time  
    #from datetime import datetime  
    #from pytz import timezone      # Python will only do once, makes this portable.  
                                   #   Move to top of algo for better efficiency.  
    c = context  # Brevity is the soul of wit -- Shakespeare [for readability]  
    if 'pvr' not in c:

        # For real money, you can modify this to total cash input minus any withdrawals  
        manual_cash = c.portfolio.starting_cash  
        time_zone   = 'US/Central'   # Optionally change to your own time zone for wall clock time

        c.pvr = {  
            'options': {  
                # # # # # # # # # #  Options  # # # # # # # # # #  
                'logging'         : 0,    # Info to logging window with some new maximums  
                'log_summary'     : 126,  # Summary every x days. 252/yr

                'record_pvr'      : 0,    # Profit vs Risk returns (percentage)  
                'record_pvrp'     : 0,    # PvR (p)roportional neg cash vs portfolio value  
                'record_cash'     : 1,    # Cash available  
                'record_max_lvrg' : 0,    # Maximum leverage encountered  
                'record_risk_hi'  : 0,    # Highest risk overall  
                'record_shorting' : 0,    # Total value of any shorts  
                'record_max_shrt' : 0,    # Max value of shorting total  
                'record_cash_low' : 0,    # Any new lowest cash level  
                'record_q_return' : 0,    # Quantopian returns (percentage)  
                'record_pnl'      : 0,    # Profit-n-Loss  
                'record_risk'     : 1,    # Risked, max cash spent or shorts beyond longs+cash  
                'record_leverage' : 0,    # End of day leverage (context.account.leverage)  
                # All records are end-of-day or the last data sent to chart during any day.  
                # The way the chart operates, only the last value of the day will be seen.  
                # # # # # # # # #  End options  # # # # # # # # #  
            },  
            'pvr'        : 0,      # Profit vs Risk returns based on maximum spent  
            'cagr'       : 0,  
            'max_lvrg'   : 0,  
            'max_shrt'   : 0,  
            'risk_hi'    : 0,  
            'days'       : 0.0,  
            'date_prv'   : '',  
            'date_end'   : get_environment('end').date(),  
            'cash_low'   : manual_cash,  
            'cash'       : manual_cash,  
            'start'      : manual_cash,  
            'tz'         : time_zone,  
            'begin'      : time.time(),  # For run time  
            'run_str'    : '{} to {}  ${}  {} {}'.format(get_environment('start').date(), get_environment('end').date(), int(manual_cash), datetime.now(timezone(time_zone)).strftime("%Y-%m-%d %H:%M"), time_zone)  
        }  
        if c.pvr['options']['record_pvrp']: c.pvr['options']['record_pvr'] = 0 # if pvrp is active, straight pvr is off  
        if get_environment('arena') not in ['backtest', 'live']: c.pvr['options']['log_summary'] = 1 # Every day when real money  
        log.info(c.pvr['run_str'])  
    p = c.pvr ; o = c.pvr['options'] ; pf = c.portfolio ; pnl = pf.portfolio_value - p['start']  
    def _pvr(c):  
        p['cagr'] = ((pf.portfolio_value / p['start']) ** (1 / (p['days'] / 252.))) - 1  
        ptype = 'PvR' if o['record_pvr'] else 'PvRp'  
        log.info('{} {} %/day   cagr {}   Portfolio value {}   PnL {}'.format(ptype, '%.4f' % (p['pvr'] / p['days']), '%.3f' % p['cagr'], '%.0f' % pf.portfolio_value, '%.0f' % pnl))  
        log.info('  Profited {} on {} activated/transacted for PvR of {}%'.format('%.0f' % pnl, '%.0f' % p['risk_hi'], '%.1f' % p['pvr']))  
        log.info('  QRet {} PvR {} CshLw {} MxLv {} RskHi {} MxShrt {}'.format('%.2f' % (100 * pf.returns), '%.2f' % p['pvr'], '%.0f' % p['cash_low'], '%.2f' % p['max_lvrg'], '%.0f' % p['risk_hi'], '%.0f' % p['max_shrt']))  
    def _minut():  
        dt = get_datetime().astimezone(timezone(p['tz']))  
        return str((dt.hour * 60) + dt.minute - 570).rjust(3)  # (-570 = 9:31a)  
    date = get_datetime().date()  
    if p['date_prv'] != date:  
        p['date_prv'] = date  
        p['days'] += 1.0  
    do_summary = 0  
    if o['log_summary'] and p['days'] % o['log_summary'] == 0 and _minut() == '100':  
        do_summary = 1              # Log summary every x days  
    if do_summary or date == p['date_end']:  
        p['cash'] = pf.cash  
    elif p['cash'] == pf.cash and not o['logging']: return  # for speed

    shorts = sum([z.amount * z.last_sale_price for s, z in pf.positions.items() if z.amount < 0])  
    new_key_hi = 0                  # To trigger logging if on.  
    cash       = pf.cash  
    cash_dip   = int(max(0, p['start'] - cash))  
    risk       = int(max(cash_dip, -shorts))

    if o['record_pvrp'] and cash < 0:   # Let negative cash ding less when portfolio is up.  
        cash_dip = int(max(0, cash_dip * p['start'] / pf.portfolio_value))  
        # Imagine: Start with 10, grows to 1000, goes negative to -10, should not be 200% risk.

    if int(cash) < p['cash_low']:             # New cash low  
        new_key_hi = 1  
        p['cash_low'] = int(cash)             # Lowest cash level hit  
        if o['record_cash_low']: record(CashLow = p['cash_low'])

    if c.account.leverage > p['max_lvrg']:  
        new_key_hi = 1  
        p['max_lvrg'] = c.account.leverage    # Maximum intraday leverage  
        if o['record_max_lvrg']: record(MaxLv   = p['max_lvrg'])

    if shorts < p['max_shrt']:  
        new_key_hi = 1  
        p['max_shrt'] = shorts                # Maximum shorts value  
        if o['record_max_shrt']: record(MxShrt  = p['max_shrt'])

    if risk > p['risk_hi']:  
        new_key_hi = 1  
        p['risk_hi'] = risk                   # Highest risk overall  
        if o['record_risk_hi']:  record(RiskHi  = p['risk_hi'])

    # Profit_vs_Risk returns based on max amount actually invested, long or short  
    if p['risk_hi'] != 0: # Avoid zero-divide  
        p['pvr'] = 100 * pnl / p['risk_hi']  
        ptype = 'PvRp' if o['record_pvrp'] else 'PvR'  
        if o['record_pvr'] or o['record_pvrp']: record(**{ptype: p['pvr']})

    if o['record_shorting']: record(Shorts = shorts)             # Shorts value as a positve  
    if o['record_leverage']: record(Lvrg   = c.account.leverage) # Leverage  
    if o['record_cash']    : record(Cash   = cash)               # Cash  
    if o['record_risk']    : record(Risk   = risk)  # Amount in play, maximum of shorts or cash used  
    if o['record_q_return']: record(QRet   = 100 * pf.returns)  
    if o['record_pnl']     : record(PnL    = pnl)                # Profit|Loss

    if o['logging'] and new_key_hi:  
        log.info('{}{}{}{}{}{}{}{}{}{}{}{}'.format(_minut(),  
            ' Lv '     + '%.1f' % c.account.leverage,  
            ' MxLv '   + '%.2f' % p['max_lvrg'],  
            ' QRet '   + '%.1f' % (100 * pf.returns),  
            ' PvR '    + '%.1f' % p['pvr'],  
            ' PnL '    + '%.0f' % pnl,  
            ' Cash '   + '%.0f' % cash,  
            ' CshLw '  + '%.0f' % p['cash_low'],  
            ' Shrt '   + '%.0f' % shorts,  
            ' MxShrt ' + '%.0f' % p['max_shrt'],  
            ' Risk '   + '%.0f' % risk,  
            ' RskHi '  + '%.0f' % p['risk_hi']  
        ))  
    if do_summary: _pvr(c)  
    if get_datetime() == get_environment('end'):   # Summary at end of run  
        _pvr(c) ; elapsed = (time.time() - p['begin']) / 60  # minutes  
        log.info( '{}\nRuntime {} hr {} min'.format(p['run_str'], int(elapsed / 60), '%.1f' % (elapsed % 60)))
There was a runtime error.

Feel free to ignore this version. I am using it as of now.

context.RobinHoodMargin = 0 # I prefer to not use margin.

MaxLeverage = 0.45 # Because we are day trading, we have to attempt to NOT use same day sell cash to buy shares, so that we can completely exit the same day if necessary. So 0.45 avoids that in a same - day sell, buy, sell situation.

Obviously, you should do what fits your situation / preferences.

Clone Algorithm
332
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
#Imports
from datetime import datetime  
from pytz import timezone      
from zipline.utils import tradingcalendar
from quantopian.pipeline.data.builtin import USEquityPricing
import statsmodels.api as sm 
import numpy as np
import pandas as pd
import functools
import quantopian.pipeline.data 
import re
import scipy
import talib
import time

History = 128

def initialize(context):
    c = context

    vixUrl = 'http://www.cboe.com/publish/scheduledtask/mktdata/datahouse/vixcurrent.csv'
    vxvUrl = 'http://www.cboe.com/publish/scheduledtask/mktdata/datahouse/vix3mdailyprices.csv'
    nokoUrl = 'http://173.212.203.121/noko.csv'  
    
    fetch_csv(nokoUrl, 
              symbol='v1', 
              date_column='Date', 
              date_format='%Y-%m-%d', 
              pre_func=addFieldsVX1, 
              post_func=shift_data)
    
    fetch_csv(nokoUrl, 
              symbol='v2', 
              date_column='Date', 
              date_format='%Y-%m-%d', 
              pre_func=addFieldsVX2, 
              post_func=shift_data)

    fetch_csv(vixUrl, 
              symbol='v', 
              skiprows=1,
              date_column='Date', 
              pre_func=addFieldsVIX,
              post_func=shift_data)

    fetch_csv(vxvUrl, 
              symbol='vxv', 
              skiprows=2,
              date_column='Date', 
              pre_func=addFieldsVXV,
              post_func=shift_data)

    context.RobinHoodMargin = 0 # I prefer to not use margin.
    
    set_benchmark(symbol('SVXY'))
    context.SVXY = symbol('SVXY')
    context.TQQQ = symbol('TQQQ')
    context.UVXY = symbol('UVXY')
    context.SPYG = symbol('SPYG')

    context.ShowMaxLev = True
    context.vix = -1
    context.BigSignalShortVIX = context.BigSignalTQQQ = context.BigSignalLongVIX = False
    context.PriceSVXY = context.PriceUVXY = 0.00
    context.BoughtShortVIX = context.BoughtLongVIX = False

    #Internal stop variables
    # context.stoploss is set in handle_data
    context.stops         = {}
    context.stoppedout    = []
    c.SellAndWait = c.SVXY_DaysNotOwned = c.UVXY_DaysOwned = 0
    c.HaveToThink = False
    c.DaysHaveToThink = 0

    context.OkToAct = context.TooLateToAct = False
    
    #Used for logging
    context.last_bar = False
    context.SignalSellSVXY = False
    context.SignalBuySVXY  = False
    context.SignalBuyUVXY  = False

    # wait after market open before doing any possible orders
    schedule_function(OkToActTrigger, date_rules.every_day(), time_rules.market_open(minutes = 3))

    # too late to act because of 15 minute delay plus 10 minute cushion
    schedule_function(TooLateToActTrigger, date_rules.every_day(), time_rules.market_close(minutes = 25))

    # apparently highly successful VIX signals
    schedule_function(MoreSignals, date_rules.every_day(), time_rules.market_open(minutes = 2))

    # On Bar Close Functions
    for x in [1,3,5]:
        schedule_function(Rebalance, date_rules.every_day(), time_rules.market_open(hours=x, minutes=59))
    schedule_function(Rebalance, date_rules.every_day(), time_rules.market_close())
    
    schedule_function(my_record_vars, date_rules.every_day(), time_rules.market_close())

    #Set commission and slippage
    set_commission(commission.PerShare(cost=0.000, min_trade_cost=0.0)) 
    set_slippage(slippage.FixedSlippage(spread=0.01))
    
def OkToActTrigger(context,data): 
    context.OkToAct = True

def TooLateToActTrigger(context,data): 
    context.TooLateToAct = True

def before_trading_start(context, data):
    c = context
    c.OkToAct = c.TooLateToAct = False
    c.mx_lvrg  = 0 # daily max leverage

    if 0 == c.portfolio.positions[c.SVXY].amount:
        c.SVXY_DaysNotOwned += 1
    else:
        c.SVXY_DaysNotOwned  = 0

    if 0 < c.portfolio.positions[c.UVXY].amount:
        c.UVXY_DaysOwned += 1
    else:
        c.UVXY_DaysOwned  = 0

    if c.HaveToThink:
        c.DaysHaveToThink += 1
    else:
        c.DaysHaveToThink  = 0
    c.HaveToThink = False

def TarPer(context, data, stock, TargetPercent):

    cancel_open_orders_for(context, data, stock)
    if DataCanTrade(context, data, stock):

        if 0 == TargetPercent:
            amount = context.portfolio.positions[stock].amount
            price = data.current(stock, 'price')
            order_target_percent(stock, 0.00)
            log.info('SOLD ALL {} {} Shares @ MarketPrice {:.2f}'\
            .format(stock.symbol, amount, price))
        else:
            # Always want money available to withdraw 
            # and also try to prevent margin related order rejections
            PV = context.portfolio.portfolio_value
            DoNotSpend = 0 # 0 is ok if MaxLeverage is less than 0.50
            RhMargin = context.RobinHoodMargin # Set to 0 if you do not have Robinhood Gold or if you do not use the margin money
            MaxLeverage = 0.45 # Because we are day trading, we have to attempt to NOT use same day sell cash to buy shares, so that we can completely exit the same day if necessary. So 0.45 avoids that in a same - day sell, buy, sell situation.
            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
            Action = "Bought" if 0 < DiffAmount else "Sold"
            if stock is context.UVXY:
                LimitPrice = price * 1.00
                order(stock, DiffAmount, style=LimitOrder(LimitPrice))
                log.info('{} {} {} Shares @ LimitPrice {:.2f}'\
                .format(Action, stock.symbol, DiffAmount, LimitPrice))
            else:
                order(stock, DiffAmount)
                log.info('{} {} {} Shares @ MarketPrice {:.2f}'\
                .format(Action, stock.symbol, DiffAmount, price))

        if stock.symbol in context.stops: del context.stops[stock.symbol]

def Rebalance(context,data):
    c = context
    if not c.BigSignalShortVIX and not c.BigSignalTQQQ and not c.BigSignalLongVIX: MoreSignals(context, data)
    cancel_open_orders(context, data)
    #looking at 4 days of minutely price history
    svxy_prices = data.history(c.SVXY, "price", 1600, "1m").resample('30T',  closed='right', label='right').last().dropna()

    #convert to 2 hour:
    svxy2hr = []
    if '20:00:00+00:00' in str(svxy_prices.index[-1]) or '21:00:00+00:00' in str(svxy_prices.index[-1]):
        svxy2hr.append([svxy_prices.index[-28], svxy_prices[-28]])
        svxy2hr.append([svxy_prices.index[-27], svxy_prices[-27]])        
        svxy2hr.append([svxy_prices.index[-23], svxy_prices[-23]])
        svxy2hr.append([svxy_prices.index[-19], svxy_prices[-19]])
        svxy2hr.append([svxy_prices.index[-15], svxy_prices[-15]])
        svxy2hr.append([svxy_prices.index[-14], svxy_prices[-14]])
        svxy2hr.append([svxy_prices.index[-10], svxy_prices[-10]])
        svxy2hr.append([svxy_prices.index[-6], svxy_prices[-6]])
        svxy2hr.append([svxy_prices.index[-2], svxy_prices[-2]])
        svxy2hr.append([svxy_prices.index[-1], svxy_prices[-1]])
        c.last_bar = True
    elif '19:30:00+00:00' in str(svxy_prices.index[-1]) or '20:30:00+00:00' in str(svxy_prices.index[-1]):
        svxy2hr.append([svxy_prices.index[-31], svxy_prices[-31]])
        svxy2hr.append([svxy_prices.index[-27], svxy_prices[-27]]) 
        svxy2hr.append([svxy_prices.index[-26], svxy_prices[-26]])
        svxy2hr.append([svxy_prices.index[-22], svxy_prices[-22]])
        svxy2hr.append([svxy_prices.index[-18], svxy_prices[-18]])
        svxy2hr.append([svxy_prices.index[-14], svxy_prices[-14]]) 
        svxy2hr.append([svxy_prices.index[-13], svxy_prices[-13]])
        svxy2hr.append([svxy_prices.index[-9], svxy_prices[-9]])
        svxy2hr.append([svxy_prices.index[-5], svxy_prices[-5]])
        svxy2hr.append([svxy_prices.index[-1], svxy_prices[-1]])
        c.last_bar = False
    elif '17:30:00+00:00' in str(svxy_prices.index[-1]) or '18:30:00+00:00' in str(svxy_prices.index[-1]):
        svxy2hr.append([svxy_prices.index[-31], svxy_prices[-31]])
        svxy2hr.append([svxy_prices.index[-27], svxy_prices[-27]]) 
        svxy2hr.append([svxy_prices.index[-23], svxy_prices[-23]])
        svxy2hr.append([svxy_prices.index[-22], svxy_prices[-22]])
        svxy2hr.append([svxy_prices.index[-18], svxy_prices[-18]])
        svxy2hr.append([svxy_prices.index[-14], svxy_prices[-14]])
        svxy2hr.append([svxy_prices.index[-10], svxy_prices[-10]])
        svxy2hr.append([svxy_prices.index[-9], svxy_prices[-9]])
        svxy2hr.append([svxy_prices.index[-5], svxy_prices[-5]])
        svxy2hr.append([svxy_prices.index[-1], svxy_prices[-1]])
        c.last_bar = False
    elif '15:30:00+00:00' in str(svxy_prices.index[-1]) or '16:30:00+00:00' in str(svxy_prices.index[-1]):
        svxy2hr.append([svxy_prices.index[-31], svxy_prices[-31]])
        svxy2hr.append([svxy_prices.index[-27], svxy_prices[-27]]) 
        svxy2hr.append([svxy_prices.index[-23], svxy_prices[-23]])
        svxy2hr.append([svxy_prices.index[-19], svxy_prices[-19]])
        svxy2hr.append([svxy_prices.index[-18], svxy_prices[-18]])
        svxy2hr.append([svxy_prices.index[-14], svxy_prices[-14]])        
        svxy2hr.append([svxy_prices.index[-10], svxy_prices[-10]])
        svxy2hr.append([svxy_prices.index[-6], svxy_prices[-6]])
        svxy2hr.append([svxy_prices.index[-5], svxy_prices[-5]])
        svxy2hr.append([svxy_prices.index[-1], svxy_prices[-1]])
        c.last_bar = False
    else:
        log.error("2 HOUR CONVERSION FAILURE")
        return
    dates, vals = zip(*svxy2hr)
    s = pd.Series(vals, index=dates)
    
    rsi  = talib.RSI(s,2)
    RSI5 = talib.RSI(s,5) 

    # BUY SVXY RULE
    if rsi[-2] < 70 and rsi[-1] >= 70 and (
        0 == c.portfolio.positions[c.UVXY].amount
        or
        0 < c.UVXY_DaysOwned
    ):
        c.SignalBuySVXY  = True
        c.BoughtShortVIX = False

    # SELL SVXY RULE or maybe BUY UVXY
    if rsi[-2] > 85 and rsi[-1] <= 85:
        c.SignalSellSVXY = True
        if RSI5[-1] < 70:
            c.SignalBuyUVXY = True
            c.BoughtLongVIX = False
    # maybe BUY UVXY
    if (
        not c.SignalBuySVXY
        and
        0 == c.portfolio.positions[c.SVXY].amount
        and
        ( c.BigSignalLongVIX or 1 < c.DaysHaveToThink )
        and
        ( 2.00 <= c.dispersion or 0 < c.SVXY_DaysNotOwned )
    ):
        c.SignalBuyUVXY = True
        c.BoughtLongVIX = False

def handle_data(context, data):
    pvr(context, data)
    c = context

    c.SellAndWait -= 1 # 1 unit is 1 minute
    c.SellAndWait = 0 if 0 > c.SellAndWait else c.SellAndWait

    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 not c.SellAndWait and c.OkToAct and not c.TooLateToAct:
        for position in c.portfolio.positions.itervalues():

            ##
            # Variable stop loss based on signals
            ##
            SVXY_Dispersion = 3.00 # SVXY 3.00 may be best overall
            UVXY_Dispersion = 3.00 # UVXY no idea what is best yet
            stoploss = 0.25 # default - probably not used
            if position.sid is c.UVXY:
                if (
                    UVXY_Dispersion <= c.dispersion
                    or
                    1 <= c.SVXY_DaysNotOwned
                ):
                    stoploss = 0.21 # 0.21 may be best
                else:
                    stoploss = 0.03 if c.BigSignalLongVIX else 0.01
            else:
                if SVXY_Dispersion <= c.dispersion:
                    stoploss = 0.25
                else:
                    if c.BigSignalTQQQ:     stoploss = 0.02
                    if c.BigSignalShortVIX: stoploss = 0.03

            if position.amount == 0:
                if position.asset.symbol in c.stops: del c.stops[position.asset.symbol]
                continue
            elif position.asset.symbol not in c.stops:
                stoploss= stoploss if position.amount > 0 else -stoploss
                c.stops[position.asset.symbol]=position.last_sale_price*(1-stoploss)
                #log.info(' ! I have added '+str(position.asset.symbol)+' to Stops @ '+str((position.last_sale_price)*(1-stoploss)))
            elif c.stops[position.asset.symbol] < position.last_sale_price*(1- stoploss) and position.amount > 0:
                c.stops[position.asset.symbol]=position.last_sale_price*(1- stoploss)
                #log.info(' ! I have updated '+str(position.asset.symbol)+'- (Long) to stop @ '+str((position.last_sale_price)*(1- stoploss)))
                # set break even
                if position.last_sale_price > position.cost_basis * 1.075:
                    if position.cost_basis > c.stops[position.asset.symbol]:
                        c.stops[position.asset.symbol] = position.cost_basis
            elif c.stops[position.asset.symbol] > position.last_sale_price and position.amount > 0:
                #sell
                log.info(' ! '+str(position.asset.symbol)+'- (Long) has hit stoploss @ '+str(position.last_sale_price))
                if get_open_orders(position.sid): cancel_open_orders_for(context, data, position.sid)
                c.stoppedout.append(position.asset.symbol)
                c.PriceSVXY = c.PriceUVXY = 0.00
                c.SignalBuySVXY = c.SignalSellSVXY = c.SignalBuyUVXY = False
                if position.sid is c.UVXY: c.SellAndWait = int(390 * 1.00)
                TarPer(context, data, position.sid, 0.00)
 
            ##
            # Sell and Wait
            ##
            SVXY_Up = 1.07 # 1.07 good 1.03 too
            UVXY_Up = 1.10 # 1.10 good 1.25 more common than 1.50
            if (
                    (position.sid is not c.UVXY and 0 < position.amount)
                    and
                    (position.last_sale_price > SVXY_Up * position.cost_basis)
            ):
                c.SellAndWait = int(390 * 1.33)
                log.warn(str(position.sid.symbol) + ' ' + str(SVXY_Up) + \
                ' growth reached SellAndWait triggered: ' + \
                str(c.SellAndWait) + ' minutes')
            elif (
                    (position.sid is c.UVXY and 0 < position.amount)
                    and
                    (position.last_sale_price > UVXY_Up * position.cost_basis)
            ):
                c.SellAndWait = int(390 * 1.66)
                log.warn(str(position.sid.symbol) + ' ' + str(UVXY_Up) + \
                ' growth reached SellAndWait triggered: ' + \
                str(c.SellAndWait) + ' minutes')

        ##
        # Sell and Wait
        ##
        if 0 < c.SellAndWait:
            c.PriceSVXY = c.PriceUVXY = 0.00
            c.SignalBuySVXY = c.SignalSellSVXY = c.SignalBuyUVXY = False
            for stock in c.portfolio.positions:
                cancel_open_orders_for(context, data, stock)
                TarPer(context, data, stock, 0.00)
        ##
        # Slippery Buy allows for some drop before buy
        ##
        else:
            SVXY_UpAfterSlip = 1.0025
            UVXY_UpAfterSlip = 1.005
            if c.SignalBuySVXY:
                if 0 < c.portfolio.positions[c.UVXY].amount:
                    c.PriceUVXY = 0.00
                    c.SignalBuyUVXY = False
                    TarPer(context, data, c.UVXY, 0.000)
                elif 0 == c.portfolio.positions[c.SVXY].amount:
                    if not c.BoughtShortVIX:
                        PriceSVXY = data.current(c.SVXY, 'price')
                        if not c.PriceSVXY: c.PriceSVXY = PriceSVXY
                        if PriceSVXY < c.PriceSVXY:
                            c.PriceSVXY = PriceSVXY
                        elif PriceSVXY > SVXY_UpAfterSlip * c.PriceSVXY:
                            c.PriceSVXY = c.PriceUVXY = 0.00
                            c.SignalBuySVXY = c.SignalSellSVXY = c.SignalBuyUVXY = False
                            c.BoughtShortVIX = True
                            TarPer(context, data, c.SVXY, 0.999)
            elif c.SignalSellSVXY:
                if c.portfolio.positions[c.SVXY].amount > 0:
                    c.PriceSVXY = c.PriceUVXY = 0.00
                    c.SignalBuySVXY = c.SignalSellSVXY = c.SignalBuyUVXY = False
                    TarPer(context, data, c.SVXY, 0)
            elif c.SignalBuyUVXY:
                if 0 < c.portfolio.positions[c.SVXY].amount:
                    c.PriceSVXY = c.PriceUVXY = 0.00
                    c.SignalBuySVXY = c.SignalSellSVXY = False
                    TarPer(context, data, c.SVXY, 0.000)
                elif 0 == c.portfolio.positions[c.UVXY].amount:
                    if not c.BoughtLongVIX:
                        PriceUVXY = data.current(c.UVXY, 'price')
                        if not c.PriceUVXY: c.PriceUVXY = PriceUVXY
                        if PriceUVXY < c.PriceUVXY:
                            c.PriceUVXY = PriceUVXY
                        elif PriceUVXY > UVXY_UpAfterSlip * c.PriceUVXY:
                            c.PriceSVXY = c.PriceUVXY = 0.00
                            c.SignalBuySVXY = c.SignalSellSVXY = c.SignalBuyUVXY = False
                            c.BoughtLongVIX = True
                            TarPer(context, data, c.UVXY, 0.999)

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 DataCanTrade(context, data, stock):

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

def cancel_open_orders(context, data):
    oo = get_open_orders()
    if len(oo) == 0:
        return
    for stock, orders in oo.iteritems():
        for order in orders:
            cancel_order(order)
            #message = 'Canceling order of {amount} shares in {stock}'
            #log.info(message.format(amount=order.amount, stock=stock))
    
def cancel_open_orders_for(context, data, security):
    oo = get_open_orders()
    if len(oo) == 0:
        return
    for stock, orders in oo.iteritems():
        for order in orders:
            if stock is security:
                if order.amount < context.portfolio.positions[stock].amount:
                    cancel_order(order)
                    #message = 'Canceling order of {amount} shares in {stock}'
                    #log.info(message.format(amount=order.amount, stock=stock))
                else: # Do NOT want to cancel stop loss order
                    #message = 'NOT Canceling stop loss order of {amount} shares in {stock}'
                    #log.info(message.format(amount=order.amount, stock=stock))
                    pass

def MoreSignals(context, data):
    c = context

    update_indices(context, data)
    target_sid = context.SVXY # default if no conditions are met
    last_vix = context.VIXprice = data.current('v', 'Close')
    last_vx1 = data.current('v1','Close')  
    last_vx2 = data.current('v2','Close')      
    last_vxv = data.current('vxv', 'Close')
    last_vix_200ma_ratio = data.current('v', '200ma Ratio')
    # Maybe use dispersion aka vix standard deviation in buying mechanism
    context.dispersion = data.current('v','20dstd')
    #context.threshold_dispersion = 0.55

    
    record(dispersion=context.dispersion)
    record(last_vixDivBy10=last_vix/10)
    # Calculating the gap between spot vix and the first month vix future
    last_ratio_v_v1 = last_vix/last_vx1

    # Calculating the contango ratio of the front and second month VIX Futures 
    last_ratio_v1_v2 = last_vx1/last_vx2

    # Blending the previous two ratios together using a weighted average
    ratio_weight = 0.7
    last_ratio = (ratio_weight*last_ratio_v_v1) + ((1-ratio_weight)*last_ratio_v1_v2) - 1
    
    vix_vxv_ratio = last_vix/last_vxv
    
    # Retrieve SPY prices for technical indicators
    prices = data.history(context.SPYG, 'open', 40, '1d')
    
    # Retrieve SPY MACD data
    macda, signal, hist = talib.MACD(prices, fastperiod=12,slowperiod=26,signalperiod=9)
    macd = macda[-1] - signal[-1]
    
    # Calculate how much vix moved the previous day
    if (context.vix <> -1) : 
        vix_ratio = last_vix/context.vix -1
    else :
        vix_ratio = 0
    context.vix = last_vix
    
    SVXY_history = data.history(context.SVXY, 'price', 2, '1d')  
    
    SVXY_ratio = SVXY_history[1]/SVXY_history[0] - 1
    
    # Setting thresholds
    #if
    threshold_vix_too_low = 10.76   # 0 
    threshold_vix_200ma_ratio_low = 0.79  # 0 
    threshold_SVXY = -0.049          # 1
    threshold_vxv_SVXY = 0.87        # 2
    threshold_uvxy = 0.049          # 3
    threshold_macd = -0.55          # 3
    threshold_vxv_uvxy = 1.3        # 4
    threshold_vix_high = 19.9       # 5
    threshold_vc_low = -0.148       # 6
    threshold_vc_high = 0.046       # 8
    threshold_vc_high_2 = -0.06     # 8 HaveToThink
    #if
    threshold_SVXY_ratio = -0.053    # 10
    threshold_uvxy_ratio = 0.08     # 11
    # 0
    if last_vix < threshold_vix_too_low \
            and last_vix_200ma_ratio < threshold_vix_200ma_ratio_low :
        log.info('! if VIX is too low, invest in UVXY with the hope of a spike')
        target_sid = context.UVXY
    # 1        
    elif last_ratio < threshold_SVXY: # 
        log.info('! if contango is high, invest in SVXY to gain from decay')
        target_sid = context.SVXY
    
    # 2
    elif vix_vxv_ratio < threshold_vxv_SVXY: # 
        log.info('! if short term vol is low compared to mid term, invest in SVXY to gain from decay')
        target_sid = context.SVXY

    # 3
    elif last_ratio > threshold_uvxy and macd > threshold_macd: # 
        log.info('! if backwardation is high, invest in UVXY to gain from decay')
        target_sid = context.UVXY

    # 4
    elif vix_vxv_ratio > threshold_vxv_uvxy: # 
        log.info('! if short term vol is high compared to mid term, invest in UVXY to gain from growth')
        target_sid = context.UVXY

    # 5
    elif last_vix > threshold_vix_high: # 
        log.info('! if VIX is too high, invest in SVXY expecting that VIX will drop')
        target_sid = context.SVXY

    # 6
    elif vix_ratio < threshold_vc_low: # 
        log.info('! Vix down sharply, invest in SVXY expecting that futures curve gets pulled down')
        target_sid = context.SVXY

    # 7
    elif vix_ratio > threshold_vc_high: # 
        log.info('! Vix up sharply, invest in UVXY expecting that futures curve gets pulled up')
        target_sid = context.UVXY

    # 8
    elif vix_ratio > threshold_vc_high_2: #
        log.info('! have to think')
        target_sid = context.SVXY
        c.HaveToThink = True

    # 10
    if (target_sid == context.SVXY and SVXY_ratio < threshold_SVXY_ratio) : 
        log.info('! indicators say SVXY but it just dropped overnight, so go for TQQQ')
        target_sid = context.TQQQ 

    # 11
    elif (target_sid == context.UVXY and SVXY_ratio > threshold_uvxy_ratio) :
        log.info('! indicators say UVXY but it just dropped overnight, so go for TQQQ')
        target_sid = context.TQQQ
    
    context.BigSignalShortVIX = True if context.SVXY is target_sid else False
    context.BigSignalLongVIX  = True if context.UVXY is target_sid else False
    context.BigSignalTQQQ     = True if context.TQQQ is target_sid else False
    
def update_indices(context, data):
    context.fetch_failed = False
    context.vix_vals = unpack_from_data(context, data, 'v')    
    context.vxv_vals = unpack_from_data(context, data, 'vxv')  
    context.vx1_vals = unpack_from_data(context, data, 'v1')
    context.vx2_vals = unpack_from_data(context, data, 'v2')

def fix_close(df,closeField):
    df = df.rename(columns={closeField:'Close'})
    # remove spurious asterisks
    df['Date'] = df['Date'].apply(lambda dt: re.sub('\*','',dt))
    # convert date column to timestamps
    df['Date'] = df['Date'].apply(lambda dt: pd.Timestamp(datetime.strptime(dt,'%m/%d/%Y')))
    df = df.sort_values(by='Date', ascending=True)
    return df

def fix_closeVX(df,closeField):
    df = df.rename(columns={closeField:'Close'})
    # remove spurious asterisks
    df['Date'] = df['Date'].apply(lambda dt: re.sub('\*','',dt))
    # convert date column to timestamps
    df['Date'] = df['Date'].apply(lambda dt: pd.Timestamp(datetime.strptime(dt,'%Y-%m-%d')))
    df = df.sort_values(by='Date', ascending=True)
    return df


def subsequent_trading_date(date):
    tdays = tradingcalendar.trading_days
    last_date = pd.to_datetime(date)
    last_dt = tradingcalendar.canonicalize_datetime(last_date)
    next_dt = tdays[tdays.searchsorted(last_dt) + 1]
    return next_dt

def add_last_bar(df):
    last_date = df.index[-1]
    subsequent_date = subsequent_trading_date(last_date)
    blank_row = pd.Series({}, index=df.columns, name=subsequent_date)
    # add today, and shift all previous data up to today. This 
    # should result in the same data frames as in backtest
    df = df.append(blank_row).shift(1).dropna(how='all')
    return df

def shift_data(df):
    df = add_last_bar(df)
    df.fillna(method='ffill') 
    df['PrevCloses'] = my_rolling_apply_series(df['Close'], to_csv_str, History)
    dates = pd.Series(df.index)
    dates.index = df.index
    df['PrevDates'] = my_rolling_apply_series(dates, to_csv_str, History)
    return df

def unpack_from_data(context, data, sym):
    try:
        v = data.current(sym, 'PrevCloses')
        i = data.current(sym, 'PrevDates')
        return from_csv_strs(i,v,True).apply(float)
    except:
        log.warn("Unable to unpack historical {s} data.".format(s=sym))
        context.fetch_failed = True

def addFieldsVIX(df):
    df = fix_close(df,'VIX Close')
    df['200ma'] = df['Close'].rolling(200).mean()
    df['200ma Ratio'] = df['Close'] / df['200ma']
    df['20dstd'] =  df['Close'].rolling(20).std()

    return df

def addFieldsVXV(df):
    df.rename(columns={'Unnamed: 0': 'Date'}, inplace=True)
    df = fix_close(df,'CLOSE')
    return df

def addFieldsVX1(df):
    df = fix_closeVX(df,'F1')
    return df

def addFieldsVX2(df):
    df = fix_closeVX(df,'F2')
    return df

# convert a series of values to a comma-separated string of said values
def to_csv_str(s):
    return functools.reduce(lambda x,y: x+','+y, pd.Series(s).apply(str))

# a specific instance of rolling apply, for Series of any type (not just numeric,
# ala pandas.rolling_apply), where the index of the series is set to the indices
# of the last elements of each subset
def my_rolling_apply_series(s_in, f, n):
    s_out = pd.Series([f(s_in[i:i+n]) for i in range(0,len(s_in)-(n-1))]) 
    s_out.index = s_in.index[n-1:]
    return s_out

# reconstitutes a Series from two csv-encoded strings, one of the index, one of the values
def from_csv_strs(x, y, idx_is_date):
    s = pd.Series(y.split(','),index=x.split(','))
    if (idx_is_date):
        s.index = s.index.map(lambda x: pd.Timestamp(x))
    return s

def pvr(context, data):
    ''' Custom chart and/or logging of profit_vs_risk returns and related information from https://www.quantopian.com/posts/pvr#569784bda73e9bf2b7000180
    '''  
    #import time  
    #from datetime import datetime  
    #from pytz import timezone      # Python will only do once, makes this portable.  
                                   #   Move to top of algo for better efficiency.  
    c = context  # Brevity is the soul of wit -- Shakespeare [for readability]  
    if 'pvr' not in c:

        # For real money, you can modify this to total cash input minus any withdrawals  
        manual_cash = c.portfolio.starting_cash  
        time_zone   = 'US/Central'   # Optionally change to your own time zone for wall clock time

        c.pvr = {  
            'options': {  
                # # # # # # # # # #  Options  # # # # # # # # # #  
                'logging'         : 0,    # Info to logging window with some new maximums  
                'log_summary'     : 126,  # Summary every x days. 252/yr

                'record_pvr'      : 0,    # Profit vs Risk returns (percentage)  
                'record_pvrp'     : 0,    # PvR (p)roportional neg cash vs portfolio value  
                'record_cash'     : 1,    # Cash available  
                'record_max_lvrg' : 0,    # Maximum leverage encountered  
                'record_risk_hi'  : 0,    # Highest risk overall  
                'record_shorting' : 0,    # Total value of any shorts  
                'record_max_shrt' : 0,    # Max value of shorting total  
                'record_cash_low' : 0,    # Any new lowest cash level  
                'record_q_return' : 0,    # Quantopian returns (percentage)  
                'record_pnl'      : 0,    # Profit-n-Loss  
                'record_risk'     : 1,    # Risked, max cash spent or shorts beyond longs+cash  
                'record_leverage' : 0,    # End of day leverage (context.account.leverage)  
                # All records are end-of-day or the last data sent to chart during any day.  
                # The way the chart operates, only the last value of the day will be seen.  
                # # # # # # # # #  End options  # # # # # # # # #  
            },  
            'pvr'        : 0,      # Profit vs Risk returns based on maximum spent  
            'cagr'       : 0,  
            'max_lvrg'   : 0,  
            'max_shrt'   : 0,  
            'risk_hi'    : 0,  
            'days'       : 0.0,  
            'date_prv'   : '',  
            'date_end'   : get_environment('end').date(),  
            'cash_low'   : manual_cash,  
            'cash'       : manual_cash,  
            'start'      : manual_cash,  
            'tz'         : time_zone,  
            'begin'      : time.time(),  # For run time  
            'run_str'    : '{} to {}  ${}  {} {}'.format(get_environment('start').date(), get_environment('end').date(), int(manual_cash), datetime.now(timezone(time_zone)).strftime("%Y-%m-%d %H:%M"), time_zone)  
        }  
        if c.pvr['options']['record_pvrp']: c.pvr['options']['record_pvr'] = 0 # if pvrp is active, straight pvr is off  
        if get_environment('arena') not in ['backtest', 'live']: c.pvr['options']['log_summary'] = 1 # Every day when real money  
        log.info(c.pvr['run_str'])  
    p = c.pvr ; o = c.pvr['options'] ; pf = c.portfolio ; pnl = pf.portfolio_value - p['start']  
    def _pvr(c):  
        p['cagr'] = ((pf.portfolio_value / p['start']) ** (1 / (p['days'] / 252.))) - 1  
        ptype = 'PvR' if o['record_pvr'] else 'PvRp'  
        log.info('{} {} %/day   cagr {}   Portfolio value {}   PnL {}'.format(ptype, '%.4f' % (p['pvr'] / p['days']), '%.3f' % p['cagr'], '%.0f' % pf.portfolio_value, '%.0f' % pnl))  
        log.info('  Profited {} on {} activated/transacted for PvR of {}%'.format('%.0f' % pnl, '%.0f' % p['risk_hi'], '%.1f' % p['pvr']))  
        log.info('  QRet {} PvR {} CshLw {} MxLv {} RskHi {} MxShrt {}'.format('%.2f' % (100 * pf.returns), '%.2f' % p['pvr'], '%.0f' % p['cash_low'], '%.2f' % p['max_lvrg'], '%.0f' % p['risk_hi'], '%.0f' % p['max_shrt']))  
    def _minut():  
        dt = get_datetime().astimezone(timezone(p['tz']))  
        return str((dt.hour * 60) + dt.minute - 570).rjust(3)  # (-570 = 9:31a)  
    date = get_datetime().date()  
    if p['date_prv'] != date:  
        p['date_prv'] = date  
        p['days'] += 1.0  
    do_summary = 0  
    if o['log_summary'] and p['days'] % o['log_summary'] == 0 and _minut() == '100':  
        do_summary = 1              # Log summary every x days  
    if do_summary or date == p['date_end']:  
        p['cash'] = pf.cash  
    elif p['cash'] == pf.cash and not o['logging']: return  # for speed

    shorts = sum([z.amount * z.last_sale_price for s, z in pf.positions.items() if z.amount < 0])  
    new_key_hi = 0                  # To trigger logging if on.  
    cash       = pf.cash  
    cash_dip   = int(max(0, p['start'] - cash))  
    risk       = int(max(cash_dip, -shorts))

    if o['record_pvrp'] and cash < 0:   # Let negative cash ding less when portfolio is up.  
        cash_dip = int(max(0, cash_dip * p['start'] / pf.portfolio_value))  
        # Imagine: Start with 10, grows to 1000, goes negative to -10, should not be 200% risk.

    if int(cash) < p['cash_low']:             # New cash low  
        new_key_hi = 1  
        p['cash_low'] = int(cash)             # Lowest cash level hit  
        if o['record_cash_low']: record(CashLow = p['cash_low'])

    if c.account.leverage > p['max_lvrg']:  
        new_key_hi = 1  
        p['max_lvrg'] = c.account.leverage    # Maximum intraday leverage  
        if o['record_max_lvrg']: record(MaxLv   = p['max_lvrg'])

    if shorts < p['max_shrt']:  
        new_key_hi = 1  
        p['max_shrt'] = shorts                # Maximum shorts value  
        if o['record_max_shrt']: record(MxShrt  = p['max_shrt'])

    if risk > p['risk_hi']:  
        new_key_hi = 1  
        p['risk_hi'] = risk                   # Highest risk overall  
        if o['record_risk_hi']:  record(RiskHi  = p['risk_hi'])

    # Profit_vs_Risk returns based on max amount actually invested, long or short  
    if p['risk_hi'] != 0: # Avoid zero-divide  
        p['pvr'] = 100 * pnl / p['risk_hi']  
        ptype = 'PvRp' if o['record_pvrp'] else 'PvR'  
        if o['record_pvr'] or o['record_pvrp']: record(**{ptype: p['pvr']})

    if o['record_shorting']: record(Shorts = shorts)             # Shorts value as a positve  
    if o['record_leverage']: record(Lvrg   = c.account.leverage) # Leverage  
    if o['record_cash']    : record(Cash   = cash)               # Cash  
    if o['record_risk']    : record(Risk   = risk)  # Amount in play, maximum of shorts or cash used  
    if o['record_q_return']: record(QRet   = 100 * pf.returns)  
    if o['record_pnl']     : record(PnL    = pnl)                # Profit|Loss

    if o['logging'] and new_key_hi:  
        log.info('{}{}{}{}{}{}{}{}{}{}{}{}'.format(_minut(),  
            ' Lv '     + '%.1f' % c.account.leverage,  
            ' MxLv '   + '%.2f' % p['max_lvrg'],  
            ' QRet '   + '%.1f' % (100 * pf.returns),  
            ' PvR '    + '%.1f' % p['pvr'],  
            ' PnL '    + '%.0f' % pnl,  
            ' Cash '   + '%.0f' % cash,  
            ' CshLw '  + '%.0f' % p['cash_low'],  
            ' Shrt '   + '%.0f' % shorts,  
            ' MxShrt ' + '%.0f' % p['max_shrt'],  
            ' Risk '   + '%.0f' % risk,  
            ' RskHi '  + '%.0f' % p['risk_hi']  
        ))  
    if do_summary: _pvr(c)  
    if get_datetime() == get_environment('end'):   # Summary at end of run  
        _pvr(c) ; elapsed = (time.time() - p['begin']) / 60  # minutes  
        log.info( '{}\nRuntime {} hr {} min'.format(p['run_str'], int(elapsed / 60), '%.1f' % (elapsed % 60)))
There was a runtime error.

A slight improvement.
Has a lower Max DrawDown
Always cash available, no rush sale just to make a withdrawal
Reasonable Risk Metrics
Pvr metrics seem good
2018-03-20 15:00 _pvr:735 INFO PvRp 1.5626 %/day cagr 0.856 Portfolio value 415818 PnL 350818
2018-03-20 15:00 _pvr:736 INFO Profited 350818 on 29697 activated/transacted for PvR of 1181.3%
2018-03-20 15:00 _pvr:737 INFO QRet 539.72 PvR 1181.32 CshLw 35302 MxLv 0.51 RskHi 29697 MxShrt 0

Clone Algorithm
332
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
#Imports
from datetime import datetime  
from pytz import timezone      
from zipline.utils import tradingcalendar
from quantopian.pipeline.data.builtin import USEquityPricing
import statsmodels.api as sm 
import numpy as np
import pandas as pd
import functools
import quantopian.pipeline.data 
import re
import scipy
import talib
import time

History = 128

def initialize(context):
    c = context

    vixUrl = 'http://www.cboe.com/publish/scheduledtask/mktdata/datahouse/vixcurrent.csv'
    vxvUrl = 'http://www.cboe.com/publish/scheduledtask/mktdata/datahouse/vix3mdailyprices.csv'
    nokoUrl = 'http://173.212.203.121/noko.csv'  
    
    fetch_csv(nokoUrl, 
              symbol='v1', 
              date_column='Date', 
              date_format='%Y-%m-%d', 
              pre_func=addFieldsVX1, 
              post_func=shift_data)
    
    fetch_csv(nokoUrl, 
              symbol='v2', 
              date_column='Date', 
              date_format='%Y-%m-%d', 
              pre_func=addFieldsVX2, 
              post_func=shift_data)

    fetch_csv(vixUrl, 
              symbol='v', 
              skiprows=1,
              date_column='Date', 
              pre_func=addFieldsVIX,
              post_func=shift_data)

    fetch_csv(vxvUrl, 
              symbol='vxv', 
              skiprows=2,
              date_column='Date', 
              pre_func=addFieldsVXV,
              post_func=shift_data)

    context.RobinHoodMargin = 0 # I prefer to not use margin.
    
    set_benchmark(symbol('SVXY'))
    context.SVXY = symbol('SVXY')
    context.TQQQ = symbol('TQQQ')
    context.UVXY = symbol('UVXY')
    context.SPYG = symbol('SPYG')

    context.ShowMaxLev = True
    context.vix = -1
    context.BigSignalShortVIX = context.BigSignalTQQQ = context.BigSignalLongVIX = False
    context.PriceSVXY = context.PriceUVXY = 0.00
    context.BoughtShortVIX = context.BoughtLongVIX = False

    #Internal stop variables
    # context.stoploss is set in handle_data
    context.stops         = {}
    context.stoppedout    = []
    c.SellAndWait = c.SVXY_DaysNotOwned = c.UVXY_DaysOwned = 0
    c.HaveToThink = False
    c.DaysHaveToThink = 0

    context.OkToAct = context.TooLateToAct = False
    
    #Used for logging
    context.last_bar = False
    context.SignalSellSVXY = False
    context.SignalBuySVXY  = False
    context.SignalBuyUVXY  = False

    # wait after market open before doing any possible orders
    schedule_function(OkToActTrigger, date_rules.every_day(), time_rules.market_open(minutes = 3))

    # too late to act because of 15 minute delay plus 10 minute cushion
    schedule_function(TooLateToActTrigger, date_rules.every_day(), time_rules.market_close(minutes = 25))

    # apparently highly successful VIX signals
    schedule_function(MoreSignals, date_rules.every_day(), time_rules.market_open(minutes = 2))

    # On Bar Close Functions
    for x in [1,3,5]:
        schedule_function(Rebalance, date_rules.every_day(), time_rules.market_open(hours=x, minutes=59))
    schedule_function(Rebalance, date_rules.every_day(), time_rules.market_close())
    
    schedule_function(my_record_vars, date_rules.every_day(), time_rules.market_close())

    #Set commission and slippage
    set_commission(commission.PerShare(cost=0.000, min_trade_cost=0.0)) 
    set_slippage(slippage.FixedSlippage(spread=0.01))
    
def OkToActTrigger(context,data): 
    context.OkToAct = True

def TooLateToActTrigger(context,data): 
    context.TooLateToAct = True

def before_trading_start(context, data):
    c = context
    c.OkToAct = c.TooLateToAct = False
    c.mx_lvrg  = 0 # daily max leverage

    if 0 == c.portfolio.positions[c.SVXY].amount:
        c.SVXY_DaysNotOwned += 1
    else:
        c.SVXY_DaysNotOwned  = 0

    if 0 < c.portfolio.positions[c.UVXY].amount:
        c.UVXY_DaysOwned += 1
    else:
        c.UVXY_DaysOwned  = 0

    if c.HaveToThink:
        c.DaysHaveToThink += 1
    else:
        c.DaysHaveToThink  = 0
    c.HaveToThink = False

def TarPer(context, data, stock, TargetPercent):

    cancel_open_orders_for(context, data, stock)
    if DataCanTrade(context, data, stock):

        if 0 == TargetPercent:
            amount = context.portfolio.positions[stock].amount
            price = data.current(stock, 'price')
            order_target_percent(stock, 0.00)
            log.info('SOLD ALL {} {} Shares @ MarketPrice {:.2f}'\
            .format(stock.symbol, amount, price))
        else:
            # Always want money available to withdraw 
            # and also try to prevent margin related order rejections
            PV = context.portfolio.portfolio_value
            DoNotSpend = 0 # 0 is ok if MaxLeverage is less than 0.50
            RhMargin = context.RobinHoodMargin # Set to 0 if you do not have Robinhood Gold or if you do not use the margin money
            MaxLeverage = 0.45 # Because we are day trading, we have to attempt to NOT use same day sell cash to buy shares, so that we can completely exit the same day if necessary. So 0.45 avoids that in a same - day sell, buy, sell situation.
            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
            Action = "Bought" if 0 < DiffAmount else "Sold"
            if stock is context.UVXY:
                LimitPrice = price * 1.00
                order(stock, DiffAmount, style=LimitOrder(LimitPrice))
                log.info('{} {} {} Shares @ LimitPrice {:.2f}'\
                .format(Action, stock.symbol, DiffAmount, LimitPrice))
            else:
                order(stock, DiffAmount)
                log.info('{} {} {} Shares @ MarketPrice {:.2f}'\
                .format(Action, stock.symbol, DiffAmount, price))

        if stock.symbol in context.stops: del context.stops[stock.symbol]

def Rebalance(context,data):
    c = context
    if not c.BigSignalShortVIX and not c.BigSignalTQQQ and not c.BigSignalLongVIX: MoreSignals(context, data)
    cancel_open_orders(context, data)
    #looking at 4 days of minutely price history
    svxy_prices = data.history(c.SVXY, "price", 1600, "1m").resample('30T',  closed='right', label='right').last().dropna()

    #convert to 2 hour:
    svxy2hr = []
    if '20:00:00+00:00' in str(svxy_prices.index[-1]) or '21:00:00+00:00' in str(svxy_prices.index[-1]):
        svxy2hr.append([svxy_prices.index[-28], svxy_prices[-28]])
        svxy2hr.append([svxy_prices.index[-27], svxy_prices[-27]])        
        svxy2hr.append([svxy_prices.index[-23], svxy_prices[-23]])
        svxy2hr.append([svxy_prices.index[-19], svxy_prices[-19]])
        svxy2hr.append([svxy_prices.index[-15], svxy_prices[-15]])
        svxy2hr.append([svxy_prices.index[-14], svxy_prices[-14]])
        svxy2hr.append([svxy_prices.index[-10], svxy_prices[-10]])
        svxy2hr.append([svxy_prices.index[-6], svxy_prices[-6]])
        svxy2hr.append([svxy_prices.index[-2], svxy_prices[-2]])
        svxy2hr.append([svxy_prices.index[-1], svxy_prices[-1]])
        c.last_bar = True
    elif '19:30:00+00:00' in str(svxy_prices.index[-1]) or '20:30:00+00:00' in str(svxy_prices.index[-1]):
        svxy2hr.append([svxy_prices.index[-31], svxy_prices[-31]])
        svxy2hr.append([svxy_prices.index[-27], svxy_prices[-27]]) 
        svxy2hr.append([svxy_prices.index[-26], svxy_prices[-26]])
        svxy2hr.append([svxy_prices.index[-22], svxy_prices[-22]])
        svxy2hr.append([svxy_prices.index[-18], svxy_prices[-18]])
        svxy2hr.append([svxy_prices.index[-14], svxy_prices[-14]]) 
        svxy2hr.append([svxy_prices.index[-13], svxy_prices[-13]])
        svxy2hr.append([svxy_prices.index[-9], svxy_prices[-9]])
        svxy2hr.append([svxy_prices.index[-5], svxy_prices[-5]])
        svxy2hr.append([svxy_prices.index[-1], svxy_prices[-1]])
        c.last_bar = False
    elif '17:30:00+00:00' in str(svxy_prices.index[-1]) or '18:30:00+00:00' in str(svxy_prices.index[-1]):
        svxy2hr.append([svxy_prices.index[-31], svxy_prices[-31]])
        svxy2hr.append([svxy_prices.index[-27], svxy_prices[-27]]) 
        svxy2hr.append([svxy_prices.index[-23], svxy_prices[-23]])
        svxy2hr.append([svxy_prices.index[-22], svxy_prices[-22]])
        svxy2hr.append([svxy_prices.index[-18], svxy_prices[-18]])
        svxy2hr.append([svxy_prices.index[-14], svxy_prices[-14]])
        svxy2hr.append([svxy_prices.index[-10], svxy_prices[-10]])
        svxy2hr.append([svxy_prices.index[-9], svxy_prices[-9]])
        svxy2hr.append([svxy_prices.index[-5], svxy_prices[-5]])
        svxy2hr.append([svxy_prices.index[-1], svxy_prices[-1]])
        c.last_bar = False
    elif '15:30:00+00:00' in str(svxy_prices.index[-1]) or '16:30:00+00:00' in str(svxy_prices.index[-1]):
        svxy2hr.append([svxy_prices.index[-31], svxy_prices[-31]])
        svxy2hr.append([svxy_prices.index[-27], svxy_prices[-27]]) 
        svxy2hr.append([svxy_prices.index[-23], svxy_prices[-23]])
        svxy2hr.append([svxy_prices.index[-19], svxy_prices[-19]])
        svxy2hr.append([svxy_prices.index[-18], svxy_prices[-18]])
        svxy2hr.append([svxy_prices.index[-14], svxy_prices[-14]])        
        svxy2hr.append([svxy_prices.index[-10], svxy_prices[-10]])
        svxy2hr.append([svxy_prices.index[-6], svxy_prices[-6]])
        svxy2hr.append([svxy_prices.index[-5], svxy_prices[-5]])
        svxy2hr.append([svxy_prices.index[-1], svxy_prices[-1]])
        c.last_bar = False
    else:
        log.error("2 HOUR CONVERSION FAILURE")
        return
    dates, vals = zip(*svxy2hr)
    s = pd.Series(vals, index=dates)
    
    rsi  = talib.RSI(s,2)
    RSI5 = talib.RSI(s,5) 

    # BUY SVXY RULE
    if rsi[-2] < 70 and rsi[-1] >= 70 and (
        0 == c.portfolio.positions[c.UVXY].amount
        or
        0 < c.UVXY_DaysOwned
    ):
        c.SignalBuySVXY  = True
        c.BoughtShortVIX = False

    # SELL SVXY RULE or maybe BUY UVXY
    if rsi[-2] > 85 and rsi[-1] <= 85:
        c.SignalSellSVXY = True
        if RSI5[-1] < 70:
            c.SignalBuyUVXY = True
            c.BoughtLongVIX = False
    # maybe BUY UVXY
    if (
        not c.SignalBuySVXY
        and
        0 == c.portfolio.positions[c.SVXY].amount
        and
        ( c.BigSignalLongVIX or 1 < c.DaysHaveToThink )
        and
        ( 2.00 <= c.dispersion or 0 < c.SVXY_DaysNotOwned )
    ):
        c.SignalBuyUVXY = True
        c.BoughtLongVIX = False

def handle_data(context, data):
    pvr(context, data)
    c = context

    c.SellAndWait -= 1 # 1 unit is 1 minute
    c.SellAndWait = 0 if 0 > c.SellAndWait else c.SellAndWait

    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 not c.SellAndWait and c.OkToAct and not c.TooLateToAct:
        for position in c.portfolio.positions.itervalues():

            ##
            # Variable stop loss and take profit based on signals
            ##
            SVXY_Dispersion = 3.00 # SVXY 3.00 may be best overall
            UVXY_Dispersion = 3.00 # UVXY no idea what is best yet
            stoploss = 0.25 # default - probably not used
            SVXY_Up = 1.07 # default - probably not used
            UVXY_Up = 1.10 # default - probably not used
            if position.sid is c.UVXY:
                if (
                    UVXY_Dispersion <= c.dispersion
                    or
                    1 <= c.SVXY_DaysNotOwned
                ):
                    stoploss = 0.21 # 0.21 may be best
                    UVXY_Up  = 1.18
                else:
                    stoploss = 0.03 if c.BigSignalLongVIX else 0.01
                    UVXY_Up  = 1.12 if c.BigSignalLongVIX else 1.10
            else:
                if SVXY_Dispersion <= c.dispersion:
                    stoploss = 0.25
                    SVXY_Up  = 1.25
                else:
                    if c.BigSignalTQQQ:
                        stoploss = 0.02
                        SVXY_Up  = 1.07
                    if c.BigSignalShortVIX:
                        stoploss = 0.03
                        SVXY_Up  = 1.15

            if position.amount == 0:
                if position.asset.symbol in c.stops: del c.stops[position.asset.symbol]
                continue
            elif position.asset.symbol not in c.stops:
                stoploss= stoploss if position.amount > 0 else -stoploss
                c.stops[position.asset.symbol]=position.last_sale_price*(1-stoploss)
                #log.info(' ! I have added '+str(position.asset.symbol)+' to Stops @ '+str((position.last_sale_price)*(1-stoploss)))
            elif c.stops[position.asset.symbol] < position.last_sale_price*(1- stoploss) and position.amount > 0:
                c.stops[position.asset.symbol]=position.last_sale_price*(1- stoploss)
                #log.info(' ! I have updated '+str(position.asset.symbol)+'- (Long) to stop @ '+str((position.last_sale_price)*(1- stoploss)))
                # set break even
                if position.last_sale_price > position.cost_basis * 1.075:
                    if position.cost_basis > c.stops[position.asset.symbol]:
                        c.stops[position.asset.symbol] = position.cost_basis
            elif c.stops[position.asset.symbol] > position.last_sale_price and position.amount > 0:
                #sell
                log.info(' ! '+str(position.asset.symbol)+'- (Long) has hit stoploss @ '+str(position.last_sale_price))
                if get_open_orders(position.sid): cancel_open_orders_for(context, data, position.sid)
                c.stoppedout.append(position.asset.symbol)
                c.PriceSVXY = c.PriceUVXY = 0.00
                c.SignalBuySVXY = c.SignalSellSVXY = c.SignalBuyUVXY = False
                if position.sid is c.UVXY: c.SellAndWait = int(390 * 1.00)
                else: c.SellAndWait = 1
 
            ##
            # Sell and Wait
            ##
            if (
                    (position.sid is not c.UVXY and 0 < position.amount)
                    and
                    (position.last_sale_price > SVXY_Up * position.cost_basis)
            ):
                c.SellAndWait = int(390 * 1.33)
                log.warn(str(position.sid.symbol) + ' ' + str(SVXY_Up) + \
                ' growth reached SellAndWait triggered: ' + \
                str(c.SellAndWait) + ' minutes')
            elif (
                    (position.sid is c.UVXY and 0 < position.amount)
                    and
                    (position.last_sale_price > UVXY_Up * position.cost_basis)
            ):
                c.SellAndWait = int(390 * 1.66)
                log.warn(str(position.sid.symbol) + ' ' + str(UVXY_Up) + \
                ' growth reached SellAndWait triggered: ' + \
                str(c.SellAndWait) + ' minutes')

        ##
        # Sell and Wait
        ##
        if 0 < c.SellAndWait:
            c.PriceSVXY = c.PriceUVXY = 0.00
            c.SignalBuySVXY = c.SignalSellSVXY = c.SignalBuyUVXY = False
            for stock in c.portfolio.positions:
                cancel_open_orders_for(context, data, stock)
                TarPer(context, data, stock, 0.00)
        ##
        # Slippery Buy allows for some drop before buy
        ##
        else:
            SVXY_UpAfterSlip = 1.0025
            UVXY_UpAfterSlip = 1.005
            if c.SignalBuySVXY:
                if 0 < c.portfolio.positions[c.UVXY].amount:
                    c.PriceUVXY = 0.00
                    c.SignalBuyUVXY = False
                    TarPer(context, data, c.UVXY, 0.000)
                elif 0 == c.portfolio.positions[c.SVXY].amount:
                    if not c.BoughtShortVIX:
                        PriceSVXY = data.current(c.SVXY, 'price')
                        if not c.PriceSVXY: c.PriceSVXY = PriceSVXY
                        if PriceSVXY < c.PriceSVXY:
                            c.PriceSVXY = PriceSVXY
                        elif PriceSVXY > SVXY_UpAfterSlip * c.PriceSVXY:
                            c.PriceSVXY = c.PriceUVXY = 0.00
                            c.SignalBuySVXY = c.SignalSellSVXY = c.SignalBuyUVXY = False
                            c.BoughtShortVIX = True
                            TarPer(context, data, c.SVXY, 0.999)
            elif c.SignalSellSVXY:
                if c.portfolio.positions[c.SVXY].amount > 0:
                    c.PriceSVXY = c.PriceUVXY = 0.00
                    c.SignalBuySVXY = c.SignalSellSVXY = c.SignalBuyUVXY = False
                    TarPer(context, data, c.SVXY, 0)
            elif c.SignalBuyUVXY:
                if 0 < c.portfolio.positions[c.SVXY].amount:
                    c.PriceSVXY = c.PriceUVXY = 0.00
                    c.SignalBuySVXY = c.SignalSellSVXY = False
                    TarPer(context, data, c.SVXY, 0.000)
                elif 0 == c.portfolio.positions[c.UVXY].amount:
                    if not c.BoughtLongVIX:
                        PriceUVXY = data.current(c.UVXY, 'price')
                        if not c.PriceUVXY: c.PriceUVXY = PriceUVXY
                        if PriceUVXY < c.PriceUVXY:
                            c.PriceUVXY = PriceUVXY
                        elif PriceUVXY > UVXY_UpAfterSlip * c.PriceUVXY:
                            c.PriceSVXY = c.PriceUVXY = 0.00
                            c.SignalBuySVXY = c.SignalSellSVXY = c.SignalBuyUVXY = False
                            c.BoughtLongVIX = True
                            TarPer(context, data, c.UVXY, 0.999)

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 DataCanTrade(context, data, stock):

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

def cancel_open_orders(context, data):
    oo = get_open_orders()
    if len(oo) == 0:
        return
    for stock, orders in oo.iteritems():
        for order in orders:
            cancel_order(order)
            #message = 'Canceling order of {amount} shares in {stock}'
            #log.info(message.format(amount=order.amount, stock=stock))
    
def cancel_open_orders_for(context, data, security):
    oo = get_open_orders()
    if len(oo) == 0:
        return
    for stock, orders in oo.iteritems():
        for order in orders:
            if stock is security:
                if order.amount < context.portfolio.positions[stock].amount:
                    cancel_order(order)
                    #message = 'Canceling order of {amount} shares in {stock}'
                    #log.info(message.format(amount=order.amount, stock=stock))
                else: # Do NOT want to cancel stop loss order
                    #message = 'NOT Canceling stop loss order of {amount} shares in {stock}'
                    #log.info(message.format(amount=order.amount, stock=stock))
                    pass

def MoreSignals(context, data):
    c = context

    update_indices(context, data)
    target_sid = context.SVXY # default if no conditions are met
    last_vix = context.VIXprice = data.current('v', 'Close')
    last_vx1 = data.current('v1','Close')  
    last_vx2 = data.current('v2','Close')      
    last_vxv = data.current('vxv', 'Close')
    last_vix_200ma_ratio = data.current('v', '200ma Ratio')
    # Maybe use dispersion aka vix standard deviation in buying mechanism
    context.dispersion = data.current('v','20dstd')
    #context.threshold_dispersion = 0.55

    
    record(dispersion=context.dispersion)
    record(last_vixDivBy10=last_vix/10)
    # Calculating the gap between spot vix and the first month vix future
    last_ratio_v_v1 = last_vix/last_vx1

    # Calculating the contango ratio of the front and second month VIX Futures 
    last_ratio_v1_v2 = last_vx1/last_vx2

    # Blending the previous two ratios together using a weighted average
    ratio_weight = 0.7
    last_ratio = (ratio_weight*last_ratio_v_v1) + ((1-ratio_weight)*last_ratio_v1_v2) - 1
    
    vix_vxv_ratio = last_vix/last_vxv
    
    # Retrieve SPY prices for technical indicators
    prices = data.history(context.SPYG, 'open', 40, '1d')
    
    # Retrieve SPY MACD data
    macda, signal, hist = talib.MACD(prices, fastperiod=12,slowperiod=26,signalperiod=9)
    macd = macda[-1] - signal[-1]
    
    # Calculate how much vix moved the previous day
    if (context.vix <> -1) : 
        vix_ratio = last_vix/context.vix -1
    else :
        vix_ratio = 0
    context.vix = last_vix
    
    SVXY_history = data.history(context.SVXY, 'price', 2, '1d')  
    
    SVXY_ratio = SVXY_history[1]/SVXY_history[0] - 1
    
    # Setting thresholds
    #if
    threshold_vix_too_low = 10.76   # 0 
    threshold_vix_200ma_ratio_low = 0.79  # 0 
    threshold_SVXY = -0.049          # 1
    threshold_vxv_SVXY = 0.87        # 2
    threshold_uvxy = 0.049          # 3
    threshold_macd = -0.55          # 3
    threshold_vxv_uvxy = 1.3        # 4
    threshold_vix_high = 19.9       # 5
    threshold_vc_low = -0.148       # 6
    threshold_vc_high = 0.046       # 8
    threshold_vc_high_2 = -0.06     # 8 HaveToThink
    #if
    threshold_SVXY_ratio = -0.053    # 10
    threshold_uvxy_ratio = 0.08     # 11
    # 0
    if last_vix < threshold_vix_too_low \
            and last_vix_200ma_ratio < threshold_vix_200ma_ratio_low :
        log.info('! if VIX is too low, invest in UVXY with the hope of a spike')
        target_sid = context.UVXY
    # 1        
    elif last_ratio < threshold_SVXY: # 
        log.info('! if contango is high, invest in SVXY to gain from decay')
        target_sid = context.SVXY
    
    # 2
    elif vix_vxv_ratio < threshold_vxv_SVXY: # 
        log.info('! if short term vol is low compared to mid term, invest in SVXY to gain from decay')
        target_sid = context.SVXY

    # 3
    elif last_ratio > threshold_uvxy and macd > threshold_macd: # 
        log.info('! if backwardation is high, invest in UVXY to gain from decay')
        target_sid = context.UVXY

    # 4
    elif vix_vxv_ratio > threshold_vxv_uvxy: # 
        log.info('! if short term vol is high compared to mid term, invest in UVXY to gain from growth')
        target_sid = context.UVXY

    # 5
    elif last_vix > threshold_vix_high: # 
        log.info('! if VIX is too high, invest in SVXY expecting that VIX will drop')
        target_sid = context.SVXY

    # 6
    elif vix_ratio < threshold_vc_low: # 
        log.info('! Vix down sharply, invest in SVXY expecting that futures curve gets pulled down')
        target_sid = context.SVXY

    # 7
    elif vix_ratio > threshold_vc_high: # 
        log.info('! Vix up sharply, invest in UVXY expecting that futures curve gets pulled up')
        target_sid = context.UVXY

    # 8
    elif vix_ratio > threshold_vc_high_2: #
        log.info('! have to think')
        target_sid = context.SVXY
        c.HaveToThink = True

    # 10
    if (target_sid == context.SVXY and SVXY_ratio < threshold_SVXY_ratio) : 
        log.info('! indicators say SVXY but it just dropped overnight, so go for TQQQ')
        target_sid = context.TQQQ 

    # 11
    elif (target_sid == context.UVXY and SVXY_ratio > threshold_uvxy_ratio) :
        log.info('! indicators say UVXY but it just dropped overnight, so go for TQQQ')
        target_sid = context.TQQQ
    
    context.BigSignalShortVIX = True if context.SVXY is target_sid else False
    context.BigSignalLongVIX  = True if context.UVXY is target_sid else False
    context.BigSignalTQQQ     = True if context.TQQQ is target_sid else False
    
def update_indices(context, data):
    context.fetch_failed = False
    context.vix_vals = unpack_from_data(context, data, 'v')    
    context.vxv_vals = unpack_from_data(context, data, 'vxv')  
    context.vx1_vals = unpack_from_data(context, data, 'v1')
    context.vx2_vals = unpack_from_data(context, data, 'v2')

def fix_close(df,closeField):
    df = df.rename(columns={closeField:'Close'})
    # remove spurious asterisks
    df['Date'] = df['Date'].apply(lambda dt: re.sub('\*','',dt))
    # convert date column to timestamps
    df['Date'] = df['Date'].apply(lambda dt: pd.Timestamp(datetime.strptime(dt,'%m/%d/%Y')))
    df = df.sort_values(by='Date', ascending=True)
    return df

def fix_closeVX(df,closeField):
    df = df.rename(columns={closeField:'Close'})
    # remove spurious asterisks
    df['Date'] = df['Date'].apply(lambda dt: re.sub('\*','',dt))
    # convert date column to timestamps
    df['Date'] = df['Date'].apply(lambda dt: pd.Timestamp(datetime.strptime(dt,'%Y-%m-%d')))
    df = df.sort_values(by='Date', ascending=True)
    return df


def subsequent_trading_date(date):
    tdays = tradingcalendar.trading_days
    last_date = pd.to_datetime(date)
    last_dt = tradingcalendar.canonicalize_datetime(last_date)
    next_dt = tdays[tdays.searchsorted(last_dt) + 1]
    return next_dt

def add_last_bar(df):
    last_date = df.index[-1]
    subsequent_date = subsequent_trading_date(last_date)
    blank_row = pd.Series({}, index=df.columns, name=subsequent_date)
    # add today, and shift all previous data up to today. This 
    # should result in the same data frames as in backtest
    df = df.append(blank_row).shift(1).dropna(how='all')
    return df

def shift_data(df):
    df = add_last_bar(df)
    df.fillna(method='ffill') 
    df['PrevCloses'] = my_rolling_apply_series(df['Close'], to_csv_str, History)
    dates = pd.Series(df.index)
    dates.index = df.index
    df['PrevDates'] = my_rolling_apply_series(dates, to_csv_str, History)
    return df

def unpack_from_data(context, data, sym):
    try:
        v = data.current(sym, 'PrevCloses')
        i = data.current(sym, 'PrevDates')
        return from_csv_strs(i,v,True).apply(float)
    except:
        log.warn("Unable to unpack historical {s} data.".format(s=sym))
        context.fetch_failed = True

def addFieldsVIX(df):
    df = fix_close(df,'VIX Close')
    df['200ma'] = df['Close'].rolling(200).mean()
    df['200ma Ratio'] = df['Close'] / df['200ma']
    df['20dstd'] =  df['Close'].rolling(20).std()

    return df

def addFieldsVXV(df):
    df.rename(columns={'Unnamed: 0': 'Date'}, inplace=True)
    df = fix_close(df,'CLOSE')
    return df

def addFieldsVX1(df):
    df = fix_closeVX(df,'F1')
    return df

def addFieldsVX2(df):
    df = fix_closeVX(df,'F2')
    return df

# convert a series of values to a comma-separated string of said values
def to_csv_str(s):
    return functools.reduce(lambda x,y: x+','+y, pd.Series(s).apply(str))

# a specific instance of rolling apply, for Series of any type (not just numeric,
# ala pandas.rolling_apply), where the index of the series is set to the indices
# of the last elements of each subset
def my_rolling_apply_series(s_in, f, n):
    s_out = pd.Series([f(s_in[i:i+n]) for i in range(0,len(s_in)-(n-1))]) 
    s_out.index = s_in.index[n-1:]
    return s_out

# reconstitutes a Series from two csv-encoded strings, one of the index, one of the values
def from_csv_strs(x, y, idx_is_date):
    s = pd.Series(y.split(','),index=x.split(','))
    if (idx_is_date):
        s.index = s.index.map(lambda x: pd.Timestamp(x))
    return s

def pvr(context, data):
    ''' Custom chart and/or logging of profit_vs_risk returns and related information from https://www.quantopian.com/posts/pvr#569784bda73e9bf2b7000180
    '''  
    #import time  
    #from datetime import datetime  
    #from pytz import timezone      # Python will only do once, makes this portable.  
                                   #   Move to top of algo for better efficiency.  
    c = context  # Brevity is the soul of wit -- Shakespeare [for readability]  
    if 'pvr' not in c:

        # For real money, you can modify this to total cash input minus any withdrawals  
        manual_cash = c.portfolio.starting_cash  
        time_zone   = 'US/Central'   # Optionally change to your own time zone for wall clock time

        c.pvr = {  
            'options': {  
                # # # # # # # # # #  Options  # # # # # # # # # #  
                'logging'         : 0,    # Info to logging window with some new maximums  
                'log_summary'     : 126,  # Summary every x days. 252/yr

                'record_pvr'      : 0,    # Profit vs Risk returns (percentage)  
                'record_pvrp'     : 0,    # PvR (p)roportional neg cash vs portfolio value  
                'record_cash'     : 1,    # Cash available  
                'record_max_lvrg' : 0,    # Maximum leverage encountered  
                'record_risk_hi'  : 0,    # Highest risk overall  
                'record_shorting' : 0,    # Total value of any shorts  
                'record_max_shrt' : 0,    # Max value of shorting total  
                'record_cash_low' : 0,    # Any new lowest cash level  
                'record_q_return' : 0,    # Quantopian returns (percentage)  
                'record_pnl'      : 0,    # Profit-n-Loss  
                'record_risk'     : 1,    # Risked, max cash spent or shorts beyond longs+cash  
                'record_leverage' : 0,    # End of day leverage (context.account.leverage)  
                # All records are end-of-day or the last data sent to chart during any day.  
                # The way the chart operates, only the last value of the day will be seen.  
                # # # # # # # # #  End options  # # # # # # # # #  
            },  
            'pvr'        : 0,      # Profit vs Risk returns based on maximum spent  
            'cagr'       : 0,  
            'max_lvrg'   : 0,  
            'max_shrt'   : 0,  
            'risk_hi'    : 0,  
            'days'       : 0.0,  
            'date_prv'   : '',  
            'date_end'   : get_environment('end').date(),  
            'cash_low'   : manual_cash,  
            'cash'       : manual_cash,  
            'start'      : manual_cash,  
            'tz'         : time_zone,  
            'begin'      : time.time(),  # For run time  
            'run_str'    : '{} to {}  ${}  {} {}'.format(get_environment('start').date(), get_environment('end').date(), int(manual_cash), datetime.now(timezone(time_zone)).strftime("%Y-%m-%d %H:%M"), time_zone)  
        }  
        if c.pvr['options']['record_pvrp']: c.pvr['options']['record_pvr'] = 0 # if pvrp is active, straight pvr is off  
        if get_environment('arena') not in ['backtest', 'live']: c.pvr['options']['log_summary'] = 1 # Every day when real money  
        log.info(c.pvr['run_str'])  
    p = c.pvr ; o = c.pvr['options'] ; pf = c.portfolio ; pnl = pf.portfolio_value - p['start']  
    def _pvr(c):  
        p['cagr'] = ((pf.portfolio_value / p['start']) ** (1 / (p['days'] / 252.))) - 1  
        ptype = 'PvR' if o['record_pvr'] else 'PvRp'  
        log.info('{} {} %/day   cagr {}   Portfolio value {}   PnL {}'.format(ptype, '%.4f' % (p['pvr'] / p['days']), '%.3f' % p['cagr'], '%.0f' % pf.portfolio_value, '%.0f' % pnl))  
        log.info('  Profited {} on {} activated/transacted for PvR of {}%'.format('%.0f' % pnl, '%.0f' % p['risk_hi'], '%.1f' % p['pvr']))  
        log.info('  QRet {} PvR {} CshLw {} MxLv {} RskHi {} MxShrt {}'.format('%.2f' % (100 * pf.returns), '%.2f' % p['pvr'], '%.0f' % p['cash_low'], '%.2f' % p['max_lvrg'], '%.0f' % p['risk_hi'], '%.0f' % p['max_shrt']))  
    def _minut():  
        dt = get_datetime().astimezone(timezone(p['tz']))  
        return str((dt.hour * 60) + dt.minute - 570).rjust(3)  # (-570 = 9:31a)  
    date = get_datetime().date()  
    if p['date_prv'] != date:  
        p['date_prv'] = date  
        p['days'] += 1.0  
    do_summary = 0  
    if o['log_summary'] and p['days'] % o['log_summary'] == 0 and _minut() == '100':  
        do_summary = 1              # Log summary every x days  
    if do_summary or date == p['date_end']:  
        p['cash'] = pf.cash  
    elif p['cash'] == pf.cash and not o['logging']: return  # for speed

    shorts = sum([z.amount * z.last_sale_price for s, z in pf.positions.items() if z.amount < 0])  
    new_key_hi = 0                  # To trigger logging if on.  
    cash       = pf.cash  
    cash_dip   = int(max(0, p['start'] - cash))  
    risk       = int(max(cash_dip, -shorts))

    if o['record_pvrp'] and cash < 0:   # Let negative cash ding less when portfolio is up.  
        cash_dip = int(max(0, cash_dip * p['start'] / pf.portfolio_value))  
        # Imagine: Start with 10, grows to 1000, goes negative to -10, should not be 200% risk.

    if int(cash) < p['cash_low']:             # New cash low  
        new_key_hi = 1  
        p['cash_low'] = int(cash)             # Lowest cash level hit  
        if o['record_cash_low']: record(CashLow = p['cash_low'])

    if c.account.leverage > p['max_lvrg']:  
        new_key_hi = 1  
        p['max_lvrg'] = c.account.leverage    # Maximum intraday leverage  
        if o['record_max_lvrg']: record(MaxLv   = p['max_lvrg'])

    if shorts < p['max_shrt']:  
        new_key_hi = 1  
        p['max_shrt'] = shorts                # Maximum shorts value  
        if o['record_max_shrt']: record(MxShrt  = p['max_shrt'])

    if risk > p['risk_hi']:  
        new_key_hi = 1  
        p['risk_hi'] = risk                   # Highest risk overall  
        if o['record_risk_hi']:  record(RiskHi  = p['risk_hi'])

    # Profit_vs_Risk returns based on max amount actually invested, long or short  
    if p['risk_hi'] != 0: # Avoid zero-divide  
        p['pvr'] = 100 * pnl / p['risk_hi']  
        ptype = 'PvRp' if o['record_pvrp'] else 'PvR'  
        if o['record_pvr'] or o['record_pvrp']: record(**{ptype: p['pvr']})

    if o['record_shorting']: record(Shorts = shorts)             # Shorts value as a positve  
    if o['record_leverage']: record(Lvrg   = c.account.leverage) # Leverage  
    if o['record_cash']    : record(Cash   = cash)               # Cash  
    if o['record_risk']    : record(Risk   = risk)  # Amount in play, maximum of shorts or cash used  
    if o['record_q_return']: record(QRet   = 100 * pf.returns)  
    if o['record_pnl']     : record(PnL    = pnl)                # Profit|Loss

    if o['logging'] and new_key_hi:  
        log.info('{}{}{}{}{}{}{}{}{}{}{}{}'.format(_minut(),  
            ' Lv '     + '%.1f' % c.account.leverage,  
            ' MxLv '   + '%.2f' % p['max_lvrg'],  
            ' QRet '   + '%.1f' % (100 * pf.returns),  
            ' PvR '    + '%.1f' % p['pvr'],  
            ' PnL '    + '%.0f' % pnl,  
            ' Cash '   + '%.0f' % cash,  
            ' CshLw '  + '%.0f' % p['cash_low'],  
            ' Shrt '   + '%.0f' % shorts,  
            ' MxShrt ' + '%.0f' % p['max_shrt'],  
            ' Risk '   + '%.0f' % risk,  
            ' RskHi '  + '%.0f' % p['risk_hi']  
        ))  
    if do_summary: _pvr(c)  
    if get_datetime() == get_environment('end'):   # Summary at end of run  
        _pvr(c) ; elapsed = (time.time() - p['begin']) / 60  # minutes  
        log.info( '{}\nRuntime {} hr {} min'.format(p['run_str'], int(elapsed / 60), '%.1f' % (elapsed % 60)))
There was a runtime error.

Sorry, yet I am confused on the function of TarPer you defined in this algo. I used the built-in order_target_percent, instead of your TarPer, and the result is totally different. Is this TarPer-style ordering more advanced?

@Di Wang,
I would not call it more advanced. It is a matter of personal preference. I like to customize the behavior. This way I know what it is doing.

I am surprised that so far no change to the RSI and Dispersion aka standard deviation seem necessary. I thought I would have to adjust those as a result of the changes that ProShares made to SVXY and UVXY effective February 28, 2018.
SVXY went from -1.0 exposure to -0.5.
UVXY went from +2.0 exposure to +1.5.
Performance since February 28 seems to be satisfactory so far.
I have not researched how RSI is calculated, so I don't know if that would need adjustment.
However, it does seem like standard deviation would result in different numbers after the change.

Thanks for sharing this. Just wondering How are you trading this live? I'm looking for options to port this. Quantconnect? MultiCharts? your own comp?

@Sam,
There has been much discussion in other threads.
I have a custom configuration on my laptop using TCL / Expect, PyAutoGUI, and Robinhood API.
My setup only executes the paper trade orders and they are always at least 15 minutes delayed, which is an obvious disadvantage.

@Charles,
Why do we need to wait for a 15 minutes delayed result, instead of trading with live data? Consider that you could already operate the robinhood api?