5.2 Strategy Implementation

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

x <- as.matrix(mat.read(indicator.file)) # GET ACTIVE/PASSIVE INDICATOR C:\\EPFR\\monthly\\ActPasCtry-monthly.csv
y <- mat.read(ret.file)  # GET TOTAL RETURN INDEX C:\\EPFR\\returns\\PsuedoReturns-Country-ETF-daily.csv

Before the data is ready, we will first need to apply the mat.daily.to.monthly() function to ensure that return data is correctly indexed by month, as mentioned previously (help: ?mat.daily.to.monthly()).

y <- mat.daily.to.monthly(y, T)

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 indicator file \(x\) and return file \(y\) must both be subset to the correct countries. Running the code below, which calls from functions contained inlibrary('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 monthly Active/Passive signal 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 codes JO and VE are unavailable.

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

ctry <- c('JO', 'VE')
x <- x[, !(dimnames(x)[[2]] %in% ctry)]

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 indicator and return files

5.2.1 Moving Sum of Indicator

Next, we set up a variable for our lookback period. This variable will be the window of time across which we smooth the signal for each country. The lookback period we choose for our demonstrations is 12 months.

lookback <- 12 # LOOKBACK (IN MONTHS)               

Now, using the function from the library('EPFR.r'), called compound.flows() we compute the moving sum of our signal over the trailing lookback period for each country.

x <- compound.flows(x, lookback, T) # COMPUTE MOVING SUM
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
202201 -5.117164 4.678409 -5.655730 2.567462 4.723583 5.463615 -5.396886 5.092788 5.806146 -8.940355 0.2127890 11.291808 6.580787 -4.331993 4.598560 1.717442 -2.337778 37.33551 -1.852158 -4.854193 4.747390 31.76519 28.36629 -2.172202 -4.960506 -0.2916124 13.09126 7.164393 -1.817267 -1.976079 -1.1403057 2.2294622 -5.361904 0.1641512 -4.941868 -1.144588 -2.466260 21.57215 9.252658 -2.382309 -1.250053 1.183348 -4.141872 1.628550 0.5623372 2.442255 36.00374 -2.001183 -4.747304 -4.489196
202202 -5.072419 4.637230 -5.621818 2.574939 5.331131 5.494082 -5.388036 5.121049 5.941346 -8.932020 0.4178712 10.642956 6.848616 -4.216254 4.004105 1.822039 -2.401967 39.95124 -1.770836 -4.858818 4.345430 30.62884 27.65854 -2.324806 -4.962109 -0.5969357 13.16735 7.116680 -1.931579 -1.983764 -1.0847287 1.6754867 -5.465089 0.2845010 -4.910503 -1.164140 -2.437013 21.85479 9.576143 -2.471491 -1.279265 1.084992 -4.192530 1.717976 0.5468860 2.468647 36.43622 -2.196695 -4.636670 -4.465992
202203 -5.015681 4.643543 -5.574692 2.597466 6.155813 5.484428 -5.358121 5.147998 6.013065 -8.930333 0.5304089 11.705001 7.138094 -4.124273 3.461151 1.894532 -2.465178 41.44260 -1.847794 -4.892082 3.413912 29.36388 26.38921 -2.492902 -4.796030 -0.8192064 13.35556 6.587172 -1.989177 -1.978598 -1.0278146 1.3565412 -5.542525 0.2927595 -4.913480 -1.203407 -2.435036 21.01725 10.011778 -2.557305 -1.358146 1.102749 -4.263043 1.870577 0.5366788 2.639549 35.90684 -2.383396 -4.548398 -4.496952
202204 -4.947918 4.535185 -5.495884 2.520172 6.801915 5.411781 -5.328401 5.101505 6.032498 -8.918860 0.4628795 10.434452 7.345398 -4.049834 2.570905 1.894301 -2.578488 43.34312 -2.056172 -4.943089 2.905736 27.74858 26.86343 -2.622450 -4.680218 -1.0788292 12.86845 6.647043 -1.984618 -1.980360 -0.9948773 1.1127796 -5.603665 0.3202932 -4.919820 -1.221229 -2.429764 19.64961 10.381683 -2.613288 -1.398923 1.051464 -4.309179 1.910272 0.6001556 2.517206 34.76934 -2.499323 -4.464717 -4.523993
202205 -4.820733 4.507865 -5.434879 2.465613 7.397669 5.387254 -5.277615 5.057851 6.143054 -8.821450 0.3577807 9.211567 7.630802 -3.913319 1.788540 1.904205 -2.730111 43.94098 -2.188964 -4.991435 2.297136 26.12656 27.10910 -2.688053 -4.603027 -1.2426424 12.76230 6.535463 -1.951538 -1.976833 -0.9690803 0.8319998 -5.711483 0.3444544 -4.914463 -1.230629 -2.437072 18.39921 10.585603 -2.657278 -1.443286 1.065102 -4.333185 2.030077 0.6219051 2.330161 33.43916 -2.482779 -4.381071 -4.558491
202206 -4.671999 4.474621 -5.356142 2.505159 7.969634 5.359422 -5.231112 4.989937 6.264760 -8.704054 0.1956019 8.055630 7.883029 -3.736385 1.005881 1.885444 -2.846336 45.64295 -2.337513 -4.882090 1.794952 24.42459 26.72583 -2.756549 -4.485215 -1.3480140 12.58793 6.386792 -1.964803 -1.980810 -0.9705799 0.5179211 -5.774074 0.3909402 -4.924925 -1.248182 -2.444340 17.20674 10.804709 -2.718384 -1.487150 1.255855 -4.375402 2.150373 0.6290547 2.121053 31.55198 -2.326925 -4.316165 -4.519375

5.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)

