4. MACD金叉死叉策略
任务目标
实现一个基于MACD指标金叉/死叉信号的量化交易策略,并在历史数据上进行回测,输出策略的累计收益、年化收益、最大回撤和夏普比率等关键绩效指标。
环境依赖
本案例使用 pandas、numpy 和 matplotlib。数据通过 yfinance 获取,若网络受限可替换为本地CSV数据文件。
策略逻辑
- 计算MACD指标:
DIF = EMA(close, 12) - EMA(close, 26),DEA = EMA(DIF, 9),MACD柱 = 2 * (DIF - DEA) - 金叉买入:DIF 上穿 DEA(前一日 DIF ≤ DEA,当日 DIF > DEA)
- 死叉卖出:DIF 下穿 DEA(前一日 DIF ≥ DEA,当日 DIF < DEA)
- 持仓状态:初始空仓,金叉次日开盘买入,死叉次日开盘卖出,全仓进出,不考虑手续费和滑点
完整实现代码
import pandas as pd
import numpy as np
import yfinance as yf
# ---------- 1. 获取数据 ----------
ticker = "AAPL"
start, end = "2020-01-01", "2024-12-31"
df = yf.download(ticker, start=start, end=end, progress=False)
df.columns = [c[0] if isinstance(c, tuple) else c for c in df.columns]
close = df["Close"].copy()
# ---------- 2. 计算MACD指标 ----------
ema12 = close.ewm(span=12, adjust=False).mean()
ema26 = close.ewm(span=26, adjust=False).mean()
dif = ema12 - ema26
dea = dif.ewm(span=9, adjust=False).mean()
macd_hist = 2 * (dif - dea)
# ---------- 3. 生成交易信号 ----------
signal = pd.Series(0, index=close.index) # 0=空仓, 1=持仓
position = 0
for i in range(1, len(close)):
if position == 0 and dif.iloc[i-1] <= dea.iloc[i-1] and dif.iloc[i] > dea.iloc[i]:
position = 1
elif position == 1 and dif.iloc[i-1] >= dea.iloc[i-1] and dif.iloc[i] < dea.iloc[i]:
position = 0
signal.iloc[i] = position
# ---------- 4. 回测:次日开盘执行 ----------
open_price = df["Open"]
trade_ret = pd.Series(0.0, index=close.index)
for i in range(1, len(close)):
if signal.iloc[i-1] == 1: # 当日持仓,用当日开盘买入价计算收益
trade_ret.iloc[i] = close.iloc[i] / open_price.iloc[i] - 1
# 策略净值(简化:持仓期间按收盘价计算,信号切换日按开盘价)
strategy_nav = (1 + trade_ret).cumprod()
benchmark_nav = (close / close.iloc[0])
# ---------- 5. 绩效指标 ----------
def max_drawdown(nav):
peak = nav.cummax()
return ((nav - peak) / peak).min()
total_return = strategy_nav.iloc[-1] - 1
years = len(close) / 252
annual_return = strategy_nav.iloc[-1] ** (1/years) - 1
daily_ret = strategy_nav.pct_change().dropna()
sharpe = np.sqrt(252) * daily_ret.mean() / daily_ret.std()
mdd = max_drawdown(strategy_nav)
print(f"策略累计收益: {total_return:.2%}")
print(f"策略年化收益: {annual_return:.2%}")
print(f"夏普比率: {sharpe:.2f}")
print(f"最大回撤: {mdd:.2%}")
print(f"基准累计收益: {benchmark_nav.iloc[-1] - 1:.2%}")
# ---------- 6. 可视化 ----------
import matplotlib.pyplot as plt
plt.rcParams["font.sans-serif"] = ["SimHei"]
plt.rcParams["axes.unicode_minus"] = False
fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(12, 8), sharex=True)
ax1.plot(close.index, close, label="收盘价", alpha=0.6)
ax1.plot(close.index, dif, label="DIF", linewidth=0.8)
ax1.plot(close.index, dea, label="DEA", linewidth=0.8)
ax1.legend(loc="upper left")
ax1.set_title(f"{ticker} MACD金叉死叉策略回测")
# 标记买卖点
buy_idx = [close.index[i] for i in range(1, len(close)) if signal.iloc[i-1]==0 and signal.iloc[i]==1]
sell_idx = [close.index[i] for i in range(1, len(close)) if signal.iloc[i-1]==1 and signal.iloc[i]==0]
ax1.scatter(buy_idx, close.loc[buy_idx], marker="^", color="red", s=80, label="金叉买入")
ax1.scatter(sell_idx, close.loc[sell_idx], marker="v", color="green", s=80, label="死叉卖出")
ax1.legend()
ax2.plot(strategy_nav.index, strategy_nav, label="策略净值", color="red")
ax2.plot(benchmark_nav.index, benchmark_nav, label="基准(买入持有)", color="blue", alpha=0.6)
ax2.legend()
ax2.set_ylabel("净值")
plt.tight_layout()
plt.show()
运行方式
- 安装依赖:
pip install pandas numpy matplotlib yfinance - 直接运行上述脚本,程序将自动下载AAPL 2020-2024年日线数据并执行回测。
验证方法与通过条件
- 信号验证:控制台输出的买卖点数量应大致均衡(金叉次数≈死叉次数±1),且买卖点交替出现,不存在连续两次买入或卖出。
- 净值验证:策略净值曲线应始终大于0,且与基准净值曲线形态可比较;若策略累计收益高于基准,说明MACD策略在该标的上有效,反之则无效——两种结果均属正常,关键是回测逻辑正确。
- 指标合理性:最大回撤应在0到-100%之间,夏普比率通常落在-1到3区间;若超出该范围,检查数据是否包含异常值或代码逻辑是否有误。
常见问题
- 信号延迟:本策略在信号产生后的次日开盘执行,避免未来函数。若需当日收盘执行,可将循环中的
signal.iloc[i-1]改为signal.iloc[i],但需注意这在实际交易中不可实现。 - 手续费影响:本案例未计入交易成本。若需更贴近实盘,可在每次买卖时扣除固定比例(如万2.5佣金+千1印花税)。
- 数据源替换:若
yfinance无法访问,可将数据替换为本地CSV,格式需包含Date, Open, High, Low, Close, Volume列,并修改数据读取部分。