在之前的笔记中,我们初步认识了指数移动平均(指数加权移动平均),本文将通过翻译一篇David Owen 在2017年的一篇博客,讨论如何确保移动平均数能够通过识别记录信息的时长,来适应新的信息。原文链接:点击这里(原文的代码为R,本文将补充py代码)
目录
如何正确地开始指数移动平均(EMA)
指数移动平均(EMA)是一个非常有用的工具。它可以让我们计算最近数据的平均值。但是,不同于简单移动平均(SMA),我们不需要保留一个样本窗口——我们可以在线更新EMA,一次一个样本。
错误的方法
假设我们有如下的输入数据:
x = [1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0]
最直接的方法是让EMA的初始值为某个任意常数(通常为0)。这意味着EMA的初始值会偏向这个常数,需要足够多的样本来“预热”后才能得到准确的结果。代码如下:
R:
make.ema0 <- function (r) {
s <- 0
list(
update=function (x) {
s <<- r * s + (1 - r) * x
}
)
}
m0 <- make.ema0(0.7)
for (i in 1:length(x)) {
y0[i] <- m0$update(x[i])
}
py:
import matplotlib.pyplot as plt
def make_ema0(r):
s = 0
def update(x):
nonlocal s
s = r * s + (1 - r) * x
return s
return update
x = [1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0]
ema0 = make_ema0(0.7)
y0 = [ema0(xi) for xi in x]
# 绘制初始值为0的EMA曲线图
plt.figure(figsize=(10, 6))
plt.plot(x, label='Original Data', marker='o')
plt.plot(y0, label='EMA with initial value 0', marker='x')
plt.title('EMA with initial value 0')
plt.xlabel('Time')
plt.ylabel('Value')
plt.legend()
plt.grid(True)
plt.show()
另一种常见的方法是将第一个样本作为EMA的初始值。代码如下:
R:
make.ema1 <- function (r) {
started <- FALSE
s <- NULL
list(
update=function (x) {
if (!started) {
started <<- TRUE
s <<- x
} else {
s <<- r * s + (1 - r) * x
}
}
)
}
m1 <- make.ema1(0.7)
for (i in 1:length(x)) {
y1[i] <- m1$update(x[i])
}
py:
def make_ema1(r):
started = False
s = None
def update(x):
nonlocal started, s
if not started:
started = True
s = x
else:
s = r * s + (1 - r) * x
return s
return update
ema1 = make_ema1(0.7)
y1 = [ema1(xi) for xi in x]
# 绘制初始值为第一个样本的EMA曲线图
plt.figure(figsize=(10, 6))
plt.plot(x, label='Original Data', marker='o')
plt.plot(y1, label='EMA with first sample as initial value', marker='x')
plt.title('EMA with first sample as initial value')
plt.xlabel('Time')
plt.ylabel('Value')
plt.legend()
plt.grid(True)
plt.show()
以上两种方法都有一个共同的错误。在第一种方法中,我们假设在第一个实际样本之前看到了一串无限的0。在第二种方法中,我们假设在开始前看到了一串无限的第一个样本。无论哪种方法,都需要足够的时间让EMA“预热”,即需要足够的时间使得假想的无限序列的影响变得可以忽略不计。
正确的方法
正确的方法是积极考虑进入EMA的数据量,以及在我们的样本到达之前EMA值中的虚拟数据量。
假设 r=0.5,我们的前三个样本依次为:3、4、5。计算指数加权和,并除以权重和,得到期望的EMA:
如果使用初始值为0的方法,得到:
s0=3.875(5×0.5+4×0.25+3×0.125+0×0.125)
如果使用第一个样本作为初始值的方法,得到:
s1=4.25(5×0.5+4×0.25+3×0.25)
正确的方法是考虑到我们已经看到了一串无限的数据,初始化我们的EMA,并尝试消除其影响。
假设 α=0,那么分子自动处理:
剩下的就是适当地缩放结果以考虑分母中的额外权重。下面的代码执行了这个修正并给出了正确的EMA:
R:
make.ema2 <- function (r) {
s <- 0
extra <- 1
list(
update=function (x) {
s <<- r * s + (1 - r) * x
extra <<- r * extra
s / (1 - extra)
}
)
}
py:
def make_ema2(r):
s = 0
extra = 1
def update(x):
nonlocal s, extra
s = r * s + (1 - r) * x
extra = r * extra
return s / (1 - extra)
return update
ema2 = make_ema2(0.7)
y2 = [ema2(xi) for xi in x]
# 绘制正确初始化的EMA曲线图
plt.figure(figsize=(10, 6))
plt.plot(x, label='Original Data', marker='o')
plt.plot(y2, label='Corrected EMA', marker='x')
plt.title('Corrected EMA')
plt.xlabel('Time')
plt.ylabel('Value')
plt.legend()
plt.grid(True)
plt.show()
这个修正可能对你的数据平均值不太重要,但同样的技术可以用来获得有意义和有用的指数移动方差等相关值。通过这些,可以构建一个完全在线的回归来拟合数据,并具有有意义的置信区间和预测区间。