Skip to content

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)
        }
    )
}

3-sma.svg

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
        }
    )
}

3-squeeze.svg

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:

3-corrected.svg

Final comparison

Finally, here’s the comparison between the finished SMA and EMA:

3-corrected2.svg

Trackbacks

No Trackbacks

Comments

Display comments as Linear | Threaded

No comments

Add Comment

E-Mail addresses will not be displayed and will only be used for E-Mail notifications.
To leave a comment you must approve it via e-mail, which will be sent to your address after submission.
Enclosing asterisks marks text as bold (*word*), underscore are made via _word_.
Form options