4、移动平均线:SMA、EMA、WMA的原理与实现,信号生成逻辑

移动平均线,说白了就是给价格序列「去噪」。高频数据里噪声特别多,一跳一抖的,你直接看原始价格根本没法做判断。我刚开始做高频策略的时候,就吃过这个亏——裸K线进场,结果被毛刺反复打脸。后来老老实实把移动平均加上,信号质量明显上来了。

今天咱们就聊三种最常见的移动平均:SMA(简单移动平均)EMA(指数移动平均)WMA(加权移动平均)。原理不难,但实现细节和适用场景,我踩过不少坑,一并分享给你。

4.1 三种移动平均的核心原理

先看一张图,把三者的关系理清楚:

移动平均线家族 SMA(简单移动平均) EMA(指数移动平均) WMA(加权移动平均) 等权重 计算简单,延迟较大 适合趋势确认 指数衰减权重 对最新价格敏感 适合高频快速响应 线性递增权重 可自定义权重分布 灵活性最高 核心区别:对历史数据的「遗忘速度」不同 SMA最慢 → EMA中等 → WMA可调(取决于权重设计)

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])
我的经验:高频数据里我一般不用 SMA。为什么?因为它对每个价格一视同仁。你想想看,5 秒前的价格和 0.1 秒前的价格,对当前行情的参考价值能一样吗?SMA 的滞后性太明显了。我只有在做日线级别的趋势跟踪时才会用 SMA。

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
关键点:EMA 的初始值怎么定?我见过有人直接取第一个价格,也有人取前 N 个价格的 SMA。我个人习惯用 SMA 做种子值,这样前几期的 EMA 不会偏差太大。高频数据里,这个初始值的影响大概在 2-3 个周期后就消失了,不用太纠结。

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
避坑指南:我曾经在 WMA 上栽过跟头。当时为了追求「更灵敏」,把权重设计成指数增长——最新价格的权重是前一个的 10 倍。结果呢?信号变得极其不稳定,频繁开平仓,手续费都亏麻了。后来我悟了:权重不能太极端,否则 WMA 就退化成「只看最新价格」了,失去了平均的意义。

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