Back to Community
Equity returns following extreme VIX & WVF movements: collaboration algo

Hi guys,

I spend some time working on an algorithm that buys SPY or SPXL based on the combination of extreme movements in the VIX and the WVF. The VIX by itself is not such a good predictor but combine with the WVF (aka The Williams’ VIX Fix is an indicator meant to roughly approximate the VIX. It can be useful in situations where there is no implied volatility index for the instrument we want to trade. The WVF is simply a measure of the distance between today’s close and the 22-day highest close)

QUSMA has a nice article about that in tow parts: part 2: http://qusma.com/2013/12/06/equity-returns-following-extreme-vix-wvf-movements-part-2/

The source of the VIX data I get from the cboe.com site.
The rules I derived from the behaviour of the curves are:

  1. ENTER: 2 days after the VIX or WVF hit a 98 perc rank and one of the 2 hit a 99 perc rank
  2. EXIT: 3 days after the moment that when the VIX or WVF crosses the 125 days EMA average after it has hit a low of the 10th perc rank

Entering and exiting with a delay is essential and now I found that I'll try to implement that in other volatility strategies.

Anyway, this is an open invitation to collaborate. If we can find ways to limit the DD I think we are on to something. Especially as a complementary strategy to a strategy that does not want to trade during times of high volatility.

The first backtest is with the EMA as part of the exit, the next one is based on the SMA

Best, Peter

Clone Algorithm
135
Loading...
Total Returns
--
Alpha
--
Beta
--
Sharpe
--
Sortino
--
Max Drawdown
--
Benchmark Returns
--
Volatility
--
Returns 1 Month 3 Month 6 Month 12 Month
Alpha 1 Month 3 Month 6 Month 12 Month
Beta 1 Month 3 Month 6 Month 12 Month
Sharpe 1 Month 3 Month 6 Month 12 Month
Sortino 1 Month 3 Month 6 Month 12 Month
Volatility 1 Month 3 Month 6 Month 12 Month
Max Drawdown 1 Month 3 Month 6 Month 12 Month
# Equity returns following extreme vix wvf movements
#
# Source:
#   QUSMA
#   http://qusma.com/2013/12/06/equity-returns-following-extreme-vix-wvf-movements-part-2/
#   December 6, 2013
#
# Implementation:
#   Peter Bakker (https://www.quantopian.com/users/51d125a71144e60865000044)
#   March 14, 2014 (v0.1)
#
# Rules:
#   1) ENTER: 2 days after the VIX or WVF hit a 98 perc rank and one of the 2 hit a 99 perc rank
#   2) EXIT:  3 days after the moment that when the VIX or WVF crosses the 125 days EMA average after it has hit a low of the 10th perc rank
#
# Known issues:
#   Nasty DD
#
# Version Log:
#   v0.1 - initial open-source release

# TODO: make DD lower....
#       improve the DF WVF code, seems a bit odd implementation


import datetime as dt
import talib as ta
import numpy as np
import pandas as pd
import pandas.tseries.offsets as pdto
import scipy.stats as stats
import pytz
import re 
from pytz import timezone
from zipline.utils import tradingcalendar
from brokers.ib import IBExchange 

def view(df):
    log.info(df.tail())


def initialize(context):
    
    context.VIX_hist = {}
    context.WVF_hist = {}
    fetch_csv("http://www.cboe.com/publish/scheduledtask/mktdata/datahouse/vixcurrent.csv",
             post_func=fixVIX,
             skiprows=1,
             date_column='Date',
             symbol='VIX')    

    context.startcash = context.portfolio.cash
    
    #significance
    context.p90  = 1.645
    context.p95  = 1.96
    context.p99  = 2.33
    context.p999 = 3.20

    #period constants
    context.day = 1
    context.week = 7*context.day
    context.quarter = int(round( context.week*13 ,0))
    context.month = int(round(context.quarter/3,0))
    context.year = 12*context.month
    context.one_day = dt.timedelta(days=1)
      
     
    set_symbol_lookup_date('2010-12-01') 
    context.SPY = symbol('SPY')
    context.SPXL= symbol('SPXL')
    set_benchmark(symbol('SPY'))
        
    #rest date
    context.today = None
    context.selldate = None
    context.dayspassed = 0.0 
    context.daysinmarket = 0.0 
    
    context.speriod = 60
    context.mperiod = 125
    context.lperiod = 150
    
    context.first_run = True
    
    #memory
    context.savedratios = []
    context.savedVIX    = []
    context.savedWVF    = []

    schedule_function(Volatility_Strategy, date_rules.every_day(),
                    time_rules.market_open())
    context.matype = 1    
    # MA_Type: 0=SMA, 1=EMA, 2=WMA, 3=DEMA, 4=TEMA, 5=TRIMA, 6=KAMA, 7=MAMA, 8=T3

    context.buySPXLdate  = None
    context.sellSPXLdate = None
    context.selltrigger  = False
    context.buydelay     = 2
    context.selldelay    = 3

    
    
