指数移动平均 EMA(Exponential Moving Average)算法详解
- 一、算法详解
- 1、引言
- 2、 什么是移动平均
- 2.1 、简单移动平均(SMA)
- 2.2、 指数移动平均(EMA)
- 3、 EMA 的数学定义
- 3.1、 平滑因子 α 与窗口 N 的关系
- 4、 递推公式的展开推导
- 5、 EMA 与 SMA 的对比
- 5.1 适用场景建议
- 6、典型应用场景
- 6.1 、量化交易中的技术指标
- 6.2 、深度学习优化器中的参数平滑
- 6.3、 系统监控与异常检测
- 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 - α)² + ... = 15、 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 副本替代原始参数进行推理。
# 伪代码示例:训练过程中维护参数 EMAema_decay=0.999ema_params={}forstep,batchinenumerate(train_loader):# 常规梯度更新optimizer.zero_grad()loss=compute_loss(batch)loss.backward()optimizer.step()# 更新 EMA 参数forname,paraminmodel.named_parameters():ifnamenotinema_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 的数学本质,有助于在实际问题中灵活选择平滑策略,从而更准确地把握数据的变化规律。
二、代码示例
#include<iostream>#include<Windows.h>#include<iostream>#include<cassert>classEMA{public:/// @brief 构造:标准EMA,alpha = 2/(N+1),K线算法,前N点SMA初始化explicitEMA(intperiod):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,传感器滤波explicitEMA(floatcustom_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结果floatupdate(floatval){if(!init_done_){sum_+=val;count_++;if(count_>=period_){ema_prev_=sum_/period_;init_done_=true;}returnema_prev_;}// 一阶平滑:首次进来直接赋值if(count_==0){ema_prev_=val;count_++;returnema_prev_;}ema_prev_=val*alpha_+ema_prev_*(1.0f-alpha_);count_++;returnema_prev_;}// 重置滤波器状态voidreset(){count_=0;sum_=0.0f;ema_prev_=0.0f;init_done_=false;}// 获取当前EMA值floatget()const{returnema_prev_;}private:floatalpha_;intperiod_;intcount_;floatsum_;floatema_prev_;boolinit_done_;};intmain(){SetConsoleOutputCP(CP_UTF8);// 方式1:标准EMA5,alpha=2/(5+1)EMAema5(5);floatprices[]={10,12,11,13,14,16,15,17,10,2,2,3,5,4,8,63,45,1000,1,2,2000};intlen=sizeof(prices)/sizeof(prices[0]);std::cout<<"===== EMA5 (周期构造) =====\n";for(inti=0;i<len;i++){floatv=ema5.update(prices[i]);std::cout<<"price="<<prices[i]<<", ema="<<v<<"\n";}// 方式2:传感器一阶平滑,直接指定alpha=0.2EMAfilter(0.2f);std::cout<<"\n===== 一阶平滑 alpha=0.2 =====\n";for(inti=0;i<len;i++){floatv=filter.update(prices[i]);std::cout<<"price="<<prices[i]<<", filter="<<v<<"\n";}return0;}运行结果:
=====EMA5(周期构造)=====price=10,ema=0price=12,ema=0price=11,ema=0price=13,ema=0price=14,ema=12price=16,ema=13.3333price=15,ema=13.8889price=17,ema=14.9259price=10,ema=13.2839price=2,ema=9.52263price=2,ema=7.01509price=3,ema=5.67672price=5,ema=5.45115price=4,ema=4.96743price=8,ema=5.97829price=63,ema=24.9855price=45,ema=31.657price=1000,ema=354.438price=1,ema=236.625price=2,ema=158.417price=2000,ema=772.278=====一阶平滑 alpha=0.2=====price=10,filter=10price=12,filter=10.4price=11,filter=10.52price=13,filter=11.016price=14,filter=11.6128price=16,filter=12.4902price=15,filter=12.9922price=17,filter=13.7938price=10,filter=13.035price=2,filter=10.828price=2,filter=9.0624price=3,filter=7.84992price=5,filter=7.27994price=4,filter=6.62395price=8,filter=6.89916price=63,filter=18.1193price=45,filter=23.4955price=1000,filter=218.796price=1,filter=175.237price=2,filter=140.59price=2000,filter=512.472d:\user\01417804\桌面\CppProject\cppTest\x64\Debug\cppTest.exe(进程3732)已退出,代码为0(0x0)。 要在调试停止时自动关闭控制台,请启用“工具”->“选项”->“调试”->“调试停止时自动关闭控制台”。 按任意键关闭此窗口...