Efficiency Ratio Update

quant
trading
Author

Ray Lai

Published

March 26, 2025

Efficiency Ratio

One great learning from Perry Kaufman is to use the efficiency ratio to measure the noise of the market. Kaufman’s Efficiency Ratio was invented in the 1970s to measure how noisy the market is, and from his book Trading Systems and Methods, 5th edition, Chapter 1.

Here’s the formula for the n-day Efficiency Ratio.

\[ ER = \frac{|P_t-P_{t-n}|}{\sum_{i = t-n}^{i = t}|P_i-P_{i-1}|} \]

The idea is simple. A trend with high efficiency ratio is smooth and linear. Conversely, a trend with low efficiency ratio is choppy.

As we see from the formula, the efficiency ratio will be based on different factors:

  • The timeframe: In lower timeframe prices tend to fluctuate more

  • The length of the window

Efficiency ratio across markets for last 5 years

We will examine the efficiency ratio across the following asset classes (or proxy ETFs)

  • Equity:

    • US: IYY, SPY, QQQ, IWM

    • Asia: FXI, EWJ, VNM, INDA, EWY, EWT

    • Europe: EWQ, EWI, EWG, EWU

    • South America: ARGT, EWW, EWZ

    • Emerging markets: ACWX, EEM

  • Bonds: SHY(1-3 years TBond), TLT (20+ Year Treasury Bond)

  • Commodities: GC, SI, HG, CL, NG, HO, RB, CT, ZC, ZS, ZL, ZW, SB

  • Forex: 6A, 6B, 6C, 6E, 6J, 6S, 6N, DX

Code
library(quantmod)
Loading required package: xts
Loading required package: zoo

Attaching package: 'zoo'
The following objects are masked from 'package:base':

    as.Date, as.Date.numeric
Loading required package: TTR
Registered S3 method overwritten by 'quantmod':
  method            from
  as.zoo.data.frame zoo 
Code
library(ggplot2)
Code
efficiency_ratio <- function(close, n = 20) {
  
  close <- na.omit(close) # Remove NAs
  close_lag <- lag(close, n)
  
  # Numerator
  close_minus_close_lag <- abs(close - close_lag)
  
  # Denominator
  d <- abs(diff(close))
  ds <- rollapply(d, n, sum)
  
  return(mean(close_minus_close_lag / ds, na.rm = TRUE))
}
Code
universe <- c(
  "IYY", "SPY", "QQQ", "IWM",
  "FXI", "EWJ", "VNM", "INDA", "EWY", "EWT",
  "EWQ", "EWI", "EWG", "EWU",
  "ARGT", "EWW", "EWZ",
  "ACWX", "EEM",
  "ZT=F", "ZF=F", "ZN=F", "ZB=F",
  "GC=F", "SI=F", "HG=F", "CL=F", "NG=F", "HO=F", "RB=F", "CT=F", "ZC=F", "ZS=F", 
  "ZL=F", "ZW=F", "SB=F",
  "AUDUSD=X", "GBPUSD=X", "CADUSD=X", "EURUSD=X", "USDJPY=X", "CHFUSD=X", "NZDUSD=X", "DX=F"
)

asset_class <- c(
  "Equity", "Equity", "Equity", "Equity",
  "Equity", "Equity", "Equity", "Equity", "Equity", "Equity",
  "Equity", "Equity", "Equity", "Equity",
  "Equity", "Equity", "Equity",
  "Equity", "Equity",
  "Bonds", "Bonds", "Bonds", "Bonds",
  "Commodities", "Commodities", "Commodities", "Commodities", "Commodities", "Commodities", 
  "Commodities", "Commodities", "Commodities", "Commodities", 
  "Commodities", "Commodities", "Commodities",
  "Forex", "Forex", "Forex", "Forex", "Forex", "Forex", "Forex", "Forex"
)

Last Quarter

Code
start_date <- "2025-01-01"
end_date   <- "2025-03-31"

getSymbols(Symbols = universe, 
           src = "yahoo", 
           index.class = "POSIXct",
           from = start_date, 
           to = end_date, 
           adjust = TRUE)
 [1] "IYY"      "SPY"      "QQQ"      "IWM"      "FXI"      "EWJ"     
 [7] "VNM"      "INDA"     "EWY"      "EWT"      "EWQ"      "EWI"     