def handle_data(context, data):
    #Handle data does the actual buying and selling
    C=context
    C.today = pd.Timestamp(get_datetime()).tz_convert('US/Eastern')
    if str(C.today)[:10]>=str(C.buySPXLdate)[:10]and not has_orders(C, data, C.SPXL):
        order_target_percent(C.SPXL,1,style=MarketOrder(exchange=IBExchange.SMART))
        C.buySPXLdate=None 
    if str(C.today)[:10]>=str(C.sellSPXLdate)[:10]and not has_orders(C, data, C.SPXL):
        order_target_percent(C.SPXL,0,style=MarketOrder(exchange=IBExchange.SMART))   
        C.sellSPXLdate=None 
    return
        
    
# fucntion that determines the entry and exit points
def Volatility_Strategy(context, data):
    C = context
    C.today = pd.Timestamp(get_datetime()).tz_convert('US/Eastern')
    if C.selldate==None:
        C.selldate = C.today-C.one_day
    
    spyhist = history(222, '1d', 'price')[C.SPY]
    i = -1
    for x in spyhist:
        if i < -221:
            break
        ii = i-21
        max22 = max(spyhist[ii:i])
        lastclose = spyhist[i]
        date = spyhist.index[i]
        WVF = 100*((max22-lastclose)/max22)
        context.WVF_hist[date] = {'price' :WVF}
        i=i-1
    WVF_df = pd.DataFrame.from_dict(context.WVF_hist, orient='index')    
        
    if 'VIX' in data:
        context.VIX_hist[get_datetime()] = {'price' :data['VIX']['price']}
    # make a dataframe 
    VIX_df = pd.DataFrame.from_dict(context.VIX_hist, orient='index')
    # merge the fields into the trade history
    # the result will mask with the history window
    hist = history(200, '1d', 'price')
    hist['VIX'] = VIX_df['price']
    hist['WVF'] = WVF_df['price']

    #log days passed and % in market
    C.dayspassed = C.dayspassed+1.0
    if C.portfolio.positions[C.SPXL].amount > 0: 
        C.daysinmarket = C.daysinmarket+1.0
    record(perc_in_market = 100*(C.daysinmarket/C.dayspassed))    

     
#######   ORDER check     
    if has_orders(C, data, C.SPXL):
        return  
#######        
    #get VIX data
    vix_data = VIX_df['price'].tail(100)
    lastvix  = hist['VIX'][-1]
    #get WVF data
    wvf_data = hist['WVF'].tail(100)
    lastwvf  = hist['WVF'][-1]
    #percentile score it
    vixscore = stats.percentileofscore(vix_data,lastvix, "rank")
    wvfscore = stats.percentileofscore(wvf_data,lastwvf, "rank")
    # add it to the memory and get MA
    C.savedVIX.append( vixscore)
    ma_vix   = ta.MA(np.array(C.savedVIX), timeperiod=C.mperiod, matype=C.matype)[-1]
    if np.isnan(ma_vix): ma_vix = 35
    C.savedWVF.append(wvfscore)
    ma_wvf   = ta.MA(np.array(C.savedWVF), timeperiod=C.mperiod,  matype=C.matype)[-1]
    if np.isnan(ma_wvf): ma_wvf = 35
    
    #make pretty graphs
    record(VIX_score= vixscore)
    record(WVF_score= wvfscore)
    record(MA_VIXscore= ma_vix)
    record(MA_WVFscore= ma_wvf)
        
    #ENTER rule
    if ((vixscore>=98 or wvfscore>=98) and (vixscore>=99 or wvfscore>=99) and ( C.portfolio.positions[C.SPXL].amount == 0) ):
        C.buySPXLdate=(C.today+(C.buydelay*C.one_day))
        C.sellSPXLdate=None # if active sell, cancel
        C.selltrigger=False
    # first leg of EXIT rule
    elif (vixscore<=10 or wvfscore<=10):# or abs(vixscore-wvfscore)> 50 ):
        C.selltrigger=True
    # second leg of EXIT rule    
    elif C.selltrigger and (vixscore>ma_vix or wvfscore>ma_wvf):           
        C.buySPXLdate=None  # if active buy, cancel
        C.sellSPXLdate=(C.today+(C.selldelay*C.one_day)) 
        C.selltrigger=False # reset trigger

        
