计算指数移动平均线(EMA)是否需要全部历史数据?PHP实现EMA的技术问询
首先先明确什么是EMA:
指数移动平均线和简单移动平均线(SMA)最大的区别在于权重分配——SMA给每一期数据相同的权重,而EMA会给近期数据更高的权重,同时对旧数据的权重进行指数级衰减。它的核心计算公式是:EMA_t = α * Price_t + (1-α) * EMA_{t-1}
其中α是平滑系数,通常取2/(周期数+1),比如10周期的话α≈0.1818。而第一个EMA值(也就是初始值),行业里通常用前N期(N为周期数)的SMA来替代。
核心问题:计算EMA是否需要处理全部历史数据?
理论层面
严格来说,是的。因为EMA是递归计算的,每一个EMA值都直接依赖前一期的EMA结果,理论上要得到第t期的准确EMA,必须从最开始的初始SMA一步步计算到t期。
实际工程层面(大数据量场景)
但当数据量极大(比如500万条)时,完全遍历所有数据确实会有性能开销。这时候可以利用EMA的指数衰减特性:旧数据的权重会以(1-α)^k的速度快速降低,经过几十期之后,更早的数据对当前EMA的影响已经微乎其微。
举个例子,10周期EMA的(1-α)=0.8182,经过20期后,最早数据的权重已经是0.8182^20≈0.01,也就是1%不到;经过30期后更是接近0.1%。这种情况下,我们可以不用遍历全部数据,而是取最近的N*K期数据(比如N=10的话取50期)来计算初始SMA,然后从那一期开始递归计算到目标点,误差已经小到可以忽略不计。
关于“用$n-20到$n-11区间SMA初始化,再算最近10个值”的可行性
这种方式属于近似计算,会和严格计算的EMA存在一定误差:
- 它跳过了$n-20之前所有数据的影响(虽然影响很小)
- 没有经过中间连续的递归步骤,相当于直接用区间SMA替代了本该由前面所有EMA累积得到的中间值
如果你的场景对精度要求不高(比如只是做趋势的粗略判断),这种方式是可以接受的;但如果是金融交易等对精度要求高的场景,更建议用更长的初始化区间(比如N*5)来降低误差,而不是仅用20期。
对你编写的EMA函数的分析
先拆解下你的函数逻辑:
function EMA($v,$lastvalue,$num=200) { if($num>(count($v)-1)) return FALSE; $MA=0; $initMA=count($v)-2*$num; if($initMA<0) $initMA=0; // 计算初始化SMA:取倒数2*num到倒数num-1区间的平均值 for($i=0,$j=$initMA;$i<$num;$i++,$j++) $MA+=$v[$j]; $EMA=$MA/$num; $a = 2/ ($num + 1); // 从倒数num期开始,递归计算到数组倒数第2个元素 for($i=$j;$i<count($v)-1;$i++) $EMA = $a * $v[$i+1]+ (1-$a) * $EMA; // 最后用传入的lastvalue计算最新的EMA $EMA = $a * $lastvalue+ (1-$a) * $EMA; return $EMA; }
这个函数是近似计算EMA的实现,核心思路是用最近2*num期的后半段num期计算初始SMA,然后计算到当前数组的末尾,再加上最新的lastvalue。
可优化的点:
- 如果要追求更准确的结果,可以把初始化区间从
2*num调整为5*num甚至10*num,进一步降低旧数据忽略带来的误差; - 如果需要严格计算全部历史数据的EMA,应该把初始化SMA改为取数组的前num个元素(假设数组是按时间从早到晚排列),然后从第num个元素开始递归计算到最后一个,再处理lastvalue;
- 可以增加对数组排序的校验(确保是时间正序),避免因数据顺序错误导致计算结果完全错误。
内容的提问来源于stack exchange,提问作者Hamboy75

