3.2 Strategy Implementation

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

x <- mat.read(flow.file) # GET FLOW PERCENTAGE C:\\EPFR\\daily\\FloPctCtry-daily.csv
y <- mat.read(ret.file)  # GET PERCENTAGE RETURNs C:\\EPFR\\returns\\PsuedoReturns-Country-ETF-daily.csv

One of our first options is choosing the universe we want to use. EPFR has tested this signal within three different universes of countries: ACWI (All Country World Index), EAFE (Europe, Australasia, and the Far East), and EM (Emerging Markets). For this example, we choose ACWI, which includes 52 countries.

idx <- "ACWI"

Depending on what universe \(idx\) the user chooses to test, the flow file \(x\) and return file \(y\) must both be subset to the correct countries. Running the code below, which calls from functions contained in library('EPFR'), helps identify every member that was included the selected universe during the period over which we are backtesting.

x <- x[, is.element(dimnames(x)[[2]], Ctry.msci.members.rng(idx, dimnames(x)[[1]][1], dimnames(x)[[1]][dim(x)[1]]))] # SUBSET TO INDEX COUNTRIES

A few other adjustments must also be applied to our daily percentage flows data to ensure that it aligns with returns. First, the internally-generated fund returns are only available with sufficient coverage from the end of 2015 onwards. Second, fund returns for country ISO code JO are unavailable.

startdate <- "20150512"
x <- x[rownames(x)>=startdate, ] # SUBSET TIME PERIOD

x <- x[, !(dimnames(x)[[2]] %in% "JO")]

Next, we will need to ensure that the data structure in \(x\) and \(y\) are completely aligned, having the same column names in the same order. Below we will subset the columns of \(y\) to use the same countries, in the same order as \(x\).

y <- y[, dimnames(x)[[2]]] # TOTAL RETURN INDEX

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

3.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

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