#do I have orders?        
def has_orders(context, data, stock):
    # Return true if there are pending orders.
    has_orders = False
    ordervalue = 0.0
    orders = get_open_orders(stock)
    if orders:
        for oo in orders:
            ordervalue = oo.amount * data[stock].price
            message = 'Open order for {amount} shares in {stockval} for {value}'  
            message = message.format(amount=oo.amount, stockval=stock, value=ordervalue)  
            #log.info(message)
        has_orders = True       
    return has_orders            
            
#fix fetcher cols
def rename_col(df):
    df = df.rename(columns={'Adj Close': 'price'})
    df = df.fillna(method='ffill')
    df = df[['price', 'sid']]
    return df
            
#fix fetcher cols
def fixVIX(df):
    df  = df.rename(columns={'VIX Close': 'price'})  
    df.fillna(method='ffill')  
    df['symbol'] = 'VIX'    
    df = df[['price','symbol', 'sid']]
    #view(df)
    return df
There was a runtime error.
4 responses

with SMA, a bit improved set of metrics

Clone Algorithm
135
Loading...
Total Returns
--
Alpha
--
Beta
--
Sharpe
--
Sortino
--
Max Drawdown
--
Benchmark Returns
--
Volatility
--
Returns 1 Month 3 Month 6 Month 12 Month
Alpha 1 Month 3 Month 6 Month 12 Month
Beta 1 Month 3 Month 6 Month 12 Month
Sharpe 1 Month 3 Month 6 Month 12 Month
Sortino 1 Month 3 Month 6 Month 12 Month
Volatility 1 Month 3 Month 6 Month 12 Month
Max Drawdown 1 Month 3 Month 6 Month 12 Month
# Equity returns following extreme vix wvf movements
#
# Source:
#   QUSMA
#   http://qusma.com/2013/12/06/equity-returns-following-extreme-vix-wvf-movements-part-2/
#   December 6, 2013
#
# Implementation:
#   Peter Bakker (https://www.quantopian.com/users/51d125a71144e60865000044)
#   March 14, 2014 (v0.1)
#
# Rules:
#   1) ENTER: 2 days after the VIX or WVF hit a 98 perc rank and one of the 2 hit a 99 perc rank
#   2) EXIT:  3 days after the moment that when the VIX or WVF crosses the 125 days EMA average after it has hit a low of the 10th perc rank
#
# Known issues:
#   Nasty DD
#
# Version Log:
#   v0.1 - initial open-source release

# TODO: make DD lower....
#       improve the DF WVF code, seems a bit odd implementation


import datetime as dt
import talib as ta
import numpy as np
import pandas as pd
import pandas.tseries.offsets as pdto
import scipy.stats as stats
import pytz
import re 
from pytz import timezone
from zipline.utils import tradingcalendar
from brokers.ib import IBExchange 

def view(df):
    log.info(df.tail())


def initialize(context):
    
    context.VIX_hist = {}
    context.WVF_hist = {}
    fetch_csv("http://www.cboe.com/publish/scheduledtask/mktdata/datahouse/vixcurrent.csv",
             post_func=fixVIX,
             skiprows=1,
             date_column='Date',
             symbol='VIX')    

    context.startcash = context.portfolio.cash
    
    #significance
    context.p90  = 1.645
    context.p95  = 1.96
    context.p99  = 2.33
    context.p999 = 3.20

    #period constants
    context.day = 1
    context.week = 7*context.day
    context.quarter = int(round( context.week*13 ,0))
    context.month = int(round(context.quarter/3,0))
    context.year = 12*context.month
    context.one_day = dt.timedelta(days=1)
      
     
    set_symbol_lookup_date('2010-12-01') 
    context.SPY = symbol('SPY')
    context.SPXL= symbol('SPXL')
    set_benchmark(symbol('SPY'))
        
    #rest date
    context.today = None
    context.selldate = None
    context.dayspassed = 0.0 
    context.daysinmarket = 0.0 
    
    context.speriod = 60
    context.mperiod = 125
    context.lperiod = 150
    
    context.first_run = True
    
    #memory
    context.savedratios = []
    context.savedVIX    = []
    context.savedWVF    = []

    schedule_function(Volatility_Strategy, date_rules.every_day(),
                    time_rules.market_open())
    context.matype = 0    
    # MA_Type: 0=SMA, 1=EMA, 2=WMA, 3=DEMA, 4=TEMA, 5=TRIMA, 6=KAMA, 7=MAMA, 8=T3

    context.buySPXLdate  = None
    context.sellSPXLdate = None
    context.selltrigger  = False
    context.buydelay     = 2
    context.selldelay    = 3

    
    
