重构经典策略(第十部分):人工智能(AI)能否为MACD提供动力?(基础篇)
◍ MACD 遇上 AI:从欧美盘实测说起
MACD 本质仍是双均线差值的派生指标,其内核没有脱离移动平均线交叉那套最古老的逻辑。社区里既有刚接触量化、想找‘最优策略’的新人,也有老技术派在实盘里长期挂 MACD 辅助判断,两者常有的共同疑问是:这指标到底有没有可验证的预测力。 本文给出的落点很具体——直接在 EUR/USD 上做 MACD 预测能力的实证检验,而不是空谈参数。顺带会给出几套建模思路,让你能把 AI 方法叠在技术指标之上,强化原先纯规则的技术分析。 外汇与贵金属本身高杠杆、高波动,任何指标叠加 AI 都只提高概率倾向,不等于信号必然生效,开 MT5 跑一遍回测再信。
「MACD 到底在盯什么」
MACD 由杰拉尔德·阿佩尔在 20 世纪 70 年代提出,核心是用快慢两条均线的差值画出主线,识别趋势方向与动能强弱。MT5 默认安装里就带它,GBPUSD 的 M1 图上加载默认参数就能直接看。 主线穿 0 轴是趋势倾向的参考:下穿 0 以下,下跌趋势概率偏大;上穿 0 以上则相反。主线数值只在价格上涨时走高、下跌时走低,所以它出现杯状转折,往往是动能切换的信号。 背离是更精细的玩法——价格冲出极端新高而 MACD 却逐步变浅并开始回落,这种反差常被视为趋势反转的早期预警,让交易者在波动放大前离场。但得泼盆冷水:MACD 本质滞后,价格动了指标才动,宏观通胀或地缘冲突这类领先事件能直接改写供求。 实战里也常见两种失灵:趋势反转前没出背离,或者出了背离却没反转。我们想试试用 AI 补它的滞后洞——若回测证明 MACD 在现有策略下仍有可信度,就接模型预测未来价或预测指标自身;误差谁低用谁。若证明没预测力,就直接选价预测最优的模型。外汇与贵金属波动剧烈,高杠杆下任何信号都只是概率。
从十万行EURUSD报价看MACD的建模陷阱
我们用一段 MQL5 脚本从 MT5 的 EURUSD M1 报价里精确抓了 100,000 行数据,连同 MACD 主值和信号线一并落盘成 CSV。可视化后一个直观结论是:MACD 对未来价格水平的区分能力偏弱,价格变动大概率和指标数值本身没什么耦合关系,反而它的计算给数据叠了一层非线性复杂结构,让后续建模更费劲。 数据到手后切了两半。前半部分走 5 折交叉验证估模型精度,后半部分留作测试。我们并行训了三个结构一致的深度神经网络:只用 OHLC 预测价格的「价格模型」、用 OHLC+MACD 预测指标值的「MACD 模型」、以及 OHLC+MACD 预测价格的「完整模型」。 测试集上价格模型拿下 69% 准确率居首,特征选择算法也指向一个事实——MT5 给的裸市场报价比 MACD 读数含更多信息量。外汇和贵金属这种高波动品种,拿弱信息量指标硬喂模型很容易过拟合,EURUSD 也不例外。 顺着最优思路做回归模型优化时翻了车:模型把训练集噪声学透了,测试集上被简单线性回归吊打。我们换掉过度优化的网络,改上 SVM,再导出成 ONNX,用「预测价格水平 + 预测 MACD」的双头结构搭了个 EA。
◍ 拉十万根 M1 和 MACD 落地成 CSV
做价格行为分析前,先得把裸 K 和指标一起抓下来。在 MT5 里用一段脚本就能把当前品种的 100,000 根 M1 历史连同 MACD 双线导出成 CSV,时间、OHLC 加两个 MACD 值一应俱全。 脚本跑起来不需要你改逻辑,把文件拖到任意货币对图表上即可,导出的文件名会自动带上品种名。外汇和贵金属杠杆高、点差跳空频繁,导数据时务必确认当前品种历史质量,否则后续统计会带偏。 核心就三件事:用 iMACD 挂 12/26/9 参数拿句柄,CopyBuffer 把主线与信号线塞进数组,再倒序写进 CSV。下面代码逐行拆开看,复制进 MetaEditor 编译就能用。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| ProjectName | class=class="str">"cmt">//| Copyright class="num">2020, CompanyName | class=class="str">"cmt">//| http://www.companyname.net | class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#class="kw">property copyright "Gamuchirai Zororo Ndawana" class="macro">#class="kw">property link "[MQL5官方文档] class="macro">#class="kw">property version "class="num">1.00" class="macro">#class="kw">property script_show_inputs class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Script Inputs | class=class="str">"cmt">//+------------------------------------------------------------------+ input class="type">int size = class="num">100000; class=class="str">"cmt">//How much data should we fetch? class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Global variables | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int indicator_handler; class="type">class="kw">double indicator_buffer[]; class="type">class="kw">double indicator_buffer_2[]; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| On start function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnStart() { class=class="str">"cmt">//--- Load indicator indicator_handler = iMACD(Symbol(),PERIOD_CURRENT,class="num">12,class="num">26,class="num">9,PRICE_CLOSE); CopyBuffer(indicator_handler,class="num">0,class="num">0,size,indicator_buffer); CopyBuffer(indicator_handler,class="num">1,class="num">0,size,indicator_buffer_2); ArraySetAsSeries(indicator_buffer,true); ArraySetAsSeries(indicator_buffer_2,true); class=class="str">"cmt">//--- File name class="type">class="kw">string file_name = "Market Data " + Symbol() +" MACD " + ".csv"; class=class="str">"cmt">//--- Write to file class="type">int file_handle=FileOpen(file_name,FILE_WRITE|FILE_ANSI|FILE_CSV,","); for(class="type">int i= size;i>=class="num">0;i--) { if(i == size) {
「把 K 线与 MACD 一起落盘」
这段逻辑干的事很直接:第一次写文件时先吐出表头,之后每根 K 线把时间、开高低收和两条 MACD 缓冲逐行写进 CSV。 注意 else 分支里 iTime / iOpen 这些函数都锁死在 PERIOD_CURRENT,也就是说你切到 M5 就只导 M5 数据,想跨周期比对得自己改参数。 FileClose 放在循环外、写盘结束后才调用,避免每根 K 线反复开关句柄拖慢 MT5。外汇与贵金属波动剧烈,导出的历史数据仅用于回测参考,实盘信号仍具高风险。
FileWrite(file_handle,"Time","Open","High","Low","Close","MACD Main","MACD Signal"); } else { FileWrite(file_handle,iTime(Symbol(),PERIOD_CURRENT,i), iOpen(Symbol(),PERIOD_CURRENT,i), iHigh(Symbol(),PERIOD_CURRENT,i), iLow(Symbol(),PERIOD_CURRENT,i), iClose(Symbol(),PERIOD_CURRENT,i), indicator_buffer[i], indicator_buffer_2[i] ); } } class=class="str">"cmt">//--- Close the file FileClose(file_handle); }
给 EURUSD 和 MACD 打二值标签
CSV 从 MT5 导出来之后,下一步是在 Python 里把它读进来并构造训练用的标签。这里以 EURUSD 的收盘价和 MACD 主线为对象,用前 20 根 K 线作为观察窗口,判断当前读数相对之后 20 根的位置关系。 核心做法是:先按 look_ahead=20 定义预测跨度,再用 shift 把 20 根之后的数值对齐到当前行。Bull Bear 列标记收盘价是否在 20 根后更高(否为 0,是为 1);MACD Bull 同理处理 MACD 主线。完成后用 iloc[:-20] 砍掉末尾因偏移产生的空行。 这套二值目标直接服务于后续监督学习:MACD Binary Target 为 1 表示当前 MACD 主线低于 20 根后的值,价格端同理。外汇与贵金属杠杆高、滑点随机,标签仅反映历史统计关系,信号失效概率不低,实盘前务必在 MT5 重跑验证。
class="macro">#Load libraries class="kw">import pandas as pd class="kw">import numpy as np class="kw">import seaborn as sns class="kw">import matplotlib.pyplot as plt class="macro">#Read in the data data = pd.read_csv("Market Data EURUSD MACD .csv") class="macro">#Forecast horizon look_ahead = class="num">20 class="macro">#Let&class="macro">#x27;s add labels data["Bull Bear"] = np.where(data["Close"] < data["Close"].shift(look_ahead),class="num">0,class="num">1) data["MACD Bull"] = np.where(data["MACD Main"] < data["MACD Main"].shift(look_ahead),class="num">0,class="num">1) data = data.loc[class="num">20:,:] data data["MACD Target"] = data["MACD Main"].shift(-look_ahead) data["Price Target"] = data["Close"].shift(-look_ahead) data["MACD Binary Target"] = np.where(data["MACD Main"] < data["MACD Target"],class="num">1,class="num">0) data["Price Binary Target"] = np.where(data["Close"] < data["Price Target"],class="num">1,class="num">0) data = data.iloc[:-class="num">20,:]