x <- compound.flows(x, lookback, F) # COMPOUND FLOWS
AU BR CA CN ID IN JP KR MX MY PH RU SG TH TR TW ZA AR CL CO CZ EG HU IL NZ NO PE PL SE CH GB AT BE DK FI FR DE GR IE IT NL PT ES HK US MA PK AE QA SA KW
20221201 0.3657387 0.4224990 0.4808457 0.5368734 0.3157191 0.7480240 0.0830883 0.5472824 0.1438829 1.2508022 1.2429050 -0.3533873 0.0363202 0.7638555 1.0299772 0.8224584 0.9616983 -0.5940234 0.7288517 0.7990553 1.2073799 2.172433 0.6709283 0.1549135 0.6233808 0.1516190 0.6989874 1.0089310 -0.1242647 -0.1104243 -0.2525826 -0.0648429 -0.5851252 -0.1891037 0.0042294 -0.3867325 -0.2631415 0.8335389 -0.2720995 -0.2816058 -0.3016538 -0.7635916 -0.1538990 -0.2608068 0.2021691 19.03328 4.993004 1.1471616 1.3818455 1.2939687 1.3832349
20221202 0.2792255 0.3352712 0.3530235 0.5487682 0.3119253 0.7260748 -0.0464898 0.5063724 0.0895826 1.1742892 1.1683882 -0.6705598 -0.0163002 0.7206426 0.9597821 0.7769506 0.8975888 -0.6018448 0.6642326 0.7286986 1.1900412 2.108605 0.5568988 0.0300507 0.5548919 0.0380753 0.5469424 0.9636947 -0.1585176 -0.1782489 -0.3305223 0.0727308 -0.5828057 -0.1509296 0.0273668 -0.3884904 -0.3331824 0.7705446 -0.5453546 -0.2345844 -0.3128145 -0.6517184 -0.1853081 -0.2713762 0.2153821 19.08983 4.795951 1.0684084 1.2749777 1.2185201 1.2998187
20221205 0.1724398 0.2548420 0.4085384 0.4214132 0.1489173 0.5683554 -0.0782973 0.3958889 0.1438615 0.9805594 0.9963927 -0.7832803 -0.0214380 0.6369698 0.8515274 0.6291915 0.7083653 -0.6160456 0.6119402 0.9768418 0.9716324 1.935081 0.3568259 -0.0332946 0.4628725 0.0425980 0.3995123 0.7092462 -0.1649645 -0.2302423 -0.3257607 0.0556167 -0.4096780 -0.2586701 -0.0303832 -0.3976363 -0.3451982 0.6475459 -0.5731725 -0.2689440 -0.3455296 -0.7150814 -0.2301144 -0.1244719 0.3838026 19.05836 4.826099 0.8915754 1.0599314 1.0016124 1.1056957
20221206 0.2299679 0.1912819 0.4133465 0.3639880 0.1311528 0.5500984 -0.0182475 0.3706797 0.0387214 0.9773771 1.0194096 -0.7908906 0.0141360 0.6079116 0.7933953 0.6100951 0.6587183 -0.7007855 0.5767637 1.1987567 0.9784491 2.506006 0.1991608 0.0457574 0.5108701 0.1009406 0.3970704 0.7023463 -0.1244939 -0.1920437 -0.2902201 0.0765696 -0.3547143 -0.2022601 0.0213123 -0.3134878 -0.2769903 0.6066405 -0.5284036 -0.1749854 -0.2501154 -0.6410596 -0.1485396 -0.0846835 0.4048652 27.46648 6.736285 0.8998665 1.0616868 1.0051809 1.1218599
20221207 0.0621649 0.0337343 0.3036316 0.3012657 0.0761375 0.4779639 -0.1626054 0.3087572 -0.1062535 0.8079657 0.9155333 -1.2414975 -0.0668150 0.4585935 0.5410927 0.4917310 0.4442067 -0.7913361 0.4167141 1.0400281 0.9721833 2.479338 0.0056754 -0.1363917 0.3828041 -0.0491937 0.2581603 0.5307903 -0.3010304 -0.3297991 -0.4188398 -0.0957277 -0.5090548 -0.2991309 -0.1789525 -0.4510904 -0.4327651 0.5838153 -0.5994066 -0.3279278 -0.3849951 -0.6018392 -0.3045944 -0.0604079 0.3882161 28.80945 7.133620 0.7255044 0.8751273 0.8508713 0.9213861
20221208 0.0746834 0.0975710 0.1825067 0.2903070 0.0902998 0.4579744 -0.3692697 0.2152557 -0.0728351 0.7463247 0.8908209 -1.2450530 -0.1471434 0.4334176 0.5789301 0.3977577 0.4806812 -0.8418411 0.4959507 1.1689154 0.9949993 2.732097 0.0722522 -0.2832261 0.3366798 0.0600291 0.0905773 0.6759885 -0.3826158 -0.6963594 -0.5799311 -0.0241919 -0.4806366 -0.4215687 -0.1488005 -0.7500690 -0.6779237 0.6782364 -0.9602590 -0.3460828 -0.5015269 -0.6577265 -0.3706197 -0.0884993 0.3604298 30.60849 7.590067 0.7544367 0.9050508 0.8959919 0.9344611

3.2.2 Total Return Index

We will now convert our percentage returns data \(y\) to 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() ).

y[is.na(y)] <- 0
y <- ret.to.idx(map.rname(y, dimnames(x)[[1]])) # CONVERT TO A TOTAL-RETURN INDEX
y <- ret.idx.gaps.fix(y)

3.2.3 Ranking Countries

Next, we sort each of the countries 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, the total return index data, and our selected universe. 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 countries into quintiles by their 20-day percentage flow, we call function the bbk() for a one-week holding period. By adding the selected backtesting universe as an input to the function, we can ensure that the model tracks additions and removals of countries over time, and is therefore able to identify all members on a point-in-time basis.

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

3.2.4 Model

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