def handle_data(context, data):
    #Handle data does the actual buying and selling
    C=context
    C.today = pd.Timestamp(get_datetime()).tz_convert('US/Eastern')
    if str(C.today)[:10]>=str(C.buySPXLdate)[:10]and not has_orders(C, data, C.SPXL):
        order_target_percent(C.SPXL,1,style=MarketOrder(exchange=IBExchange.SMART))
        C.buySPXLdate=None 
    if str(C.today)[:10]>=str(C.sellSPXLdate)[:10]and not has_orders(C, data, C.SPXL):
        order_target_percent(C.SPXL,0,style=MarketOrder(exchange=IBExchange.SMART))   
        C.sellSPXLdate=None 
    return
        
    
# fucntion that determines the entry and exit points
def Volatility_Strategy(context, data):
    C = context
    C.today = pd.Timestamp(get_datetime()).tz_convert('US/Eastern')
    if C.selldate==None:
        C.selldate = C.today-C.one_day
    
    spyhist = history(222, '1d', 'price')[C.SPY]
    i = -1
    for x in spyhist:
        if i < -221:
            break
        ii = i-21
        max22 = max(spyhist[ii:i])
        lastclose = spyhist[i]
        date = spyhist.index[i]
        WVF = 100*((max22-lastclose)/max22)
        context.WVF_hist[date] = {'price' :WVF}
        i=i-1
    WVF_df = pd.DataFrame.from_dict(context.WVF_hist, orient='index')    
        
    if 'VIX' in data:
        context.VIX_hist[get_datetime()] = {'price' :data['VIX']['price']}
    # make a dataframe 
    VIX_df = pd.DataFrame.from_dict(context.VIX_hist, orient='index')
    # merge the fields into the trade history
    # the result will mask with the history window
    hist = history(200, '1d', 'price')
    hist['VIX'] = VIX_df['price']
    hist['WVF'] = WVF_df['price']

    #log days passed and % in market
    C.dayspassed = C.dayspassed+1.0
    if C.portfolio.positions[C.SPXL].amount > 0: 
        C.daysinmarket = C.daysinmarket+1.0
    record(perc_in_market = 100*(C.daysinmarket/C.dayspassed))    

     
#######   ORDER check     
    if has_orders(C, data, C.SPXL):
        return  
#######        
    #get VIX data
    vix_data = VIX_df['price'].tail(100)
    lastvix  = hist['VIX'][-1]
    #get WVF data
    wvf_data = hist['WVF'].tail(100)
    lastwvf  = hist['WVF'][-1]
    #percentile score it
    vixscore = stats.percentileofscore(vix_data,lastvix, "rank")
    wvfscore = stats.percentileofscore(wvf_data,lastwvf, "rank")
    # add it to the memory and get MA
    C.savedVIX.append( vixscore)
    ma_vix   = ta.MA(np.array(C.savedVIX), timeperiod=C.mperiod, matype=C.matype)[-1]
    if np.isnan(ma_vix): ma_vix = 35
    C.savedWVF.append(wvfscore)
    ma_wvf   = ta.MA(np.array(C.savedWVF), timeperiod=C.mperiod,  matype=C.matype)[-1]
    if np.isnan(ma_wvf): ma_wvf = 35
    
    #make pretty graphs
    record(VIX_score= vixscore)
    record(WVF_score= wvfscore)
    record(MA_VIXscore= ma_vix)
    record(MA_WVFscore= ma_wvf)
        
    #ENTER rule
    if ((vixscore>=98 or wvfscore>=98) and (vixscore>=99 or wvfscore>=99) and ( C.portfolio.positions[C.SPXL].amount == 0) ):
        C.buySPXLdate=(C.today+(C.buydelay*C.one_day))
        C.sellSPXLdate=None # if active sell, cancel
        C.selltrigger=False
    # first leg of EXIT rule
    elif (vixscore<=10 or wvfscore<=10):# or abs(vixscore-wvfscore)> 50 ):
        C.selltrigger=True
    # second leg of EXIT rule    
    elif C.selltrigger and (vixscore>ma_vix or wvfscore>ma_wvf):           
        C.buySPXLdate=None  # if active buy, cancel
        C.sellSPXLdate=(C.today+(C.selldelay*C.one_day)) 
        C.selltrigger=False # reset trigger

        
#do I have orders?        
def has_orders(context, data, stock):
    # Return true if there are pending orders.
    has_orders = False
    ordervalue = 0.0
    orders = get_open_orders(stock)
    if orders:
        for oo in orders:
            ordervalue = oo.amount * data[stock].price
            message = 'Open order for {amount} shares in {stockval} for {value}'  
            message = message.format(amount=oo.amount, stockval=stock, value=ordervalue)  
            #log.info(message)
        has_orders = True       
    return has_orders            
            
#fix fetcher cols
def rename_col(df):
    df = df.rename(columns={'Adj Close': 'price'})
    df = df.fillna(method='ffill')
    df = df[['price', 'sid']]
    return df
            
