很多教程都会说“MACD 金叉买、死叉卖”,但真正用 Python 回测时,常见问题集中在四点:MACD 口径不一致、金叉/死叉穿越判定写错、没有计入手续费、没有和买入持有做基准对比。下面用 AlphaFeed 取复权日线,按 DIF 上穿 DEA 买入、DIF 下穿 DEA 卖出,写一个几十行可运行的回测脚本,并给出多标的对比结果。
一、回测环境和参数
AlphaFeed 提供长历史复权日线,用它的 klines 接口取日线数据。关键参数:period='1d',count=500,adjust='forward',to_dataframe=True。回测里手续费/滑点单边取 fee=0.0013,约万 13;MACD 参数取 fast=12、slow=26、signal=9;循环从第 35 根开始,跳过 MACD 预热期,约等于 slow+signal。
二、计算 MACD 的 DIF 和 DEA
DIF=快 EMA−慢 EMA,DEA=DIF 的 EMA。这里必须用 ewm(span=..., adjust=False),否则和常见行情软件口径容易不一致。
- def macd(close, fast=12, slow=26, signal=9):
- ef = close.ewm(span=fast, adjust=False).mean()
- es = close.ewm(span=slow, adjust=False).mean()
- dif = ef - es # 快慢 EMA 之差
- dea = dif.ewm(span=signal, adjust=False).mean() # DIF 的 EMA
- return dif, dea
复制代码
三、金叉买入、死叉卖出的回测函数
金叉不能用“DIF 大于 DEA”来判断,而要用穿越:昨天 DIF≤DEA、今天 DIF>DEA。死叉同理:昨天 DIF≥DEA、今天 DIF<DEA。买入时用现金扣除手续费后按当日收盘价买入,卖出时按当日收盘价扣除手续费后变回现金。最后用 cash + shares * 最新收盘价 计算策略权益,用最新收盘价 / 第 35 根收盘价 计算买入持有收益。
- from alphafeed import AlphaFeed
- af = AlphaFeed(api_key='your-api-key') # 或 export ALPHAFEED_API_KEY 后 AlphaFeed()
- def macd_backtest(symbol, count=500, fee=0.0013):
- df = af.klines.get(symbol, period='1d', count=count,
- adjust='forward', to_dataframe=True).sort_values('trade_date').reset_index(drop=True)
- dif, dea = macd(df['close'])
- pos, cash, shares, trades = 0, 1.0, 0.0, 0
- for i in range(35, len(df)): # 跳过 MACD 预热期
- px = df['close'].iloc[i]
- gold = dif.iloc[i-1] <= dea.iloc[i-1] and dif.iloc[i] > dea.iloc[i] # 金叉
- dead = dif.iloc[i-1] >= dea.iloc[i-1] and dif.iloc[i] < dea.iloc[i] # 死叉
- if pos == 0 and gold:
- shares = cash * (1 - fee) / px
- cash = 0.0
- pos = 1
- trades += 1
- elif pos == 1 and dead:
- cash = shares * px * (1 - fee)
- shares = 0.0
- pos = 0
- equity = cash + shares * df['close'].iloc[-1]
- bh = df['close'].iloc[-1] / df['close'].iloc[35]
- return {'strategy_ret_%': round((equity - 1) * 100, 1),
- 'buyhold_ret_%': round((bh - 1) * 100, 1), 'trades': trades}
复制代码
调用方式就是传入标的代码,例如 600519.SH、000001.SZ、600036.SH。
四、多标的对比:MACD 金叉死叉并不稳赚
可以用下面几行代码跑多标的对比:
- for s in ['600519.SH', '000001.SZ', '600036.SH']:
- print(s, macd_backtest(s))
复制代码
原文给出一组约 500 个交易日、含手续费的实测结果:
贵州茅台 600519.SH:策略收益 -16.3%,买入持有 -13.5%,交易 19 次,跑输。
平安银行 000001.SZ:策略收益 +4.7%,买入持有 +7.3%,交易 17 次,略跑输。
招商银行 600036.SH:策略收益 -0.6%,买入持有 +13.4%,交易 20 次,大幅跑输。
结论很直接:在这几只标的、这段区间里,MACD 金叉死叉策略都跑输买入持有,而且每只交易 17~20 次,频繁交易的手续费和震荡假信号会严重侵蚀收益。
五、关键坑和注意事项
1. 金叉不等于上涨。MACD 是滞后指标,金叉出现时往往已经涨了一段;震荡市里金叉死叉频繁切换,会产生大量假信号。
2. 交易频繁吃手续费。每只 17~20 次交易,含费后收益更吃亏。信号越多,成本累积越可观。
3. 别单标的调参。改 fast/slow/signal 在单标的历史上凑出好看结果,就是过拟合,样本外大概率失效。
4. 可以加过滤器。实务中常给 MACD 叠加趋势过滤,例如只在 ADX≥25 或均线多头时才做多,用于减少震荡假信号;但那是另一套系统。
5. 成交假设要小心。本文用当日收盘价成交是简化;更严格应次日开盘成交,避免用当日收盘信息当日成交,也就是避免未来函数。
六、常见问题
Q:为什么 MACD 金叉策略经常跑输买入持有?
A:它是滞后+择时策略,金叉时已涨一段、死叉时已跌一段,还要付频繁交易的手续费。趋势行情里不如躺着不动,震荡行情里被假信号反复消耗。
Q:改用周线 MACD 会更好吗?
A:周线信号更少、更稳,可能减少假信号,但也更滞后。切勿只看单标的历史结果下结论,应多标的多周期验证。
Q:这个回测能直接实盘吗?
A:不能。它是教学级简化,当日成交、无涨跌停停牌处理、未含冲击成本与幸存者偏差。实盘前需要更严格的撮合与风控。
Q:需要付费吗?
A:单只标的 500 根日线在免费额度内即可试;多标的/长历史批量需 Starter 及以上,详见 AlphaFeed 定价页。
小结:用 Python 回测 MACD 金叉死叉的价值,在于用真实数据打破“金叉买、死叉卖稳赚”的幻觉。含手续费后它交易频繁、常跑输买入持有;AlphaFeed 提供长历史复权日线,可以让你几十行完成回测和对比,但务必计入手续费、避免过拟合,并考虑叠加趋势过滤,不要把单指标择时当成圣杯。 |