Asset Allocation Model

pg. 161 from "Trading Evolved" by Andreas F. Clenow

"The rules of this first ETF model are the following. We will use five ETFs to allocate our assets to. Each ETF will have a target weight, and at the beginning of each month we will reset the allocation to this target weight."

25% SPY - S&P500 Index Tracker ETF 30% TLT - 20 Year Treasury ETF 30% IEF - 7-10 Year Treasury ETF 7.5% GLD - Gold Tracker ETF 7.5% DBC - General Commodity Tracker ETF


In [1]:
import pandas as pd
import matplotlib.pyplot as plt
import datetime
from talib.abstract import *
#import numpy as np

import pinkfish as pf

# format price data
pd.options.display.float_format = '{:0.2f}'.format

%matplotlib inline

In [2]:
# set size of inline plots
'''note: rcParams can't be in same cell as import matplotlib
   or %matplotlib inline
   
   %matplotlib notebook: will lead to interactive plots embedded within
   the notebook, you can zoom and resize the figure
   
   %matplotlib inline: only draw static images in the notebook
'''
plt.rcParams["figure.figsize"] = (10, 7)

Portfolios


In [3]:
# large diversified ETFs
large_diversified_weights = {
    
    # stocks:US
    'SPY': 0.20, #1993
    'QQQ': 0.10, #1999
    # stocks:global
    'EFA': 0.05, #2001
    # stocks:emerging markets
    'EEM': 0.05, #2003
    # 40% stocks
    
    # bonds:treasuries
    'TLT': 0.05, #2002
    'IEF': 0.05, #2002
    'TIP': 0.05, #2003
    'BSV': 0.05, #2007
    # bonds:corporate bonds
    'LQD': 0.05, #2002
    # bonds: fixed income
    'AGG': 0.05, #2003
    # 30% bonds
    
    # metals
    'GLD': 0.10, #2004
    'SLV': 0.05, #2006
    # 15% metals
    
    # commodities 5%
    'DBC': 0.05, #2006
    
    # real estate 10%
    'NLY': 0.10  #1997 
}

# pinkfish portfolio
pinkfish_weights = \
    {'SPY': 0.20, 'QQQ':0.20, 'TLT': 0.20, 'NLY': 0.20, 'GLD': 0.20}

# 50% S&P 500, 50% treasury
fifty_fifty_weights = \
    {'SPY': 0.50, 'TLT': 0.50}

# ETF portfolio in "Trading Evolved"
trading_evolved_weights = \
    {'SPY': 0.25, 'TLT': 0.30, 'IEF': 0.30, 'GLD': 0.075, 'DBC': 0.075}

# Dave Ramsey - 25% Growth and income, 25% Growth, 25% Aggressive growth, 25% International
dave_ramsey_weights = \
    {'SPY': 0.25, 'VQNPX': 0.25, 'RPG': 0.25, 'EFA': 0.25}

# Warren Buffett Retirement Fund: 90% S&P500, 10% Short Term Treasuries
warren_buffett_weights = \
    {'SPY': 0.90, 'SHY': 0.10}

# https://www.forbes.com/sites/baldwin/2016/07/25/eight-famous-portfolios/#42c50d7d5cce
# american households: 25% real estate, 30% stocks, 30% fixed income, 5% cash, 10% alternatives
american_households_weights = \
    {'NLY': 0.25, 'SPY': 0.30, 'AGG': 0.30, 'GLD': 0.10}

# janet yellen: 50% fixed income, 40% S&P500, 10% cash
janet_yellen_weights = \
    {'AGG': 0.50, 'SPY': 0.40}

Some global data


In [4]:
#select one of the above
weights = pinkfish_weights

symbols = list(weights.keys())
capital = 10000
margin = pf.Margin.CASH
start = datetime.datetime(1900, 1, 1)
end = datetime.datetime.now()

use_cache = True

In [5]:
# fetch timeseries
portfolio = pf.Portfolio()
ts = portfolio.fetch_timeseries(symbols, start, end, use_cache=use_cache)
ts


Out[5]:
SPY_high SPY_low SPY_close QQQ_high QQQ_low QQQ_close TLT_high TLT_low TLT_close NLY_high NLY_low NLY_close GLD_high GLD_low GLD_close
date
1993-01-29 26.20 26.07 26.18 nan nan nan nan nan nan nan nan nan nan nan nan
1993-02-01 26.37 26.20 26.37 nan nan nan nan nan nan nan nan nan nan nan nan
1993-02-02 26.44 26.30 26.43 nan nan nan nan nan nan nan nan nan nan nan nan
1993-02-03 26.72 26.44 26.71 nan nan nan nan nan nan nan nan nan nan nan nan
1993-02-04 26.87 26.50 26.82 nan nan nan nan nan nan nan nan nan nan nan nan
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
2020-07-14 319.76 312.00 318.92 nan nan nan 168.14 166.98 167.12 nan nan nan 170.20 168.81 170.19
2020-07-15 323.04 319.27 321.85 nan nan nan 166.95 165.83 166.34 nan nan nan 170.43 169.39 170.34
2020-07-16 321.28 319.09 320.79 nan nan nan 167.77 167.04 167.13 nan nan nan 170.00 168.65 168.73
2020-07-17 322.57 319.74 321.72 nan nan nan 167.40 166.47 166.78 nan nan nan 170.27 169.61 170.12
2020-07-20 323.14 320.62 323.00 nan nan nan 167.62 167.09 167.21 nan nan nan 171.07 170.27 170.65