#fix fetcher cols
def fixVIX(df):
    df  = df.rename(columns={'VIX Close': 'price'})  
    df.fillna(method='ffill')  
    df['symbol'] = 'VIX'    
    df = df[['price','symbol', 'sid']]
    #view(df)
    return df
There was a runtime error.

one using SPY instead of SPXL and a bit longer sell delay

Clone Algorithm
54
Loading...
Total Returns
--
Alpha
--
Beta
--
Sharpe
--
Sortino
--
Max Drawdown
--
Benchmark Returns
--
Volatility
--
Returns 1 Month 3 Month 6 Month 12 Month
Alpha 1 Month 3 Month 6 Month 12 Month
Beta 1 Month 3 Month 6 Month 12 Month
Sharpe 1 Month 3 Month 6 Month 12 Month
Sortino 1 Month 3 Month 6 Month 12 Month
Volatility 1 Month 3 Month 6 Month 12 Month
Max Drawdown 1 Month 3 Month 6 Month 12 Month
# Equity returns following extreme vix wvf movements
#
# Source:
#   QUSMA
#   http://qusma.com/2013/12/06/equity-returns-following-extreme-vix-wvf-movements-part-2/
#   December 6, 2013
#
# Implementation:
#   Peter Bakker (https://www.quantopian.com/users/51d125a71144e60865000044)
#   March 14, 2014 (v0.1)
#
# Rules:
#   1) ENTER: 2 days after the VIX or WVF hit a 98 perc rank and one of the 2 hit a 99 perc rank
#   2) EXIT:  3 days after the moment that when the VIX or WVF crosses the 125 days EMA average after it has hit a low of the 10th perc rank
#
# Known issues:
#   Nasty DD
#
# Version Log:
#   v0.1 - initial open-source release

# TODO: make DD lower....
#       improve the DF WVF code, seems a bit odd implementation


import datetime as dt
import talib as ta
import numpy as np
import pandas as pd
import pandas.tseries.offsets as pdto
import scipy.stats as stats
import pytz
import re 
from pytz import timezone
from zipline.utils import tradingcalendar
from brokers.ib import IBExchange 

def view(df):
    log.info(df.tail())


def initialize(context):
    
    context.VIX_hist = {}
    context.WVF_hist = {}
    fetch_csv("http://www.cboe.com/publish/scheduledtask/mktdata/datahouse/vixcurrent.csv",
             post_func=fixVIX,
             skiprows=1,
             date_column='Date',
             symbol='VIX')    

    context.startcash = context.portfolio.cash
    
    #significance
    context.p90  = 1.645
    context.p95  = 1.96
    context.p99  = 2.33
    context.p999 = 3.20

    #period constants
    context.day = 1
    context.week = 7*context.day
    context.quarter = int(round( context.week*13 ,0))
    context.month = int(round(context.quarter/3,0))
    context.year = 12*context.month
    context.one_day = dt.timedelta(days=1)
      
     
    set_symbol_lookup_date('2010-12-01') 
    context.SPY = symbol('SPY')
    set_benchmark(symbol('SPY'))
        
    #rest date
    context.today = None
    context.selldate = None
    context.dayspassed = 0.0 
    context.daysinmarket = 0.0 
    
    context.speriod = 60
    context.mperiod = 125
    context.lperiod = 150
    
    context.first_run = True
    
    #memory
    context.savedratios = []
    context.savedVIX    = []
    context.savedWVF    = []

    schedule_function(Volatility_Strategy, date_rules.every_day(),
                    time_rules.market_open())
    context.matype = 0    
    # MA_Type: 0=SMA, 1=EMA, 2=WMA, 3=DEMA, 4=TEMA, 5=TRIMA, 6=KAMA, 7=MAMA, 8=T3

    context.buySPYdate  = None
    context.sellSPYdate = None
    context.selltrigger  = False
    context.buydelay     = 2
    context.selldelay    = 8

    
    
def handle_data(context, data):
    #Handle data does the actual buying and selling
    C=context
    C.today = pd.Timestamp(get_datetime()).tz_convert('US/Eastern')
    if str(C.today)[:10]>=str(C.buySPYdate)[:10]and not has_orders(C, data, C.SPY):
        order_target_percent(C.SPY,1,style=MarketOrder(exchange=IBExchange.SMART))
        C.buySPYdate=None 
    if str(C.today)[:10]>=str(C.sellSPYdate)[:10]and not has_orders(C, data, C.SPY):
        order_target_percent(C.SPY,0,style=MarketOrder(exchange=IBExchange.SMART))   
        C.sellSPYdate=None 
    return
        
    
