2.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\\FX-daily.csv"
y <- 1/mat.read(ret.file) # GET EXCHANGE RATES "C:\\EPFR\\returns\\ExchRates-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), G10 (ten of the most heavily traded currencies), and EM (Emerging Markets). For this example, we choose ACWI, which includes 52 countries and 37 currencies.

idx <- "ACWI" # ACWI/G10/EM

Whenever the spot for Chinese currency CNH is N/A, we fill any gaps with spot rates for Chinese currency CNY.

y$CNY <- ifelse(is.na(y$CNH), y$CNY, y$CNH) # USE CNH WHENEVER POSSIBLE 

We must also add a column for the US Dollar (USD) to ensure that we can trade this currency when necessary. Since at this stage of the analysis, all other currencies’ spot rates are presented against the USD, set the USD spot to 1 across time.

y$USD <- rep(1, dim(y)[1]) # ADD IN USD 

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

idx.curr <- unique(Ctry.info(Ctry.msci.members(idx, ""), "Curr")) # CURRENCY CLASSIFICATION 2016
if (idx != "G10") idx.curr <- union(Ctry.info(Ctry.msci(idx)[, "CCODE"], "Curr"), idx.curr) else idx <- NULL # CAPTURE INDEX CHANGES
if (is.element("EM", idx)) idx.curr <- setdiff(idx.curr, c("USD", "EUR")) # ENSURE NO OVERLAP BETWEEN DEVELOPED AND EM CURRENCIES

x <- x[, is.element(dimnames(x)[[2]], idx.curr)] # SUBSET TO CURRENCIES OF INTEREST     

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 currencies, in the same order as \(x\). In this step, we will also divide each exchange rate by the historical USD/SDR spot. This resets the base currency away from the dollar and allows our backtests to include this currency in portfolio returns.

y <- y[, dimnames(x)[[2]]]/y[, "XDR"] # CURRENCIES OF INTEREST ON AN SDR BASE (OTHERWISE <get.fwdRet> THINKS THE USD NEVER TRADES!)     

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

2.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 currency.

x <- compound.flows(x, lookback, F) # COMPOUND FLOWS
ARS AUD BRL CAD CHF CLP CNY CZK EGP GBP HUF IDR ILS INR JPY KRW MXN MYR NOK NZD PEN PHP PLN RUB SEK SGD THB TRY TWD ZAR USD EUR COP PKR MAD VND KWD
20221201 -0.5940234 0.3657387 0.4224990 0.4808457 -0.1104243 0.7288517 0.5368734 1.2073799 2.172433 -0.2525826 0.6709283 0.3157191 0.1549135 0.7480240 0.0830883 0.5472824 0.1438829 1.2508022 0.1516190 0.6233808 0.6989874 1.2429050 1.0089310 -0.3533873 -0.1242647 0.0363202 0.7638555 1.0299772 0.8224584 0.9616983 0.2340294 -0.2956698 0.7990553 4.993004 19.03328 2.872918 1.3832349
20221202 -0.6018448 0.2792255 0.3352712 0.3530235 -0.1782489 0.6642326 0.5487682 1.1900412 2.108605 -0.3305223 0.5568988 0.3119253 0.0300507 0.7260748 -0.0464898 0.5063724 0.0895826 1.1742892 0.0380753 0.5548919 0.5469424 1.1683882 0.9636947 -0.6705598 -0.1585176 -0.0163002 0.7206426 0.9597821 0.7769506 0.8975888 0.2407246 -0.3160371 0.7286986 4.795951 19.08983 2.902651 1.2998187
20221205 -0.6160456 0.1724398 0.2548420 0.4085384 -0.2302423 0.6119402 0.4214132 0.9716324 1.935081 -0.3257607 0.3568259 0.1489173 -0.0332946 0.5683554 -0.0782973 0.3958889 0.1438615 0.9805594 0.0425980 0.4628725 0.3995123 0.9963927 0.7092462 -0.7832803 -0.1649645 -0.0214380 0.6369698 0.8515274 0.6291915 0.7083653 0.3859377 -0.3391601 0.9768418 4.826099 19.05836 2.821159 1.1056957
20221206 -0.7007855 0.2299679 0.1912819 0.4133465 -0.1920437 0.5767637 0.3639880 0.9784491 2.506006 -0.2902201 0.1991608 0.1311528 0.0457574 0.5500984 -0.0182475 0.3706797 0.0387214 0.9773771 0.1009406 0.5108701 0.3970704 1.0194096 0.7023463 -0.7908906 -0.1244939 0.0141360 0.6079116 0.7933953 0.6100951 0.6587183 0.4078085 -0.2624574 1.1987567 6.736285 27.46648 4.050283 1.1218599
20221207 -0.7913361 0.0621649 0.0337343 0.3036316 -0.3297991 0.4167141 0.3012657 0.9721833 2.479338 -0.4188398 0.0056754 0.0761375 -0.1363917 0.4779639 -0.1626054 0.3087572 -0.1062535 0.8079657 -0.0491937 0.3828041 0.2581603 0.9155333 0.5307903 -1.2414975 -0.3010304 -0.0668150 0.4585935 0.5410927 0.4917310 0.4442067 0.3851289 -0.4016989 1.0400281 7.133620 28.80945 4.396673 0.9213861
20221208 -0.8418411 0.0746834 0.0975710 0.1825067 -0.6963594 0.4959507 0.2903070 0.9949993 2.732097 -0.5799311 0.0722522 0.0902998 -0.2832261 0.4579744 -0.3692697 0.2152557 -0.0728351 0.7463247 0.0600291 0.3366798 0.0905773 0.8908209 0.6759885 -1.2450530 -0.3826158 -0.1471434 0.4334176 0.5789301 0.3977577 0.4806812 0.3612104 -0.5992051 1.1689154 7.590067 30.60849 4.876754 0.9344611