6918 rows × 15 columns


In [6]:
# add calendar columns
ts = portfolio.calendar(ts)

In [7]:
ts, start = portfolio.finalize_timeseries(ts, start)

In [8]:
portfolio.init_trade_logs(ts, capital, margin)
pf.TradeLog.instance


Out[8]:
{'SPY': <pinkfish.trade.TradeLog at 0x7f3f7ee23e80>,
 'QQQ': <pinkfish.trade.TradeLog at 0x7f3f7ee23eb0>,
 'TLT': <pinkfish.trade.TradeLog at 0x7f3f7ee23970>,
 'NLY': <pinkfish.trade.TradeLog at 0x7f3f7ee23ee0>,
 'GLD': <pinkfish.trade.TradeLog at 0x7f3f7ee23f10>}

In [9]:
# trading algorithm
for i, row in enumerate(ts.itertuples()):

    date = row.Index.to_pydatetime()
    end_flag = pf.is_last_row(ts, i)

    # rebalance on the first trading day of each month
    if row.first_dotm or end_flag:
        #portfolio.print_holdings(date, row)
        #TODO: use share_percent() and adjust positions that need reducing first
        for symbol in portfolio.symbols:
            #print(symbol, portfolio.share_percent(row, symbol))
            price = portfolio.get_row_column_price(row, symbol)
            weight = 0 if end_flag else weights[symbol]
            portfolio.adjust_percent(date, price, weight, symbol, row)
    # record daily balance
    portfolio.record_daily_balance(date, row)

In [10]:
# get logs
rlog, tlog, dbal = portfolio.get_logs()

In [11]:
rlog.head()


Out[11]:
date price shares entry_exit direction symbol
0 2004-12-01 86.95 23 entry LONG SPY
1 2004-12-01 34.82 57 entry LONG QQQ
2 2004-12-01 51.47 38 entry LONG TLT
3 2004-12-01 3.39 589 entry LONG NLY
4 2004-12-01 45.38 44 entry LONG GLD

In [12]:
tlog.tail()


Out[12]:
entry_date entry_price exit_date exit_price pl_points pl_cash qty cumul_total direction symbol
609 2020-05-01 167.54 2020-06-29 164.54 -3.00 -18.01 6 31655.15 LONG TLT
610 2020-05-01 159.78 2020-06-29 166.63 6.85 20.55 3 31675.70 LONG GLD
611 2020-06-01 162.09 2020-06-29 164.54 2.45 7.34 3 31683.05 LONG TLT
612 2020-06-01 6.07 2020-06-29 6.54 0.47 8.95 19 31692.00 LONG NLY
613 2020-06-01 163.66 2020-06-29 166.63 2.97 2.97 1 31694.97 LONG GLD

In [13]:
dbal.head()


Out[13]:
high low close shares cash leverage state
date
2004-11-18 10000.00 10000.00 10000.00 0 10000.00 1.00 -
2004-11-19 10000.00 10000.00 10000.00 0 10000.00 1.00 -
2004-11-22 10000.00 10000.00 10000.00 0 10000.00 1.00 -
2004-11-23 10000.00 10000.00 10000.00 0 10000.00 1.00 -
2004-11-24 10000.00 10000.00 10000.00 0 10000.00 1.00 -

In [14]:
stats = pf.stats(ts, tlog, dbal, capital)
pf.print_full(stats)