# fucntion that determines the entry and exit points
def Volatility_Strategy(context, data):
    C = context
    C.today = pd.Timestamp(get_datetime()).tz_convert('US/Eastern')
    if C.selldate==None:
        C.selldate = C.today-C.one_day
    
    spyhist = history(222, '1d', 'price')[C.SPY]
    i = -1
    for x in spyhist:
        if i < -221:
            break
        ii = i-21
        max22 = max(spyhist[ii:i])
        lastclose = spyhist[i]
        date = spyhist.index[i]
        WVF = 100*((max22-lastclose)/max22)
        context.WVF_hist[date] = {'price' :WVF}
        i=i-1
    WVF_df = pd.DataFrame.from_dict(context.WVF_hist, orient='index')    
        
    if 'VIX' in data:
        context.VIX_hist[get_datetime()] = {'price' :data['VIX']['price']}
    # make a dataframe 
    VIX_df = pd.DataFrame.from_dict(context.VIX_hist, orient='index')
    # merge the fields into the trade history
    # the result will mask with the history window
    hist = history(200, '1d', 'price')
    hist['VIX'] = VIX_df['price']
    hist['WVF'] = WVF_df['price']

    #log days passed and % in market
    C.dayspassed = C.dayspassed+1.0
    if C.portfolio.positions[C.SPY].amount > 0: 
        C.daysinmarket = C.daysinmarket+1.0
    record(perc_in_market = 100*(C.daysinmarket/C.dayspassed))    

     
#######   ORDER check     
    if has_orders(C, data, C.SPY):
        return  
#######        
    #get VIX data
    vix_data = VIX_df['price'].tail(100)
    lastvix  = hist['VIX'][-1]
    #get WVF data
    wvf_data = hist['WVF'].tail(100)
    lastwvf  = hist['WVF'][-1]
    #percentile score it
    vixscore = stats.percentileofscore(vix_data,lastvix, "rank")
    wvfscore = stats.percentileofscore(wvf_data,lastwvf, "rank")
    # add it to the memory and get MA
    C.savedVIX.append( vixscore)
    ma_vix   = ta.MA(np.array(C.savedVIX), timeperiod=C.mperiod, matype=C.matype)[-1]
    if np.isnan(ma_vix): ma_vix = 35
    C.savedWVF.append(wvfscore)
    ma_wvf   = ta.MA(np.array(C.savedWVF), timeperiod=C.mperiod,  matype=C.matype)[-1]
    if np.isnan(ma_wvf): ma_wvf = 35
    
    #make pretty graphs
    record(VIX_score= vixscore)
    record(WVF_score= wvfscore)
    record(MA_VIXscore= ma_vix)
    record(MA_WVFscore= ma_wvf)
        
    #ENTER rule
    if ((vixscore>=98 or wvfscore>=98) and (vixscore>=99 or wvfscore>=99) and ( C.portfolio.positions[C.SPY].amount == 0) ):
        C.buySPYdate=(C.today+(C.buydelay*C.one_day))
        C.sellSPYdate=None # if active sell, cancel
        C.selltrigger=False
    # first leg of EXIT rule
    elif (vixscore<=10 or wvfscore<=10):# or abs(vixscore-wvfscore)> 50 ):
        C.selltrigger=True
    # second leg of EXIT rule    
    elif C.selltrigger and (vixscore>ma_vix or wvfscore>ma_wvf):           
        C.buySPYdate=None  # if active buy, cancel
        C.sellSPYdate=(C.today+(C.selldelay*C.one_day)) 
        C.selltrigger=False # reset trigger

        
#do I have orders?        
def has_orders(context, data, stock):
    # Return true if there are pending orders.
    has_orders = False
    ordervalue = 0.0
    orders = get_open_orders(stock)
    if orders:
        for oo in orders:
            ordervalue = oo.amount * data[stock].price
            message = 'Open order for {amount} shares in {stockval} for {value}'  
            message = message.format(amount=oo.amount, stockval=stock, value=ordervalue)  
            #log.info(message)
        has_orders = True       
    return has_orders            
            
#fix fetcher cols
def rename_col(df):
    df = df.rename(columns={'Adj Close': 'price'})
    df = df.fillna(method='ffill')
    df = df[['price', 'sid']]
    return df
            
#fix fetcher cols
def fixVIX(df):
    df  = df.rename(columns={'VIX Close': 'price'})  
    df.fillna(method='ffill')  
    df['symbol'] = 'VIX'    
    df = df[['price','symbol', 'sid']]
    #view(df)
    return df
There was a runtime error.

Peter,

I've made some changes, mostly based off what I saw in the Vixscore and the Wvfscore. I'm not a programmer, so the code is probably a mess and I didn't update any of the notes. This is the SPXL algorithm. I'm barely starting on the SPY.

