This post will show how to use Monte Carlo to test for signal intelligence.

Although I had rejected this strategy in the last post, I was asked to do a monte-carlo analysis of a thousand random portfolios to see how the various signal processes performed against said distribution. Essentially, the process is quite simple: as I’m selecting one asset each month to hold, I simply generate a random number between 1 and the amount of assets (5 in this case), and hold it for the month. Repeat this process for the number of months, and then repeat this process a thousand times, and see where the signal processes fall across that distribution.

I didn’t use parallel processing here since Windows and Linux-based R have different parallel libraries, and in the interest of having the code work across all machines, I decided to leave it off.

Here’s the code:

randomAssetPortfolio <- function(returns) { numAssets <- ncol(returns) numPeriods <- nrow(returns) assetSequence <- sample.int(numAssets, numPeriods, replace=TRUE) wts <- matrix(nrow = numPeriods, ncol=numAssets, 0) wts <- xts(wts, order.by=index(returns)) for(i in 1:nrow(wts)) { wts[i,assetSequence[i]] <- 1 } randomPortfolio <- Return.portfolio(R = returns, weights = wts) return(randomPortfolio) } t1 <- Sys.time() randomPortfolios <- list() set.seed(123) for(i in 1:1000) { randomPortfolios[[i]] <- randomAssetPortfolio(monthRets) } randomPortfolios <- do.call(cbind, randomPortfolios) t2 <- Sys.time() print(t2-t1) algoPortfolios <- sigBoxplots[,1:12] randomStats <- table.AnnualizedReturns(randomPortfolios) algoStats <- table.AnnualizedReturns(algoPortfolios) par(mfrow=c(3,1)) hist(as.numeric(randomStats[1,]), breaks = 20, main = 'histogram of monte carlo annualized returns', xlab='annualized returns') abline(v=as.numeric(algoStats[1,]), col='red') hist(as.numeric(randomStats[2,]), breaks = 20, main = 'histogram of monte carlo volatilities', xlab='annualized vol') abline(v=as.numeric(algoStats[2,]), col='red') hist(as.numeric(randomStats[3,]), breaks = 20, main = 'histogram of monte carlo Sharpes', xlab='Sharpe ratio') abline(v=as.numeric(algoStats[3,]), col='red') allStats <- cbind(randomStats, algoStats) aggregateMean <- apply(allStats, 1, mean) aggregateDevs <- apply(allStats, 1, sd) algoPs <- 1-pnorm(as.matrix((algoStats - aggregateMean)/aggregateDevs)) plot(as.numeric(algoPs[1,])~c(1:12), main='Return p-values', xlab='Formation period', ylab='P-value') abline(h=0.05, col='red') abline(h=.1, col='green') plot(1-as.numeric(algoPs[2,])~c(1:12), ylim=c(0, .5), main='Annualized vol p-values', xlab='Formation period', ylab='P-value') abline(h=0.05, col='red') abline(h=.1, col='green') plot(as.numeric(algoPs[3,])~c(1:12), main='Sharpe p-values', xlab='Formation period', ylab='P-value') abline(h=0.05, col='red') abline(h=.1, col='green')

And here are the results:

In short, compared to monkeys throwing darts, to use some phrasing from the Price Action Lab blog, these signal processes are only marginally intelligent, if at all, depending on the variation one chooses. Still, I was recommended to see this process through the end, and evaluate rules, so next time, I’ll evaluate one easy-to-implement rule.

Thanks for reading.

NOTE: while I am currently consulting, I am always open to networking, meeting up (Philadelphia and New York City both work), consulting arrangements, and job discussions. Contact me through my email at ilya.kipnis@gmail.com, or through my LinkedIn, found here.