Probabilistic Momentum Spreadsheet
In the last post, I introduced the concept of viewing momentum as a probability of one asset outperforming the other versus a binary decision driven by whichever return is greater between a pair of assets. This method incorporates the joint distribution between two assets that factors in their variance and covariance. The difference in the two mean returns are compared to the tracking error between two assets to compute the information ratio. This ratio is then converted to a probability via the t-distribution to provide a more intelligent confidence-based buffer to avoid costly switching. A good article by Corey Hoffstein at Newfound discusses a related concept here. Many readers have inquired about a spreadsheet example for probabilistic momentum which can be found here : probabilistic momentum worksheet.
hi David, thanks for sharing. few questions:
1. in the degree of freedom, you used 60. however you mentioned earlier it is lookback period minus 1, right?
2. when i set T-stat < 0, the probability calculation shows error.
thanks!
David, here’s a version that lets you tinker with rolling values. You can compare results easily for different values of window length and smoothing. (Sorry, don’t see how to post a file).
Victor
Is the Information Ratio calculated in the spreadsheet?
Thanks David – I’m looking forward to playing around with it.
I still don’t get the same backtest. The standard momentum strategy does better. When did start your backtest?
I have tried to replicate the research in excel and R (I know both very well) without any luck – not even close to the returns presented here.
It seems no one can replicate the results, which can only mean 2 things:
1. The backtest and the results are incorrect – can easily happen to anyone considering even a tiny mistake can have a drastic impact on end results.
2. The formula presented does not disclose the whole strategy – again all it takes is for one small detail to be left out which will make it very difficult to replicate the results.
The spreadsheet presented is also inconsistent with the explanation in the first article and will not allow to confirm the results…
Either way, I am suspect now having seen the lack of feedback!!
Someone please shed light on us all! 🙂
Hi James,
I’ll post what I’ve got so far tomorrow or the day after so that we can compare our understandings.
Here is my code in R.
Strategy.ProbaMomentum = function (tickers=c(“SPY”,”TLT”),keep=2,perf.days=60,switch.threshold=0.6,chart=”n”) {
nb.tickers = length(tickers)
data = new.env()
getSymbols(tickers,src=”yahoo”,from=”2009-12-30″,env=data,auto.assign=T)
i = 1
tmp = data[[tickers[i]]][,paste(tickers[i],”.Close”,sep=””)]
for (i in 2:nb.tickers) {
tmp = cbind(tmp,data[[tickers[i]]][,paste(tickers[i],”.Close”,sep=””)])
}
tmp = tmp[“2010-09-30::”,]
tmp = tmp[!is.na(rowSums(tmp)),]
tmp.perfs = ROC(tmp,type=”discrete”)
tmp.weights = tmp.perfs
tmp.weights[] = 0
tmp.weights.momentum = tmp.weights
colnames(tmp.weights) = paste(tickers,”.weights”,sep=””)
tmp.perfs.momentum = ROC(tmp,n=perf.days,type=”discrete”)
tmp.ir = tmp.perfs[,1]
tmp.ir[] = NA
colnames(tmp.ir) = “InformationRatio”
for (i in (perf.days+1):nrow(tmp.perfs)) {
tmp.ir[i] = InformationRatio(tmp.perfs[(i-perf.days+1):i,1],tmp.perfs[(i-perf.days+1):i,2],scale=60)
if (tmp.perfs.momentum[i,1]>tmp.perfs.momentum[i,2]) {
tmp.weights.momentum[i,1] = 1
} else {
tmp.weights.momentum[i,2] = 1
}
}
tmp.proba = pt(tmp.ir,df=perf.days-1)
tmp.weights[tmp.proba>switch.threshold,1] = 1
tmp.weights[tmp.proba<(1-switch.threshold),2] = 1
tmp.weights = lag(tmp.weights,1)
tmp.weights[1,] = 0
tmp.weights.momentum = lag(tmp.weights.momentum,1)
tmp.weights.momentum[1,] = 0
write.table(cbind(tmp,tmp.weights,tmp.ir),"~/Google Drive/R/Rotation.csv",sep=",",row.names=index(tmp),col.names=TRUE)
tmp.ret = ROC(tmp,type="discrete")
tmp.ret[1,] = 0
strat = xts(rowSums(tmp.ret*tmp.weights),order.by=index(tmp.ret))
strat = cumprod(1+strat)
colnames(strat) = "Strat"
strat.bench = xts(rowSums(tmp.ret*tmp.weights.momentum),order.by=index(tmp.ret))
strat.bench[1,]=0
strat.bench = cumprod(1+strat.bench)
colnames(strat.bench) = "Bench"
if (chart=="y") {
par(mar=c(2,2,1,2))
layout(rbind(c(1,1),c(1,1),c(2,2),c(3,3),c(4,4)))
plot(as.Date(time(strat)),coredata(strat),type="l",main="Rotation",cex.main=0.65,cex.axis=0.65,xaxt="n",ylab="",xlab="")
axis.dates = seq(as.Date(first(time(strat))),as.Date(last(time(strat))),length.out=8)
axis.Date(1,at=axis.dates,labels=TRUE,cex.axis=0.65,format="%d-%b-%Y")
abline(v=axis.dates,col="lightgray")
lines(as.Date(time(strat.bench)),coredata(strat.bench),col="red",lty="dashed")
barplot(tmp.weights[,1],main="",cex.main=0.65,cex.axis=0.65,xaxt="n",ylab="",xlab="")
barplot(tmp.weights.momentum[,1],main="",cex.main=0.65,cex.axis=0.65,xaxt="n",ylab="",xlab="")
plot.table(as.matrix(table.CalendarReturns(ROC(strat[endpoints(strat),],type="discrete"))),text.cex=0.75)
}
return(cbind(strat,strat.bench,tmp.weights))
}
Backtesting from Sep 2010, I get a CAGR of 10.3% on this probabilistic momentum and a CAGR of 13.7% for the normal momentum strategy.
Do you get similar results?
You can find the code on http://systematicinvestor.wordpress.com/2014/02/17/probabilistic-momentum/. The point that was not clear in the description from David is that you switch from SPY to TLT or to TLT from SPY once you reach the threshold of 60%, otherwise the weights stay as they are.