Ted

Clone Algorithm
133
Loading...
Total Returns
--
Alpha
--
Beta
--
Sharpe
--
Sortino
--
Max Drawdown
--
Benchmark Returns
--
Volatility
--
Returns 1 Month 3 Month 6 Month 12 Month
Alpha 1 Month 3 Month 6 Month 12 Month
Beta 1 Month 3 Month 6 Month 12 Month
Sharpe 1 Month 3 Month 6 Month 12 Month
Sortino 1 Month 3 Month 6 Month 12 Month
Volatility 1 Month 3 Month 6 Month 12 Month
Max Drawdown 1 Month 3 Month 6 Month 12 Month
# Equity returns following extreme vix wvf movements
#
# Source:
#   QUSMA
#   http://qusma.com/2013/12/06/equity-returns-following-extreme-vix-wvf-movements-part-2/
#   December 6, 2013
#
# Implementation:
#   Peter Bakker (https://www.quantopian.com/users/51d125a71144e60865000044)
#   March 14, 2014 (v0.1)
#
# Rules:
#   1) ENTER: 2 days after the VIX or WVF hit a 98 perc rank and one of the 2 hit a 99 perc rank
#   2) EXIT:  3 days after the moment that when the VIX or WVF crosses the 125 days EMA average after it has hit a low of the 10th perc rank
#
# Known issues:
#   Nasty DD
#
# Version Log:
#   v0.1 - initial open-source release

# TODO: make DD lower....
#       improve the DF WVF code, seems a bit odd implementation


import datetime as dt
import talib as ta
import numpy as np
import pandas as pd
import pandas.tseries.offsets as pdto
import scipy.stats as stats
import pytz
import re 
from pytz import timezone
from zipline.utils import tradingcalendar
from brokers.ib import IBExchange 

def view(df):
    log.info(df.tail())


def initialize(context):
    
    context.VIX_hist = {}
    context.WVF_hist = {}
    fetch_csv("http://www.cboe.com/publish/scheduledtask/mktdata/datahouse/vixcurrent.csv",
             post_func=fixVIX,
             skiprows=1,
             date_column='Date',
             symbol='VIX')    

    context.startcash = context.portfolio.cash
    
    #significance
    context.p90  = 1.645
    context.p95  = 1.96
    context.p99  = 2.33
    context.p999 = 3.20

    #period constants
    context.day = 1
    context.week = 7*context.day
    context.quarter = int(round( context.week*13 ,0))
    context.month = int(round(context.quarter/3,0))
    context.year = 12*context.month
    context.one_day = dt.timedelta(days=1)
      
     
    set_symbol_lookup_date('2010-12-01') 
    context.SPY = symbol('SPY')
    context.SPXL= symbol('SPXL')
    set_benchmark(symbol('SPY'))
        
    #rest date
    context.today = None
    context.selldate = None
    context.dayspassed = 0.0 
    context.daysinmarket = 0.0 
    
    context.speriod = 60
    context.mperiod = 125
    context.lperiod = 150
    
    context.first_run = True
    
    #memory
    context.savedratios = []
    context.savedVIX    = []
    context.savedWVF    = []

    schedule_function(Volatility_Strategy, date_rules.every_day(),
                    time_rules.market_open())
    context.matype = 1    
    # MA_Type: 0=SMA, 1=EMA, 2=WMA, 3=DEMA, 4=TEMA, 5=TRIMA, 6=KAMA, 7=MAMA, 8=T3

    context.buySPXLdate  = None
    context.sellSPXLdate = None
    context.selltrigger  = False
    context.buydelay     = 2
    context.selldelay    = 2

    
    
def handle_data(context, data):
    #Handle data does the actual buying and selling
    C=context
    C.today = pd.Timestamp(get_datetime()).tz_convert('US/Eastern')
    if str(C.today)[:10]>=str(C.buySPXLdate)[:10]and not has_orders(C, data, C.SPXL):
        order_target_percent(C.SPXL,1,style=MarketOrder(exchange=IBExchange.SMART))
        C.buySPXLdate=None 
    if str(C.today)[:10]>=str(C.sellSPXLdate)[:10]and not has_orders(C, data, C.SPXL):
        order_target_percent(C.SPXL,0,style=MarketOrder(exchange=IBExchange.SMART))   
        C.sellSPXLdate=None 
    return
        
    
