1.2 Strategy Implementation

The final step is implementing this strategy to create a signal. Let’s begin by reading in the flow files to our R studio workspace.

x.rgn <- mat.read(flow.file.rgn) # GET RGN FLOW PERCENTAGE C:\\EPFR\\daily\\MultiAsset-Rgn-daily.csv
x.FI <- mat.read(flow.file.FI) # GET FI FLOW PERCENTAGE C:\\EPFR\\daily\\MultiAsset-FI-daily.csv

The next step is to ensure both of our flow files have the same rows so that we can combine the two files, like we do below.

x.rgn <- x.rgn[is.element(dimnames(x.rgn)[[1]], dimnames(x.FI)[[1]]), ] # ENSURES RGN MATCHES FI ROWS
x.FI <- x.FI[is.element(dimnames(x.FI)[[1]], dimnames(x.rgn)[[1]]), ] # ENSURES FI MATCHES RGN ROWS

x <- cbind(x.rgn, x.FI) # COMBINES RGN AND FI DATA

One of the first options we have is choosing the universe we want to backtest. EPFR has tested this signal within two different universes of asset classes: Rgn (7 Region Equity) and FI (Fixed Income). For this example, we choose Multi, which includes both of our equity and fixed-income asset classes, but users can also choose to backtest within either the Rgn or FI universes separately.

idx <- "Multi" # Multi/Rgn/FI

1.2.1 Compounding Flows

Next, we set up a variable for our lookback period, which can also be called a flow window. This variable will be the window of time we use to create a trailing compounded daily percentage flow. The lookback period we choose for our demonstrations is 20 days.

lookback <- 20 # FLOW WINDOW (IN WEEKDAYS) - 20 day look back period

Using a function from the library('EPFR.r'), compound.flows() compounds our daily percentage flow over the trailing lookback period for each asset class.

x <- compound.flows(x, lookback, F) # COMPOUND FLOWS
AsiaXJP EurXGB Japan LatAm PacXJP UK USA GLOBEM WESEUR HYIELD FLOATS USTRIN USTRLT USTRST CASH USMUNI GLOFIX
20221201 -0.5935744 -0.9011096 0.1759815 -1.231081 -0.3122597 -0.8392634 -0.4846786 0.6630384 0.5921352 1.9514429 -2.125939 0.8615195 7.368753 -0.5797696 1.324865 0.1061790 0.2467439
20221202 -0.4957430 -0.6686647 0.2507892 -1.508537 -0.3909206 -0.7661013 -0.5061060 0.7765973 0.5282304 1.9381506 -2.151642 0.9314150 7.247511 -0.6783618 1.771157 0.1854309 0.2729250
20221205 -0.4947291 -0.7995004 0.0388963 -1.531783 -0.4224801 -0.8483539 -0.5699878 0.9884008 0.4988437 1.6447813 -2.064563 0.2088554 7.170713 -1.3358431 1.654928 0.2551084 0.3154037
20221206 -0.4729045 -0.6938171 -0.0446434 -1.628549 -0.3301458 -0.8645179 -0.6412650 1.0813465 0.5051112 1.4909559 -2.218451 0.4746187 6.925902 -2.1067769 1.780230 0.3172435 0.3053755
20221207 -0.2520256 -0.5759144 -0.1187730 -1.695810 -0.3475549 -0.8860501 -0.6878389 1.0946509 0.5293832 1.3865604 -2.436764 0.3590598 6.852528 -2.4679185 1.562598 0.3119034 0.3425063
20221208 -0.0925901 -0.6546987 0.0569879 -1.612896 -0.4813274 -0.8700669 -0.7834505 1.2062479 0.4497268 0.4233311 -2.837551 0.9271004 6.053509 -1.3176497 2.342189 0.2374214 0.3654252

1.2.2 Total Return Index

We will then import our two total return files and modify them to become one data frame of total index returns indexed so that time moves forward. To do this, we will use the function ret.to.idx() from library('EPFR.r'). We will also use the functions map.rname() to ensure the row names of the matrices line up with our flow file, and ret.idx.gaps.fix() to replace any NA values. Please refer to the library documentation for the complete list of parameters for these functions (tip: ?ret.to.idx(),?ret.idx.gaps.fix() ).