5.2.3 Ranking Countries

Next, we sort each of the countries in our universe into five equal bins based on the last 12-month active/passive indicator 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 12-month active/passive indicator, 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+23 day lag and is released around 5:00 pm EST, we account for a T+1 month delay in our model.

delay <- 1 # DELAY IN KNOWING DATA (IN MONTHS)  

The day of the week the rebalancing occurs is at the user’s discretion, but for this example we will set the day of the week to trade as N/A.This allows the bbk() function to default to monthly-indexed returns, and avoids trading on a specific weekday on each rebalancing date.

doW <- NULL # 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 monthly, quarterly, semi-annual, and annual rebalancing, but is it important to note that this model can be re-balanced as desired.

hz <- c(1, 3, 6, 12) # RETURN HORIZON (IN MONTHS)

Now that we have defined all of our inputs, to rank the countries into quintiles by their 12-month active/passive indicator, we call the function bbk() for a one-month 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)

5.2.4 Model

12-month cumulative indicator 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
202210 NA 2 5 2 1 2 5 2 1 5 3 NA NA 4 3 2 4 NA 4 5 NA 1 1 4 NA 3 1 1 3 4 3 3 5 2 5 3 4 1 1 4 3 2 5 NA 2 NA NA 3 4 5
202209 NA 2 5 2 1 2 5 2 1 5 3 NA NA 4 3 2 4 NA 4 5 2 1 1 4 NA 3 1 1 3 4 3 3 5 2 5 3 4 1 1 4 3 2 5 NA 2 NA NA 4 4 5
202208 NA 2 5 2 1 2 5 2 1 5 3 NA NA 4 3 2 4 NA 4 5 2 1 1 4 NA 3 1 1 3 4 3 3 5 2 5 3 4 1 1 4 3 2 5 NA 2 NA NA 4 4 5
202207 NA 2 5 2 1 2 5 2 1 5 3 NA NA 4 2 2 4 NA 4 5 2 1 1 4 NA 3 1 1 3 4 3 3 5 3 5 3 4 1 1 4 3 2 5 NA 2 NA NA 4 4 5
202206 NA 2 5 2 1 2 5 2 1 5 3 NA NA 4 2 2 4 NA 4 5 2 1 1 4 NA 3 1 1 3 4 3 2 5 3 5 3 4 1 1 4 3 2 4 NA 3 NA NA 4 5 5
202205 NA 2 5 2 1 2 5 2 1 5 3 NA NA 4 2 2 4 NA 4 5 2 1 1 4 NA 3 1 1 4 3 3 2 5 3 5 3 4 1 1 4 3 2 4 NA 3 NA NA 4 5 5

Quintile returns over the equal-weight universe

