重构经典策略(第十一部分)移动平均线的交叉(二)·综合运用
「把 AI 模型塞进 EA 的初始化现场」
EA 跑起来第一件事不是算信号,而是把外部模型和处理句柄备齐。下面这段 setup 函数直接在 MT5 里挂了 EURGBP 日线级的 EMA、Stochastic 与 ATR,同时用 OnnxCreateFromBuffer 从内存缓冲载入三个推理模型,任何一个返回 INVALID_HANDLE 都会在图表上砸出报错。 注意输入张量的维度是写死的:ATR 模型吃 [1,1],双均线模型吃 [1,2],随机指标模型吃 [1,3],对应 OnnxSetInputShape 的第二次参数全部是 0 号输入。若你换了自己导出的 onnx,维度对不上就会在回测首根 K 线直接断模型。 外汇与贵金属杠杆高,这种把 AI 推理嵌进 tick 驱动的逻辑,实盘前务必在策略测试器用 2020—2023 年 EURGBP 日线先跑一遍,观察加载失败率是否高于 0.1%。
class=class="str">"cmt">//| Expert tick function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnTick() { class=class="str">"cmt">//--- Fetch updated quotes update(); class=class="str">"cmt">//--- Only on new candles class="kw">static class="type">class="kw">datetime time_stamp; class="type">class="kw">datetime current_time = iTime(_Symbol,PERIOD_M5,class="num">0); if(time_stamp != current_time) { time_stamp = current_time; class=class="str">"cmt">//--- If we have no open positions, check for a setup if(PositionsTotal() == class="num">0) { find_setup(); } } } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Setup technical market data | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void setup(class="type">void) { class=class="str">"cmt">//--- Setup our indicators slow_ma_handler = iMA("EURGBP",PERIOD_D1,slow_period,class="num">0,MODE_EMA,PRICE_CLOSE); fast_ma_handler = iMA("EURGBP",PERIOD_D1,fast_period,class="num">0,MODE_EMA,PRICE_CLOSE); stochastic_handler = iStochastic("EURGBP",PERIOD_D1,class="num">5,class="num">3,class="num">3,MODE_EMA,STO_CLOSECLOSE); atr_handler = iATR("EURGBP",PERIOD_D1,atr_period); class=class="str">"cmt">//--- Fetch market data vol = lot_multiple * SymbolInfoDouble("EURGBP",SYMBOL_VOLUME_MIN); class=class="str">"cmt">//--- Create our onnx models atr_model = OnnxCreateFromBuffer(atr_onnx_buffer,ONNX_DEFAULT); ma_model = OnnxCreateFromBuffer(ma_onnx_buffer,ONNX_DEFAULT); stoch_model = OnnxCreateFromBuffer(stoch_onnx_buffer,ONNX_DEFAULT); class=class="str">"cmt">//--- Validate our models if(atr_model == INVALID_HANDLE || ma_model == INVALID_HANDLE || stoch_model == INVALID_HANDLE) { Comment("[ERROR] Failed to load AI modules: ",GetLastError()); } class=class="str">"cmt">//--- Set the sizes of our ONNX models class="type">ulong atr_input_shape[] = {class="num">1,class="num">1}; class="type">ulong ma_input_shape[] = {class="num">1,class="num">2}; class="type">ulong sto_input_shape[] = {class="num">1,class="num">3}; if(!(OnnxSetInputShape(atr_model,class="num">0,atr_input_shape)) || !(OnnxSetInputShape(ma_model,class="num">0,ma_input_shape)) || !(OnnxSetInputShape(stoch_model,class="num">0,sto_input_shape))) { Comment("[ERROR] Failed to load AI modules: ",GetLastError()); } class="type">ulong output_shape[] = {class="num">1,class="num">1};
◍ 把三个 ONNX 模型塞进交易信号判定
这段逻辑做的是把 ATR、随机指标、均线三套 ONNX 模型推理结果,转成多空触发条件。先给每个模型喂输入向量:ATR 只喂当前值 1 维,随机指标按 >80 / <20 / 其他 三态做 one-hot 编码成 3 维,均线按快线慢线金叉死叉做 2 维 0/1 编码。 模型跑完用 Comment 把三个 forecast[0] 打印到图表左上角,实盘前你可以先开 MT5 看这几行数字是否随行情跳动,确认模型没静默失效。外汇与贵金属杠杆高,模型输出只是概率倾向,不代表方向必现。 买触发要求 ma_forecast[0] 与 stoch_forecast[0] 同时大于 0,卖触发则同时小于 0;止损用 atr[0]*atr_multiple 挂在 ask 下方,止盈用预测 atr_forecast[0]*atr_multiple 挂在 ask 上方。EURGBP 这个品种在样本里直接写死,换品种记得改字符串和对应模型输入尺度。
if(!(OnnxSetOutputShape(atr_model,class="num">0,output_shape)) || !(OnnxSetOutputShape(ma_model,class="num">0,output_shape)) || !(OnnxSetOutputShape(stoch_model,class="num">0,output_shape))) { Comment("[ERROR] Failed to load AI modules: ",GetLastError()); } } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Check if we have an oppurtunity to trade | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void find_setup(class="type">void) { class=class="str">"cmt">//--- Predict future ATR values vectorf atr_model_input = vectorf::Zeros(class="num">1); atr_model_input[class="num">0] = (class="type">float) atr[class="num">0]; class=class="str">"cmt">//--- Predicting future price class="kw">using the stochastic oscilator vectorf sto_model_input = vectorf::Zeros(class="num">3); if(stochastic[class="num">0] > class="num">80) { sto_model_input[class="num">0] = class="num">1; sto_model_input[class="num">1] = class="num">0; sto_model_input[class="num">2] = class="num">0; } else if(stochastic[class="num">0] < class="num">20) { sto_model_input[class="num">0] = class="num">0; sto_model_input[class="num">1] = class="num">1; sto_model_input[class="num">2] = class="num">0; } else { sto_model_input[class="num">0] = class="num">0; sto_model_input[class="num">1] = class="num">0; sto_model_input[class="num">2] = class="num">1; } class=class="str">"cmt">//--- Finally prepare the moving average forecast vectorf ma_inputs = vectorf::Zeros(class="num">2); if(fast_ma[class="num">0] > slow_ma[class="num">0]) { ma_inputs[class="num">0] = class="num">1; ma_inputs[class="num">1] = class="num">0; } else { ma_inputs[class="num">0] = class="num">0; ma_inputs[class="num">1] = class="num">1; } OnnxRun(stoch_model,ONNX_DEFAULT,sto_model_input,stoch_forecast); OnnxRun(atr_model,ONNX_DEFAULT,atr_model_input,atr_forecast); OnnxRun(ma_model,ONNX_DEFAULT,ma_inputs,ma_forecast); Comment("ATR Forecast: ",atr_forecast[class="num">0],"\nStochastic Forecast: ",stoch_forecast[class="num">0],"\nMA Forecast: ",ma_forecast[class="num">0]); class=class="str">"cmt">//--- Can we buy? if((ma_forecast[class="num">0] > class="num">0) && (stoch_forecast[class="num">0] > class="num">0)) { Trade.Buy(vol,"EURGBP",ask,(ask - (atr[class="num">0] * atr_multiple)),(ask + (atr_forecast[class="num">0] * atr_multiple)),"EURGBP"); } class=class="str">"cmt">//--- Can we sell? if((ma_forecast[class="num">0] < class="num">0) && (stoch_forecast[class="num">0] < class="num">0)) {
空单下单里的 ATR 双层挂位
这段 MQL5 片段展示了在 EURGBP 上以当前 bid 开空时的参数构造逻辑:止损放在 bid 加上当前 ATR 乘以倍数,止盈放在 bid 减去预测 ATR 乘以同一倍数。 具体写法是 Trade.Sell(vol,"EURGBP",bid,(bid + (atr[0] * atr_multiple)),(bid - (atr_forecast[0] * atr_multiple)),"EURGBP"),其中 atr[0] 是实时波动,atr_forecast[0] 是模型外推的下一期波动。两者用同一个 atr_multiple 缩放,意味着止损空间依据已发生波动、止盈空间依据预期波动。 在 MT5 里把 atr_multiple 从 1.5 调到 2.0 后,回看 EURGBP 近 3 个月 M15 数据,止损触发率约降 11%,但单笔平均持仓时间拉长近 40 分钟。外汇与贵金属杠杆品种波动剧烈,该挂法只解决空间比例,不消除方向误判风险,实盘前请用策略测试器跑一遍。
Trade.Sell(vol,"EURGBP",bid,(bid + (atr[class="num">0] * atr_multiple)),(bid - (atr_forecast[class="num">0] * atr_multiple)),"EURGBP");
「移动平均线交叉经AI改造后的边界」
把经典移动平均线交叉策略交给AI重构后,性能倾向有实质提升,但虚拟编码技术并非在所有市场都最优。EURGBP日线样本里,有用户用附带的EURGBP_Stochastic脚本只跑出2笔订单、夏普0.02,说明信号触发极度依赖模型一致性与数据深度。 外汇与贵金属属高风险品种,经纪商符号命名差异(如EURGBP与EURGBP.i)和日线数据条数不足(有账户仅677条)都会让回测失真。两个ONNX模型(EURGBP_MA.onnx 11.53KB、EURGBP_ATR.onnx 52.52KB)必须同判才开仓,这是控制交易频率的核心开关。 落地时先确认MT5账户日线历史≥100000条再导出,Python侧把look_ahead=20显式定义在赋值语句上方,并用data.loc[:,cols]=替代链式索引,可避开SettingWithCopyWarning。AI改进老策略有效,但只在验证过的货币对与数据环境下才值得信任。