2.2.2 Ranking Currencies

Next, we sort each of the currencies 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, exchange rate 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 currencies into quintiles by their 20-day percentage flow, we call the function 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 currencies 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)

2.2.3 Model

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

z[["bins"]]
ARS AUD BRL CAD CHF CLP CNY CZK EGP GBP HUF IDR ILS INR JPY KRW MXN MYR NOK NZD PEN PHP PLN RUB SEK SGD THB TRY TWD ZAR USD EUR COP PKR MAD VND KWD
20221230 NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA
20221223 NA 3 4 2 5 2 3 1 1 5 5 3 4 3 4 3 5 1 4 3 4 1 2 NA 5 4 2 2 3 2 NA 5 1 NA NA NA 1
20221216 NA 3 4 2 5 2 3 1 1 5 5 4 4 2 4 3 5 1 4 3 4 1 2 NA 5 4 2 2 3 3 2 5 1 NA NA NA 1
20221209 NA 4 4 3 5 2 3 1 1 5 4 4 5 2 5 3 4 1 4 3 3 1 2 NA 5 4 2 2 2 2 3 5 1 NA NA NA 1
20221202 NA 4 4 3 5 2 3 1 1 5 3 4 5 3 4 3 4 1 4 3 2 1 2 NA 5 5 2 2 2 2 4 5 1 NA NA NA 1

Quintile returns over the equal-weight universe

z[["rets"]]
Q1 Q2 Q3 Q4 Q5 TxB uRet
20221230 NA NA NA NA NA NA NaN
20221223 -0.6773156 0.1876951 0.3914613 -0.1935814 0.2264971 -0.9038127 0.3502183
20221216 -0.2285290 0.0173119 0.1991964 0.2014266 -0.2258623 -0.0026668 0.6364195
20221209 0.3103277 -0.5951894 -0.0661117 0.4634301 -0.0904968 0.4008245 -0.1902268
20221202 0.2504880 0.7269355 -0.3562323 -0.7883338 0.1773757 0.0731123 -0.1915118

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


