Let's compare the state of the art in OnLine Portfolio Selection (OLPS) algorithms and determine if they can enhance a rebalanced passive strategy in practice. Online Portfolio Selection: A Survey by Bin Li and Steven C. H. Hoi provides the most comprehensive review of multi-period portfolio allocation optimization algorithms. The authors developed the OLPS Toolbox, but here we use Mojmir Vinkler's implementation and extend his comparison to a more recent timeline with a set of ETFs to avoid survivorship bias (as suggested by Ernie Chan) and idiosyncratic risk.
Vinkler does all the hard work in his thesis, and concludes that Universal Portfolios work practically the same as Constant Rebalanced Portfolios, and work better for an uncorrelated set of small and volatile stocks. Here I'm looking to find if any strategy is applicable to a set of ETFs.
The agorithms compared are:
Type | Name | Algo | Reference |
---|---|---|---|
Benchmark | BAH | Buy and Hold | |
Benchmark | CRP | Constant Rebalanced Portfolio | T. Cover. Universal Portfolios, 1991. |
Benchmark | UCRP | Uniform CRP (UCRP), a special case of CRP with all weights being equal | T. Cover. Universal Portfolios, 1991. |
Benchmark | BCRP | Best Constant Rebalanced Portfolio | T. Cover. Universal Portfolios, 1991. |
Follow-the-Winner | UP | Universal Portfolio | T. Cover. Universal Portfolios, 1991. |
Follow-the-Winner | EG | Exponential Gradient | Helmbold, David P., et al. On‐Line Portfolio Selection Using Multiplicative Updates Mathematical Finance 8.4 (1998): 325-347. |
Follow-the-Winner | ONS | Online Newton Step | A. Agarwal, E. Hazan, S. Kale, R. E. Schapire. Algorithms for Portfolio Management based on the Newton Method, 2006. |
Follow-the-Loser | Anticor | Anticorrelation | A. Borodin, R. El-Yaniv, and V. Gogan. Can we learn to beat the best stock, 2005 |
Follow-the-Loser | PAMR | Passive Aggressive Mean Reversion | B. Li, P. Zhao, S. C.H. Hoi, and V. Gopalkrishnan. Pamr: Passive aggressive mean reversion strategy for portfolio selection, 2012. |
Follow-the-Loser | CWMR | Confidence Weighted Mean Reversion | B. Li, S. C. H. Hoi, P. L. Zhao, and V. Gopalkrishnan.Confidence weighted mean reversion strategy for online portfolio selection, 2013. |
Follow-the-Loser | OLMAR | Online Moving Average Reversion | Bin Li and Steven C. H. Hoi On-Line Portfolio Selection with Moving Average Reversion |
Follow-the-Loser | RMR | Robust Median Reversion | D. Huang, J. Zhou, B. Li, S. C.vH. Hoi, S. Zhou Robust Median Reversion Strategy for On-Line Portfolio Selection, 2013. |
Pattern Matching | Kelly | Kelly fractional betting | Kelly Criterion |
Pattern Matching | BNN | nonparametric nearest neighbor log-optimal | L. Gyorfi, G. Lugosi, and F. Udina. Nonparametric kernel based sequential investment strategies. Mathematical Finance 16 (2006) 337–357. |
Pattern Matching | CORN | correlation-driven nonparametric learning | B. Li, S. C. H. Hoi, and V. Gopalkrishnan. Corn: correlation-driven nonparametric learning approach for portfolio selection, 2011. |
We pick 6 ETFs to avoid survivorship bias and capture broad market diversification. We select the longest running ETF per assset class: VTI, EFA, EEM, TLT, TIP, VNQ . We train and select the best parameters on market data from 2005-2012 inclusive (8 years), and test on 2013-2014 inclusive (2 years).
# You will first need to either download or install universal-portfolios from Vinkler
# one way to do it is uncomment the line below and execute
# !pip install --upgrade universal-portfolios
# or
# !pip install --upgrade -e git+git@github.com:Marigold/universal-portfolios.git@master#egg=universal-portfolios
#
# if the above fail, git clone git@github.com:marigold/universal-portfolios.git and python setup.py install
Initialize and set debugging level to debug
to track progress.
%matplotlib inline
import numpy as np
import pandas as pd
from pandas.io.data import DataReader
from datetime import datetime
import six
import universal as up
from universal import tools
from universal import algos
import logging
# we would like to see algos progress
logging.basicConfig(format='%(asctime)s %(message)s', level=logging.DEBUG)
import matplotlib as mpl
import matplotlib.pyplot as plt
mpl.rcParams['figure.figsize'] = (16, 10) # increase the size of graphs
mpl.rcParams['legend.fontsize'] = 12
mpl.rcParams['lines.linewidth'] = 1
default_color_cycle = mpl.rcParams['axes.color_cycle'] # save this as we will want it back later
# note what versions we are on:
import sys
print('Python: '+sys.version)
print('Pandas: '+pd.__version__)
import pkg_resources
print('universal-portfolios: '+pkg_resources.get_distribution("universal-portfolios").version)
Python: 2.7.9 |Anaconda 2.2.0 (x86_64)| (default, Dec 15 2014, 10:37:34) [GCC 4.2.1 (Apple Inc. build 5577)] Pandas: 0.16.0 universal-portfolios: 0.1
We want to train on market data from 2005-2012 inclusive (8 years), and test on 2013-2014 inclusive (2 years). But at this point we accept the default parameters for the respective algorithms and we essentially are looking at two independent time periods. In the future we will want to optimize the paramaters on the train set.
# load data from Yahoo
# Be careful if you cange the order or types of ETFs to also change the CRP weight %'s in the swensen_allocation
etfs = ['VTI', 'EFA', 'EEM', 'TLT', 'TIP', 'VNQ']
# Swensen allocation from http://www.bogleheads.org/wiki/Lazy_portfolios#David_Swensen.27s_lazy_portfolio
# as later updated here : https://www.yalealumnimagazine.com/articles/2398/david-swensen-s-guide-to-sleeping-soundly
swensen_allocation = [0.3, 0.15, 0.1, 0.15, 0.15, 0.15]
benchmark = ['SPY']
train_start = datetime(2005,1,1)
train_end = datetime(2012,12,31)
test_start = datetime(2013,1,1)
test_end = datetime(2014,12,31)
train = DataReader(etfs, 'yahoo', start=train_start, end=train_end)['Adj Close']
test = DataReader(etfs, 'yahoo', start=test_start, end=test_end)['Adj Close']
train_b = DataReader(benchmark, 'yahoo', start=train_start, end=train_end)['Adj Close']
test_b = DataReader(benchmark, 'yahoo', start=test_start, end=test_end)['Adj Close']
# plot normalized prices of the train set
ax1 = (train / train.iloc[0,:]).plot()
(train_b / train_b.iloc[0,:]).plot(ax=ax1)
<matplotlib.axes._subplots.AxesSubplot at 0x1067c2d90>
# plot normalized prices of the test set
ax2 = (test / test.iloc[0,:]).plot()
(test_b / test_b.iloc[0,:]).plot(ax=ax2)
<matplotlib.axes._subplots.AxesSubplot at 0x10cbebf10>
We want to train on market data from a number of years, and test out of sample for a duration smaller than the train set. To get started we accept the default parameters for the respective algorithms and we essentially are just looking at two independent time periods. In the future we will want to optimize the paramaters on the train set.
#list all the algos
olps_algos = [
algos.Anticor(),
algos.BAH(),
algos.BCRP(),
algos.BNN(),
algos.CORN(),
algos.CRP(b=swensen_allocation), # Non Uniform CRP (the Swensen allocation)
algos.CWMR(),
algos.EG(),
algos.Kelly(),
algos.OLMAR(),
algos.ONS(),
algos.PAMR(),
algos.RMR(),
algos.UP()
]
# put all the algos in a dataframe
algo_names = [a.__class__.__name__ for a in olps_algos]
algo_data = ['algo', 'results', 'profit', 'sharpe', 'information', 'annualized_return', 'drawdown_period','winning_pct']
metrics = algo_data[2:]
olps_train = pd.DataFrame(index=algo_names, columns=algo_data)
olps_train.algo = olps_algos
At this point we could train all the algos to find the best parameters for each.
# run all algos - this takes more than a minute
for name, alg in zip(olps_train.index, olps_train.algo):
olps_train.ix[name,'results'] = alg.run(train)
# Let's make sure the fees are set to 0 at first
for k, r in olps_train.results.iteritems():
r.fee = 0.0
# we need 14 colors for the plot
n_lines = 14
color_idx = np.linspace(0, 1, n_lines)
mpl.rcParams['axes.color_cycle']=[plt.cm.rainbow(i) for i in color_idx]
# plot as if we had no fees
# get the first result so we can grab the figure axes from the plot
ax = olps_train.results[0].plot(assets=False, weights=False, ucrp=True, portfolio_label=olps_train.index[0])
for k, r in olps_train.results.iteritems():
if k == olps_train.results.keys()[0]: # skip the first item because we have it already
continue
r.plot(assets=False, weights=False, ucrp=False, portfolio_label=k, ax=ax[0])
def olps_stats(df):
for name, r in df.results.iteritems():
df.ix[name,'profit'] = r.profit_factor
df.ix[name,'sharpe'] = r.sharpe
df.ix[name,'information'] = r.information
df.ix[name,'annualized_return'] = r.annualized_return * 100
df.ix[name,'drawdown_period'] = r.drawdown_period
df.ix[name,'winning_pct'] = r.winning_pct * 100
return df
olps_stats(olps_train)
olps_train[metrics].sort('profit', ascending=False)
profit | sharpe | information | annualized_return | drawdown_period | winning_pct | |
---|---|---|---|---|---|---|
RMR | 1.344883 | 1.417639 | 1.581531 | 57.1563 | 108 | 55.55556 |
CWMR | 1.30964 | 1.29867 | 1.419667 | 49.69137 | 134 | 54.7047 |
PAMR | 1.307667 | 1.284661 | 1.405146 | 49.15968 | 113 | 54.55912 |
OLMAR | 1.303725 | 1.275474 | 1.358441 | 49.43 | 128 | 54.95495 |
BNN | 1.16316 | 0.7406326 | 0.597467 | 24.49535 | 516 | 53.74625 |
ONS | 1.124495 | 0.5324337 | 0.4103257 | 12.69247 | 301 | 54.77137 |
CORN | 1.120664 | 0.5868915 | 0.3335868 | 16.03551 | 619 | 54.42346 |
BCRP | 1.113103 | 0.5587799 | 0.3926754 | 12.47399 | 493 | 52.53479 |
EG | 1.095291 | 0.4654109 | -0.5754063 | 8.796127 | 691 | 54.02584 |
UP | 1.093509 | 0.4591855 | -0.5701021 | 8.627557 | 720 | 54.12525 |
CRP | 1.08585 | 0.4206153 | 0.1402307 | 9.493241 | 725 | 54.12525 |
BAH | 1.075043 | 0.3852003 | -0.646195 | 6.960725 | 874 | 54.12525 |
Anticor | 1.06997 | 0.279532 | 0.04357109 | 10.00163 | 836 | 53.43968 |
Kelly | 0.8724567 | -0.7194364 | -0.7692659 | -73.22097 | 2012 | 51.58002 |
# Let's add fees of 0.1% per transaction (we pay $1 for every $1000 of stocks bought or sold).
for k, r in olps_train.results.iteritems():
r.fee = 0.001
# plot with fees
# get the first result so we can grab the figure axes from the plot
ax = olps_train.results[0].plot(assets=False, weights=False, ucrp=True, portfolio_label=olps_train.index[0])
for k, r in olps_train.results.iteritems():
if k == olps_train.results.keys()[0]: # skip the first item because we have it already
continue
r.plot(assets=False, weights=False, ucrp=False, portfolio_label=k, ax=ax[0])
olps_stats(olps_train)
olps_train[metrics].sort('profit', ascending=False)
profit | sharpe | information | annualized_return | drawdown_period | winning_pct | |
---|---|---|---|---|---|---|
RMR | 1.132369 | 0.5880135 | 0.4408147 | 20.23296 | 800 | 51.57107 |
ONS | 1.116896 | 0.5017011 | 0.3281846 | 11.91852 | 303 | 54.64481 |
BCRP | 1.110684 | 0.5474909 | 0.3639968 | 12.20745 | 496 | 52.50869 |
OLMAR | 1.103785 | 0.4692946 | 0.2657746 | 15.62398 | 808 | 50.97354 |
EG | 1.09293 | 0.4544007 | -1.921296 | 8.579679 | 720 | 53.94933 |
UP | 1.091364 | 0.4491232 | -1.002317 | 8.43104 | 721 | 54.04868 |
CRP | 1.083823 | 0.4110887 | 0.08867675 | 9.268793 | 731 | 54.09836 |
BAH | 1.074893 | 0.3844829 | -0.6506861 | 6.947344 | 874 | 54.09836 |
Anticor | 1.019879 | 0.08123565 | -0.2464923 | 2.801569 | 1108 | 51.76881 |
CWMR | 1.013093 | 0.06186626 | -0.3053956 | 1.904562 | 995 | 47.28991 |
PAMR | 1.007116 | 0.03362724 | -0.3451079 | 1.033495 | 996 | 46.54401 |
BNN | 0.9001447 | -0.5143973 | -1.059632 | -14.07822 | 1975 | 47.01789 |
CORN | 0.8659736 | -0.7379091 | -1.434188 | -16.89046 | 1975 | 47.54098 |
Kelly | 0.750211 | -1.459831 | -1.510671 | -93.97284 | 2012 | 50.5349 |
# create the test set dataframe
olps_test = pd.DataFrame(index=algo_names, columns=algo_data)
olps_test.algo = olps_algos
# run all algos
for name, alg in zip(olps_test.index, olps_test.algo):
olps_test.ix[name,'results'] = alg.run(test)
# Let's make sure the fees are 0 at first
for k, r in olps_test.results.iteritems():
r.fee = 0.0
# plot as if we had no fees
# get the first result so we can grab the figure axes from the plot
ax = olps_test.results[0].plot(assets=False, weights=False, ucrp=True, portfolio_label=olps_test.index[0])
for k, r in olps_test.results.iteritems():
if k == olps_test.results.keys()[0]: # skip the first item because we have it already
continue
r.plot(assets=False, weights=False, ucrp=False, portfolio_label=k, ax=ax[0])
# plot as if we had no fees
# get the first result so we can grab the figure axes from the plot
ax = olps_test.results[0].plot(assets=False, weights=False, ucrp=True, portfolio_label=olps_test.index[0])
for k, r in olps_test.results.iteritems():
if k == olps_test.results.keys()[0] or k == 'Kelly': # skip the first item because we have it already
continue
r.plot(assets=False, weights=False, ucrp=False, portfolio_label=k, ax=ax[0])
olps_stats(olps_test)
olps_test[metrics].sort('profit', ascending=False)
profit | sharpe | information | annualized_return | drawdown_period | winning_pct | |
---|---|---|---|---|---|---|
BCRP | 1.323158 | 1.687346 | 1.75396 | 21.09194 | 36 | 59.04573 |
Anticor | 1.213151 | 1.119712 | 0.9792509 | 17.03448 | 86 | 56.88623 |
PAMR | 1.198056 | 1.066684 | 0.8037021 | 16.56317 | 127 | 53.90782 |
CWMR | 1.192981 | 1.038446 | 0.7643975 | 16.02498 | 127 | 54.10822 |
CORN | 1.187963 | 1.042635 | 0.625535 | 12.58479 | 136 | 55.4672 |
BNN | 1.18066 | 0.9848432 | 0.5932079 | 13.03065 | 122 | 56.6 |
BAH | 1.150074 | 0.8247777 | 0.5070828 | 7.09091 | 193 | 55.666 |
UP | 1.14739 | 0.8091129 | 0.3482341 | 6.841178 | 193 | 55.86481 |
EG | 1.1467 | 0.8053731 | 0.491308 | 6.826939 | 194 | 54.87078 |
ONS | 1.121838 | 0.6733858 | -0.5299079 | 5.597352 | 229 | 53.87674 |
CRP | 1.0989 | 0.5622322 | -0.6019041 | 5.547421 | 224 | 53.28032 |
RMR | 1.094977 | 0.5463773 | 0.1040624 | 7.934969 | 247 | 54.50902 |
OLMAR | 1.032958 | 0.1938946 | -0.38272 | 2.766207 | 291 | 54.30862 |
Kelly | 0 | NaN | 0 | -100 | 443 | 58.0574 |
Instead of using the default parameters, we will test several window
parameters to see if we can get OLMAR to improve.
# we need need fewer colors so let's reset the colors_cycle
mpl.rcParams['axes.color_cycle']= default_color_cycle
train_olmar = algos.OLMAR.run_combination(train, window=[3,5,10,15], eps=10)
train_olmar.plot()
<matplotlib.axes._subplots.AxesSubplot at 0x10ea53050>
print(train_olmar.summary())
Summary for window=3: Profit factor: 1.30 Sharpe ratio: 1.24 Information ratio (wrt UCRP): 1.34 Annualized return: 48.33% Longest drawdown: 132 days Winning days: 55.0% Summary for window=5: Profit factor: 1.30 Sharpe ratio: 1.28 Information ratio (wrt UCRP): 1.36 Annualized return: 49.43% Longest drawdown: 128 days Winning days: 55.0% Summary for window=10: Profit factor: 1.29 Sharpe ratio: 1.18 Information ratio (wrt UCRP): 1.31 Annualized return: 47.25% Longest drawdown: 125 days Winning days: 55.5% Summary for window=15: Profit factor: 1.26 Sharpe ratio: 1.06 Information ratio (wrt UCRP): 1.15 Annualized return: 41.48% Longest drawdown: 169 days Winning days: 54.6%
train_olmar = algos.OLMAR.run_combination(train, window=5, eps=[3,5,10,15])
train_olmar.plot()
<matplotlib.axes._subplots.AxesSubplot at 0x10e921d50>
print(train_olmar.summary())
Summary for eps=3: Profit factor: 1.30 Sharpe ratio: 1.27 Information ratio (wrt UCRP): 1.36 Annualized return: 49.15% Longest drawdown: 128 days Winning days: 54.9% Summary for eps=5: Profit factor: 1.31 Sharpe ratio: 1.28 Information ratio (wrt UCRP): 1.37 Annualized return: 49.73% Longest drawdown: 128 days Winning days: 55.0% Summary for eps=10: Profit factor: 1.30 Sharpe ratio: 1.28 Information ratio (wrt UCRP): 1.36 Annualized return: 49.43% Longest drawdown: 128 days Winning days: 55.0% Summary for eps=15: Profit factor: 1.30 Sharpe ratio: 1.27 Information ratio (wrt UCRP): 1.36 Annualized return: 49.37% Longest drawdown: 128 days Winning days: 55.0%
# OLMAR vs UCRP
best_olmar = train_olmar[1]
ax1 = best_olmar.plot(ucrp=True, bah=True, weights=False, assets=False, portfolio_label='OLMAR')
olps_train.loc['CRP'].results.plot(ucrp=False, bah=False, weights=False, assets=False, ax=ax1[0], portfolio_label='CRP')
[<matplotlib.axes._subplots.AxesSubplot at 0x10e412a50>]
# let's print the stats
print(best_olmar.summary())
Summary: Profit factor: 1.31 Sharpe ratio: 1.28 Information ratio (wrt UCRP): 1.37 Annualized return: 49.73% Longest drawdown: 128 days Winning days: 55.0%
best_olmar.plot_decomposition(legend=True, logy=True)
<matplotlib.axes._subplots.AxesSubplot at 0x10e82e690>
best_olmar.plot_decomposition(legend=True, logy=False)
<matplotlib.axes._subplots.AxesSubplot at 0x10cd918d0>
best_olmar.plot(weights=True, assets=True, ucrp=False, logy=True, portfolio_label='OLMAR')
[<matplotlib.axes._subplots.AxesSubplot at 0x10de2b910>, <matplotlib.axes._subplots.AxesSubplot at 0x10e144150>]
# find the name of the most profitable asset
most_profitable = best_olmar.equity_decomposed.iloc[-1].argmax()
# rerun algorithm on data without it
result_without = algos.OLMAR().run(train.drop([most_profitable], 1))
# and print results
print(result_without.summary())
result_without.plot(weights=False, assets=False, bah=True, ucrp=True, logy=True, portfolio_label='OLMAR-VNQ')
Summary: Profit factor: 1.23 Sharpe ratio: 1.02 Information ratio (wrt UCRP): 0.99 Annualized return: 32.37% Longest drawdown: 153 days Winning days: 54.7%
[<matplotlib.axes._subplots.AxesSubplot at 0x10e1ea310>]
result_without.plot_decomposition(legend=True, logy=False)
<matplotlib.axes._subplots.AxesSubplot at 0x10ec18850>
best_olmar.fee = 0.001
print(best_olmar.summary())
best_olmar.plot(weights=False, assets=False, bah=True, ucrp=True, logy=True, portfolio_label='OLMAR')
Summary: Profit factor: 1.11 Sharpe ratio: 0.48 Information ratio (wrt UCRP): 0.28 Annualized return: 15.97% Longest drawdown: 808 days Winning days: 51.1%
[<matplotlib.axes._subplots.AxesSubplot at 0x10f919850>]
test_olmar = algos.OLMAR(window=5, eps=5).run(test)
#print(train_olmar.summary())
test_olmar.plot(ucrp=True, bah=True, weights=False, assets=False, portfolio_label='OLMAR')
[<matplotlib.axes._subplots.AxesSubplot at 0x111f2b690>]
test_olmar.fee = 0.001
print(test_olmar.summary())
test_olmar.plot(weights=False, assets=False, bah=True, ucrp=True, logy=True, portfolio_label='OLMAR')
Summary: Profit factor: 0.76 Sharpe ratio: -1.62 Information ratio (wrt UCRP): -3.05 Annualized return: -19.52% Longest drawdown: 461 days Winning days: 49.1%
[<matplotlib.axes._subplots.AxesSubplot at 0x1121ad290>]
The 2008-2009 recession was unique. Let's try it all again starting in 2010, with a train set from 2010-2013 inclusive, and a test set of 2014.
# set train and test time periods
train_start_2010= datetime(2010,1,1)
train_end_2010 = datetime(2013,12,31)
test_start_2010 = datetime(2014,1,1)
test_end_2010 = datetime(2014,12,31)
# load data from Yahoo
train_2010 = DataReader(etfs, 'yahoo', start=train_start_2010, end=train_end_2010)['Adj Close']
test_2010 = DataReader(etfs, 'yahoo', start=test_start_2010, end=test_end_2010)['Adj Close']
# plot normalized prices of these stocks
(train_2010 / train_2010.iloc[0,:]).plot()
<matplotlib.axes._subplots.AxesSubplot at 0x11225cf10>
# plot normalized prices of these stocks
(test_2010 / test_2010.iloc[0,:]).plot()
<matplotlib.axes._subplots.AxesSubplot at 0x10d0b9f90>
train_olmar_2010 = algos.OLMAR().run(train_2010)
train_crp_2010 = algos.CRP(b=swensen_allocation).run(train_2010)
ax1 = train_olmar_2010.plot(assets=True, weights=False, ucrp=True, bah=True, portfolio_label='OLMAR')
train_crp_2010.plot(ucrp=False, bah=False, weights=False, assets=False, ax=ax1[0], portfolio_label='CRP')
[<matplotlib.axes._subplots.AxesSubplot at 0x10cfbffd0>]
print(train_olmar_2010.summary())
Summary: Profit factor: 1.19 Sharpe ratio: 1.00 Information ratio (wrt UCRP): 0.75 Annualized return: 23.83% Longest drawdown: 209 days Winning days: 55.1%
train_olmar_2010.plot_decomposition(legend=True, logy=True)
<matplotlib.axes._subplots.AxesSubplot at 0x10ced8d50>
Not bad, with a Sharpe at 1 and no one ETF dominating the portfolio. Now let's see how it fairs in 2014.
test_olmar_2010 = algos.OLMAR().run(test_2010)
test_crp_2010 = algos.CRP(b=swensen_allocation).run(test_2010)
ax1 = test_olmar_2010.plot(assets=True, weights=False, ucrp=True, bah=True, portfolio_label='OLMAR')
test_crp_2010.plot(ucrp=False, bah=False, weights=False, assets=False, ax=ax1[0], portfolio_label='CRP')
[<matplotlib.axes._subplots.AxesSubplot at 0x112e24650>]
print(test_olmar_2010.summary())
Summary: Profit factor: 1.06 Sharpe ratio: 0.36 Information ratio (wrt UCRP): -0.63 Annualized return: 4.72% Longest drawdown: 83 days Winning days: 57.9%
We just happen to be looking at a different time period and now the Sharpe drops below 0.5 and OLMAR fails to beat BAH. Not good.
test_olmar_2010.plot_decomposition(legend=True, logy=True)
<matplotlib.axes._subplots.AxesSubplot at 0x114cd8550>
Let's step back and simplify this by looking at OLMAR on a SPY and TLT portfolio. We should also compare this portfolio to a rebalanced 70/30 mix of SPY and TLT.
# load data from Yahoo
spy_tlt_data = DataReader(['SPY', 'TLT'], 'yahoo', start=datetime(2010,1,1))['Adj Close']
# plot normalized prices of these stocks
(spy_tlt_data / spy_tlt_data.iloc[0,:]).plot()
<matplotlib.axes._subplots.AxesSubplot at 0x115057790>
spy_tlt_olmar_2010 = algos.OLMAR().run(spy_tlt_data)
spy_tlt_olmar_2010.plot(assets=True, weights=True, ucrp=True, bah=True, portfolio_label='OLMAR')
[<matplotlib.axes._subplots.AxesSubplot at 0x112ef2d50>, <matplotlib.axes._subplots.AxesSubplot at 0x115745410>]
spy_tlt_olmar_2010.plot_decomposition(legend=True, logy=True)
<matplotlib.axes._subplots.AxesSubplot at 0x11581d310>
print(spy_tlt_olmar_2010.summary())
Summary: Profit factor: 1.22 Sharpe ratio: 1.18 Information ratio (wrt UCRP): 0.48 Annualized return: 21.35% Longest drawdown: 210 days Winning days: 56.2%
spy_tlt_2010 = algos.CRP(b=[0.7, 0.3]).run(spy_tlt_data)
ax1 = spy_tlt_olmar_2010.plot(assets=False, weights=False, ucrp=True, bah=True, portfolio_label='OLMAR')
spy_tlt_2010.plot(assets=False, weights=False, ucrp=False, bah=False, portfolio_label='CRP', ax=ax1[0])
[<matplotlib.axes._subplots.AxesSubplot at 0x115797ed0>]
Let's look at algo behavior on market sectors:
sectors = ['XLY','XLF','XLK','XLE','XLV','XLI','XLP','XLB','XLU']
train_sectors = DataReader(sectors, 'yahoo', start=train_start_2010, end=train_end_2010)['Adj Close']
test_sectors = DataReader(sectors, 'yahoo', start=test_start_2010, end=test_end_2010)['Adj Close']
# plot normalized prices of these stocks
(train_sectors / train_sectors.iloc[0,:]).plot()
<matplotlib.axes._subplots.AxesSubplot at 0x115c8a8d0>
# plot normalized prices of these stocks
(test_sectors / test_sectors.iloc[0,:]).plot()
<matplotlib.axes._subplots.AxesSubplot at 0x10dd2d510>
train_olmar_sectors = algos.OLMAR().run(train_sectors)
train_olmar_sectors.plot(assets=True, weights=False, ucrp=True, bah=True, portfolio_label='OLMAR')
[<matplotlib.axes._subplots.AxesSubplot at 0x112d31ad0>]
print(train_olmar_sectors.summary())
Summary: Profit factor: 1.12 Sharpe ratio: 0.64 Information ratio (wrt UCRP): -0.13 Annualized return: 13.94% Longest drawdown: 173 days Winning days: 53.0%
train_olmar_sectors.plot(assets=False, weights=False, ucrp=True, bah=True, portfolio_label='OLMAR')
[<matplotlib.axes._subplots.AxesSubplot at 0x115e81590>]
test_olmar_sectors = algos.OLMAR().run(test_sectors)
test_olmar_sectors.plot(assets=True, weights=False, ucrp=True, bah=True, portfolio_label='OLMAR')
[<matplotlib.axes._subplots.AxesSubplot at 0x116801050>]
test_olmar_sectors = algos.OLMAR().run(test_sectors)
test_olmar_sectors.plot(assets=False, weights=False, ucrp=True, bah=True, portfolio_label='OLMAR')
[<matplotlib.axes._subplots.AxesSubplot at 0x11682a510>]
#list all the algos
olps_algos_sectors = [
algos.Anticor(),
algos.BAH(),
algos.BCRP(),
algos.BNN(),
algos.CORN(),
algos.CRP(), # removed weights, and thus equivalent to UCRP
algos.CWMR(),
algos.EG(),
algos.Kelly(),
algos.OLMAR(),
algos.ONS(),
algos.PAMR(),
algos.RMR(),
algos.UP()
]
olps_sectors_train = pd.DataFrame(index=algo_names, columns=algo_data)
olps_sectors_train.algo = olps_algos_sectors
# run all algos - this takes more than a minute
for name, alg in zip(olps_sectors_train.index, olps_sectors_train.algo):
olps_sectors_train.ix[name,'results'] = alg.run(train_sectors)
# we need 14 colors for the plot
n_lines = 14
color_idx = np.linspace(0, 1, n_lines)
mpl.rcParams['axes.color_cycle']=[plt.cm.rainbow(i) for i in color_idx]
# plot as if we had no fees
# get the first result so we can grab the figure axes from the plot
olps_df = olps_sectors_train
ax = olps_df.results[0].plot(assets=False, weights=False, ucrp=True, portfolio_label=olps_df.index[0])
for k, r in olps_df.results.iteritems():
if k == olps_df.results.keys()[0]: # skip the first item because we have it already
continue
r.plot(assets=False, weights=False, ucrp=False, portfolio_label=k, ax=ax[0])
# Kelly went wild, so let's remove it
# get the first result so we can grab the figure axes from the plot
olps_df = olps_sectors_train
ax = olps_df.results[0].plot(assets=False, weights=False, ucrp=True, portfolio_label=olps_df.index[0])
for k, r in olps_df.results.iteritems():
if k == olps_df.results.keys()[0] or k == 'Kelly' : # skip the first item because we have it already
continue
r.plot(assets=False, weights=False, ucrp=False, portfolio_label=k, ax=ax[0])
olps_stats(olps_sectors_train)
olps_sectors_train[metrics].sort('profit', ascending=False)
profit | sharpe | information | annualized_return | drawdown_period | winning_pct | |
---|---|---|---|---|---|---|
BCRP | 1.226919 | 1.168603 | 1.148355 | 24.06737 | 134 | 55.667 |
ONS | 1.170012 | 0.8586938 | 0.4181884 | 16.35309 | 134 | 55.8209 |
UP | 1.166523 | 0.8495217 | 0.3003208 | 15.33203 | 145 | 55.32338 |
CRP | 1.166265 | 0.8482206 | 0 | 15.31564 | 145 | 55.42289 |
EG | 1.166244 | 0.8481597 | -0.3349534 | 15.30911 | 145 | 55.52239 |
BAH | 1.166026 | 0.8479684 | -0.2642631 | 15.21354 | 145 | 55.72139 |
Anticor | 1.14944 | 0.7725771 | 0.1426251 | 16.78452 | 178 | 53.60721 |
OLMAR | 1.12479 | 0.6402547 | -0.1256112 | 13.94468 | 173 | 52.97679 |
RMR | 1.118902 | 0.614074 | -0.1753967 | 13.41708 | 216 | 53.08392 |
CORN | 1.095282 | 0.5037723 | -0.6879739 | 9.378424 | 398 | 56.0199 |
CWMR | 1.095178 | 0.4939886 | -0.3888658 | 10.98823 | 249 | 53.18504 |
PAMR | 1.092942 | 0.4832585 | -0.4119575 | 10.72079 | 252 | 53.5497 |
BNN | 1.050962 | 0.2778638 | -1.042531 | 5.400526 | 536 | 52.12121 |
Kelly | 0 | NaN | 0 | -100 | 948 | 53.71728 |
# create the test set dataframe
olps_sectors_test = pd.DataFrame(index=algo_names, columns=algo_data)
olps_sectors_test.algo = olps_algos_sectors
# run all algos
for name, alg in zip(olps_sectors_test.index, olps_sectors_test.algo):
olps_sectors_test.ix[name,'results'] = alg.run(test_sectors)
# plot as if we had no fees
# get the first result so we can grab the figure axes from the plot
olps_df = olps_sectors_test
ax = olps_df.results[0].plot(assets=False, weights=False, ucrp=True, portfolio_label=olps_df.index[0])
for k, r in olps_df.results.iteritems():
if k == olps_df.results.keys()[0] : #or k == 'Kelly': # skip the first item because we have it already
continue
r.plot(assets=False, weights=False, ucrp=False, portfolio_label=k, ax=ax[0])
# drop Kelly !
# get the first result so we can grab the figure axes from the plot
olps_df = olps_sectors_test
ax = olps_df.results[0].plot(assets=False, weights=False, ucrp=True, portfolio_label=olps_df.index[0])
for k, r in olps_df.results.iteritems():
if k == olps_df.results.keys()[0] or k == 'Kelly': # skip the first item because we have it already
continue
r.plot(assets=False, weights=False, ucrp=False, portfolio_label=k, ax=ax[0])
olps_stats(olps_sectors_test)
olps_sectors_test[metrics].sort('profit', ascending=False)
profit | sharpe | information | annualized_return | drawdown_period | winning_pct | |
---|---|---|---|---|---|---|
BCRP | 1.372667 | 1.999838 | 1.068856 | 30.93763 | 79 | 56.45161 |
BAH | 1.238424 | 1.255041 | 0.274977 | 14.64473 | 39 | 59.36255 |
EG | 1.235216 | 1.239731 | 0.2910832 | 14.5555 | 39 | 58.96414 |
CRP | 1.235039 | 1.238862 | 0 | 14.55038 | 39 | 58.96414 |
UP | 1.234865 | 1.237929 | -0.5728829 | 14.53445 | 39 | 58.96414 |
CORN | 1.233172 | 1.290861 | 0.4536957 | 19.69628 | 35 | 54.58167 |
ONS | 1.208885 | 1.10624 | -0.3155632 | 13.80497 | 43 | 58.16733 |
RMR | 1.141392 | 0.7458201 | -0.06157608 | 13.71809 | 33 | 59.83936 |
OLMAR | 1.135582 | 0.7181107 | -0.07290339 | 13.54462 | 47 | 60.64257 |
PAMR | 1.130602 | 0.7027512 | -0.1423942 | 12.65033 | 41 | 58.63454 |
CWMR | 1.121708 | 0.6561141 | -0.20992 | 11.76061 | 41 | 58.23293 |
BNN | 1.049022 | 0.2901784 | -0.9299598 | 4.194964 | 125 | 54.61847 |
Anticor | 0.9770185 | -0.1272582 | -1.357038 | -2.208917 | 74 | 53.38645 |
Kelly | 0 | NaN | 0 | -100 | 251 | 53.23383 |
run_subsets
featureRMR and OLMAR do add value to a Lazy Portfolio if tested or run over a long enough period of time. This gives RMR and OLMAR a chance to grab onto a period of volatility. But in an up market (2013-1014) you want to Follow-the-Leader, not Follow-the-Looser. Of the other algo's, CRP or BAH are decent, and maybe it's worth understanding what ONS is doing.