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 = 30
context.today = None
# Number of observations used in calculations.
context.nobs = 250
context.min_nobs = 50
context.re_invest_cash = 1
context.allow_shorts = 1
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