重构经典策略(第九部分):多时间框架分析(第二部分)·综合运用
(3/3)·月线与小时图出现双低误差区,但残差无规律分布,最优周期远非直觉所能判定
EA 骨架:先把 ONNX 和指标句柄立起来
把深度学习模型塞进 MT5 做日内决策,第一步不是算信号,而是把资源句柄和状态变量在 OnInit 里一次性挂好。这段初始化逻辑直接绑定 EURUSD 日线,布林带周期 30、RSI 周期 14、EMA 周期 20,三个指标全部用 PRICE_CLOSE 计算——意味着模型输入偏重收盘价结构,对盘中毛刺不敏感。 OnTick 的主循环非常薄:先 update_market_data 刷行情,再 model_predict 取模型输出,最后 display_stats 打印状态。真正下单只在 PositionsTotal()==0 时触发,且严格依赖 model_state 的符号——等于 1 才查多头条件,等于 -1 才查空头条件,模型不表态就空仓。 持仓管理给了两条退出路径:user_close_type 切到 MA_CLOSE 就走均线死叉/金叉逻辑,否则交给 ai_close_positions 用模型自己判断离场。外汇与贵金属杠杆高,这种双退出机制能降低单一模型误判的暴露,但回测前务必在策略测试器里确认 load_onnx_model 返回的模型版本与指标周期匹配。
vectorf model_output = vectorf::Zeros(class="num">1); class="type">class="kw">double bid,ask; class="type">int ma_hanlder; class="type">class="kw">double ma_buffer[]; class="type">int bb_hanlder; class="type">class="kw">double bb_mid_buffer[]; class="type">class="kw">double bb_high_buffer[]; class="type">class="kw">double bb_low_buffer[]; class="type">int rsi_hanlder; class="type">class="kw">double rsi_buffer[]; class="type">int system_state = class="num">0,model_state=class="num">0; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Expert initialization function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int OnInit() { class=class="str">"cmt">//--- Load our ONNX function if(!load_onnx_model()) { class="kw">return(INIT_FAILED); } class=class="str">"cmt">//--- Load our technical indicators bb_hanlder = iBands("EURUSD",PERIOD_D1,class="num">30,class="num">0,class="num">1,PRICE_CLOSE); rsi_hanlder = iRSI("EURUSD",PERIOD_D1,class="num">14,PRICE_CLOSE); ma_hanlder = iMA("EURUSD",PERIOD_D1,class="num">20,class="num">0,MODE_EMA,PRICE_CLOSE); class=class="str">"cmt">//--- class="kw">return(INIT_SUCCEEDED); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Expert deinitialization function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnDeinit(const class="type">int reason) { class=class="str">"cmt">//--- Release the resources we don&class="macro">#x27;t need release_resources(); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Expert tick function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnTick() { class=class="str">"cmt">//--- Update market data update_market_data(); class=class="str">"cmt">//--- Fetch a prediction from our model model_predict(); class=class="str">"cmt">//--- Display stats display_stats(); class=class="str">"cmt">//--- Find a position if(PositionsTotal() == class="num">0) { if(model_state == class="num">1) check_bullish_setup(); else if(model_state == -class="num">1) check_bearish_setup(); } class=class="str">"cmt">//--- Manage the position we have else { class=class="str">"cmt">//--- How should we close our positions? if(user_close_type == MA_CLOSE) { ma_close_positions(); } else { ai_close_positions(); } } } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+
「EURUSD 日线多空触发与 AI 反转平仓」
下面这段逻辑直接在 MT5 的 EA 里跑,用日线(PERIOD_D1)收盘价做判定,品种写死 EURUSD,外汇品种波动大、杠杆高,实盘前务必先开模拟账户验证。 AI 反转平仓函数 ai_close_positions 只做一件事:当内部状态 system_state 与模型输出 model_state 不一致,弹 Alert 并平掉 EURUSD 仓位。这属于兜底逻辑,模型判势和持仓状态冲突时优先离场,避免扛单。 均线平仓函数 ma_close_positions 分两种情况:system_state==1 表示持多,若日线收盘价跌破 ma_buffer[0] 则平多;system_state==-1 表示持空,若日线收盘价上破 ma_buffer[0] 则平空。这里 ma_buffer[0] 是当日均线值,价格回破均线倾向视为趋势减弱。 空头建仓 check_bearish_setup:日线收盘价低于布林下轨 bb_low_buffer[0],且 RSI 低于 50,且收盘价低于均线,才 Trade.Sell(0.3,...) 并置 system_state=-1。多头 check_bullish_setup 对称:收盘价高于布林上轨、RSI 大于 50、收盘价高于均线才买。三重过滤把假突破概率压下来,但 EURUSD 日线一根 bar 走 80~120 点很常见,止损必须另设。 让小布替你跑这套 把代码贴进 MT5 策略测试器,品种选 EURUSD 日线,重点看 ma_buffer / bb_low_buffer / rsi_buffer 三个数组有没有正确赋值,否则 PositionClose 永远不会触发。
class="type">void ai_close_positions(class="type">void) { if(system_state != model_state) { Alert("Reversal detected by our AI system,closing open positions"); Trade.PositionClose("EURUSD"); } } class="type">void ma_close_positions(class="type">void) { class=class="str">"cmt">//--- Is our buy position possibly weakening? if(system_state == class="num">1) { if(iClose("EURUSD",PERIOD_D1,class="num">0) < ma_buffer[class="num">0]) Trade.PositionClose("EURUSD"); } class=class="str">"cmt">//--- Is our sell position possibly weakening? if(system_state == -class="num">1) { if(iClose("EURUSD",PERIOD_D1,class="num">0) > ma_buffer[class="num">0]) Trade.PositionClose("EURUSD"); } } class="type">void check_bearish_setup(class="type">void) { if(iClose("EURUSD",PERIOD_D1,class="num">0) < bb_low_buffer[class="num">0]) { if(class="num">50 > rsi_buffer[class="num">0]) { if(iClose("EURUSD",PERIOD_D1,class="num">0) < ma_buffer[class="num">0]) { Trade.Sell(class="num">0.3,"EURUSD",bid,class="num">0,class="num">0,"EURUSD MTF AI"); system_state = -class="num">1; } } } } class="type">void check_bullish_setup(class="type">void) { if(iClose("EURUSD",PERIOD_D1,class="num">0) > bb_high_buffer[class="num">0]) { if(class="num">50 < rsi_buffer[class="num">0]) { if(iClose("EURUSD",PERIOD_D1,class="num">0) > ma_buffer[class="num">0]) {
◍ 把模型信号接进下单与资源回收
这段逻辑把 ONNX 模型的输出直接转成交易动作与状态机标记。当预测值低于月线收盘价时,model_state 置为 1,上层据此用 Trade.Buy(0.3,"EURUSD",ask,0,0,"EURUSD MTF AI") 以市价 ask 开 0.3 手多单,并把 system_state 切到 1,说明信号已被消费。 display_stats 仅用 Comment 把 model_output[0] 打印到图表左上角,方便肉眼核对预测值,不干涉任何仓位。model_predict 则从 EURUSD 月线(PERIOD_MN1)取 1 根收盘价喂给 OnnxRun,再按输出与输入大小关系写 model_state,预测值高于输入则置 -1,低于则置 1,等于的情况留空。 release_resources 在退出前释放 onnx_model 与三个指标句柄(ma/rsi/bb),最后调 ExpertRemove 卸载 EA。update_market_data 每次刷新 bid、ask 并各取 1 根缓冲到数组:MA 主缓冲、RSI 主线、布林中轨/上轨/下轨分别来自 bb_hanlder 的 0/1/2 号缓冲。外汇与贵金属杠杆高,模型信号仅作概率参考,实盘前请在 MT5 策略测试器用历史数据验证这套复制逻辑是否丢帧。
Trade.Buy(class="num">0.3,"EURUSD",ask,class="num">0,class="num">0,"EURUSD MTF AI"); system_state = class="num">1; } } } } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Display account stats | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void display_stats(class="type">void) { Comment("Forecast: ",model_output[class="num">0]); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Fetch a prediction from our model | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void model_predict(class="type">void) { class=class="str">"cmt">//--- Get inputs model_input.CopyRates("EURUSD",PERIOD_MN1,COPY_RATES_CLOSE,class="num">0,class="num">1); class=class="str">"cmt">//--- Fetch a prediction from our model OnnxRun(onnx_model,ONNX_DEFAULT,model_input,model_output); class=class="str">"cmt">//--- Store the model&class="macro">#x27;s prediction as a flag if(model_output[class="num">0] > model_input[class="num">0]) { model_state = -class="num">1; } else if(model_output[class="num">0] < model_input[class="num">0]) { model_state = class="num">1; } } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Release the resources we don&class="macro">#x27;t need | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void release_resources(class="type">void) { OnnxRelease(onnx_model); IndicatorRelease(ma_hanlder); IndicatorRelease(rsi_hanlder); IndicatorRelease(bb_hanlder); ExpertRemove(); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Update our market data | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void update_market_data(class="type">void) { class=class="str">"cmt">//--- Update all our technical data bid = SymbolInfoDouble("EURUSD",SYMBOL_BID); ask = SymbolInfoDouble("EURUSD",SYMBOL_ASK); CopyBuffer(ma_hanlder,class="num">0,class="num">0,class="num">1,ma_buffer); CopyBuffer(rsi_hanlder,class="num">0,class="num">0,class="num">1,rsi_buffer); CopyBuffer(bb_hanlder,class="num">0,class="num">0,class="num">1,bb_mid_buffer); CopyBuffer(bb_hanlder,class="num">1,class="num">0,class="num">1,bb_high_buffer); CopyBuffer(bb_hanlder,class="num">2,class="num">0,class="num">1,bb_low_buffer); } class=class="str">"cmt">//+------------------------------------------------------------------+
把训练好的模型塞进 MT5 内存
在 MQL5 里跑 AI 推理,第一步不是画信号线,而是把 ONNX 二进制缓冲加载成可用句柄。OnnxCreateFromBuffer 吃的是内存里的 onnx_buffer,第二个参数用 ONNX_DEFAULT 即可,返回 INVALID_HANDLE 就说明模型本身损坏或版本不兼容。 加载成功后必须显式声明输入输出维度。上面这段写死成 {1,1},意思是单次喂 1 个特征、吐 1 个值——如果你的网络是 30 根 K 线的收盘价序列,这里就得改成 {1,30},否则 OnnxSetInputShape 会直接返回 false。 别把 {1,1} 当通用模板抄。shape 设错不会让 EA 崩,但后续推理结果会是垃圾值,且 MT5 只在图表左上角 Comment 一行提示,日志里不报红,排查起来很隐蔽。外汇与贵金属杠杆高,模型加载异常时任何信号都不可信,先确认返回 true 再谈交易。
class="type">bool load_onnx_model(class="type">void) { class=class="str">"cmt">//--- Create the ONNX model from our buffer onnx_model = OnnxCreateFromBuffer(onnx_buffer,ONNX_DEFAULT); class=class="str">"cmt">//--- Validate the model if(onnx_model == INVALID_HANDLE) { class=class="str">"cmt">//--- Give feedback Comment("Failed to create the ONNX model"); class=class="str">"cmt">//--- We failed to create the model class="kw">return(false); } class=class="str">"cmt">//--- Specify the I/O shapes class="type">ulong input_shape[] = {class="num">1,class="num">1}; class="type">ulong output_shape[] = {class="num">1,class="num">1}; class=class="str">"cmt">//--- Validate the I/O shapes if(!(OnnxSetInputShape(onnx_model,class="num">0,input_shape)) || !(OnnxSetOutputShape(onnx_model,class="num">0,output_shape))) { class=class="str">"cmt">//--- Give feedback Comment("We failed to define the correct input shapes"); class=class="str">"cmt">//--- We failed to define the correct I/O shape class="kw">return(false); } class="kw">return(true); }
「把工具请下神坛」
回测里月线与小时图在欧元兑美元上跑出了相对最稳的框架,但这只覆盖单一货币对,不能外推到全市场。附件里的 EURUSD_MTF_AI.mq5 和对应的 .onnx 权重只是验证思路的载体,不是圣杯。 真正该做的,是自己在 MT5 里换几个非欧系货币对复跑,看误差指标会不会垮。时间框架组合搜索、损失函数微调这些活儿,得靠你本地算力接着挖。 外汇和贵金属杠杆高、跳空频繁,任何历史最优框架遇到流动性断裂都可能失效。把脚本当探针用,别当预言机。