查看: 1588|回复: 3

Python回测MACD金叉死叉策略:含手续费与买入持有对

[复制链接]
发表于 昨天 10:00 | 显示全部楼层 |阅读模式
很多教程都会说“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),否则和常见行情软件口径容易不一致。
  1. def macd(close, fast=12, slow=26, signal=9):
  2.     ef = close.ewm(span=fast, adjust=False).mean()
  3.     es = close.ewm(span=slow, adjust=False).mean()
  4.     dif = ef - es  # 快慢 EMA 之差
  5.     dea = dif.ewm(span=signal, adjust=False).mean()  # DIF 的 EMA
  6.     return dif, dea
复制代码

三、金叉买入、死叉卖出的回测函数

金叉不能用“DIF 大于 DEA”来判断,而要用穿越:昨天 DIF≤DEA、今天 DIF>DEA。死叉同理:昨天 DIF≥DEA、今天 DIF<DEA。买入时用现金扣除手续费后按当日收盘价买入,卖出时按当日收盘价扣除手续费后变回现金。最后用 cash + shares * 最新收盘价 计算策略权益,用最新收盘价 / 第 35 根收盘价 计算买入持有收益。
  1. from alphafeed import AlphaFeed
  2. af = AlphaFeed(api_key='your-api-key')  # 或 export ALPHAFEED_API_KEY 后 AlphaFeed()
  3. def macd_backtest(symbol, count=500, fee=0.0013):
  4.     df = af.klines.get(symbol, period='1d', count=count,
  5.                        adjust='forward', to_dataframe=True).sort_values('trade_date').reset_index(drop=True)
  6.     dif, dea = macd(df['close'])
  7.     pos, cash, shares, trades = 0, 1.0, 0.0, 0
  8.     for i in range(35, len(df)):  # 跳过 MACD 预热期
  9.         px = df['close'].iloc[i]
  10.         gold = dif.iloc[i-1] <= dea.iloc[i-1] and dif.iloc[i] > dea.iloc[i]  # 金叉
  11.         dead = dif.iloc[i-1] >= dea.iloc[i-1] and dif.iloc[i] < dea.iloc[i]  # 死叉
  12.         if pos == 0 and gold:
  13.             shares = cash * (1 - fee) / px
  14.             cash = 0.0
  15.             pos = 1
  16.             trades += 1
  17.         elif pos == 1 and dead:
  18.             cash = shares * px * (1 - fee)
  19.             shares = 0.0
  20.             pos = 0
  21.     equity = cash + shares * df['close'].iloc[-1]
  22.     bh = df['close'].iloc[-1] / df['close'].iloc[35]
  23.     return {'strategy_ret_%': round((equity - 1) * 100, 1),
  24.             'buyhold_ret_%': round((bh - 1) * 100, 1), 'trades': trades}
复制代码

调用方式就是传入标的代码,例如 600519.SH、000001.SZ、600036.SH。

四、多标的对比:MACD 金叉死叉并不稳赚

可以用下面几行代码跑多标的对比:
  1. for s in ['600519.SH', '000001.SZ', '600036.SH']:
  2.     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 提供长历史复权日线,可以让你几十行完成回测和对比,但务必计入手续费、避免过拟合,并考虑叠加趋势过滤,不要把单指标择时当成圣杯。
回复

使用道具 举报

发表于 昨天 19:00 | 显示全部楼层

Re: Python回测MACD金叉死叉策略:含手续费与买入持有对

这个分享很实在,把 MACD 回测里最容易糊弄过去的几个点都点出来了。特别是金叉死叉必须用昨今穿越判断,而不是简单看 DIF 是否大于 DEA,这个细节一旦写错,回测结果基本没法看。用 ewm(adjust=False) 统一口径也很关键,不然很容易和行情软件对不上。 含万 13 手续费并和买入持有做对比后,结论就很有说服力:这几只标的、这段区间里 MACD 择时并没有优势,交易 17 到 20 次,成本和震荡假信号确实会吃掉收益。你提的别单标的调参、警惕当日收盘成交的未来函数、加趋势过滤减少假信号,我都认同。 如果后面再扩展,可以多跑几段区间和更多标的,把最大回撤、年化、胜率也列出来,参考价值会更高。感谢分享,代码骨架很清楚,适合拿来当回测模板改。
回复 支持 反对

使用道具 举报

发表于 昨天 19:10 | 显示全部楼层

Re: Python回测MACD金叉死叉策略:含手续费与买入持有对

写得挺完整,尤其是把金叉死叉用穿越来判断,而不是简单比较 DIF 和 DEA 的大小,这点很容易写错。手续费、买入持有基准、过拟合和未来函数这几个坑也都点到了,结论也比较有说服力。 代码里有两处索引好像漏了:px = df['close'].iloc 后面应该接 ,条件里的 dif.iloc 和 dea.iloc 也应该带上 ,不然直接跑会报错或者判断不对。成交假设那里,如果更严格一点,改成次日开盘成交会更贴近实盘,也能避免用当日收盘信息当日成交。 整体看,MACD 金叉死叉在震荡市里假信号多,加上每只 17 到 20 次交易,手续费侵蚀确实明显。后续如果继续做,可以先把复权、费用和成交口径统一,再考虑加 ADX 或均线过滤,但别在单标的上调参凑结果。
回复 支持 反对

使用道具 举报

发表于 昨天 19:20 | 显示全部楼层

Re: Python回测MACD金叉死叉策略:含手续费与买入持有对

感谢分享,写得很清楚。把 MACD 口径、穿越判定、手续费和买入持有基准这四点单独拎出来,确实比很多只讲“金叉买死叉卖”的教程靠谱。用 ewm(adjust=False) 这点也很关键,不然和行情软件对不上。 从你给的三只标的实测看,这段区间里策略都跑输买入持有,而且交易十七到二十次,手续费和震荡假信号的影响确实很明显。金叉不等于上涨、别单标的调参、成交假设要小心,这几条都很实在。 另外代码里 px = df['close'].iloc 那里好像少了当前循环下标,应该取第 i 根收盘价;如果最后一天仍持仓,按最新收盘价估算权益时没扣潜在卖出费用,影响可能不大,但说明一下会更严谨。整体很适合当基线,后面叠加趋势过滤再复测会更有意义。
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

指导单位

江苏省公安厅

江苏省通信管理局

浙江省台州刑侦支队

DEFCON GROUP 86025

Hacking Group 021A

旗下站点

态势感知中心

应急响应中心

红盟安全

联系我们

官方QQ群:112851260

官方邮箱:security#ihonker.org(#改成@)

官方核心成员

关注微信公众号

Archiver|手机版|小黑屋| ( 沪ICP备2021026908号 )

GMT+8, 2026-10-2 05:44 , Processed in 0.024652 second(s), 17 queries , Gzip On, Redis On.

Powered by ihonker.com

Copyright © 2015-现在.

  • 返回顶部