z[["bins"]]
AU BR CA CN ID IN JP KR MX MY PH RU SG TH TR TW ZA AR CL CO CZ EG HU IL NZ NO PE PL SE CH GB AT BE DK FI FR DE GR IE IT NL PT ES HK US MA PK AE QA SA KW
20221230 NA 3 2 3 4 3 3 3 4 1 1 NA NA 2 2 3 3 NA 1 1 1 1 5 4 NA 3 3 2 4 5 5 2 4 5 4 5 5 2 5 4 5 5 4 NA 1 NA NA 1 1 2 2
20221223 NA 4 2 3 3 3 4 3 5 1 1 NA NA 2 3 3 2 NA 2 1 1 1 5 4 NA 4 3 2 5 5 5 2 3 5 3 5 4 2 5 4 4 5 4 NA 1 NA NA 1 1 1 2
20221216 NA 3 2 3 3 2 4 3 5 1 1 NA NA 3 2 3 3 NA 2 1 1 1 4 4 NA 3 4 2 5 5 5 2 4 5 3 5 4 2 5 4 5 5 4 NA 2 NA NA 1 1 1 1
20221209 NA 3 3 3 3 2 4 3 4 1 1 NA NA 2 2 2 2 NA 2 1 1 1 3 4 NA 3 3 2 4 5 5 4 5 4 4 5 5 2 5 5 5 5 4 NA 3 NA NA 1 1 1 1
20221202 NA 3 3 3 3 2 4 3 3 1 1 NA NA 2 2 2 2 NA 2 1 1 1 3 4 NA 4 2 2 4 4 4 4 5 5 4 5 5 3 5 5 5 5 5 NA 3 NA NA 1 1 1 1

Quintile returns over the equal-weight universe

z[["rets"]]
Q1 Q2 Q3 Q4 Q5 TxB uRet
20221230 -0.9795159 -1.3743891 -0.4380914 1.0566192 1.7000695 -2.6795854 2.4790751
20221223 0.7041127 1.0115504 -0.0679902 -0.4191189 -1.1627283 1.8668411 -0.0001828
20221216 -0.6237331 0.6838879 0.0785362 -0.4426389 0.3307534 -0.9544865 0.7636063
20221209 0.5003205 1.0745838 -0.0545324 -0.7424331 -0.7410332 1.2413537 -1.4807999
20221202 0.1750194 0.1539427 -0.7111622 -0.0041247 0.4029712 -0.2279518 -1.5048217

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


3.2.5 Performance

Go long the top basket and short the bottom basket.

Performance over all holding periods

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

