4、移动平均线:SMA、EMA、WMA的原理与实现,信号生成逻辑
移动平均线,说白了就是给价格序列「去噪」。高频数据里噪声特别多,一跳一抖的,你直接看原始价格根本没法做判断。我刚开始做高频策略的时候,就吃过这个亏——裸K线进场,结果被毛刺反复打脸。后来老老实实把移动平均加上,信号质量明显上来了。
今天咱们就聊三种最常见的移动平均:SMA(简单移动平均)、EMA(指数移动平均)和WMA(加权移动平均)。原理不难,但实现细节和适用场景,我踩过不少坑,一并分享给你。
4.1 三种移动平均的核心原理
先看一张图,把三者的关系理清楚:
4.2 SMA:简单移动平均
SMA 就是取最近 N 个价格的算术平均。公式很简单:
SMA(t) = (P(t) + P(t-1) + ... + P(t-N+1)) / N
其中 P(t) 是当前价格,N 是窗口长度。
实现起来也直接:
def sma(prices, window):
"""
计算简单移动平均
prices: list or array, 价格序列
window: int, 窗口大小
"""
import numpy as np
arr = np.array(prices)
result = np.convolve(arr, np.ones(window)/window, mode='valid')
# 前面补 NaN 保持长度一致
return np.concatenate([np.full(window-1, np.nan), result])
4.3 EMA:指数移动平均
EMA 就聪明多了。它给近期的价格更高的权重,权重按指数衰减。公式:
EMA(t) = α * P(t) + (1 - α) * EMA(t-1)
其中 α = 2 / (N + 1),N 是窗口长度。
注意,EMA 是递归定义的——当前值依赖上一个 EMA 值。这意味着你没法并行计算,必须顺序处理。不过在高频场景下,这反而是个优点:计算量小,实时更新快。
def ema(prices, window):
"""
计算指数移动平均
"""
import numpy as np
alpha = 2 / (window + 1)
result = np.full_like(prices, np.nan, dtype=float)
# 初始值用 SMA
result[window-1] = np.mean(prices[:window])
for i in range(window, len(prices)):
result[i] = alpha * prices[i] + (1 - alpha) * result[i-1]
return result
4.4 WMA:加权移动平均
WMA 给了你最大的自由度。你可以自己定义每个位置的权重。最常见的做法是线性加权:越近的价格权重越大。
WMA(t) = (N*P(t) + (N-1)*P(t-1) + ... + 1*P(t-N+1)) / (N + (N-1) + ... + 1)
分母是权重之和,也就是 N*(N+1)/2。
def wma(prices, window):
"""
计算线性加权移动平均
"""
import numpy as np
weights = np.arange(window, 0, -1) # [N, N-1, ..., 1]
weights_sum = weights.sum()
result = np.full_like(prices, np.nan, dtype=float)
for i in range(window-1, len(prices)):
result[i] = np.dot(prices[i-window+1:i+1], weights) / weights_sum
return result
4.5 信号生成逻辑
有了移动平均线,怎么生成交易信号?最经典的做法就是看「交叉」。
4.5.1 单均线交叉
价格上穿均线 → 买入信号
价格下穿均线 → 卖出信号
这个逻辑简单粗暴,但高频数据里容易出假信号。我一般会加一个「确认阈值」:价格必须超过均线一定比例(比如 0.1%)才算有效突破。
def single_ma_signal(prices, ma, threshold=0.001):
"""
单均线交叉信号
threshold: 确认阈值(百分比)
"""
signals = np.zeros_like(prices)
for i in range(1, len(prices)):
if np.isnan(ma[i]) or np.isnan(ma[i-1]):
continue
# 上穿
if prices[i] > ma[i] * (1 + threshold) and prices[i-1] <= ma[i-1]:
signals[i] = 1 # 买入
# 下穿
elif prices[i] < ma[i] * (1 - threshold) and prices[i-1] >= ma[i-1]:
signals[i] = -1 # 卖出
return signals
4.5.2 双均线交叉
用两条不同周期的均线:快线上穿慢线 → 买入;快线下穿慢线 → 卖出。
高频数据里,我常用 EMA(5) 和 EMA(20) 的组合。5 周期快线负责捕捉短期动量,20 周期慢线负责过滤噪声。
def dual_ma_signal(fast_ma, slow_ma):
"""
双均线交叉信号
"""
signals = np.zeros_like(fast_ma)
for i in range(1, len(fast_ma)):
if np.isnan(fast_ma[i]) or np.isnan(slow_ma[i]):
continue
# 金叉
if fast_ma[i] > slow_ma[i] and fast_ma[i-1] <= slow_ma[i-1]:
signals[i] = 1
# 死叉
elif fast_ma[i] < slow_ma[i] and fast_ma[i-1] >= slow_ma[i-1]:
signals[i] = -1
return signals
4.6 三种均线的对比
| 特性 | SMA | EMA | WMA |
|---|---|---|---|
| 计算复杂度 | O(N) | O(1) 递归 | O(N) |
| 对最新价格的敏感度 | 低 | 中 | 高(可调) |
| 滞后性 | 大 | 中 | 小(可调) |
| 高频适用性 | 差 | 好 | 中等 |
| 参数可解释性 | 强 | 中 | 弱(权重需自定义) |
你看这个表就明白了。高频场景下,EMA 是性价比最高的选择。计算量小、响应快、参数少。WMA 虽然灵活,但权重设计是个坑,搞不好就过拟合了。
嗯,移动平均线这部分就聊到这儿。记住一句话:均线不是预测工具,而是确认工具。它告诉你趋势在哪里,但不告诉你趋势会怎么变。别指望靠均线抓顶抄底,那是另一套逻辑了。
公众号:蓝海资料掘金营,微信deep3321