z[["rets"]]
Q1 Q2 Q3 Q4 Q5 TxB uRet
202210 0.1123069 0.5675860 -0.2833644 0.0805593 -0.4416673 0.5539742 1.2956635
202209 -0.0535121 0.1320834 -0.0766896 0.0116607 -0.0315104 -0.0220016 0.1737691
202208 -0.2578973 0.0816368 0.2255144 0.1625414 -0.2423176 -0.0155797 0.3692333
202207 0.1518092 0.4501508 -0.0677247 -0.2478474 -0.3116759 0.4634851 -0.6708180
202206 0.2348840 -0.2209259 0.6335878 0.0079502 -0.6288742 0.8637582 0.8609815
202205 0.0731573 -0.1315785 -0.0236833 -0.0810249 0.1897048 -0.1165475 -1.4172222

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


5.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
Monthly
AnnMn 0.0 0.1 0.6 -0.6 -0.4 0.4 -1.1
AnnSd 2.0 1.3 1.0 1.2 0.9 2.6 2.3
Sharpe 1.9 8.7 64.6 -48.4 -41.0 15.4 -49.1
HitRate 9.0 2.6 6.4 -5.1 -7.7 12.8 -10.8
Beta NA NA NA NA NA NA 1.0
Alpha NA NA NA NA NA NA 0.0
DrawDn -5.0 -2.8 -1.6 -6.4 -2.9 -6.4 -12.6
DDnBeg 202201 201908 202104 201708 201606 202201 201908
DDnN 4 20 8 49 54 5 34
AnnTo 100 122 128 125 88 188 0
Quarterly
AnnMn 0.1 0.1 0.8 -0.6 -0.4 0.5 -1.3
AnnSd 2.1 1.3 1.0 1.2 0.8 2.6 2.1
Sharpe 4.4 10.2 76.1 -51.7 -52.5 20.0 -62.0
HitRate 7.8 3.8 17.1 -9.2 -11.9 23.6 -8.5
Beta NA NA NA NA NA NA 1.0
Alpha NA NA NA NA NA NA 0.0
DrawDn -4.7 -2.6 -1.1 -5.9 -2.9 -5.8 -12.1
DDnBeg 202141 201941 202037 201709 201607 202141 201909
DDnN 2 6 3 16 17 2 12
AnnTo 53 67 66 67 44 97 0
Semi-Annual
AnnMn 0.2 0.2 0.8 -0.7 -0.5 0.7 -1.4
AnnSd 2.0 1.2 1.1 1.2 0.9 2.5 2.4
Sharpe 9.0 18.2 72.5 -55.0 -57.8 28.1 -59.1
HitRate 14.3 2.0 25.4 -9.0 -17.0 19.9 -5.4
Beta NA NA NA NA NA NA 1.0
Alpha NA NA NA NA NA NA 0.0
DrawDn -3.7 -2.2 -0.8 -5.6 -3.5 -4.6 -11.4
DDnBeg 202125 201924 201971 201710 201608 202124 201908
DDnN 1 4 1 8 10 1 6
AnnTo 44 53 53 53 37 81 0
Annually
AnnMn 0.2 0.2 0.9 -0.7 -0.6 0.8 -1.5
AnnSd 1.8 1.0 0.9 1.2 0.7 2.3 2.8
Sharpe 10.5 25.1 133.6 -60.9 -89.6 36.4 -53.1
HitRate 9.2 10.6 35.0 -22.5 -26.4 21.4 -16.7
Beta NA NA NA NA NA NA 1.0
Alpha NA NA NA NA NA NA 0.0
DrawDn -2.8 -1.5 -0.3 -4.8 -3.7 -2.9 -10.3
DDnBeg 202073 201923 202032 201706 201648 202082 201906
DDnN 1 2 1 4 4 1 3
AnnTo 36 42 42 43 34 69 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
2016 2.1 -2.5 0.5 1.6 -1.7 3.8 2.8 6
2017 -0.2 -0.5 1.0 0.0 -0.4 0.1 -0.2 12
2018 -1.3 1.1 1.7 -1.6 0.1 -1.4 0.5 12
2019 0.9 1.1 -0.8 0.2 -1.4 2.3 1.1 12
2020 2.3 -1.0 2.7 -3.6 -0.4 2.6 -4.6 12
2021 2.1 -1.2 -1.2 -0.7 0.8 1.3 -5.6 12
2022 -4.5 2.5 0.6 1.3 -0.3 -4.2 -0.3 12