Archive
Permanent Portfolio
First, just a quick update: I’m moving the release date of the SIT package a few months down the road, probably in November.
Now back to the post. Recently I came across a series of interesting posts about the Permanent Portfolio at the GestaltU blog. Today I want to show you how to back-test the Permanent Portfolio using the Systematic Investor Toolbox.
The simple version of the Permanent Portfolio consists of equal allocations to stocks(SPY), gold(GLD), treasuries(TLT), and cash(SHY). [25% allocation each] The portfolio is rebalanced once a year if any allocation breaks out from the 15% – 35% range.
############################################################################### # Load Systematic Investor Toolbox (SIT) # https://systematicinvestor.wordpress.com/systematic-investor-toolbox/ ############################################################################### setInternet2(TRUE) con = gzcon(url('http://www.systematicportfolio.com/sit.gz', 'rb')) source(con) close(con) #***************************************************************** # Load historical data #****************************************************************** load.packages('quantmod') tickers = spl('SPY,TLT,GLD,SHY') data <- new.env() getSymbols(tickers, src = 'yahoo', from = '1980-01-01', env = data, auto.assign = T) for(i in ls(data)) data[[i]] = adjustOHLC(data[[i]], use.Adjusted=T) # extend GLD with Gold.PM - London Gold afternoon fixing prices data$GLD = extend.GLD(data$GLD) bt.prep(data, align='remove.na') #***************************************************************** # Code Strategies #****************************************************************** prices = data$prices n = ncol(prices) nperiods = nrow(prices) # annual period.ends = endpoints(prices, 'years') period.ends = period.ends[period.ends > 0] period.ends.y = c(1, period.ends) # quarterly period.ends = endpoints(prices, 'quarters') period.ends = period.ends[period.ends > 0] period.ends.q = c(1, period.ends) models = list() #***************************************************************** # Code Strategies #****************************************************************** target.allocation = matrix(rep(1/n,n), nrow=1) # Buy & Hold data$weight[] = NA data$weight[period.ends.y[1],] = target.allocation models$buy.hold = bt.run.share(data, clean.signal=F) # Equal Weight Annual data$weight[] = NA data$weight[period.ends.y,] = ntop(prices[period.ends.y,], n) models$equal.weight.y = bt.run.share(data, clean.signal=F) # Equal Weight Quarterly data$weight[] = NA data$weight[period.ends.q,] = ntop(prices[period.ends.q,], n) models$equal.weight.q = bt.run.share(data, clean.signal=F)
To rebalance base on the 10% threshold (i.e. portfolio weights breaking out from the 15% – 35% range) I will use bt.max.deviation.rebalancing() function introduced in the Backtesting Rebalancing methods post.
#***************************************************************** # Rebalance based on threshold #****************************************************************** # Rebalance only when threshold is broken models$threshold.y = bt.max.deviation.rebalancing(data, models$buy.hold, target.allocation, 10/100, 0, period.ends = period.ends.y) # Rebalance only when threshold is broken models$threshold.q = bt.max.deviation.rebalancing(data, models$buy.hold, target.allocation, 10/100, 0, period.ends = period.ends.q) #***************************************************************** # Create Report #****************************************************************** plotbt.custom.report.part1(models) plotbt.strategy.sidebyside(models) # Plot Portfolio Turnover for each Rebalancing method layout(1:2) barplot.with.labels(sapply(models, compute.turnover, data), 'Average Annual Portfolio Turnover', F) barplot.with.labels(sapply(models, compute.max.deviation, target.allocation), 'Maximum Deviation from Target Mix')
The Quarterly rebalancing with 10% threshold produces an attractive portfolio with top performance and low turnover.
To view the complete source code for this example, please have a look at the bt.permanent.portfolio.test() function in bt.test.r at github.
Extending Gold time series
While back-testing trading strategies I want all assets to have long history. Unfortunately, sometimes there is no tradeable stock or ETF with sufficient history. For example, I might use GLD as a proxy for Gold allocation, but GLD is only began trading in November of 2004.
We can extend the GLD’s historical returns with its benchmark index – London Gold afternoon fixing prices. Let’s first download and plot side by side the GLD and London Gold afternoon fixing prices from http://www.kitco.com/gold.londonfix.html the convenient CSV download is provided by Wikiposit.
############################################################################### # Load Systematic Investor Toolbox (SIT) # https://systematicinvestor.wordpress.com/systematic-investor-toolbox/ ############################################################################### setInternet2(TRUE) con = gzcon(url('http://www.systematicportfolio.com/sit.gz', 'rb')) source(con) close(con) #***************************************************************** # Load historical data #****************************************************************** load.packages('quantmod') GLD = getSymbols('GLD', src = 'yahoo', from = '1970-01-01', auto.assign = F) GLD = adjustOHLC(GLD, use.Adjusted=T) # get Gold.PM - London Gold afternoon fixing prices temp = read.csv('http://wikiposit.org/w?action=dl&dltypes=comma%20separated&sp=daily&uid=KITCO',skip=4,header=TRUE, stringsAsFactors=F) Gold.PM = make.xts(as.double(temp$Gold.PM) / 10, as.Date(temp$Date, '%d-%b-%Y')) Gold.PM = Gold.PM[ !is.na(Gold.PM), ] # merge GLD and Gold.PM data <- new.env() data$GLD = GLD data$Gold.PM = Gold.PM bt.prep(data, align='remove.na') #***************************************************************** # Plot GLD and Gold.PM #****************************************************************** layout(1:2) plota(data$GLD, type='l', col='black', plotX=F) plota.lines(data$Gold.PM, col='blue') plota.legend('GLD,Gold.PM', 'black,blue', list(data$GLD, data$Gold.PM)) # plot GLD and London afternoon gold fix spread spread = 100 * (Cl(data$GLD) - data$Gold.PM) / data$Gold.PM plota(spread , type='l', col='black') plota.legend('GLD vs Gold.PM % spread', 'black', spread)
There is a divergence between GLD and its benchmark index. Please read “Gold’s ‘Paper’ Price” article by B. Zigler for a detailed discussion and explanation for this divergence.
Next let’s extend the GLD’s time series with its benchmark ( I created a helper function extend.GLD() function in data.r at github to simplify the process ) and use it for a simple equal weight strategy
#***************************************************************** # Create simple equal weight back-test #****************************************************************** tickers = spl('GLD,TLT') data <- new.env() getSymbols(tickers, src = 'yahoo', from = '1980-01-01', env = data, auto.assign = T) for(i in ls(data)) data[[i]] = adjustOHLC(data[[i]], use.Adjusted=T) # extend GLD with Gold.PM - London Gold afternoon fixing prices data$GLD = extend.GLD(data$GLD) bt.prep(data, align='remove.na') #***************************************************************** # Code Strategies #****************************************************************** prices = data$prices n = ncol(prices) # find period ends period.ends = endpoints(prices, 'months') period.ends = period.ends[period.ends > 0] models = list() #***************************************************************** # Equal Weight #****************************************************************** data$weight[] = NA data$weight[period.ends,] = ntop(prices[period.ends,], n) models$equal.weight = bt.run.share(data, clean.signal=F) #***************************************************************** # Create Report #****************************************************************** plotbt.custom.report.part1(models) plotbt.custom.report.part2(models)
By extending GLD’s time series, the back-test now goes back to the June 2002, the TLT’s first trading date.
To view the complete source code for this example, please have a look at the bt.extend.GLD.test() function in bt.test.r at github.
Merging Current Stock Quotes with Historical Prices
I got a question last week about going from the backtest to the trading. For example, if our system is based on today’s close, we can approximate the close value by the price at say 3:30pm, determine the signal and still have time enter the trade. It is not perfect, but one of possible solutions.
Unfortunately calling the getSymbols() function during the trading session will only return closing prices for the prior session. Following is an example I run today during the trading session:
#***************************************************************** # Load historical data #****************************************************************** load.packages('quantmod') tickers = spl('VTI,EFA,SHY') data <- new.env() getSymbols(tickers, src = 'yahoo', from = '1980-01-01', env = data, auto.assign = T) for(i in ls(data)) data[[i]] = adjustOHLC(data[[i]], use.Adjusted=T) bt.prep(data) # look at the data last(data$prices, 2)
> last(data$prices, 2) EFA SHY VTI 2012-08-30 51.15 84.46 71.78 2012-08-31 51.60 84.51 72.21
There is no information for September 4th.
To incorporate current stock quotes, we need to combine current quotes from getQuote() function with historical prices from getSymbols() function. Following is an example I run today during the trading session:
#***************************************************************** # Load historical data #****************************************************************** load.packages('quantmod') tickers = spl('VTI,EFA,SHY') data <- new.env() getSymbols(tickers, src = 'yahoo', from = '1980-01-01', env = data, auto.assign = T) for(i in ls(data)) data[[i]] = adjustOHLC(data[[i]], use.Adjusted=T) # current quotes logic quotes = getQuote(tickers) for(i in ls(data)) if( last(index(data[[i]])) < as.Date(quotes[i, 'Trade Time']) ) { data[[i]] = rbind( data[[i]], make.xts(quotes[i, spl('Open,High,Low,Last,Volume,Last')], as.Date(quotes[i, 'Trade Time']))) } bt.prep(data) # look at the data last(data$prices, 2)
> last(data$prices, 2) EFA SHY VTI 2012-08-31 51.6000 84.510 72.21 2012-09-04 51.2561 84.495 71.87
The current stock quotes are now present. We can run our strategy at say around 3:30pm, determine the signal and still have time enter the trade.
To view the complete source code for this example, please have a look at the bt.current.quote.test() function in bt.test.r at github.