Back to Community
Minute Data, Minimum Variance

I used the history() data frame to move this over to minute data. I tested it against the daily results and the two are different but history() is back filled so there is a window where they don't have the same data.

The one I ran on daily data also runs on minute data, the results are close when going from one to the other.

Clone Algorithm
27
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
import numpy as np 
import pandas as pd
from scipy.optimize import nnls
from pytz import timezone


def initialize(context):
    context.stocks =[
        sid(12662), sid(2174), sid(5787), sid(4963), sid(21612), sid(1062), sid(5719),
        sid(7580), sid(8857), sid(2000), sid(5938), sid(19917), sid(12267), 
        sid(3695), sid(14520), sid(2663), sid(24074), sid(12350)
    ]
    # Cash buffer: reduces investment on a per trade basis
    context.cash_buffer = 0.0
    context.EPSILON = 1e-8
    context.max_weight = 2 # Weights won't get to 2, I want to see how it does unconstrained
    context.order_cushion = .2
    # Days between rebalance 
    context.rebal_days = 10
    context.today = None
    
    # Number of observations used in calculations.
    context.nobs = 250
    context.trading_days = 0
    context.re_invest_cash = True
    context.allow_shorts = True
    context.invest_position = True # Uses starting cash if False, only used if not re-investing cahs
    
    
def handle_data(context, data):
    prices = history(bar_count=250, frequency='1d',field='price')
    dt = get_datetime().astimezone(timezone('US/Eastern'))
    P = context.portfolio
    if context.trading_days == 0:
        w = (1 - context.cash_buffer) / len(context.stocks)
        for sym in data.keys():
            order_percent(sym, w)
    record(cash=P.cash, positions=P.positions_value)
    
            
    if not dt.day == context.today:
        context.today = dt.day
        context.trading_days += 1
        
    if in_trade_window(context):
        returns = prices.pct_change().dropna()
                  
        weights = min_var_weights(returns, allow_shorts=context.allow_shorts)
        # log.info("B4: %s"%weights)
        for i in weights:
            if weights[i] > context.max_weight:
                log.debug(weights)
                weights = catch_max_weight(weights, context, data)
                break
            if abs(weights[i]) < context.EPSILON:
                weights[i] = 0
        orders = {}
        for sym in context.stocks:
            old_pos = P.positions[sym].amount 
            if context.re_invest_cash:# and context.trading_days != context.rebal_days:
                new_pos = re_invest_order(sym, weights, context, data)
                
            else:
                if context.invest_position:
                    new_pos = int(
                        (weights[sym] *max(P.positions_value, P.starting_cash, P.cash
                        ) / data[sym].price)*(1 - context.cash_buffer)
                    )
                else:
                    new_pos = int(
                       (weights[sym] * max(
                           P.starting_cash, P.cash) / data[sym].price)*(1 - context.cash_buffer)
                )
            cost = data[sym].price * new_pos
            orders[sym] = (old_pos, new_pos, data[sym].price ,weights[sym] ,cost)
        
        
        if check_orders(orders, context, data):
            for sym in orders: 
                order_target(sym, orders[sym][1])

            
            
def check_orders(orders, context, data):
    P = context.portfolio
    total_cost = sum([orders[i][-1] for i in orders])
    if total_cost > (P.positions_value + P.cash) * (1 + context.order_cushion):
        return False
    log.info('\nTotal Order: $ %s\n'%total_cost)
    log.info({i.symbol: orders[i] for i in orders})
    return True
            
def catch_max_weight(weights, context, data):
    return {i: 1.0 / len(weights.keys()) for i in weights}
    
def re_invest_order(sym, weights, context, data):
    P = context.portfolio
    if P.cash > 0:
        new_pos = int(
            (weights[sym] * (P.positions_value + P.cash) / data[sym].price)*(1 - context.cash_buffer))
    else:
        new_pos = int(
            (weights[sym] * (P.positions_value) / data[sym].price)*(1 - context.cash_buffer))
    return new_pos#, new_pos * data[sym].price
  

