指数移动平均 EMA(Exponential Moving Average)算法详解

指数移动平均 EMA(Exponential Moving Average)算法详解

  • 一、算法详解
    • 1、引言
    • [2、 什么是移动平均](#2、 什么是移动平均)
      • [2.1 、简单移动平均(SMA)](#2.1 、简单移动平均(SMA))
      • [2.2、 指数移动平均(EMA)](#2.2、 指数移动平均(EMA))
    • [3、 EMA 的数学定义](#3、 EMA 的数学定义)
      • [3.1、 平滑因子 α 与窗口 N 的关系](#3.1、 平滑因子 α 与窗口 N 的关系)
    • [4、 递推公式的展开推导](#4、 递推公式的展开推导)
    • [5、 EMA 与 SMA 的对比](#5、 EMA 与 SMA 的对比)
      • [5.1 适用场景建议](#5.1 适用场景建议)
    • 6、典型应用场景
      • [6.1 、量化交易中的技术指标](#6.1 、量化交易中的技术指标)
      • [6.2 、深度学习优化器中的参数平滑](#6.2 、深度学习优化器中的参数平滑)
      • [6.3、 系统监控与异常检测](#6.3、 系统监控与异常检测)
    • [7. 总结](#7. 总结)
  • 二、代码示例

一、算法详解

1、引言

在时间序列分析、技术指标计算和深度学习优化器中,指数移动平均(Exponential Moving Average,简称 EMA) 是一种极其常用的统计方法。与简单移动平均(SMA)不同,EMA 对近期数据赋予更高的权重,因此能更快地反映数据的最新变化趋势。

本文将从数学原理出发,逐步推导 EMA 的递推公式,并通过代码示例展示其实现方式,最后介绍 EMA 在量化交易和深度学习中的典型应用场景。

2、 什么是移动平均

移动平均(Moving Average,MA)是一种用于平滑时间序列数据的技术,其核心思想是取一段时间窗口内数据的平均值,以消除短期波动、凸显长期趋势。

2.1 、简单移动平均(SMA)

简单移动平均对窗口内的所有数据点赋予相同权重,其计算公式为:

复制代码
SMA_t = (x_t + x_{t-1} + ... + x_{t-N+1}) / N

其中 N 为窗口大小。SMA 的缺点是:窗口外的数据被完全丢弃,窗口内的数据权重相同,导致对最新变化的响应不够灵敏。

2.2、 指数移动平均(EMA)

EMA 则采用指数衰减的权重分配方式,越近的数据权重越大,越远的数据权重越小,且权重永远不会归零。这使得 EMA 既能平滑噪声,又能更快地跟踪趋势变化。

3、 EMA 的数学定义

EMA 在时刻 t 的取值 EMA_t 定义如下:

复制代码
EMA_t = α * x_t + (1 - α) * EMA_{t-1}

其中:

  • x_t 为当前时刻的观测值;
  • EMA_{t-1} 为上一时刻的 EMA 值;
  • α 为平滑因子(smoothing factor),取值范围为 0 < α ≤ 1。

3.1、 平滑因子 α 与窗口 N 的关系

在实际应用中,α 通常由窗口长度 N 换算而来:

复制代码
α = 2 / (N + 1)

例如:

  • 当 N = 5 时,α = 2 / 6 ≈ 0.333;
  • 当 N = 10 时,α = 2 / 11 ≈ 0.182;
  • 当 N = 20 时,α = 2 / 21 ≈ 0.095。

窗口 N 越大,α 越小,平滑效果越强,但对新数据的响应越慢;反之,N 越小,α 越大,对最新数据越敏感。

4、 递推公式的展开推导

将 EMA 的递推公式逐层展开,可以更直观地理解其权重分配机制。

假设从时刻 0 开始,初始值 EMA_0 = x_0,则:

复制代码
EMA_1 = α * x_1 + (1 - α) * EMA_0
      = α * x_1 + (1 - α) * x_0

EMA_2 = α * x_2 + (1 - α) * EMA_1
      = α * x_2 + (1 - α) * [α * x_1 + (1 - α) * x_0]
      = α * x_2 + α(1 - α) * x_1 + (1 - α)² * x_0

以此类推,可以得到通项公式:

复制代码
EMA_t = α * x_t + α(1 - α) * x_{t-1} + α(1 - α)² * x_{t-2} + ... + (1 - α)^t * x_0

可以看出:

  • 当前时刻数据 x_t 的权重为 α;
  • 前一个时刻数据 x_{t-1} 的权重为 α(1 - α);
  • 前两个时刻数据 x_{t-2} 的权重为 α(1 - α)²;
  • 权重按 (1 - α) 的幂次指数衰减,这正是「指数移动平均」名称的由来。

所有权重之和收敛于 1:

复制代码
α + α(1 - α) + α(1 - α)² + ... = 1

5、 EMA 与 SMA 的对比

特性 SMA EMA
权重分配 窗口内等权 近期权重高,指数衰减
响应速度 较慢 较快
对异常值敏感度 较低 较高
计算复杂度 O(N) O(1) 递推
数据存储 需保留窗口内全部数据 只需保留上一时刻 EMA 值

5.1 适用场景建议

  • SMA:适合数据噪声较大、希望获得更平滑曲线的场景;
  • EMA:适合需要快速捕捉趋势变化、对实时性要求较高的场景。

6、典型应用场景

6.1 、量化交易中的技术指标

EMA 是技术分析中最常用的指标之一,常被用于:

  • 趋势判断:当短期 EMA(如 EMA12)上穿长期 EMA(如 EMA26)时,形成「金叉」,视为买入信号;反之形成「死叉」,视为卖出信号。
  • 动态支撑与压力位:价格在 EMA 上方运行时,EMA 常被视为支撑位;价格在 EMA 下方运行时,EMA 常被视为压力位。

6.2 、深度学习优化器中的参数平滑

在深度学习训练中,EMA 被广泛用于模型参数的滑动平均。具体做法是:在训练过程中,维护一份模型参数的 EMA 副本,训练结束后用 EMA 副本替代原始参数进行推理。

python 复制代码
# 伪代码示例:训练过程中维护参数 EMA
ema_decay = 0.999
ema_params = {}

for step, batch in enumerate(train_loader):
    # 常规梯度更新
    optimizer.zero_grad()
    loss = compute_loss(batch)
    loss.backward()
    optimizer.step()

    # 更新 EMA 参数
    for name, param in model.named_parameters():
        if name not in ema_params:
            ema_params[name] = param.data.clone()
        else:
            ema_params[name] = ema_decay * ema_params[name] + (1 - ema_decay) * param.data

这样做的好处是:

  • 对参数更新过程中的噪声进行平滑,使模型更加稳定;
  • 在训练后期,EMA 参数往往能取得比原始参数更好的泛化性能。

6.3、 系统监控与异常检测

在服务器监控场景中,EMA 常用于平滑 CPU 使用率、内存占用等指标,帮助运维人员识别长期趋势,同时过滤瞬时抖动造成的误报。

7. 总结

本文详细介绍了指数移动平均(EMA)的数学原理、递推公式推导、代码实现及典型应用场景。核心要点总结如下:

  • EMA 通过指数衰减的权重分配,对近期数据赋予更高权重;
  • 平滑因子 α = 2 / (N + 1) 建立了与窗口大小的换算关系;
  • EMA 只需保存上一时刻的值即可递推计算,空间复杂度为 O(1);
  • 相比 SMA,EMA 响应更快,更适合实时趋势跟踪场景;
  • EMA 在量化交易、深度学习参数平滑、系统监控等领域有广泛应用。

理解 EMA 的数学本质,有助于在实际问题中灵活选择平滑策略,从而更准确地把握数据的变化规律。

二、代码示例

cpp 复制代码
#include <iostream>
#include <Windows.h>

#include <iostream>
#include <cassert>

class EMA
{
public:
    /// @brief 构造:标准EMA,alpha = 2/(N+1),K线算法,前N点SMA初始化
    explicit EMA(int period)
        : alpha_(0.0f),
        period_(period),
        count_(0),
        sum_(0.0f),
        ema_prev_(0.0f),
        init_done_(false)
    {
        assert(period > 0);
        alpha_ = 2.0f / (period + 1.0f);
    }

    /// @brief 构造:一阶指数平滑,自定义alpha,传感器滤波
    explicit EMA(float custom_alpha)
        : alpha_(custom_alpha),
        period_(0),
        count_(0),
        sum_(0.0f),
        ema_prev_(0.0f),
        init_done_(true)
    {
        assert(custom_alpha > 0.0f && custom_alpha <= 1.0f);
    }

    // 送入新采样值,返回当前EMA结果
    float update(float val)
    {
        if (!init_done_)
        {
            sum_ += val;
            count_++;
            if (count_ >= period_)
            {
                ema_prev_ = sum_ / period_;
                init_done_ = true;
            }
            return ema_prev_;
        }
        // 一阶平滑:首次进来直接赋值
        if (count_ == 0)
        {
            ema_prev_ = val;
            count_++;
            return ema_prev_;
        }
        ema_prev_ = val * alpha_ + ema_prev_ * (1.0f - alpha_);
        count_++;
        return ema_prev_;
    }

    // 重置滤波器状态
    void reset()
    {
        count_ = 0;
        sum_ = 0.0f;
        ema_prev_ = 0.0f;
        init_done_ = false;
    }

    // 获取当前EMA值
    float get() const
    {
        return ema_prev_;
    }

private:
    float alpha_;
    int   period_;
    int   count_;
    float sum_;
    float ema_prev_;
    bool  init_done_;
};


int main()
{
    SetConsoleOutputCP(CP_UTF8);
    // 方式1:标准EMA5,alpha=2/(5+1)
    EMA ema5(5);
    float prices[] = { 10, 12, 11, 13, 14, 16, 15, 17,10,2,2,3,5,4,8,63,45,1000,1,2,2000 };
    int len = sizeof(prices) / sizeof(prices[0]);

    std::cout << "===== EMA5 (周期构造) =====\n";
    for (int i = 0; i < len; i++)
    {
        float v = ema5.update(prices[i]);
        std::cout << "price=" << prices[i] << ", ema=" << v << "\n";
    }

    // 方式2:传感器一阶平滑,直接指定alpha=0.2
    EMA filter(0.2f);
    std::cout << "\n===== 一阶平滑 alpha=0.2 =====\n";
    for (int i = 0; i < len; i++)
    {
        float v = filter.update(prices[i]);
        std::cout << "price=" << prices[i] << ", filter=" << v << "\n";
    }

    return 0;
}

运行结果:

cpp 复制代码
===== EMA5 (周期构造) =====
price=10, ema=0
price=12, ema=0
price=11, ema=0
price=13, ema=0
price=14, ema=12
price=16, ema=13.3333
price=15, ema=13.8889
price=17, ema=14.9259
price=10, ema=13.2839
price=2, ema=9.52263
price=2, ema=7.01509
price=3, ema=5.67672
price=5, ema=5.45115
price=4, ema=4.96743
price=8, ema=5.97829
price=63, ema=24.9855
price=45, ema=31.657
price=1000, ema=354.438
price=1, ema=236.625
price=2, ema=158.417
price=2000, ema=772.278

===== 一阶平滑 alpha=0.2 =====
price=10, filter=10
price=12, filter=10.4
price=11, filter=10.52
price=13, filter=11.016
price=14, filter=11.6128
price=16, filter=12.4902
price=15, filter=12.9922
price=17, filter=13.7938
price=10, filter=13.035
price=2, filter=10.828
price=2, filter=9.0624
price=3, filter=7.84992
price=5, filter=7.27994
price=4, filter=6.62395
price=8, filter=6.89916
price=63, filter=18.1193
price=45, filter=23.4955
price=1000, filter=218.796
price=1, filter=175.237
price=2, filter=140.59
price=2000, filter=512.472

d:\user\01417804\桌面\CppProject\cppTest\x64\Debug\cppTest.exe (进程 3732)已退出,代码为 0 (0x0)。
要在调试停止时自动关闭控制台,请启用"工具"->"选项"->"调试"->"调试停止时自动关闭控制台"。
按任意键关闭此窗口. . .
相关推荐
Uncertainty!!2 年前
Exponential Moving Average (EMA) in Stable Diffusion
stable diffusion·ema