# Optimizer Functions12################################################################################3# FUNCTIONS:4#5# BruteForcePortfolios (R,weightgrid,yeargrid)6# BacktestData ()7# Backtest (R,portfolioreturns, yeargrid, cutat=1000000, methods, p, ... )8# weight.grid (columnnames, seqstart=.05, seqend=.25, seqstep=.05)9# maxdrawdown (R)10# cut.returns (R, cutrow, startrow=1)11# weighttest (weightgrid, test=1)12# WeightedPortfolioUtility (R, weightgrid, from, to, methods, p, ...)13# backtestDisplay (R, portfolioreturns, yeargrid, backtestresults, show="Cumulative.Return" )14# MonthlyBacktestResults (R, weightgrid, yeargrid, backtestweights)15#16################################################################################1718# Check to see if the required libraries are loaded19if(!require("PerformanceAnalytics", quietly=TRUE)) {20stop("package", sQuote("PerformanceAnalytics"), "is needed. Stopping")21}22#source("optim_functions.R")2324# ------------------------------------------------------------------------------25cut.returns =26function (R, cutrow, startrow=1)27{ # @author Brian G. Peterson2829# Description:3031# FUNCTION:32result = R[startrow:cutrow,]3334# Return Value:35result36}3738# ------------------------------------------------------------------------------39weighttest =40function (weightgrid, test=1)41{42rows=nrow(weightgrid)4344result=NA4546for(row in 1:rows) {47r = as.vector(weightgrid[row,])48#if (!is.numeric(r)) stop("The selected row is not numeric")4950if (sum(r) == test) {51#if (result=NA) {52#create data.frame53# result=data.frame(r=r)54#} else {55r=data.frame(r=r)56result=cbind(result,r)57#}58}59} #end rows loop6061# Return Value:62result6364}6566# ------------------------------------------------------------------------------67# @todo: use zoo rollapply in BruteForcePortfolios() fn68#WeightedPortfolioUtility =69WeightedPortfolioUtility =70function (R, weightgrid, from, to,71methods=c( "PeriodGVaR", "ThreeYrGVaR", "InceptionGVaR", "PeriodmodVaR", "ThreeYrmodVaR", "InceptionmodVaR",72"PeriodGES", "ThreeYrGES", "InceptionGES", "PeriodmodES", "ThreeYrmodES", "InceptionmodES",73"PeriodStdDev", "ThreeYrStdDev", "InceptionStdDev",74"PeriodReturn", "maxdd", "omega" )75, p=0.95, ... )76{ # @author Brian G. Peterson and Kris Boudt7778# Description:79# This is the workhorse of the backtest architecture.80# Any optimization backtesting model must81# - create portfolios,82# - analyze their returns,83# - and allow you to use some metric, function, or algorithm84# to analyze the results85#86# function takes a set of returns, a set of portfolio weights,87# and a timeframe to operate within, and generates a set of statistics88# for every possible weight.89#90# When you calculate statistics for all possible weights generated by91# a brute force method, an estimation method, some function,92# a regression, etc., you can then run tests against the results "in-sample"93# to determine which weighting vector you want to use "out-of-sample"94# in the next rolling period.95#96# This function is extremely computationally intensive, because it is doing97# a lot of things with every single possible weighting vector, so you want98# to be able to run it and store the results for later analysis.99# It is best to strike a balance between placing functions in here that100# you will need to help choose the optimal weighting vector, and be101# parsimonious, as these calculations may be run tens of thousands of times102# in any given backtest. Remember that once you have chosen an optimal103# portfolio or a sub-set of possible optimal portfolios, you can run more104# exhaustive analytics on each of your candidates later, rather than on the105# entire set of all possible portfolios.106#107# R data structure of historical returns108# weightgrid each row contains one weighting vector, same number of columns as your returns109# from where to cut the beginning of the return stream110# to where to cut the end of the return stream111#112# @returns data frame where each row has the same index number as a weighting vector,113# and each column is one of your metrics from inside this function114#115# @todo don't recalculate if Period and ThreeYr or Inception and ThreeYr are the same116117# Setup:118# there's a risk here is sampling from weightgrid that119# your row names and numbers won;t match, need to be careful120121# data type conditionals122# cut the return series for from:to123if (class(R) == "timeSeries") {124R = R@Data125}126127if (from < 1) from = 1128if (to > nrow(R)) to = nrow(R)129130131if (ncol(weightgrid) != ncol(R)) stop ("The Weighting Vector and Return Collection do not have the same number of Columns.")132133# Compute multivariate moments134# should probably change this part to use zoo's rollapply to create the various groupings135136threeyrfrom = to - 35; #for monthly data 36-35=1 for three year period137if (threeyrfrom < 1 ) threeyrfrom = 1138139R.inception = R[1:to , ];140mu.inception = apply(R.inception,2,'mean');141sigma.inception = cov(R.inception);142M3.inception = M3.MM(R.inception);143M4.inception = M4.MM(R.inception);144145R.period = R[from:to, ];146mu.period = apply(R.period,2,'mean');147sigma.period = cov(R.period);148M3.period = M3.MM(R.period);149M4.period = M4.MM(R.period);150151R.3yr = R[ threeyrfrom:to, ];152mu.3yr = apply(R.3yr,2,'mean');153sigma.3yr = cov(R.3yr);154M3.3yr = M3.MM(R.3yr);155M4.3yr = M4.MM(R.3yr);156157rows=nrow(weightgrid)158# Function:159# this outer loop makes me very angry.160# R seems to hang and never return on very large matrices or data.frame's161# to get around this, we subset the data into chunks that R can handle,162# we then run the inner for loop against these, and reassemble the list163# into a single structure at the end.164#165# if we *must* do this to work around limitations in R, perhaps we can166# figure out how to examine the length of weightgrid and subset it automatically167subsetrows= matrix(c(1,31000,31001,62000,62001,rows), nrow=3,byrow=TRUE)168# subsetrows= matrix(c(1,10,11,20,21,30), nrow=3,byrow=TRUE)169resultlist=vector("list", 3)170weightgridsave=weightgrid171for (srow in 1:3) {172weightgrid=weightgrid[subsetrows[srow,1]:subsetrows[srow,2],,drop=FALSE]173result=NULL174175for(row in rownames(weightgrid)) {176# at some point consider parallelizing this by using a clustered apply177# to call a sub-function so that this could get distributed178# to multiple processor cores or threads179180# construct a data structure that holds each result for this row181resultrow=data.frame(row.names = row)182183w = as.numeric(weightgrid[row,])184# test each row in the weighting vectors against the right dates in the return collection185186# problem of NAs? Not solved yet !!!!187# should really solve this by calling checkData in ButeForcePortfolios188#if (any(is.na(R))) {189# print( paste("NA\'s in returns: ",row, " ",w," from ", from) )190# #browser()191#}192193mean.inception = mean.MM( w , mu.inception );194mean.period = mean.MM( w , mu.period) ;195mean.3yr = mean.MM( w , mu.3yr ) ;196197for (method in methods) {198switch(method,199PeriodStdDev = {200# Standard Deviation201PeriodStdDev = StdDev.MM(w,sigma=sigma.period)202PeriodSRStdDev = mean.period/ PeriodStdDev203colnames(PeriodStdDev) = "StdDev.period"204colnames(PeriodSRStdDev)="SR.StdDev.period"205resultrow= cbind(resultrow,PeriodStdDev,PeriodSRStdDev)206},207ThreeYrStdDev = {208# Standard Deviation209ThreeYrStdDev = StdDev.MM(w,sigma=sigma.3yr)210ThreeYrSRStdDev = mean.3yr/ThreeYrStdDev;211colnames(ThreeYrStdDev) = "StdDev.3yr"212colnames(ThreeYrStdDev) = "SR.StdDev.3yr"213resultrow= cbind(resultrow,ThreeYrStdDev,ThreeYrSRStdDev)214},215InceptionStdDev = {216# Standard Deviation217InceptionStdDev = StdDev.MM(w,sigma=sigma.inception)218InceptionSRStdDev = mean.inception/InceptionStdDev219colnames(InceptionStdDev) = "StdDev.inception"220colnames(InceptionSRStdDev) = "SR.StdDev.inception"221resultrow= cbind(resultrow,InceptionStdDev, InceptionSRStdDev)222},223PeriodGVaR = {224PeriodGVaR = GVaR.MM(w=w, mu=mu.period, sigma = sigma.period, p=p )225PeriodSRGVaR = mean.period/PeriodGVaR226colnames(PeriodGVaR)="GVaR.period"227colnames(PeriodSRGVaR)="SR.GVaR.period"228resultrow= cbind(resultrow,PeriodGVaR,PeriodSRGVaR)229},230ThreeYrGVaR = {231ThreeYrGVaR = GVaR.MM(w=w, mu=mu.3yr, sigma = sigma.3yr, p=p )232ThreeYrSRGVaR = mean.3yr/ThreeYrGVaR233colnames(ThreeYrGVaR)="GVaR.3yr"234colnames(ThreeYrSRGVaR)="SR.GVaR.3yr"235resultrow= cbind(resultrow,ThreeYrGVaR,ThreeYrSRGVaR)236},237InceptionGVaR = {238InceptionGVaR = GVaR.MM(w=w, mu=mu.inception, sigma = sigma.inception, p=p )239InceptionSRGVaR = mean.inception/InceptionGVaR240colnames(InceptionGVaR)="GVaR.inception"241colnames(InceptionSRGVaR)="SR.GVaR.inception"242resultrow= cbind(resultrow,InceptionGVaR,InceptionSRGVaR)243},244PeriodmodVaR = {245PeriodmodVaR = mVaR.MM(w=w, mu=mu.period, sigma = sigma.period, M3=M3.period , M4 =M4.period , p=p )246PeriodSRmodVaR = mean.period/PeriodmodVaR247colnames(PeriodmodVaR)="modVaR.period"248colnames(PeriodSRmodVaR)="SR.modVaR.period"249resultrow= cbind(resultrow,PeriodmodVaR,PeriodSRmodVaR)250},251InceptionmodVaR = {252InceptionmodVaR = mVaR.MM(w=w, mu=mu.inception, sigma = sigma.inception, M3=M3.inception , M4 =M4.inception , p=p )253InceptionSRmodVaR = mean.inception/InceptionmodVaR254colnames(InceptionmodVaR)="modVaR.inception"255colnames(InceptionSRmodVaR)="SR.modVaR.inception"256resultrow= cbind(resultrow,InceptionmodVaR,InceptionSRmodVaR)257},258ThreeYrmodVaR = {259ThreeYrmodVaR = mVaR.MM(w=w, mu=mu.3yr, sigma = sigma.3yr, M3=M3.3yr , M4 =M4.3yr, p=p )260ThreeYrSRmodVaR = mean.3yr/ThreeYrmodVaR261colnames(ThreeYrmodVaR)="modVaR.3yr"262colnames(ThreeYrSRmodVaR)="SR.modVaR.3yr"263resultrow= cbind(resultrow,ThreeYrmodVaR,ThreeYrSRmodVaR)264},265InceptionGES = {266InceptionGES = GES.MM(w=w, mu=mu.inception, sigma = sigma.inception, p=p )267InceptionSRGES = mean.inception/InceptionGES268colnames(InceptionGES)="GES.inception"269colnames(InceptionSRGES)="SR.GES.inception"270resultrow= cbind(resultrow,InceptionGES,InceptionSRGES)271},272PeriodGES = {273PeriodGES = GES.MM(w=w, mu=mu.period, sigma = sigma.period, p=p )274PeriodSRGES = mean.period/PeriodGES275colnames(PeriodGES)="GES.period"276colnames(PeriodSRGES)="SR.GES.period"277resultrow= cbind(resultrow,PeriodGES,PeriodSRGES)278},279ThreeYrGES = {280ThreeYrGES = GES.MM(w=w, mu=mu.3yr, sigma = sigma.3yr, p=p )281ThreeYrSRGES = mean.3yr/ThreeYrGES282colnames(ThreeYrGES)="GES.3yr"283colnames(ThreeYrSRGES)="SR.GES.3yr"284resultrow= cbind(resultrow,ThreeYrGES,ThreeYrSRGES)285},286PeriodmodES = {287PeriodmodES = mES.MM(w=w, mu=mu.period, sigma = sigma.period, M3=M3.period , M4 =M4.period , p=p )288PeriodSRmodES = mean.period/PeriodmodES289colnames(PeriodmodES)="modES.period"290colnames(PeriodSRmodES)="SR.modES.period"291resultrow= cbind(resultrow,PeriodmodES,PeriodSRmodES)292},293ThreeYrmodES = {294ThreeYrmodES = mES.MM(w=w, mu=mu.3yr, sigma = sigma.3yr, M3=M3.3yr , M4 =M4.3yr , p=p )295ThreeYrSRmodES = mean.3yr/ThreeYrmodES296colnames(ThreeYrmodES)="modES.3yr"297colnames(ThreeYrSRmodES)="SR.modES.3yr"298resultrow= cbind(resultrow,ThreeYrmodES,ThreeYrSRmodES)299},300InceptionmodES = {301InceptionmodES = mES.MM(w=w, mu=mu.inception, sigma = sigma.inception, M3=M3.inception , M4 =M4.inception, p=p )302InceptionSRmodES = mean.inception/InceptionmodES303colnames(InceptionmodES)="modES.inception"304colnames(InceptionSRmodES)="SR.modES.inception"305resultrow= cbind(resultrow,InceptionmodES,InceptionSRmodES)306}307# @todo put Return.portfolio, maxdrawdown, omega back, think about others308)#end switch function309}# end loop over methods310311if (is.null(result)){312result=matrix(nrow=nrow(weightgrid),ncol=ncol(resultrow),byrow=TRUE)313rownames(result)=rownames(weightgrid)314colnames(result)=colnames(resultrow)315}316317# print( paste("Completed row: ",rownames(resultrow),":",date()) )318# then rbind the rows319# result = rbind(result,resultrow)320result[as.character(row),]=as.matrix(resultrow)321322} #end rows loop323resultlist[[srow]] <- result324weightgrid=weightgridsave325print(paste("Row ",subsetrows[srow,2]," completed ",date()))326} #end subset loop327328result=rbind(resultlist[[1]],resultlist[[2]],resultlist[[3]])329330# Return Value:331result332333}334335336# ------------------------------------------------------------------------------337# @todo: use zoo rollapply in BruteForcePortfolios() fn338BruteForcePortfolios =339function(R,weightgrid,yeargrid,340methods=c( "PeriodGVaR", "ThreeYrGVaR", "InceptionGVaR", "PeriodmodVaR", "ThreeYrmodVaR", "InceptionmodVaR",341"PeriodGES", "ThreeYrGES", "InceptionGES", "PeriodmodES", "ThreeYrmodES", "InceptionmodES",342"maxdd", "omega", "PeriodStdDev", "ThreeYrStdDev", "InceptionStdDev" )343, p=0.95, ...344)345{ # @author Brian G. Peterson346347# Description:348#349# Performs the looping and storage of the base analytics for your series350# of possible portfolios. I've titled the third parameter 'yeargrid',351# but it is really a 'rolling window grid', although these will often be years.352# We slice the computation into these years or rolling periods,353# and store the results separately, because each of these rolling periods will354# have one solution for each possible weighting vector. By generating them and355# storing them for all weighting vectors in every period, you can do all the hard356# computational work in one pass, and then reuse the data set over and over again357# in multiple analytic tests, methods, or hypotheses for choosing in-sample results358# to use as out-of-sample weights.359#360# @todo add optional subdirectory tp keep different backtests apart361#362# R data frame of historical returns363#364# weightgrid each row contains one weighting vector, same number of columns as your returns365#366# yeargrid list of from/to vectors for the periods we want to backtest over367#368# Return:369# portfolioreturns list of data frames of set of returns for all possible portfolios370# (output of BruteForcePortfolios function)371372# Setup:373rows=nrow(yeargrid)374375# Function:376print( paste("Started:",date()) )377for (rnum in 1:rows) {378row = yeargrid[rnum,]379yearname=rownames(row)380from = row [,1]381to = row [,2]382383if ( 1 > from ) from = 1384if ( rows > to ) to = rows385386# construct a data structure that holds each result for this year387resultarray = WeightedPortfolioUtility(R, weightgrid, from, to, methods=methods, p=p, ...=...)388# at some point parallelize the call to WeightedPortfolioUtility by using a clustered apply389# to call WeightedPortfolioUtility so that this could get distributed390# to multiple processor cores or threads391392# then write a CSV393write.table(resultarray, file = paste(yearname,".csv",sep=""),394append = FALSE, quote = TRUE, sep = ",",395eol = "\n", na = "NA", dec = ".", row.names = TRUE,396col.names = TRUE, qmethod = "escape")397398print( paste("Completed",yearname,":",date()) )399# print(resultarray)400} # end row loop401}402403# ------------------------------------------------------------------------------404BacktestData =405function(yeargrid)406{ # @author Brian G. Peterson407408# Description:409#410# load the data into a list suitable for use by Backtest and BacktestDisplay functions411# use the yeargrid used by BruteForcePortfolios to figure out which files to load412#413# @todo add optional subdirectory to keep different backtests apart414#415# yeargrid list of from/to vectors for the periods we've run the backtest over416# yeargrid will have one row for the last out of sample year,417# which is not calculated by BruteForcePortfolios418419# Function:420rows=nrow(yeargrid)-1 # take out the out of sample year, for which there is no data421422for (rnum in 1:rows) {423row = yeargrid[rnum,]424yearname=rownames(row)425# print(yearname)426currentyeardata = read.table(paste(yearname,".csv",sep=""),header=TRUE, row.names = 1,sep = ",")427if (rnum==1) {428#create e,ty list, as c() doesn't work the way you would expect with a list429result=vector("list")430}431432#assign each year into the list using the yearname as the index433result[[yearname]]=currentyeardata434435}436names(result) = t(rownames(yeargrid[1:rows,]))437438#Return:439result440}441442# ------------------------------------------------------------------------------443Backtest =444function(R,bfresults, yeargrid, cutat=1000000, benchmarkreturns )445{446# Description:447#448# complete brute force hackjob to get out of sample results449#450# Given a set of historical returns R, and a set of portfolio returns calculated from451# the BruteForcePortfolios function, we can now apply several utility functions to452# find the best portfolio out of the universe of all sample portfolios.453#454# Basically, we find an in-sample solution to each utility function, and store the455# weighting vector for that portfolio as our strategic weight for use out of sample.456# by testing several utility functions, we can examine the models for bias, and determine457# which utility function produces the most acceptable out of sample results.458#459# R data frame of historical returns460#461# bfresults list of data frames of set of utility fn results for all possible portfolios462# (output of BruteForcePortfolios function)463#464# yeargrid list of from/to vectors for the periods we want to backtest over465#466# cutat numerical index to stop comparing the portfolioreturns at.467# used to slice the weighted returns at particular weight468#469# benchmarkreturns return vector for benchmark, should match the dates on470# the in-sample portfolio returns471472473# Setup:474rows=nrow(yeargrid-1)475476benchmarkreturns = as.vector(benchmarkreturns)477478# construct a matrix for the results that's the same size and labels as the input lists479result=matrix(nrow=nrow(yeargrid[-1,]),ncol=ncol(bfresults[[1]]))480rownames(result)=rownames(yeargrid[-1,])481colnames(result)=colnames(bfresults[[1]])482colns= colnames(bfresults[[1]])483portfoliorows=nrow(bfresults[[1]])484if (cutat<portfoliorows) {485portfoliorows=cutat486}487488# Function:489for (rnum in 1:(rows-1)) {490insample = yeargrid[rnum,]491outofsample = yeargrid[rnum+1,]492yearname = rownames(insample)493outname = rownames(outofsample)494inresults = bfresults[[yearname]][1:portfoliorows,]495496#Check Utility fn for insample , and apply to out of sample row497print(paste("Starting",yearname,date()))498for (coln in colns) {499##################################500# Risk/Reward maximization utility functions501# for utility function502# w' = max(meanreturn/riskmeasure)503#504# These are the Sharpe Ratio and modified Sharpe Ratio methods505#506# These utility functions are called "Constant Relative Risk Aversion (CRRA)" (verify this!)507##################################508for (maxmethod in c("SR.StdDev.period", "SR.StdDev.3yr", "SR.StdDev.inception",509"SR.GVaR.period", "SR.GVaR.3yr", "SR.GVaR.inception",510"SR.modVaR.period", "SR.modVaR.inception", "SR.modVaR.3yr",511"SR.GES.inception", "SR.GES.period", "SR.GES.3yr",512"SR.modES.period", "SR.modES.3yr", "SR.modES.inception")){513if (maxmethod==coln){514# return the max of the BF in-sample results for that column515result[outname,coln]= rownames(inresults[which.max(inresults[1:portfoliorows,coln]),])516}517} #end maxmethod518519##################################520# Risk Reduction utility functions521# for utility function522# w' = min(riskmeasure)523#524# These utility functions are called "Constant Absolute Risk Aversion (CARA)"525##################################526for (minmethod in c("StdDev.period", "StdDev.3yr", "StdDev.inception",527"GVaR.period", "GVaR.3yr", "GVaR.inception",528"modVaR.period", "modVaR.inception", "modVaR.3yr",529"GES.inception", "GES.period", "GES.3yr",530"modES.period", "modES.3yr", "modES.inception")){531if (minmethod==coln){532# return the min of the BF in-sample results for that column533result[outname,coln]= rownames(inresults[which.min(inresults[1:portfoliorows,coln]),])534}535} #end minmethod536} # end columns loop537538print(paste("Completed",yearname,date()))539540# add Equalweighted541# EqualWeighted = 1542543# ##################################544# # Risk Reduction utility functions545# # for utility function546# # w' = min(VaR.CornishFisher(p=0.95))547# minmodVaR = which.min(portfolioreturns[[yearname]][1:portfoliorows,"VaR.CornishFisher.period"])548# #minmodVaRi = which.min(portfolioreturns[[yearname]][1:portfoliorows,"VaR.CornishFisher.inception"])549# minmodVaR3yr = which.min(portfolioreturns[[yearname]][1:portfoliorows,"VaR.CornishFisher.3yr"])550#551# # for utility function552# # w' = max(return/VaR.CornishFisher) for both VaR.CornishFisher.period and VaR.CornishFisher.inception553# #SharpeRatio.modified = which.max(portfolioreturns[[yearname]][1:portfoliorows,"Cumulative.Return"]/portfolioreturns[[yearname]][1:portfoliorows,"VaR.CornishFisher.period"])554# SharpeRatio.modified3yr = which.max(portfolioreturns[[yearname]][1:portfoliorows,"Cumulative.Return"]/portfolioreturns[[yearname]][1:portfoliorows,"VaR.CornishFisher.3yr"])555# #SharpeRatio.modifiedi = which.max(portfolioreturns[[yearname]][1:portfoliorows,"Cumulative.Return"]/portfolioreturns[[yearname]][1:portfoliorows,"VaR.CornishFisher.inception"])556#557# # for utility function558# # w' = max(return/Max.Drawdown)559# ReturnOverDrawdown = which.max(portfolioreturns[[yearname]][1:portfoliorows,"Cumulative.Return"]/portfolioreturns[[yearname]][1:portfoliorows,"Max.Drawdown"])560#561# # for utility function562# # w' = max(return)563# maxReturn = which.max(portfolioreturns[[yearname]][1:portfoliorows,"Cumulative.Return"])564#565# # for utility function566# # w' = min(VaR.CornishFisher(p=0.95)) such that return is greater than the benchmark567# minVaRretoverBM = which.min(portfolioreturns[[yearname]][which(portfolioreturns[[yearname]][1:portfoliorows,"Cumulative.Return"]>=Return.cumulative(benchmarkreturns[infrom:into])),"VaR.CornishFisher.period"])568# if (length(minVaRretoverBM)==0) { minVaRretoverBM = maxReturn }569#570# #for utility function571# #w' = max(return) such that VaR.CornishFisher is less than VaR.CornishFisher(benchmark)572# maxmodVaRltBM=which.max(portfolioreturns[[yearname]][which(portfolioreturns[[yearname]][1:portfoliorows,"VaR.CornishFisher.period"]<VaR.CornishFisher(benchmarkreturns[infrom:into],p=0.95)),"Cumulative.Return"])573# if (length(maxmodVaRltBM)==0) { maxmodVaRltBM = 1 }574#575# #add utility functions tor Equal weighted portfolio576# # for utility function577# # w' = min(VaR.CornishFisher(p=0.95)) such that return is greater than equal weighted portfolio578# minVaRretoverEW = which.min(portfolioreturns[[yearname]][which(portfolioreturns[[yearname]][1:portfoliorows,"Cumulative.Return"]>=portfolioreturns[[yearname]][1,"Cumulative.Return"]),"VaR.CornishFisher.period"])579# if (length(minVaRretoverEW)==0) { minVaRretoverEW = 1 }580#581# #for utility function582# #w' = max(return) such that VaR.CornishFisher is less than VaR.CornishFisher(equal weighted)583# maxmodVaRltEW=which.max(portfolioreturns[[yearname]][which(portfolioreturns[[yearname]][1:portfoliorows,"VaR.CornishFisher.period"]<portfolioreturns[[yearname]][1,"Cumulative.Return"]),"Cumulative.Return"])584# if (length(maxmodVaRltEW)==0) { maxmodVaRltEW = NA }585#586# #for utility function587# #w' = max(omega)588# maxOmega = which.max(portfolioreturns[[yearname]][1:portfoliorows,"Omega"])589#590# #for utility function591# #w' = max(Sharpe.period)592# maxPeriodSharpe = which.max(portfolioreturns[[yearname]][1:portfoliorows,"Sharpe.period"])593#594# #for utility function595# #w' = max(Sharpe.3.yr)596# max3yrSharpe = which.max(portfolioreturns[[yearname]][1:portfoliorows,"Sharpe.3.yr"])597#598# #for utility function599# #w' = max(Sharpe.inception)600# #maxInceptionSharpe = which.max(portfolioreturns[[yearname]][1:portfoliorows,"Sharpe.inception"])601#602#603# ########## end of utility functions ##########604# # construct a data structure that holds each result for this row605# if (rnum==2) {606# #create data.frame607# result=data.frame()608# resultrow=data.frame()609# }610# # first cbind the columns611# resultrow = cbind(EqualWeighted, minmodVaR, minmodVaR3yr, SharpeRatio.modified3yr, ReturnOverDrawdown, maxReturn,612# minVaRretoverBM, maxmodVaRltBM, minVaRretoverEW, maxmodVaRltEW,613# maxPeriodSharpe, max3yrSharpe, maxOmega )614#615# rownames(resultrow) = outname616#617# # then rbind the rows618# result = rbind(result,resultrow)619# # print(resultarray)620621} # end row loop622623# Result:624result625}626627# ------------------------------------------------------------------------------628BacktestDisplay =629function (R, bfresults, yeargrid, backtestresults, benchmarkreturns )630{ # a function by Brian G. Peterson631632# Description:633# This function lets us use the output of the Backtest() function to do some634# comparative analysis of how each utility function performed out of sample.635# It takes as input all the component parts, and shows a single summary statistic636# for each out of sample period for each utility function. Periods are rows,637# utility functions are columns in the output.638# Eventually, we'll want to make this more sophisticated, and return a639# data structure with *all* the summary statistics for each out-of-sample portfolio,640# but this works for now.641#642# R data frame of historical returns643#644# bfresults list of data frames of set of returns for all possible portfolios645# (output of BruteForcePortfolios function)646#647# yeargrid list of from/to vectors for the periods we've run the backtest over648# yeargrid will have one row for the last out of sample year,649# which is not calculated by BruteForcePortfolios650#651# weightgrid each row contains one weighting vector, same number of columns as your returns652#653# backtestresults data frame of set of weighting vectors for each654# utility function in each year/period655# (output of Backtest function)656#657# benchmarkreturns return vector for benchmark, should match the dates on658# the in-sample portfolio returns659660661cols = ncol(backtestresults) # get the number of utility functions662663# add column for equal weighted portfolio in backtestresults664# probably rep 1 for number of rows, and cbind665equalcol= t(t(rep(1,nrow(backtestresults))))666colnames(equalcol)="Equal.Weighted"667backtestresults=cbind(backtestresults,as.matrix(equalcol))668669result=vector("list")670671# Function:672673for (row in 1:(nrow(backtestresults)-1)){674yearrow = backtestresults[row,,drop=FALSE]675from = yeargrid [row,1]676to = yeargrid [row,2]677yearname = rownames(backtestresults[row,,drop=F])678679# print(rownames(backtestresults[row,,drop=FALSE]))680681resultcols = ncol(backtestresults)+2682resultmatrix=matrix(nrow=ncol(backtestresults),ncol=resultcols,byrow=TRUE )683colnames(resultmatrix)=c("Return.Portfolio","skewness","kurtosis",colnames(backtestresults)[-(resultcols-2)])684rownames(resultmatrix)=colnames(backtestresults)685686for (col in 1:ncol(backtestresults)){687tcols=resultcols-3688targetportfolio= backtestresults[row,col]689resultmatrix[col,4:resultcols]=as.matrix(bfresults[[yearname]][targetportfolio,1:(resultcols-3)])690# calc Return of the portfolio using Portfolio.Return function691pwealth=Return.portfolio(R[from:to,], weights=weightgrid[targetportfolio,], wealth.index = TRUE)692totalreturn=pwealth[length(pwealth)]-1693pwealth=rbind(1,pwealth)694preturn=t(diff(log(pwealth)))695# calc skewness696pskew=skewness(as.vector(preturn))[1]697# calc kurtosis698pkurt=kurtosis(as.vector(preturn))[1]699resultmatrix[col,1]=totalreturn700resultmatrix[col,2]=pskew701resultmatrix[col,3]=pkurt702}703704# @todo add row for benchmark portfolios705# Call WeightedPortfolioUtility with weight of 1?706707#browser()708result[[yearname]]=resultmatrix709}710711#Return:712result713714}715716# ------------------------------------------------------------------------------717BacktestWeightDisplay =718function(backtestresults, weightgrid)719{ # @author Brian G. Peterson720721# Description:722# Display the weights chosen for each year/period for each utility function.723#724# backtestresults data frame of set of weighting vectors for each725# utility function in each year/period726# (output of Backtest function)727#728# weightgrid each row contains one weighting vector,729# same number of columns as your returns730# you probably want to have the column names of the weightgrid match your asset names731#732# yeargrid list of from/to vectors for the periods we've run the backtest over733# yeargrid will have one row for the last out of sample year,734# which is not calculated by BruteForcePortfolios735#736# Return:737# list of years/periods with each period containing a data frame of weights by utiltiy function738739# Setup:740741cols = ncol(backtestresults) # get the number of utility functions742743result=vector("list")744745# Function:746747for (col in 1:ncol(backtestresults)){748# we're looping on each column in the backtest results (objective functions)749resultmatrix=matrix(nrow=ncol(weightgrid),ncol=nrow(backtestresults),byrow=TRUE )750colnames(resultmatrix)=rownames(backtestresults) # years751rownames(resultmatrix)=colnames(weightgrid) # instruments752for (row in 1:nrow(backtestresults)){753# now loop on years754resultmatrix[1:ncol(weightgrid),row]=t(weightgrid[backtestresults[row,col],])755}756objname=colnames(backtestresults[,col,drop=F])757#browser()758result[[objname]]=resultmatrix759}760761# @todo put a method in here or something to let the user decide how to arrange762# arrange list by years with rows as objective functions and columns as instruments763# for (row in 1:nrow(backtestresults)){764# # print(rownames(backtestresults[row,,drop=FALSE]))765# resultmatrix=matrix(nrow=ncol(backtestresults),ncol=ncol(weightgrid),byrow=TRUE )766# colnames(resultmatrix)=colnames(weightgrid)767# rownames(resultmatrix)=colnames(backtestresults)768# for (col in 1:ncol(backtestresults)){769# resultmatrix[col,1:ncol(weightgrid)]=t(weightgrid[backtestresults[row,col],])770# }771# yearname=rownames(backtestresults[row,,drop=F])772# # browser()773# result[[yearname]]=resultmatrix774# }775776#Return:777result778}779780781# ------------------------------------------------------------------------------782#MonthlyBacktestResults =783# use pfolioReturn(returnarray,weightingvector) from fPortfolio784MonthlyBacktestResults =785function (R, weightgrid, yeargrid, backtestresults)786{ # @author Brian G. Peterson787788# R data structure of component returns789#790# weightgrid each row contains one weighting vector, same number of columns as your returns791#792# yeargrid list of from/to vectors for the periods we want to backtest over793#794# backtestresults data frame of set of weighting vectors for each795# utility function in each year/period796# (output of Backtest function)797798# Setup:799result=NULL800resultcols=NULL801802# data type conditionals803# cut the return series for from:to804if (class(R) == "timeSeries") {805R = R@Data806} else {807R = R808}809810if (ncol(weightgrid) != ncol(R)) stop ("The Weighting Vector and Return Collection do not have the same number of Columns.")811812# add column for equal weighted portfolio in backtestresults813# probably rep 1 for number of rows, and cbind814equalcol= t(t(rep(1,nrow(backtestresults))))815colnames(equalcol)="Equal.Weighted"816backtestresults=cbind(backtestresults,as.matrix(equalcol))817818result=data.frame()819for (row in 1:(nrow(backtestresults)-1)){820# ok, we're looping on year.821# for each year, we need to apply the weights in our backtest results to the portfolio and get a monthly return822yearrow = backtestresults[row,,drop=FALSE]823yearname = rownames(backtestresults[row,,drop=F])824from = yeargrid [yearname,1]825to = yeargrid [yearname,2]826print(paste("Starting",yearname,date()))827resultcols=NULL828829for (col in 1:ncol(backtestresults)){830# look up the weighting vector of the target portfolio831targetportfolio= backtestresults[row,col]832# calc Return of the portfolio using Portfolio.Return function833preturn=Return.portfolio(R[from:to,], weights=weightgrid[targetportfolio,], wealth.index = FALSE,method="compound")834if (col==1) {835#create data.frame836resultcols=preturn837} else {838resultcols=cbind(resultcols,preturn)839}840# @todo add col for benchmark portfolios841}842if (row==1) {843#create data.frame844result=resultcols845} else {846result=rbind(result,resultcols)847}848print(paste("Ending",yearname,date()))849}850851colnames(result)=colnames(backtestresults)852853# Return Value:854result855856}857858# Return.portfolio.multiweight <- function (R, weights, yeargrid, ...){859# result=data.frame()860#861# weights=checkData(weights,method="matrix")862#863# # take only the first method864# # method = method[1]865#866# # then loop:867# for (row in 1:nrow(yeargrid)){868# from =yeargrid[row,1]869# to = yeargrid[row,2]870# if(row==1){ startingwealth=1 }871# resultreturns=Return.portfolio(R[from:to,],weights=t(weights[row,]), startingwealth=startingwealth, ...=...)872# startingwealth=resultreturns[nrow(resultreturns),"portfolio.wealthindex"]873# # the [,-1] takes out the weighted returns, which you don't care874# # about for contribution, although you may care about it for875# # graphing, and want to pull it into another var876#877# result=rbind(result,resultreturns)878# }879# result880# }881# # ------------------------------------------------------------------------------882# # Return.portfolio - replaces RMetrics pfolioReturn fn883# # move this function and the pfolioReturn wrapper into Performanceanalytics and remove from this file884#885# Return.portfolio <- function (R, weights=NULL, wealth.index = FALSE, contribution=FALSE, method = c("compound","simple"), startingwealth=1)886# { # @author Brian G. Peterson887#888# # Function to calculate weighted portfolio returns889# #890# # R data structure of component returns891# #892# # weights usually a numeric vector which has the length of the number893# # of assets. The weights measures the normalized weights of894# # the individual assets. By default 'NULL', then an equally895# # weighted set of assets is assumed.896# #897# # method: "simple", "compound"898# #899# # wealth.index if wealth.index is TRUE, return a wealth index, if false, return a return vector for each period900# #901# # contribution if contribution is TRUE, add the weighted return contributed by the asset in this period902# #903# # @todo method param doesn't really do anything right now. the calculation of the price series would be different for simple than compound904# # @todo add contribution905#906# # Setup:907# R=checkData(R,method="zoo")908#909# # take only the first method910# method = method[1]911#912# if (is.null(weights)){913# # set up an equal weighted portfolio914# weights = t(rep(1/ncol(R), ncol(R)))915# }916#917# if (ncol(weights) != ncol(R)) stop ("The Weighting Vector and Return Collection do not have the same number of Columns.")918#919# #Function:920#921#922# # if(method=="compound") {923# # construct the wealth index of unweighted assets924# wealthindex.assets=cumprod(startingwealth+R)925#926# # I don't currently think that the weighted wealth index is the correct route927# # I'll uncomment this and leave it in here so that we can compare928# # build a structure for our weighted results929# wealthindex.weighted = matrix(nrow=nrow(R),ncol=ncol(R))930# colnames(wealthindex.weighted)=colnames(wealthindex.assets)931# rownames(wealthindex.weighted)=rownames(wealthindex.assets)932#933# # weight the results934# for (col in 1:ncol(weights)){935# wealthindex.weighted[,col]=weights[,col]*wealthindex.assets[,col]936# }937# wealthindex=apply(wealthindex.weighted,1,sum)938#939# # weighted cumulative returns940# weightedcumcont=t(apply (wealthindex.assets,1, function(x,weights){ as.vector((x-startingwealth)* weights)},weights=weights))941# weightedreturns=diff(rbind(0,weightedcumcont))942# colnames(weightedreturns)=colnames(wealthindex.assets)943# #browser()944# wealthindex=matrix(cumprod(startingwealth + as.matrix(apply(weightedreturns,1, sum), ncol = 1)),ncol=1)945# # or, the equivalent946# #wealthindex=matrix(startingwealth+apply(weightedcumcont,1,sum),ncol=1)947# # }948#949# if(method=="simple"){950# # stop("Calculating wealth index for simple returns not yet supported.")951# #weighted simple returns952# # probably need to add 1 to the column before doing this953# weightedreturns=Return.calculate(wealthindex,method="simple")954# }955#956# # uncomment this to test957# #browser()958#959# if (!wealth.index){960# result=as.matrix(apply(weightedreturns,1,sum),ncol=1)961# colnames(result)="portfolio.returns"962# } else {963# # stop("This is broken right now. Calculate your wealth index from the return series.")964# result=wealthindex965# colnames(result)="portfolio.wealthindex"966# }967#968# if (contribution==TRUE){969# # show the contribution to the returns in each period.970# result=cbind(weightedreturns,result)971# }972#973# result974# } # end function Return.portfolio975#976# pfolioReturn <- function (x, weights=NULL, ...)977# { # @author Brian G. Peterson978# # pfolioReturn wrapper - replaces RMetrics pfolioReturn fn979#980# Return.portfolio(R=x, weights=weights, ...=...)981# }982###############################################################################983# R (http://r-project.org/) Numeric Methods for Optimization of Portfolios984#985# Copyright (c) 2004-2014 Kris Boudt, Peter Carl and Brian G. Peterson986#987# This library is distributed under the terms of the GNU Public License (GPL)988# for full details see the file COPYING989#990# $Id$991#992###############################################################################993# $Log: not supported by cvs2svn $994# Revision 1.85 2009-09-22 21:21:37 peter995# - added licensing details996#997# Revision 1.84 2008-01-31 17:31:00 brian998# - add startingweight to contribution calc999#1000# Revision 1.83 2008/01/31 12:21:50 brian1001# - add wealth index calcs back in1002# - uncomment the old wealthindex.weighted for comparison1003#1004# Revision 1.82 2008/01/31 04:16:24 peter1005# - removed method line from Return.portfolio.multiweight1006#1007# Revision 1.81 2008/01/31 02:10:32 brian1008# - pass ... instead of method argument in Return.portfolio.multiweights1009#1010# Revision 1.80 2008/01/31 01:23:00 brian1011# - add as.vector in method simle for weights in Return.portfolio1012# - **not sure the simple method works properly**1013#1014# Revision 1.79 2008/01/31 01:15:07 brian1015# - add method argument to Return.portfolio.multiweights1016#1017# Revision 1.78 2008/01/31 00:48:49 brian1018# - add new function Return.portfolio.multiweight1019#1020# Revision 1.77 2008/01/30 23:41:54 brian1021# - add contributions as output again to Return.portfolio1022#1023# Revision 1.76 2008/01/30 23:34:00 brian1024# - add rowname for cumulative returns1025#1026# Revision 1.74 2008/01/29 20:16:21 brian1027# - fixed, tested contribution option for Return.portfolio fn1028#1029# Revision 1.73 2008/01/29 18:23:20 brian1030# - add contribution to Return.portfolio1031#1032# Revision 1.72 2008/01/29 02:59:24 brian1033# - comment browser() command1034#1035# Revision 1.71 2008/01/29 02:58:30 brian1036# - reverse output of BacktestWeightDisplay fn to make it easier to graph weights.1037#1038# Revision 1.70 2008/01/25 02:14:06 brian1039# - fix errors in data frrame/matrix handling in Return generating functions1040#1041# Revision 1.69 2008/01/25 01:28:54 brian1042# - complete function MonthlyBacktestResults1043#1044# Revision 1.68 2008/01/24 23:59:38 brian1045# - add Return, skewness, kurtosis to BacktestDisplay1046# - always display results for equal weighted portfolio1047#1048# Revision 1.67 2008/01/24 22:10:01 brian1049# - working version of BacktestDisplay fn1050# - still needs Return, skewness, kurtosis1051#1052# Revision 1.66 2008/01/24 01:51:12 brian1053# - resolve conflicts from editing in two directories1054#1055# Revision 1.65 2008/01/24 01:48:29 brian1056# - lay groundwork for rewriting BacktestDisplay fn1057#1058# Revision 1.64 2008/01/24 00:13:43 brian1059# - rename pfolioReturn fn Return.portfolio1060# - create wrapper for RMetrics pfolioReturn fn for compatibility1061# - add notes about contribution of assets to portfolio return1062#1063# Revision 1.63 2008/01/23 21:48:35 brian1064# - move rbind outside inner if statements to remove dup code1065#1066# Revision 1.62 2008/01/23 21:46:44 brian1067# - update to not lose first period in return stream1068# - add column names to make clear what the output is1069#1070# Revision 1.60 2008-01-23 05:34 brian1071# - new version of BacktestWeightDisplay works with output of Backtest fn1072#1073# Revision 1.61 2008/01/23 20:32:52 brian1074# - replacement pfolioReturn function to calculate weighted returns1075#1076# Revision 1.59 2008/01/23 11:04:17 brian1077# - fix 3yr/period confusion in two ThreeYr utility functions in WeightedPortfolios1078#1079# Revision 1.58 2008/01/22 20:33:49 brian1080# - update three yr to to-35 for proper 36 month window1081#1082# Revision 1.57 2008/01/22 02:58:56 brian1083# - Backtest fn now tested and working with new output of BruteForcePortfolios->BacktestData1084#1085# Revision 1.56 2008/01/22 02:10:20 brian1086# -working much better, Backtest fn still fails with a subscriupt out of bounds error on some data1087#1088# Revision 1.55 2008/01/21 23:40:42 brian1089# - adjust the way we reference list elements to do it numberically. apparently it's not easy to reference by a string1090#1091# Revision 1.54 2008/01/21 17:50:24 brian1092# - partial fix, Backtest fn still not working around lines 515-5181093#1094# Revision 1.53 2008/01/21 17:24:09 brian1095# - adjust use of rownames for insample/outofsample in Backtest fn1096#1097# Revision 1.52 2008/01/21 17:18:38 brian1098# - fix typo in matrix assignment1099#1100# Revision 1.51 2008/01/21 17:16:12 brian1101# - add matrix for reult to Backtest fn1102# - change max and min tests in Backtest fn to insert rowname of weighting vector rather than array index1103#1104# Revision 1.50 2008/01/21 16:31:36 brian1105# - revise Backtest function to have utility functions for maximizing and minimizing lists1106# - still need to initialize the result matrix1107#1108# Revision 1.49 2008/01/21 13:52:46 brian1109# - fix typo in ThreeYrStdDev method in WeightedPortfolioUtility1110#1111# Revision 1.48 2008/01/21 13:49:09 brian1112# - fix typo in ThreeYrGVaR method in WeightedPortfolioUtility1113# - add comments on the subsetting method we had to use to make this work on large weightgrid1114#1115# Revision 1.47 2008/01/21 04:41:47 brian1116# - fix naming and assignment of results in subsets1117#1118# Revision 1.46 2008/01/21 03:58:58 brian1119# - move weightgridsave out of the outer loopy1120#1121# Revision 1.45 2008/01/21 03:26:32 brian1122# - add drop=FALSE to subsetting of weightgrid to preserve rownames1123#1124# Revision 1.44 2008/01/21 03:02:19 brian1125# - add ugly outer loop hack so this will work1126#1127# Revision 1.43 2008/01/21 01:39:15 brian1128# - add switch and print statements for debug1129#1130# Revision 1.42 2008/01/20 23:07:58 brian1131# - use matrix for results to avoid data.frame factor BS1132#1133# Revision 1.41 2008/01/20 21:13:59 brian1134# - set colnames on result var when we create the object1135#1136# Revision 1.40 2008/01/20 21:05:45 brian1137# - change to create structure with correct number of rows and columns on first pass1138# - avoids warnings about replacing 0-element row with x-element row1139#1140# Revision 1.39 2008/01/20 19:55:33 brian1141# - create empty result var dataframe with right names for resultrows1142# - assign resultrow by index to avoid memcopy problem1143#1144# Revision 1.38 2008/01/20 17:22:15 brian1145# - fix row.names in initialization of resultrow data.frame1146#1147# Revision 1.37 2008/01/20 17:08:24 kris1148# Compute SR more efficiently1149#1150# Revision 1.36 2008/01/20 16:30:50 brian1151# - move multivariate moment calculations outside the row loop1152#1153# Revision 1.35 2008/01/20 16:26:21 brian1154# - add M3 and M4 moments to parameters for all modSR function calls1155#1156# Revision 1.34 2008/01/20 16:14:53 brian1157# - initialize result outside the loop in WeightedPortfolioUtility1158# - initialize resultrow to have one row1159#1160# Revision 1.33 2008/01/20 14:54:55 brian1161# - quote methods1162#1163# Revision 1.32 2008/01/20 14:53:05 brian1164# - fix syntax errors and handling of inception, threeyr, form, to1165#1166# Revision 1.31 2008/01/20 13:55:44 brian1167# - fix syntax error in switch on InceptionStdDev (missing comma)1168#1169# Revision 1.30 2008/01/20 13:49:39 brian1170# - add Kris to copyright line1171#1172# Revision 1.29 2008/01/20 13:48:37 brian1173# - fix syntax error1174# - update methods to reflect changes to multivariate moments1175#1176# Revision 1.28 2008/01/20 13:35:34 brian1177# - add missing brace in switch statement1178#1179# Revision 1.27 2008/01/20 12:07:24 kris1180# - Changed function definitions in optim_functions.R and updated the function calls in optimizer.R to these functions1181#1182# Revision 1.26 2008/01/20 06:23:12 brian1183# - convert GVaR functions1184#1185# Revision 1.25 2008/01/20 05:02:17 brian1186# - add in centered moments and standard variables for period, inception, and 3yr series1187#1188# Revision 1.24 2008/01/19 16:48:01 brian1189# - fix column name issues1190# - add period, inception, and 3yr for each method1191#1192# Revision 1.23 2008/01/05 04:49:25 brian1193# - add 'methods' argument to parameterize WeightedPortfolioUtility and BrutForcePortfolios functions1194# - not yet tested, may have issues with column names1195#1196# Revision 1.22 2008/01/04 14:27:07 brian1197# - convert to use functions from package PerformanceAnalytics1198#1199# Revision 1.21 2007/01/31 18:23:16 brian1200# - cascade function name standardization from performance-analytics.R1201#1202# Revision 1.20 2007/01/30 15:54:57 brian1203# - cascade function name standardization from extra_moments.R1204#1205# Revision 1.19 2007/01/26 13:24:02 brian1206# - fix typo1207#1208# Revision 1.18 2006/12/03 17:42:02 brian1209# - adjust utility functions in Backtest() to match 3yr statistices from BruteForcePortfolios1210# - add more descriptive comments to BacktestDisplay()1211# - add BacktestWeightDisplay() fn, not yet complete1212# Bug 8401213#1214# Revision 1.17 2006/12/02 12:54:53 brian1215# - modify BacktestData() fn to take a 'yeargrid' that matches1216# yeargrid from BruteForcePortfolios and loads those portfolios1217# Bug 8401218#1219# Revision 1.16 2006/11/30 00:24:11 brian1220# - remove 'inception' statistics from BruteForcePortfolios and Backtest fns1221# - fix error in 'show' parameter of BacktestDisplay fn1222# Bug 8401223#1224# Revision 1.15 2006/11/28 02:52:03 brian1225# - add 3yr SharpeRatio.modified and 3yr modVaR1226# - add fOptions require for Omega1227# Bug 8401228#1229# Revision 1.14 2006/10/12 17:42:48 brian1230# - put back omega utility fn1231#1232# Revision 1.13 2006/09/26 23:42:53 brian1233# - add Equalweighted as column in Backtest vector fns1234# - clean up NA handling in utility functions1235# - add more error handling1236#1237# Revision 1.12 2006/09/26 21:53:41 brian1238# - more fixes for NA's in data1239#1240# Revision 1.11 2006/09/26 12:07:04 brian1241# - add more NA handlinf and data series size checks1242# - comment out Omega for speed for now1243#1244# Revision 1.10 2006/09/22 15:30:44 brian1245# - add separate vector to BacktestDisplay fn for benchmark returns1246#1247# Revision 1.9 2006/09/22 12:45:45 brian1248# - add separate vector for benckmarkreturns to Backtest fn1249# - better describe inputs to Backtest fn in comments1250#1251# Revision 1.8 2006/09/21 13:43:39 brian1252# - add start timestamp for Backtest function1253#1254# Revision 1.7 2006/09/12 14:37:43 brian1255# - add CVS tags1256# - add CVS log1257# - add confidentiality notice to top of file1258#1259# Revision 1.6 2006-09-12 09:31:37 brian1260# - snapshot 2006-09-05 15:361261# - add functions and tweak existing to better handle larger data sets, cutting data1262#1263# Revision 1.5 2006-09-12 09:29:12 brian1264# - snapshot 2006-08-30 19:031265# - add functions for conthly compounding returns1266#1267# Revision 1.4 2006-09-12 09:28:01 brian1268# - snapshot 2006-08-29 23:011269# - add equal weighted utility functions1270#1271# Revision 1.3 2006-09-12 09:27:00 brian1272# - snapshot 2006-08-29 21:581273# - Add functions to actually perform the backtest1274# using the results of the brute force statistics1275# generated on all possible portfolios.1276#1277# Revision 1.2 2006-09-12 09:25:06 brian1278# - snapshot 2006-08-291279# - add BruteForcePortfolios, WeightedPortfolioUtility, and other small utility functions1280#1281# Revision 1.1 2006-09-12 09:23:14 brian1282# - initial revision 2006-08-281283# Bug 8401284###############################################################################128512861287