[13] "EWG"      "EWU"      "ARGT"     "EWW"      "EWZ"      "ACWX"    
[19] "EEM"      "ZT=F"     "ZF=F"     "ZN=F"     "ZB=F"     "GC=F"    
[25] "SI=F"     "HG=F"     "CL=F"     "NG=F"     "HO=F"     "RB=F"    
[31] "CT=F"     "ZC=F"     "ZS=F"     "ZL=F"     "ZW=F"     "SB=F"    
[37] "AUDUSD=X" "GBPUSD=X" "CADUSD=X" "EURUSD=X" "USDJPY=X" "CHFUSD=X"
[43] "NZDUSD=X" "DX=F"    
Code
efficiency_ratios <- c()
for (symbol in universe) {
  efficiency_ratios <- c(efficiency_ratios, efficiency_ratio(Cl(get(symbol)), n = 20))
}

result <- data.frame(
  symbol = universe,
  asset_class = asset_class,
  efficiency_ratio = efficiency_ratios
)

ggplot(result, aes(x = reorder(symbol, -efficiency_ratio), y = efficiency_ratio, fill = asset_class)) +
  geom_bar(stat = "identity") +
  theme(axis.text.x=element_text(angle=45, hjust=1))

Last year

Code
start_date <- "2024-03-01"
end_date   <- "2025-03-31"

getSymbols(Symbols = universe, 
           src = "yahoo", 
           index.class = "POSIXct",
           from = start_date, 
           to = end_date, 
           adjust = TRUE)
 [1] "IYY"      "SPY"      "QQQ"      "IWM"      "FXI"      "EWJ"     
 [7] "VNM"      "INDA"     "EWY"      "EWT"      "EWQ"      "EWI"     
[13] "EWG"      "EWU"      "ARGT"     "EWW"      "EWZ"      "ACWX"    
[19] "EEM"      "ZT=F"     "ZF=F"     "ZN=F"     "ZB=F"     "GC=F"    
[25] "SI=F"     "HG=F"     "CL=F"     "NG=F"     "HO=F"     "RB=F"    
[31] "CT=F"     "ZC=F"     "ZS=F"     "ZL=F"     "ZW=F"     "SB=F"    
[37] "AUDUSD=X" "GBPUSD=X" "CADUSD=X" "EURUSD=X" "USDJPY=X" "CHFUSD=X"
[43] "NZDUSD=X" "DX=F"    
Code
efficiency_ratios <- c()
for (symbol in universe) {
  efficiency_ratios <- c(efficiency_ratios, efficiency_ratio(Cl(get(symbol)), n = 20))
}

result <- data.frame(
  symbol = universe,
  asset_class = asset_class,
  efficiency_ratio = efficiency_ratios
)

ggplot(result, aes(x = reorder(symbol, -efficiency_ratio), y = efficiency_ratio, fill = asset_class)) +
  geom_bar(stat = "identity") +
  theme(axis.text.x=element_text(angle=45, hjust=1))

5 year timeframe

Code
start_date <- "2019-01-01"
end_date   <- "2024-12-31"

getSymbols(Symbols = universe, 
           src = "yahoo", 
           index.class = "POSIXct",
           from = start_date, 
           to = end_date, 
           adjust = TRUE)
 [1] "IYY"      "SPY"      "QQQ"      "IWM"      "FXI"      "EWJ"     
 [7] "VNM"      "INDA"     "EWY"      "EWT"      "EWQ"      "EWI"     
[13] "EWG"      "EWU"      "ARGT"     "EWW"      "EWZ"      "ACWX"    
[19] "EEM"      "ZT=F"     "ZF=F"     "ZN=F"     "ZB=F"     "GC=F"    
[25] "SI=F"     "HG=F"     "CL=F"     "NG=F"     "HO=F"     "RB=F"    
[31] "CT=F"     "ZC=F"     "ZS=F"     "ZL=F"     "ZW=F"     "SB=F"    
[37] "AUDUSD=X" "GBPUSD=X" "CADUSD=X" "EURUSD=X" "USDJPY=X" "CHFUSD=X"
[43] "NZDUSD=X" "DX=F"    
Code
efficiency_ratios <- c()
for (symbol in universe) {
  efficiency_ratios <- c(efficiency_ratios, efficiency_ratio(Cl(get(symbol)), n = 20))
}

result <- data.frame(
  symbol = universe,
  asset_class = asset_class,
  efficiency_ratio = efficiency_ratios
)