sapply(split(hz, hz), fcn, simplify = "array") # WRITE SUMMARIES
Q1 Q2 Q3 Q4 Q5 TxB uRet
Weekly
AnnMn -2.2 0.2 -0.6 -0.5 3.1 -5.3 4.3
AnnSd 6.5 5.3 4.8 5.1 7.3 12.1 15.1
Sharpe -33.8 4.1 -12.8 -9.8 43.1 -43.9 28.2
HitRate 0.3 2.3 -1.0 -3.0 3.0 -3.8 6.3
Beta -0.1 0.0 0.0 0.1 0.0 -0.1 1.0
Alpha -1.8 0.1 -0.5 -0.7 3.0 -4.8 0.0
DrawDn -26.3 -24.6 -10.3 -15.5 -17.5 -59.9 -40.0
DDnBeg 20150807 20161230 20151002 20190412 20211015 20150807 20180126
DDnN 207 186 68 185 20 323 112
AnnTo 1147 2288 2350 2189 1173 2320 0
Fortnightly
AnnMn -4.2 0.5 -0.1 1.2 2.7 -6.8 4.4
AnnSd 6.3 5.2 4.6 5.2 7.5 12.0 15.9
Sharpe -66.7 10.3 -0.1 22.6 35.3 -57.0 27.9
HitRate -4.9 -1.1 0.1 3.2 3.2 -4.7 6.9
Beta -0.1 0.0 0.0 0.0 0.1 -0.2 1.0
Alpha -3.8 0.6 0.0 1.0 2.3 -6.1 0.0
DrawDn -37.6 -21.5 -11.3 -12.2 -16.5 -68.9 -36.0
DDnBeg 20150810 20160670 20155664 20195763 20215564 20150818 20180122
DDnN 157 105 93 38 15 168 56
AnnTo 865 1467 1527 1440 904 1770 0
Monthly
AnnMn -5.0 0.3 1.1 2.2 1.5 -6.6 4.4
AnnSd 6.1 5.2 4.5 5.0 6.9 11.1 16.3
Sharpe -82.8 6.9 23.8 44.7 21.6 -58.6 27.0
HitRate -10.5 1.1 1.4 4.9 6.0 -6.7 8.0
Beta -0.1 0.0 0.0 0.0 0.1 -0.2 1.0
Alpha -4.6 0.3 1.1 2.0 1.2 -5.9 0.0
DrawDn -42.5 -20.4 -8.5 -9.8 -15.6 -65.8 -32.3
DDnBeg 20153139 20160749 20183166 20193190 20217869 20153212 20180140
DDnN 92 49 13 26 8 83 28
AnnTo 657 912 909 885 680 1337 0
Bi-Monthly
AnnMn -4.7 0.0 1.1 2.0 1.7 -6.4 4.3
AnnSd 6.1 5.5 4.2 4.7 7.0 11.5 16.2
Sharpe -79.5 1.3 24.7 41.1 24.8 -57.7 26.4
HitRate -15.9 -2.9 2.1 7.2 8.7 -12.6 11.0
Beta -0.2 0.0 0.0 0.0 0.1 -0.2 1.0
Alpha -4.1 -0.1 1.1 1.8 1.3 -5.4 0.0
DrawDn -40.7 -22.0 -8.5 -8.9 -15.9 -63.9 -27.4
DDnBeg 20155972 20158572 20177292 20178397 20204958 20157084 20189013
DDnN 37 22 14 9 9 31 8
AnnTo 368 435 436 441 360 728 0
Quarterly
AnnMn -3.6 -0.3 0.6 1.4 2.0 -5.5 4.4
AnnSd 6.1 5.5 4.2 4.5 6.8 11.4 15.5
Sharpe -59.5 -4.5 14.9 30.1 31.3 -50.3 29.7
HitRate -11.6 -1.1 4.5 5.7 8.9 -10.0 10.9
Beta -0.2 0.0 0.0 0.0 0.1 -0.3 1.0
Alpha -2.8 -0.5 0.5 1.3 1.6 -4.4 0.0
DrawDn -30.3 -20.6 -9.9 -10.1 -14.4 -52.0 -24.0
DDnBeg 20155308 20157615 20167678 20176972 20199133 20154608 20184230
DDnN 26 16 12 7 6 21 5
AnnTo 274 310 311 309 268 543 0
Semi-Annual
AnnMn -1.7 -0.6 0.5 1.3 0.5 -2.2 4.4
AnnSd 5.6 5.2 4.2 4.7 6.2 10.5 14.7
Sharpe -31.5 -10.7 12.2 31.0 3.4 -17.2 30.5
HitRate -12.8 -1.6 0.6 8.9 1.9 -7.1 13.2
Beta -0.1 0.0 0.0 0.0 0.1 -0.2 1.0
Alpha -1.1 -0.7 0.3 1.3 0.1 -1.2 0.0
DrawDn -19.2 -15.9 -9.0 -8.0 -14.7 -34.6 -18.3
DDnBeg 20157332 20162195 20169563 20186938 20189197 20153850 20183313
DDnN 11 7 5 4 5 8 3
AnnTo 142 163 163 158 154 296 0

Annualized mean one-week returns

bbk(x, y, 1, hz[1], nBin, doW, T, 0, delay, idx)$annSumm # DISPLAY CALENDAR-YEAR RETURNS    
Q1 Q2 Q3 Q4 Q5 TxB uRet nPrds
2015 5.8 7.2 -1.7 -10.4 -0.9 6.7 -21.5 28
2016 -4.6 2.5 -6.9 0.8 8.1 -12.7 8.6 53
2017 -14.5 -4.4 7.5 8.7 2.0 -16.6 18.2 52
2018 0.6 -4.4 -0.5 2.1 2.5 -2.0 -12.9 52
2019 -2.7 -3.0 -1.5 -2.1 9.1 -11.8 15.8 52
2020 5.7 -6.5 2.4 -6.0 4.4 1.3 5.5 52
2021 -3.3 -1.5 0.0 0.3 4.8 -8.1 13.2 53
2022 0.0 17.7 -4.8 -2.6 -9.3 9.3 -10.4 52