# fucntion that determines the entry and exit points
def Volatility_Strategy(context, data):
    C = context
    C.today = pd.Timestamp(get_datetime()).tz_convert('US/Eastern')
    if C.selldate==None:
        C.selldate = C.today - C.one_day
    
    spyhist = history(222, '1d', 'price')[C.SPY]
    i = -1
    for x in spyhist:
        if i < -221:
            break
        ii = i-21
        max22 = max(spyhist[ii:i])
        lastclose = spyhist[i]
        date = spyhist.index[i]
        WVF = 100*((max22-lastclose)/max22)
        context.WVF_hist[date] = {'price' :WVF}
        i=i-1
    WVF_df = pd.DataFrame.from_dict(context.WVF_hist, orient='index')    
        
    if 'VIX' in data:
        context.VIX_hist[get_datetime()] = {'price' :data['VIX']['price']}
    # make a dataframe 
    VIX_df = pd.DataFrame.from_dict(context.VIX_hist, orient='index')
    # merge the fields into the trade history
    # the result will mask with the history window
    hist = history(200, '1d', 'price')
    hist['VIX'] = VIX_df['price']
    hist['WVF'] = WVF_df['price']

    #log days passed and % in market
    C.dayspassed = C.dayspassed+1.0
    if C.portfolio.positions[C.SPXL].amount > 0: 
        C.daysinmarket = C.daysinmarket+1.0
    record(perc_in_market = 100*(C.daysinmarket/C.dayspassed))    

     
#######   ORDER check     
    if has_orders(C, data, C.SPXL):
        return  
#######        
    #get VIX data
    vix_data = VIX_df['price'].tail(100)
    lastvix  = hist['VIX'][-1]
    #get WVF data
    wvf_data = hist['WVF'].tail(100)
    lastwvf  = hist['WVF'][-1]
    #percentile score it
    vixscore = stats.percentileofscore(vix_data,lastvix, "rank")
    wvfscore = stats.percentileofscore(wvf_data,lastwvf, "rank")
    # add it to the memory and get MA
    C.savedVIX.append( vixscore)
    ma_vix   = ta.MA(np.array(C.savedVIX), timeperiod=C.mperiod, matype=C.matype)[-1]
    if np.isnan(ma_vix): ma_vix = 35
    C.savedWVF.append(wvfscore)
    ma_wvf   = ta.MA(np.array(C.savedWVF), timeperiod=C.mperiod,  matype=C.matype)[-1]
    if np.isnan(ma_wvf): ma_wvf = 35
    
    #make pretty graphs
    record(VIX_score= vixscore)
    record(WVF_score= wvfscore)
    record(MA_VIXscore= ma_vix)
    record(MA_WVFscore= ma_wvf)
    
    
    #ENTER rule
    if (((wvfscore>=80 and vixscore>=75) or (wvfscore>=75 and vixscore>= 80)) and (abs(vixscore-wvfscore)<2) and (abs(wvfscore-vixscore)<2) and ( C.portfolio.positions[C.SPXL].amount == 0)):
        C.buySPXLdate=(C.today+(C.buydelay*C.one_day))
        C.sellSPXLdate=None # if active sell, cancel
        C.selltrigger=False
    # first leg of EXIT rule
    elif (vixscore<70) or (wvfscore<70):# or abs(vixscore-wvfscore)> 50 ):
        C.selltrigger=True
    # second leg of EXIT rule    
    elif C.selltrigger or (vixscore>=ma_vix or wvfscore>=ma_wvf) or abs(vixscore-wvfscore)>0.01:           
        C.buySPXLdate=None  # if active buy, cancel
        C.sellSPXLdate=(C.today+(C.selldelay*C.one_day)) 
        C.selltrigger=False # reset trigger

        
#do I have orders?        
def has_orders(context, data, stock):
    # Return true if there are pending orders.
    has_orders = False
    ordervalue = 0.0
    orders = get_open_orders(stock)
    if orders:
        for oo in orders:
            ordervalue = oo.amount * data[stock].price
            message = 'Open order for {amount} shares in {stockval} for {value}'  
            message = message.format(amount=oo.amount, stockval=stock, value=ordervalue)  
            #log.info(message)
        has_orders = True       
    return has_orders            
            
#fix fetcher cols
def rename_col(df):
    df = df.rename(columns={'Adj Close': 'price'})
    df = df.fillna(method='ffill')
    df = df[['price', 'sid']]
    return df
            
#fix fetcher cols
def fixVIX(df):
    df  = df.rename(columns={'VIX Close': 'price'})  
    df.fillna(method='ffill')  
    df['symbol'] = 'VIX'    
    df = df[['price','symbol', 'sid']]
    #view(df)
    return df

There was a runtime error.

thanks Ted, I'll look at it, great that you managed to get a lower volatility and DD, good observations. I have been playing with other vol strategies and I'll try to marry them and see if we can get it more stable. The first thing I want to get rid off is the hard coded numbers, I'll try to move it to a zscore so that they become more dynamic... Thanks for looking at it