ggplot(result, aes(x = reorder(symbol, -efficiency_ratio), y = efficiency_ratio, fill = asset_class)) +
  geom_bar(stat = "identity") +
  theme(axis.text.x=element_text(angle=45, hjust=1))

10-year timeframe

Code
start_date <- "2014-01-01"
end_date   <- "2024-12-31"

getSymbols(Symbols = universe, 
           src = "yahoo", 
           index.class = "POSIXct",
           from = start_date, 
           to = end_date, 
           adjust = TRUE)
 [1] "IYY"      "SPY"      "QQQ"      "IWM"      "FXI"      "EWJ"     
 [7] "VNM"      "INDA"     "EWY"      "EWT"      "EWQ"      "EWI"     
[13] "EWG"      "EWU"      "ARGT"     "EWW"      "EWZ"      "ACWX"    
[19] "EEM"      "ZT=F"     "ZF=F"     "ZN=F"     "ZB=F"     "GC=F"    
[25] "SI=F"     "HG=F"     "CL=F"     "NG=F"     "HO=F"     "RB=F"    
[31] "CT=F"     "ZC=F"     "ZS=F"     "ZL=F"     "ZW=F"     "SB=F"    
[37] "AUDUSD=X" "GBPUSD=X" "CADUSD=X" "EURUSD=X" "USDJPY=X" "CHFUSD=X"
[43] "NZDUSD=X" "DX=F"    
Code
efficiency_ratios <- c()
for (symbol in universe) {
  efficiency_ratios <- c(efficiency_ratios, efficiency_ratio(Cl(get(symbol)), n = 20))
}

result <- data.frame(
  symbol = universe,
  asset_class = asset_class,
  efficiency_ratio = efficiency_ratios
)

ggplot(result, aes(x = reorder(symbol, -efficiency_ratio), y = efficiency_ratio, fill = asset_class)) +
  geom_bar(stat = "identity") +
  theme(axis.text.x=element_text(angle=45, hjust=1))

Code
efficiency_ratios <- c()
for (symbol in universe) {
  efficiency_ratios <- c(efficiency_ratios, efficiency_ratio(Cl(get(symbol)), n = 40))
}

result <- data.frame(
  symbol = universe,
  asset_class = asset_class,
  efficiency_ratio = efficiency_ratios
)

ggplot(result, aes(x = reorder(symbol, -efficiency_ratio), y = efficiency_ratio, fill = asset_class)) +
  geom_bar(stat = "identity") +
  theme(axis.text.x=element_text(angle=45, hjust=1))

20-year timeframe

Code
start_date <- "2004-01-01"
end_date   <- "2024-12-31"

getSymbols(Symbols = universe, 
           src = "yahoo", 
           index.class = "POSIXct",
           from = start_date, 
           to = end_date, 
           adjust = TRUE)
 [1] "IYY"      "SPY"      "QQQ"      "IWM"      "FXI"      "EWJ"     
 [7] "VNM"      "INDA"     "EWY"      "EWT"      "EWQ"      "EWI"     
[13] "EWG"      "EWU"      "ARGT"     "EWW"      "EWZ"      "ACWX"    
[19] "EEM"      "ZT=F"     "ZF=F"     "ZN=F"     "ZB=F"     "GC=F"    
[25] "SI=F"     "HG=F"     "CL=F"     "NG=F"     "HO=F"     "RB=F"    
[31] "CT=F"     "ZC=F"     "ZS=F"     "ZL=F"     "ZW=F"     "SB=F"    
[37] "AUDUSD=X" "GBPUSD=X" "CADUSD=X" "EURUSD=X" "USDJPY=X" "CHFUSD=X"
[43] "NZDUSD=X" "DX=F"    
Code
efficiency_ratios <- c()
for (symbol in universe) {
  efficiency_ratios <- c(efficiency_ratios, efficiency_ratio(Cl(get(symbol)), n = 20))
}

result <- data.frame(
  symbol = universe,
  asset_class = asset_class,
  efficiency_ratio = efficiency_ratios
)

ggplot(result, aes(x = reorder(symbol, -efficiency_ratio), y = efficiency_ratio, fill = asset_class)) +
  geom_bar(stat = "identity") +
  theme(axis.text.x=element_text(angle=45, hjust=1))

Conclusion

It is interesting to see that the findings is different from what Kraufman suggested in his book (p. 13). It could be due to the fact that the market is always changing.

In past 5 years, futures market are trending and linear, while emerging market and small-caps are choppy. It could be a good insights to design relevant trading strategies.