def min_var_weights(returns, allow_shorts=False):
    '''
    Returns a dictionary of sid:weight pairs.

    allow_shorts=True --> minimum variance weights returned
    allow_shorts=False --> least squares regression finds non-negative
                           weights that minimize the variance 
    '''
    cov = 2*returns.cov()
    x = np.array([0.]*(len(cov) + 1))
    x[-1] = 1.0
    p = lagrangize(cov)
    if allow_shorts:  
        weights = np.linalg.solve(p, x)[:-1]
    else:
        weights = nnls(p, x)[0][:-1]
    return {sym: weights[i] for i, sym in enumerate(returns)}


def lagrangize(df):
    '''
    Utility funcion to format a DataFrame 
    in order to solve a Lagrangian sysem. 
    '''
    df = df
    df['lambda'] = np.ones(len(df))
    z = np.ones(len(df) + 1)
    x = np.ones(len(df) + 1)
    z[-1] = 0.0
    x[-1] = 1.0
    m = [i for i in df.as_matrix()]
    m.append(z)    
    return np.array(m)

def in_trade_window(context):
    n = context.trading_days
    if n % context.rebal_days:
        return False
    # Convert all time-zones into US EST to avoid confusion
    loc_dt = get_datetime().astimezone(timezone('US/Eastern'))
    if loc_dt.hour == 12 and loc_dt.minute == 0:
        return True
    else:
        return False
    
This backtest was created using an older version of the backtester. Please re-run this backtest to see results using the latest backtester. Learn more about the recent changes.
There was a runtime error.
1 response

This is the one that ran on daily data.

Clone Algorithm
129
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
import numpy as np 
import pandas as pd
from scipy.optimize import nnls
from pytz import timezone
#
# This is a version of the min variance algo without history().
# It can run on daily or min data, just change context.period to 
# anything but 'daily'
#

def initialize(context):
    context.period = 'daily'
    context.stocks =[
        sid(12662), sid(2174), sid(5787), sid(4963), sid(21612), sid(1062), 
        sid(5719),sid(7580), sid(8857), sid(2000), sid(5938), sid(19917), 
        sid(12267), sid(3695), sid(14520), sid(2663), sid(24074), sid(12350)  
    ]   
    context.cash_buffer = 0.0
    context.EPSILON = 1e-8
    context.max_weight = 2
    context.order_cushion = .2
    # Days between rebalance 
    context.rebal_days = 10
    context.today = None
    # Number of observations used in calculations.
    context.nobs = 250
    context.min_nobs = 50
    
    context.re_invest_cash = 1
    context.allow_shorts = 0
    context.invest_position = 1 # Uses starting cash if False, only used if not re-investing cahs
    
    context.data = { 
        i: []  for i in context.stocks
    }
    context.trading_days = 0
    