#RGN RETURNS
y <- mat.read(ret.file.rgn) # GET RGN PERCENTAGE RETURNS C:\\EPFR\\returns\\PsuedoReturns-MultiAsset-Rgn-daily.csv
y <- ret.to.idx(map.rname(y, dimnames(x)[[1]])) # CONVERT TO A TOTAL-RETURN INDEX
y <- ret.idx.gaps.fix(y)

#F.I. RETURNS
z <- mat.read(ret.file.FI) # GET FI PERCENTAGE RETURNS C:\\EPFR\\returns\\PsuedoReturns-MultiAsset-FI-daily.csv
z <- ret.to.idx(map.rname(z, dimnames(y)[[1]])) # CONVERT TO A TOTAL-RETURN INDEX   

#COMBINE RETURN FILES
y <- data.frame(y, z)[, dimnames(x)[[2]]] # FINAL TOTAL-RETURN INDEX TABLE
y <- y[max(simplify2array(lapply(y, function(x) {find.data(!is.na(x), T)}))):min(simplify2array(lapply(y, function(x) {find.data(!is.na(x), F)}))), ] # ENSURE ALL PIECES HAVE RETURN OVER THE SAME HORIZON

1.2.3 Subset files

Then, to subset the flow file \(x\) and return file \(y\) to the selected universe \(idx\) of asset classes, the following code is used. During this process, we also ensure that the data structure in \(x\) and \(y\) are completely aligned, having the same column names in the same order.

if (idx == "Rgn") { 
    x <- x[, 1:7]
    y <- y[, 1:7]
} else if (idx == "FI") {   
    x <- x[, 8:dim(x)[2]]
    y <- y[, 8:dim(y)[2]]
}  

* Note: subsetting can also be done when creating the flow and return files

1.2.4 Ranking Asset Classes

Next, we sort each of the asset classes in our universe into five equal bins based on their compounded percentage flow values for the selected holding period. To do this, we will use the function from library('EPFR.r'), called bbk(). This function will output a standardized backtest result.

The bbk() function requires our daily percentage flow data compounded over a desired period and the total return index data. Please refer to the library documentation for the complete list of parameters of this function (tip: ?bbk()).

The first parameter we add is the number of bins we want to use. For our case, we want to use 5 because our strategy is to go long the top fifth and short the bottom fifth.

nBin <- 5 # NUMBER OF BINS

Since EPFR data is published with a T+1 day lag and is released around 5:00 pm EST, we account for a T+2 day delay in our model. Users interested in more timely signals can also use the T+2 open prices for backtesting purposes. Alternatively, EPFR’s Premium Daily offering collects an earlier release of end-of-day data which includes a significant subset of its original fund-level flow information.

delay <- 2 # DELAY IN KNOWING DATA (IN WEEKDAYS) - data takes time to have

It is also important to note that this model will need to be re-balanced weekly. The day of the week the rebalancing occurs is at the user’s discretion. For this example, we will set the day of the week to trade as Friday.

doW <- 5 # DAY OF THE WEEK YOU WILL TRADE ON (5 = FRIDAYS)

Additionally, we also evaluate the returns for different holding periods. The user can input the return horizons that they are interested in here. For this example, we define a return horizon for weekly, fortnightly, monthly, bi-monthly, quarterly, and semi-annual rebalancing.

hz <- c(5, 10, 20, 45, 65, 130) # RETURN HORIZON (IN WEEKDAYS) - holding periods

Now that we have defined all of our inputs, to rank the asset classes into quintiles by their 20-day percentage flow, we call the function bbk() for a one-week holding period.

z <- bbk(x, y, 1, hz[1], nBin, doW, T, 0, delay)

1.2.5 Model

20-day flow percentage ranked into quintiles (computed only where forward returns are available)

z[["bins"]]
AsiaXJP EurXGB Japan LatAm PacXJP UK USA GLOBEM WESEUR HYIELD FLOATS USTRIN USTRLT USTRST CASH USMUNI GLOFIX
20221230 2 4 3 4 3 4 5 1 2 5 5 2 1 1 3 4 2
20221223 2 4 3 5 3 4 4 1 1 5 5 2 1 2 3 4 2
20221216 2 4 3 5 3 4 4 1 2 2 5 4 1 5 1 3 2
20221209 3 4 3 5 4 4 4 2 2 1 5 2 1 5 1 3 2
20221202 4 4 3 5 4 5 4 2 2 1 5 1 1 2 2 3 3

