量化交易:技术指标MACD的原理与实现

MACD(Moving Average Convergence Divergence)是最常用的趋势跟踪指标之一。本文推导其计算公式,并用Python从零实现。

1. MACD的构成

MACD由三部分组成:

  • DIF(快线):短期EMA - 长期EMA,通常为EMA(12) - EMA(26)
  • DEA(慢线):DIF的EMA,通常周期为9
  • MACD柱状图:(DIF - DEA) * 2

交易信号:

  • DIF上穿DEA(金叉)→ 做多信号
  • DIF下穿DEA(死叉)→ 做空信号
  • 柱状图由负转正 → 趋势转强

2. EMA的递推计算

EMA(指数移动平均)给近期数据更高的权重:

EMA_today = price_today * k + EMA_yesterday * (1 - k)

其中 k = 2 / (N + 1),N是周期。

对比SMA(简单移动平均),EMA对价格变化的响应更快,更适合捕捉趋势转折。

第一个EMA值通常用前N个数据的SMA作为初始值。

3. Python实现

import numpy as np

def ema(prices: np.ndarray, period: int) -> np.ndarray:
    '''计算EMA,前period个值用SMA初始化'''
    result = np.full_like(prices, np.nan, dtype=float)
    if len(prices) < period:
        return result

    # 用SMA作为第一个EMA值
    result[period - 1] = np.mean(prices[:period])
    k = 2.0 / (period + 1)

    for i in range(period, len(prices)):
        result[i] = prices[i] * k + result[i - 1] * (1 - k)

    return result


def macd(prices: np.ndarray,
         fast: int = 12,
         slow: int = 26,
         signal: int = 9):
    '''
    计算MACD指标
    返回: dif, dea, histogram
    '''
    ema_fast = ema(prices, fast)
    ema_slow = ema(prices, slow)
    dif = ema_fast - ema_slow

    # DEA = DIF的EMA
    # 从DIF有值的位置开始计算
    valid_start = slow - 1
    dif_valid = dif[valid_start:]
    dea_valid = ema(dif_valid, signal)

    dea = np.full_like(prices, np.nan, dtype=float)
    dea[valid_start:] = dea_valid

    histogram = (dif - dea) * 2

    return dif, dea, histogram

测试一下:

# 模拟30天价格数据
np.random.seed(42)
prices = 100 + np.cumsum(np.random.randn(60) * 0.5)

dif, dea, hist = macd(prices)
print(f"最新DIF: {dif[-1]:.4f}")
print(f"最新DEA: {dea[-1]:.4f}")
print(f"最新MACD柱: {hist[-1]:.4f}")

4. matplotlib可视化

import matplotlib.pyplot as plt

fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(12, 8),
                                 gridspec_kw={'height_ratios': [2, 1]})

# 上图:价格走势
ax1.plot(prices, color='black', linewidth=1)
ax1.set_title('Price')
ax1.grid(True, alpha=0.3)

# 下图:MACD
x = np.arange(len(prices))
ax2.plot(x, dif, label='DIF', color='blue', linewidth=1)
ax2.plot(x, dea, label='DEA', color='orange', linewidth=1)

# 柱状图:正值红色,负值绿色
colors = ['red' if v >= 0 else 'green' for v in hist]
ax2.bar(x, hist, color=colors, alpha=0.6, width=0.8)
ax2.axhline(y=0, color='gray', linewidth=0.5)
ax2.legend()
ax2.set_title('MACD')
ax2.grid(True, alpha=0.3)

plt.tight_layout()
plt.savefig('macd_chart.png', dpi=100)
plt.show()

5. 金叉/死叉信号检测

def find_crossovers(dif, dea):
    '''找出金叉和死叉的位置'''
    signals = []
    for i in range(1, len(dif)):
        if np.isnan(dif[i]) or np.isnan(dea[i]):
            continue
        if np.isnan(dif[i-1]) or np.isnan(dea[i-1]):
            continue

        prev_diff = dif[i-1] - dea[i-1]
        curr_diff = dif[i] - dea[i]

        if prev_diff <= 0 and curr_diff > 0:
            signals.append((i, 'golden_cross'))
        elif prev_diff >= 0 and curr_diff < 0:
            signals.append((i, 'death_cross'))

    return signals

signals = find_crossovers(dif, dea)
for idx, sig_type in signals:
    print(f"Day {idx}: {sig_type} (price={prices[idx]:.2f})")

注意事项

  • MACD是滞后指标,在震荡行情中容易产生大量假信号
  • 实盘中通常与其他指标(RSI、布林带)配合使用
  • 参数(12, 26, 9)不是固定的,不同品种和周期可以优化
  • 回测时注意避免前视偏差(look-ahead bias)

MACD的计算本身很简单,难的是如何在实际策略中正确使用它。