2.2.4 Performance

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.7 -0.3 -0.5 0.0 -1.8 4.6 -3.0
AnnSd 3.6 3.0 3.3 3.8 3.8 6.0 9.8
Sharpe 77.0 -10.9 -14.2 0.5 -47.7 75.7 -30.9
HitRate 4.5 0.1 0.0 -0.1 -1.0 3.7 -2.6
Beta 0.0 -0.1 -0.1 0.0 0.1 -0.1 1.0
Alpha 2.7 -0.5 -0.7 0.1 -1.5 4.3 0.0
DrawDn -8.9 -16.4 -14.5 -18.3 -36.3 -11.8 -77.7
DDnBeg 20190830 20081024 20071012 20130705 20080523 20190823 20110429
DDnN 98 595 485 380 749 108 598
AnnTo 1144 2271 2509 2429 1301 2445 0
Fortnightly
AnnMn 2.4 -0.4 -0.1 -0.6 -1.2 3.6 -3.1
AnnSd 3.5 3.0 3.2 3.4 3.7 5.9 10.1
Sharpe 69.6 -13.5 -3.9 -17.9 -33.2 62.0 -30.2
HitRate 4.4 -0.6 0.9 1.7 -0.8 4.1 -0.6
Beta 0.0 0.0 -0.1 0.0 0.1 -0.1 1.0
Alpha 2.4 -0.5 -0.3 -0.5 -0.9 3.3 0.0
DrawDn -8.5 -16.6 -10.5 -27.5 -27.1 -16.1 -76.9
DDnBeg 20195624 20085254 20076210 20110166 20080478 20190617 20110579
DDnN 42 162 164 274 320 62 296
AnnTo 881 1487 1584 1574 990 1870 0
Monthly
AnnMn 2.2 -0.3 0.4 -1.0 -1.2 3.4 -3.0
AnnSd 3.4 2.8 3.1 3.4 3.7 6.0 10.5
Sharpe 65.1 -9.9 11.9 -30.4 -32.0 57.0 -28.7
HitRate 6.5 -0.1 2.9 -0.1 -1.3 4.5 -1.9
Beta 0.0 0.0 -0.1 0.0 0.1 -0.1 1.0
Alpha 2.1 -0.4 0.2 -0.9 -0.9 3.0 0.0
DrawDn -9.2 -15.6 -10.9 -27.6 -24.9 -14.8 -75.8
DDnBeg 20185568 20078191 20080990 20122892 20075844 20192816 20110548
DDnN 37 152 75 112 165 32 148
AnnTo 655 906 939 914 732 1387 0
Bi-Monthly
AnnMn 1.3 0.0 0.4 -0.7 -0.9 2.2 -3.2
AnnSd 3.2 2.7 2.9 3.3 3.6 5.7 11.0
Sharpe 38.9 0.2 14.4 -20.7 -26.6 38.8 -28.9
HitRate 4.6 1.0 4.0 -0.4 -0.9 4.2 -4.0
Beta -0.1 0.0 0.0 0.0 0.1 -0.1 1.0
Alpha 1.1 -0.1 0.3 -0.5 -0.7 1.8 0.0
DrawDn -7.9 -10.4 -12.1 -21.2 -22.4 -11.1 -73.5
DDnBeg 20178449 20091634 20084116 20109758 20087558 20148439 20110617
DDnN 12 42 34 53 70 14 66
AnnTo 345 424 433 433 365 710 0
Quarterly
AnnMn 1.3 0.2 0.1 -0.6 -0.9 2.2 -3.3
AnnSd 3.0 2.7 2.8 3.3 3.7 5.5 11.2
Sharpe 41.3 6.9 6.7 -19.3 -25.4 40.3 -29.7
HitRate 7.3 0.7 1.1 -0.6 -2.5 6.3 -7.4
Beta -0.1 0.0 0.0 0.0 0.1 -0.2 1.0
Alpha 1.1 0.1 0.1 -0.6 -0.6 1.7 0.0
DrawDn -7.7 -10.7 -12.3 -20.7 -21.5 -9.8 -71.3
DDnBeg 20148201 20097454 20100664 20102271 20088438 20148252 20108315
DDnN 10 20 21 39 47 7 46
AnnTo 257 306 301 306 265 522 0
Semi-Annual
AnnMn 1.0 0.5 -0.1 -0.3 -1.0 2.0 -3.4
AnnSd 3.1 2.7 2.8 3.1 3.4 5.3 11.8
Sharpe 31.1 18.4 -4.2 -9.4 -31.3 38.0 -28.9
HitRate 8.4 5.0 -1.1 1.0 -4.6 8.6 -9.9
Beta 0.0 0.0 0.0 0.0 0.1 -0.1 1.0
Alpha 0.8 0.4 -0.2 -0.3 -0.8 1.6 0.0
DrawDn -6.8 -8.7 -12.2 -16.9 -21.6 -11.8 -67.4
DDnBeg 20130244 20101065 20084601 20109159 20106900 20113664 20105973
DDnN 4 9 16 16 19 5 24
AnnTo 135 156 156 157 146 281 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
2007 -7.8 0.8 -1.5 2.4 6.1 -14.0 16.1 31
2008 4.6 -1.6 -0.6 0.8 -3.3 7.9 -13.2 52
2009 1.6 0.5 -0.2 -2.1 -0.4 2.0 10.2 52
2010 1.6 -3.4 -2.1 7.2 -2.8 4.3 3.0 53
2011 7.0 -4.7 1.2 0.2 -4.4 11.4 -4.1 52
2012 1.8 0.3 -3.2 2.9 -1.4 3.2 3.5 52
2013 5.7 -1.6 -0.5 -3.5 -0.5 6.2 -4.9 52
2014 7.2 1.7 -0.8 -1.4 -6.7 13.8 -16.2 52
2015 7.8 -0.3 -1.8 -5.0 0.1 7.7 -14.6 52
2016 4.6 0.0 -4.2 -0.7 0.4 4.2 -6.1 53
2017 2.9 -0.9 2.0 -2.1 -1.6 4.4 12.6 52
2018 3.2 0.5 0.6 -0.6 -3.7 6.9 -9.3 52
2019 -0.8 -1.8 2.3 1.2 -1.1 0.3 -1.0 52
2020 -2.9 2.3 1.7 -3.2 2.4 -5.3 4.8 52
2021 -1.6 0.4 -0.4 6.5 -4.0 2.4 -8.2 53
2022 4.8 3.0 -0.3 -1.6 -5.3 10.2 -13.4 52