Quintile returns over the equal-weight universe

z[["rets"]]
Q1 Q2 Q3 Q4 Q5 TxB uRet
20221230 0.1281527 0.0754571 -1.4629180 1.2088600 -0.3776576 0.5058103 1.7369770
20221223 -0.4086635 0.2329289 0.2585288 0.3556384 -0.6346217 0.2259583 -0.5369834
20221216 -1.0962074 -0.4775815 -1.2348837 0.3551931 2.4942756 -3.5904830 -0.1111863
20221209 1.0920910 0.7708809 0.0294642 -1.0666550 -0.7271897 1.8192807 -0.8059708
20221202 0.4451042 0.6385878 0.5724600 -0.5517372 -1.1333650 1.5784691 -0.5930231

Def: TxB represents summary statistics for the long/short portfolio (top - bottom = Q1 - Q5 = overall portfolio returns)


1.2.6 Performance

Performance over all holding periods

fcn <- function(retW) {as.matrix(bbk(x, y, 1, retW, nBin, doW, T, 0, delay)$summ)} # DEFINE SUMMARY FUNCTION

sapply(split(hz, hz), fcn, simplify = "array") # WRITE SUMMARIES
Q1 Q2 Q3 Q4 Q5 TxB uRet
Weekly
AnnMn 1.5 -1.4 0.9 2.2 -3.5 5.0 3.1
AnnSd 7.9 5.3 6.1 6.1 6.8 12.0 8.6
Sharpe 18.7 -26.4 14.9 36.1 -51.3 41.5 35.9
HitRate -0.2 -2.9 2.9 3.0 -3.0 1.0 6.7
Beta -0.6 -0.1 0.2 0.3 0.1 -0.7 1.0
Alpha 3.3 -1.1 0.3 1.3 -3.8 7.1 0.0
DrawDn -18.7 -35.8 -26.7 -18.9 -55.5 -28.1 -35.6
DDnBeg 20140124 20080104 20100226 20180727 20070525 20140103 20071026
DDnN 312 600 242 91 791 315 71
AnnTo 1185 1821 2567 2008 1395 2580 0
Fortnightly
AnnMn 0.8 -0.5 1.2 1.7 -3.7 4.5 3.0
AnnSd 8.0 5.3 5.5 6.5 6.3 11.8 8.8
Sharpe 10.4 -9.8 22.5 26.5 -58.2 38.4 34.5
HitRate -1.1 -1.0 3.4 2.3 -5.4 2.1 8.8
Beta -0.6 -0.1 0.1 0.4 0.1 -0.7 1.0
Alpha 2.6 -0.3 0.9 0.5 -3.9 6.5 0.0
DrawDn -30.2 -26.1 -17.8 -17.7 -60.1 -32.8 -35.2
DDnBeg 20145512 20075666 20150672 20130374 20070604 20130520 20071064
DDnN 158 305 100 44 394 148 32
AnnTo 911 1260 1632 1322 1031 1942 0
Monthly
AnnMn 0.6 -0.4 1.3 1.1 -2.9 3.5 3.1
AnnSd 8.2 5.0 5.4 6.4 6.4 12.2 9.2
Sharpe 6.9 -7.7 25.6 17.6 -46.2 29.1 33.4
HitRate -2.0 -2.0 2.8 3.7 -7.1 2.1 10.5
Beta -0.6 -0.1 0.1 0.4 0.1 -0.7 1.0
Alpha 2.4 0.0 0.9 0.0 -3.3 5.8 0.0
DrawDn -30.4 -22.6 -16.4 -21.5 -50.7 -29.7 -33.4
DDnBeg 20110790 20115758 20093091 20135358 20070716 20175692 20071040
DDnN 115 78 43 25 187 44 17
AnnTo 676 794 935 795 745 1420 0
Bi-Monthly
AnnMn 0.3 -0.5 0.7 1.2 -1.9 2.3 3.0
AnnSd 7.8 4.9 5.5 6.0 6.4 11.7 9.4
Sharpe 3.7 -11.8 11.3 19.9 -30.6 18.9 31.7
HitRate -6.1 -3.9 2.0 3.8 -7.3 2.0 12.9
Beta -0.5 -0.1 0.2 0.3 0.0 -0.6 1.0
Alpha 1.9 -0.2 0.1 0.3 -2.1 4.0 0.0
DrawDn -33.0 -24.6 -19.1 -19.2 -43.3 -35.9 -30.3
DDnBeg 20106159 20098564 20092837 20091709 20084162 20132922 20072094
DDnN 66 49 24 18 69 41 7
AnnTo 336 371 436 380 380 716 0
Quarterly
AnnMn 0.3 -0.8 0.6 1.2 -1.5 1.8 3.0
AnnSd 8.0 4.8 5.6 5.6 6.2 11.7 9.4
Sharpe 4.5 -16.2 11.2 21.8 -25.1 14.9 32.0
HitRate -5.3 -3.7 1.0 5.6 -6.5 0.8 15.1
Beta -0.5 -0.1 0.3 0.3 0.0 -0.5 1.0
Alpha 1.9 -0.5 -0.2 0.3 -1.4 3.2 0.0
DrawDn -34.6 -26.3 -18.7 -17.0 -36.8 -37.3 -28.1
DDnBeg 20114462 20095990 20086889 20101398 20087622 20134500 20072539
DDnN 40 40 20 11 44 30 4
AnnTo 246 267 301 265 271 518 0
Semi-Annual
AnnMn -0.1 -1.3 0.8 1.5 -1.0 0.9 2.9
AnnSd 7.6 5.0 5.7 5.7 6.4 11.1 9.9
Sharpe -1.3 -26.0 13.0 25.4 -16.9 8.0 29.5
HitRate -2.6 -10.8 4.8 7.3 -7.1 -1.6 16.8
Beta -0.4 -0.1 0.3 0.3 -0.2 -0.2 1.0
Alpha 1.0 -1.0 0.0 0.7 -0.6 1.5 0.0
DrawDn -31.4 -31.8 -17.0 -15.8 -31.6 -41.0 -24.8
DDnBeg 20110205 20099885 20100391 20094253 20087249 20117096 20072774
DDnN 18 22 11 6 17 14 2
AnnTo 133 141 152 140 142 276 0

