Exponential Moving Average (EMA) Rates, part 3
In the last post, we created an online implementation of an EMA to measure the rate of a Poisson event. However, it has the “warm-up” period seen in most EMA implementations.
This time, we’ll correct that. The technique is similar to what I wrote in The correct way to start an Exponential Moving Average (EMA).
Fixing the SMA
We’ll also fix the SMA implementation so that we have something to compare against. The fix is really simple. Instead of dividing the number of occurences in the window by the declared width, we’ll divide by the window’s actual width, which is the smaller of the current time and the declared width:
make.sma2 <- function (width) { force(width) data <- NULL clipped <- function (time) { data[data > time - width] } list( update=function (time) { data <<- c(clipped(time), time) }, read=function (time) { data <<- clipped(time) length(data) / min(width, time) } ) }
EMA
Like a normal EMA, this EMA-for-rates begins with what is essentially an infinite history of data. Because the count is initialized to 0, it’s as if it has observed an infinite duration of time with no events.
We could just as easliy preload it with a false observed rate of twice what our data has, by initially setting count <- 1200
. Plotting both this altered EMA and the original show them asymptotically approaching a “true” EMA:
make.ema2 <- function (width) { force(width) count <- 1200 last <- 0 advance <- function (time) { count <<- count * exp((last - time) / width) last <<- time } list( update=function (time) { advance(time) count <<- count + 1 }, read=function (time) { advance(time) count / width } ) }
Correcting the rate EMA is done in a similar way as correcting a normal EMA. The preloading is, in essence, entirely in the count
variable. Whatever its initial value, that value will be adjusted to carry smaller weight in the final result as time passes.
All there is to do is to determine how much weight the initial value carries, subtract it out (a no-op when its initial value is 0), then renormalize the remaining value.
In code, this is done entirely with a new divisor on the return value of the read
function:
make.ema3 <- function (width) { force(width) count <- 0 last <- 0 advance <- function (time) { count <<- count * exp((last - time) / width) last <<- time } list( update=function (time) { advance(time) count <<- count + 1 }, read=function (time) { advance(time) count / width / (1 - exp(-time/width)) } ) }
We can clearly see this corrected EMA go right between the two bounds we previously plotted:
Final comparison
Finally, here’s the comparison between the finished SMA and EMA:
Trackbacks
The author does not allow comments to this entry
Comments
Display comments as Linear | Threaded