start                                                   2004-11-18
end                                                     2020-06-29
beginning_balance                                            10000
ending_balance                                            41694.97
total_net_profit                                          31694.97
gross_profit                                              33369.55
gross_loss                                                -1674.58
profit_factor                                                19.93
return_on_initial_capital                                   316.95
annual_return_rate                                            9.58
trading_period                           15 years 7 months 11 days
pct_time_in_market                                           99.80
margin                                                           1
avg_leverage                                                  1.00
max_leverage                                                  1.00
min_leverage                                                  1.00
total_num_trades                                               614
trades_per_year                                              39.33
num_winning_trades                                             535
num_losing_trades                                               79
num_even_trades                                                  0
pct_profitable_trades                                        87.13
avg_profit_per_trade                                         51.62
avg_profit_per_winning_trade                                 62.37
avg_loss_per_losing_trade                                   -21.20
ratio_avg_profit_win_loss                                     2.94
largest_profit_winning_trade                               1170.09
largest_loss_losing_trade                                  -101.80
num_winning_points                                        21972.72
num_losing_points                                          -633.41
total_net_points                                          21339.31
avg_points                                                   34.75
largest_points_winning_trade                                214.80
largest_points_losing_trade                                 -47.74
avg_pct_gain_per_trade                                       66.60
largest_pct_winning_trade                                   421.54
largest_pct_losing_trade                                    -30.97
max_consecutive_winning_trades                                  98
max_consecutive_losing_trades                                    7
avg_bars_winning_trades                                    1183.95
avg_bars_losing_trades                                      781.71
max_closed_out_drawdown                                     -26.84
max_closed_out_drawdown_start_date                      2008-02-28
max_closed_out_drawdown_end_date                        2008-11-20
max_closed_out_drawdown_recovery_date                   2009-09-08
drawdown_recovery                                            -0.73
drawdown_annualized_return                                   -2.80
max_intra_day_drawdown                                      -26.84
avg_yearly_closed_out_drawdown                               -8.18
max_yearly_closed_out_drawdown                              -26.84
avg_monthly_closed_out_drawdown                              -2.43
max_monthly_closed_out_drawdown                             -22.86
avg_weekly_closed_out_drawdown                               -1.00
max_weekly_closed_out_drawdown                              -13.67
avg_yearly_closed_out_runup                                  16.87
max_yearly_closed_out_runup                                  48.01
avg_monthly_closed_out_runup                                  3.14
max_monthly_closed_out_runup                                 20.54
avg_weekly_closed_out_runup                                   1.18
max_weekly_closed_out_runup                                  13.37
pct_profitable_years                                         85.83
best_year                                                    47.06
worst_year                                                  -22.74
avg_year                                                     10.13
annual_std                                                    9.47
pct_profitable_months                                        65.67
best_month                                                   19.57
worst_month                                                 -22.75
avg_month                                                     0.78
monthly_std                                                   3.00
pct_profitable_weeks                                         59.89
best_week                                                    13.35
worst_week                                                  -13.67
avg_week                                                      0.19
weekly_std                                                    1.59
sharpe_ratio                                                  0.82
sortino_ratio                                                 1.03
dtype: object

In [15]:
totals = portfolio.performance_per_symbol(weights)
totals


Out[15]:
cumul_total weight pct_cumul_total relative_performance
SPY $6,517.25 0.20 0.21 1.03
QQQ $10,411.11 0.20 0.33 1.64
TLT $5,855.85 0.20 0.18 0.92
NLY $3,770.16 0.20 0.12 0.59
GLD $5,140.60 0.20 0.16 0.81
TOTAL $31,694.97 1.00 1.00 1.00

In [16]:
corr_df = portfolio.correlation_map(ts)
corr_df


Out[16]:
SPY QQQ TLT NLY GLD
SPY 1.00 0.99 0.87 0.81 0.42
QQQ 0.99 1.00 0.89 0.81 0.46
TLT 0.87 0.89 1.00 0.84 0.68
NLY 0.81 0.81 0.84 1.00 0.74
GLD 0.42 0.46 0.68 0.74 1.00

In [17]:
benchmark = pf.Benchmark('SPY', capital, start, end, use_adj=True)
benchmark.run()


2004-11-18 00:00:00 BUY  115 SPY @ 86.59
2020-07-20 00:00:00 SELL 115 SPY @ 323.00

In [18]:
benchmark.tlog, benchmark.dbal = benchmark.get_logs()

In [19]:
benchmark.stats = benchmark.get_stats()

In [20]:
pf.plot_equity_curve(dbal, benchmark=benchmark.dbal)



In [21]:
df = pf.summary(stats, benchmark.stats, metrics=pf.currency_metrics)
df


Out[21]:
strategy benchmark
beginning_balance $10,000.00 $10,000.00
ending_balance $41,694.97 $37,186.74
total_net_profit $31,694.97 $27,186.74
gross_profit $33,369.55 $27,186.74
gross_loss -$1,674.58 $0.00

In [22]:
df = pf.plot_bar_graph(stats, benchmark.stats)
df


Out[22]:
strategy benchmark
annual_return_rate 9.58 8.74
max_closed_out_drawdown -26.84 -55.02
drawdown_annualized_return -2.80 -6.29
drawdown_recovery -0.73 -1.42
best_month 19.57 23.46
worst_month -22.75 -30.95
sharpe_ratio 0.82 0.53
sortino_ratio 1.03 0.63
monthly_std 3.00 4.57