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的计算本身很简单,难的是如何在实际策略中正确使用它。