"""
This algorithm rebalances a portfolio according to the classical mean-variance Markowitz model.
"""
import numpy as np
import pandas as pd
from quantopian.algorithm import attach_pipeline, pipeline_output
from quantopian.pipeline import Pipeline
from quantopian.pipeline.data.builtin import USEquityPricing
from quantopian.pipeline.factors import AverageDollarVolume
import cvxopt as opt
from cvxopt import blas, solvers
solvers.options['show_progress']=False
def optimal_portfolio(returns):
"""
This is the classic quadratic convex portfolio optimization, as suggested by Thomas Wiecki, at:
https://blog.quantopian.com/markowitz-portfolio-optimization-2/
https://www.quantopian.com/posts/the-efficient-frontier-markowitz-portfolio-optimization-in-python-using-cvxopt
"""
n = len(returns)
returns = np.asmatrix(returns)
N = 100
mus = [10**(5.0*t/N - 1.0) for t in range(N)]
#Convert to cvxopt matrices
S = opt.matrix(np.cov(returns))
pbar = opt.matrix(np.mean(returns, axis=1))
#Create constraint matrices
G = -opt.matrix(np.eye(n))
h = opt.matrix(0.0, (n, 1))
A = opt.matrix(1.0, (1, n))
b = opt.matrix(1.0)
# Calculate efficient frontier weights using quadratic programming
portfolios = [solvers.qp(mu*S, -pbar, G, h, A, b)['x']
for mu in mus]
# Calculate risks and returns for frontier
returns = [blas.dot(pbar, x) for x in portfolios]
risks = [np.sqrt(blas.dot(x, S*x)) for x in portfolios]
###############################################################################################
###-------------This part is somewhat wrong, because instead of finding the portfolio with the
###-------------highest return/risk ratio (highest sharpe ratio), instead it just finds the
###-------------portfolio with the highest return.--------------------------------------------
## Calculate the 2nd degree polynomial of the frontier curve
#m1 = np.polyfit(returns, risk, 2)
#x1 = np.sqrt(m1[2] / m1[0])
## Calculate the optimal portfolio
#wt = solvers.qp(opt.matrix(x1 * S). -pbar, G, h, A, b)['x']
###############################################################################################
# So instead of that, we are gonna just try brute force search for the portfolio with the
#highest sharpe.
hsharpe = 0
hmu = 0
for mu in range(0, len(mus)):
sharpe = returns[mu] / risks[mu]
if sharpe > hsharpe:
hsharpe = sharpe
hmu = mu
#and return
wt = solvers.qp(mus[hmu]*S, -pbar, G, h, A, b)['x']
return np.asarray(wt), returns, risks
def initialize(context):
"""
Called once at the start of the algorithm.
"""
context.assets = [sid(24), sid(5061), sid(8347), sid(3149), sid(4151), #AAPL MSFT XOM GE JNJ
sid(11100), sid(16841), sid(8151), sid(26578), sid(6653), #BRK-B AMZN WFC GOOGL T
sid(5938), sid(25006), sid(21839), sid(5923), sid(4283), #PG JPM VZ PFE KO
sid(23112), sid(3496), sid(2190), sid(5029), sid(3951), #CVX HD DIS MRK INTC
sid(700), sid(5885)]#, #BAC PEP --- FB and V discarded ##Cut here in order to observe survivorship bias
#sid(1335), sid(3212), sid(1637), sid(1900), sid(3766), #C GILD CMCSA CSCO IBM
#sid(368), sid(5692), sid(4954), sid(980), sid(7792), #AMGN ORCL MO BMY UNH
#sid(4707), sid(4799), sid(4758), sid(1406), sid(4922), #MCD CVS MDT CELG MMM
#sid(698), sid(6928), sid(6683), sid(5328), sid(4487)] #BA SLB SBUX NKE LLY
context.tick = 0
# Rebalance every day, 1 hour after market open.
schedule_function(my_rebalance, date_rules.every_day(), time_rules.market_open(hours=1))
# Record tracking variables at the end of each day.
schedule_function(my_record_vars, date_rules.every_day(), time_rules.market_close())
# Create our dynamic stock selector.
attach_pipeline(make_pipeline(), 'my_pipeline')
def make_pipeline():
"""
A function to create our dynamic stock selector (pipeline). Documentation on
pipeline can be found here: https://www.quantopian.com/help#pipeline-title
"""
# Create a dollar volume factor.
dollar_volume = AverageDollarVolume(window_length=1)
# Pick the top 1% of stocks ranked by dollar volume.
high_dollar_volume = dollar_volume.percentile_between(99, 100)
pipe = Pipeline(
screen = high_dollar_volume,
columns = {
'dollar_volume': dollar_volume
}
)
return pipe
def before_trading_start(context, data):
"""
Called every day before market open.
"""
context.output = pipeline_output('my_pipeline')
# These are the securities that we are interested in trading each day.
context.security_list = context.output.index
def my_assign_weights(context, data):
"""
Assign weights to securities that we want to order.
"""
pass
def my_rebalance(context,data):
"""
Rebalance daily
"""
#Only after 100 days passed. (This isn't necessary in Quantopian, but in Zipline)
context.tick += 1
if context.tick < 100:
return
prices = data.history(context.assets, 'close', 100, '1d')
returns = prices.pct_change().dropna()
weights, _, _ = optimal_portfolio(returns.T)
for stock, weight in zip(prices.columns, weights):
order_target_percent(stock, weight)
pass
def my_record_vars(context, data):
"""
Plot variables at the end of each day.
"""
pass
def handle_data(context,data):
"""
Called every minute.
"""
pass