Annualized mean one-week returns

bbk(x, y, 1, hz[1], nBin, doW, T, 0, delay)$annSumm # DISPLAY CALENDAR-YEAR RETURNS
Q1 Q2 Q3 Q4 Q5 TxB uRet nPrds
2007 -3.6 9.8 0.9 0.8 -12.6 9.0 5.1 31
2008 29.4 -8.6 7.2 -4.6 -19.0 48.4 -26.4 52
2009 1.5 -1.9 13.7 4.1 -18.1 19.6 24.4 52
2010 1.3 -1.6 -3.5 4.2 -1.3 2.6 8.8 53
2011 -1.3 -1.2 -15.6 9.3 6.2 -7.6 -1.7 52
2012 -0.2 1.4 0.0 1.6 -3.7 3.5 10.0 52
2013 6.0 0.2 -0.6 0.5 -6.3 12.4 4.8 52
2014 -9.5 -1.1 -3.6 5.7 6.9 -16.5 3.5 52
2015 -0.1 2.6 1.3 2.2 -7.6 7.5 -2.7 52
2016 -1.2 -6.5 6.1 1.0 2.3 -3.5 6.6 53
2017 -1.5 -4.8 -0.6 4.9 1.9 -3.4 8.5 52
2018 1.2 -9.7 9.6 -1.8 4.5 -3.3 -5.5 52
2019 -6.8 -3.1 5.8 2.9 1.1 -7.9 11.7 52
2020 20.0 5.3 -11.8 -7.3 -5.5 25.5 4.1 52
2021 -2.6 0.0 0.0 7.7 -7.6 4.9 4.0 53
2022 -8.7 2.3 4.6 0.4 0.4 -9.1 -9.5 52