Below I will try to adapt a code from David’s post:
#*****************************************************************
# Load historical data
#*****************************************************************
library(SIT)load.packages('quantmod')# load saved Proxies Raw Data, data.proxy.raw, to extend DBC and SHY
# please see http://systematicinvestor.github.io/Data-Proxy/ for more details
load('data/data.proxy.raw.Rdata')tickers='
LQD + VWESX
DBC + CRB
VTI +VTSMX # (or SPY)
ICF + VGSIX # (or IYR)
CASH = SHY + TB3Y
'data<-new.env()getSymbols.extra(tickers,src='yahoo',from='1970-01-01',env=data,raw.data=data.proxy.raw,set.symbolnames=T,auto.assign=T)for(iindata$symbolnames)data[[i]]=adjustOHLC(data[[i]],use.Adjusted=T)#print(bt.start.dates(data))
bt.prep(data,align='remove.na',fill.gaps=T)#*****************************************************************
# Setup
#*****************************************************************
data$universe=data$prices>0# do not allocate to CASH, or BENCH
data$universe$CASH=NAprices=data$prices*data$universen=ncol(prices)nperiods=nrow(prices)frequency='months'# find period ends, can be 'weeks', 'months', 'quarters', 'years'
period.ends=endpoints(prices,frequency)period.ends=period.ends[period.ends>0]models=list()commission=list(cps=0.01,fixed=10.0,percentage=0.0)# lag prices by 1 day
#prices = mlag(prices)
#*****************************************************************
# Equal Weight each re-balancing period
#******************************************************************
data$weight[]=NAdata$weight[period.ends,]=ntop(prices[period.ends,],n)models$ew=bt.run.share(data,clean.signal=F,commission=commission,trade.summary=T,silent=T)#*****************************************************************
# Risk Parity each re-balancing period
#******************************************************************
ret=diff(log(prices))hist.vol=bt.apply.matrix(ret,runSD,n=20)# risk-parity
weight=1/hist.volrp.weight=weight/rowSums(weight,na.rm=T)data$weight[]=NAdata$weight[period.ends,]=rp.weight[period.ends,]models$rp=bt.run.share(data,clean.signal=F,commission=commission,trade.summary=T,silent=T)#*****************************************************************
# Strategy:
#
# 1) Use 60,120,180, 252-day percentile channels
# - corresponding to 3,6,9 and 12 months in the momentum literature-
# (4 separate systems) with a .75 long entry and .25 exit threshold with
# long triggered above .75 and holding through until exiting below .25
# (just like in the previous post) - no shorts!!!
#
# 2) If the indicator shows that you should be in cash, hold SHY
#
# 3) Use 20-day historical volatility for risk parity position-sizing
# among active assets (no leverage is used). This is 1/volatility (asset A)
# divided by the sum of 1/volatility for all assets to determine the position size.
#******************************************************************
# load conditional qunatile function developed in the [Run Channel in Rcpp](/Run-Channel-Rcpp) post
load.packages('Rcpp')# you can download `channel.cpp` file at [channel.cpp](/public/doc/channel.cpp)
sourceCpp('channel.cpp')allocation=0*ifna(prices,0)for(lookback.leninc(60,120,180,252)){high.channel=NA*priceslow.channel=NA*pricesfor(iin1:ncol(prices)){temp=run_quantile_weight(prices[,i],lookback.len,0.25,0.75)low.channel[,i]=temp[,1]high.channel[,i]=temp[,2]}signal=iif(cross.up(prices,high.channel),1,iif(cross.dn(prices,low.channel),-1,NA))allocation=allocation+ifna(bt.apply.matrix(signal,ifna.prev),0)}# (A) Channel score
allocation=ifna(allocation/4,0)# equal-weight
weight=abs(allocation)/rowSums(abs(allocation))weight[allocation<0]=0weight$CASH=1-rowSums(weight,na.rm=T)data$weight[]=NAdata$weight[period.ends,]=weight[period.ends,]models$channel.ew=bt.run.share(data,clean.signal=F,commission=commission,trade.summary=T,silent=T)# risk-parity: (C)
weight=allocation*1/hist.volweight=abs(weight)/rowSums(abs(weight),na.rm=T)weight[allocation<0]=0weight$CASH=1-rowSums(weight,na.rm=T)data$weight[]=NAdata$weight[period.ends,]=ifna(weight[period.ends,],0)models$channel.rp=bt.run.share(data,clean.signal=F,commission=commission,trade.summary=T,silent=T)# let's verify
last.period=last(period.ends)print(allocation[last.period,])
#*****************************************************************
#The [Quantitative Approach To Tactical Asset Allocation Strategy(QATAA) by Mebane T. Faber](http://mebfaber.com/timing-model/)
#[SSRN paper](http://papers.ssrn.com/sol3/papers.cfm?abstract_id=962461)
#******************************************************************
# compute 10 month moving average
sma=bt.apply.matrix(prices,SMA,200)# go to cash if prices falls below 10 month moving average
go2cash=prices<smago2cash=ifna(go2cash,T)# equal weight target allocation
target.allocation=ntop(prices,n)# If asset is above it's 10 month moving average it gets allocation
weight=iif(go2cash,0,target.allocation)# otherwise, it's weight is allocated to cash
weight$CASH=1-rowSums(weight)data$weight[]=NAdata$weight[period.ends,]=weight[period.ends,]models$QATAA=bt.run.share(data,clean.signal=F,commission=commission,trade.summary=T,silent=T)#*****************************************************************
# Report
#*****************************************************************
#strategy.performance.snapshoot(models, T)
plotbt(models,plotX=T,log='y',LeftMargin=3,main=NULL)mtext('Cumulative Performance',side=2,line=1)
Just a quick update with new code for Channel function. I.e. fix for a lower channel outlined at
Run Channel in Rcpp Update post.
#*****************************************************************
# Strategy:
#
# 1) Use 60,120,180, 252-day percentile channels
# - corresponding to 3,6,9 and 12 months in the momentum literature-
# (4 separate systems) with a .75 long entry and .25 exit threshold with
# long triggered above .75 and holding through until exiting below .25
# (just like in the previous post) - no shorts!!!
#
# 2) If the indicator shows that you should be in cash, hold SHY
#
# 3) Use 20-day historical volatility for risk parity position-sizing
# among active assets (no leverage is used). This is 1/volatility (asset A)
# divided by the sum of 1/volatility for all assets to determine the position size.
#******************************************************************
# load conditional qunatile function developed in the [Run Channel in Rcpp Update](/Run-Channel-Rcpp-Update) post
load.packages('Rcpp')# you can download `channel1.cpp` file at [channel1.cpp](/public/doc/channel1.cpp)
sourceCpp('channel1.cpp')allocation=0*ifna(prices,0)for(lookback.leninc(60,120,180,252)){channels=bt.apply.matrix.ex2(prices,run_quantile_weight,lookback.len,0.25,0.75)signal=iif(cross.up(prices,channels$high),1,iif(cross.dn(prices,channels$low),-1,NA))allocation=allocation+ifna(bt.apply.matrix(signal,ifna.prev),0)}# (A) Channel score
allocation=ifna(allocation/4,0)# equal-weight
weight=abs(allocation)/rowSums(abs(allocation))weight[allocation<0]=0weight$CASH=1-rowSums(weight,na.rm=T)data$weight[]=NAdata$weight[period.ends,]=weight[period.ends,]models$channel.ew=bt.run.share(data,clean.signal=F,commission=commission,trade.summary=T,silent=T)# risk-parity: (C)
weight=allocation*1/hist.volweight=abs(weight)/rowSums(abs(weight),na.rm=T)weight[allocation<0]=0weight$CASH=1-rowSums(weight,na.rm=T)data$weight[]=NAdata$weight[period.ends,]=ifna(weight[period.ends,],0)models$channel.rp=bt.run.share(data,clean.signal=F,commission=commission,trade.summary=T,silent=T)#*****************************************************************
# Report
#*****************************************************************
#strategy.performance.snapshoot(models, T)
plotbt(models,plotX=T,log='y',LeftMargin=3,main=NULL)mtext('Cumulative Performance',side=2,line=1)