def handle_data(context, data):
    #data = history(bar_count=context.nobs, frequency
    dt = get_datetime().astimezone(timezone('US/Eastern'))
    P = context.portfolio
    if context.trading_days == 0:
        w = 1./len(context.stocks)
        for sym in context.stocks:
            shares = (P.starting_cash * w // data[sym].price)*(1 - context.cash_buffer)
            order(sym, shares)
    record(cash=P.cash, positions=P.positions_value)
    
    
    if not dt.day == context.today:
        append_data(context, data)
        context.today = dt.day
        context.trading_days += 1
        
    if in_trade_window(context):
        vwaps = pd.DataFrame(context.data)
        context.df = vwaps.pct_change().dropna()
                  
        weights = min_var_weights(context.df, allow_shorts=context.allow_shorts)
        for i in weights:
            if weights[i] > context.max_weight:
                
                weights = catch_max_weight(weights, context, data)
            if abs(weights[i]) < context.EPSILON:
                weights[i] = 0
        #logs = {sym.symbol: weights[sym] for sym in context.stocks if weights[sym] !=0}
        #log.info(logs)
        orders = {}
        for sym in context.stocks:
            old_pos = P.positions[sym].amount 
            if context.re_invest_cash:# and context.trading_days != context.rebal_days:
                new_pos = re_invest_order(sym, weights, context, data) 
            else:
                if context.invest_position:
                    new_pos = int((1 - context.cash_buffer) * (
                        weights[sym] * max(P.positions_value, P.starting_cash, P.cash)) / data[sym].price
                    )
                    
                else:
                    new_pos = int((1 - context.cash_buffer) * (
                        weights[sym] * max(P.starting_cash, P.cash)) / data[sym].price
                    )
                    
            cost = abs(data[sym].price * new_pos)
            orders[sym] = (old_pos, new_pos, data[sym].price ,weights[sym] ,cost)
        
        if check_orders(orders, context, data):
            for sym in orders: 
                order_target(sym, orders[sym][1])

            
def check_orders(orders, context, data):
    P = context.portfolio
    total_cost = sum([orders[i][-1] for i in orders])
    if total_cost > (P.positions_value + P.cash) * (1 + context.order_cushion):
        return False
    log.info('\nTotal Order: $ %s\n'%total_cost)
    log.info({i.symbol: orders[i] for i in orders})
    return True
            
def catch_max_weight(weights, context, data):
    log.debug("\nOVER WEIGHT LIMIT\n%s"%{x.symbol: weights[x] for x in data.keys()})
    return {i: 1. / len(context.stocks) for i in weights}

def append_data(context, data):
    for i in context.stocks:
        context.data[i].append(data[i].vwap(1))
        if len(context.data[i]) > context.nobs:
            context.data[i] = context.data[i][-1*context.nobs:]
            
    
def re_invest_order(sym, weights, context, data):
    P = context.portfolio
    if P.cash > 0:
        new_pos = int((1 - context.cash_buffer) * (
            weights[sym] * (P.positions_value + P.cash)) / data[sym].price
        )
    else:
        new_pos = int((1 - context.cash_buffer) * (
            weights[sym] * (P.positions_value)) / data[sym].price
        )
    return new_pos
  

def min_var_weights(returns, allow_shorts=False):
    '''
    Returns a dictionary of sid:weight pairs.

    allow_shorts=True --> minimum variance weights returned
    allow_shorts=False --> least squares regression finds non-negative
                           weights that minimize the variance 
    '''
    cov = 2*returns.cov()
    x = np.array([0.]*(len(cov)+1))
    #x = np.ones(len(cov) + 1)
    x[-1] = 1.0
    p = lagrangize(cov)
    if allow_shorts:  
        weights = np.linalg.solve(p, x)[:-1]
    else:
        weights = nnls(p, x)[0][:-1]
    return {sym: weights[i] for i, sym in enumerate(returns)}

def lagrangize(df):
    '''
    Utility funcion to format a DataFrame 
    in order to solve a Lagrangian sysem. 
    '''
    df = df
    df['lambda'] = np.ones(len(df))
    z = np.ones(len(df) + 1)
    x = np.ones(len(df) + 1)
    z[-1] = 0.0
    x[-1] = 1.0
    m = [i for i in df.as_matrix()]
    m.append(z)    
    return pd.DataFrame(np.array(m))

def in_trade_window(context):
    n = context.trading_days
    if n < context.min_nobs or n % context.rebal_days:
        return False
    if context.period == 'daily':
        return True
    # Converts all time-zones into US EST to avoid confusion
    loc_dt = get_datetime().astimezone(timezone('US/Eastern'))
    if loc_dt.hour == 12 and loc_dt.minute == 0:
        return True
    else:
        return False
This backtest was created using an older version of the backtester. Please re-run this backtest to see results using the latest backtester. Learn more about the recent changes.
There was a runtime error.