经典策略重塑(第12部分):欧元兑美元(EURUSD)突破交易策略·综合运用
📘

经典策略重塑(第12部分):欧元兑美元(EURUSD)突破交易策略·综合运用

第 3/3 篇

「用Python验证均线比裸价更好预测」

把之前落地的 EURUSD 历史 CSV 读进来,时间列最后一条停在 2019-04-18,这是故意留的缺口——后面回测从 2020-01-01 起算,模型训练阶段看不到测试答案,避免自己骗自己。 为了看清虚假突破过滤里该拿什么当预测目标,跑了 30 个结构相同的神经网络(隐藏层 10×4,lbfgs 求解),分别压测三件事:未来 24 根 K 线的收盘价、5 周期 MA、60 周期 MA。所有目标都向前看 24 步。 误差画出来之后,60 周期均线的预测误差带明显最宽,5 周期均线比直接猜价格更窄。也就是说,短期平滑值比裸价本身更可被模型捕捉,长期均线反而最悬。 落地到导出环节,短期用 Ridge 岭回归就够,长期 60 周期均线换神经网络硬啃,再统一转 ONNX 丢给 MT5 端调用。外汇和贵金属杠杆高,这套误差结论只说明“可能”更好用,实盘仍以小仓位验证为准。

MQL5 / C++
class="kw">import pandas as pd
class="kw">import numpy  as np
from   sklearn.model_selection class="kw">import TimeSeriesSplit,cross_val_score
from   sklearn.linear_model      class="kw">import Ridge
from sklearn.metrics class="kw">import mean_squared_error
class="kw">import matplotlib.pyplot as plt
class="kw">import seaborn as sns
class="macro">#Define the forecast horizon
look_ahead       = class="num">24
class="macro">#Read in the data
data             = pd.read_csv(&class="macro">#x27;Market Data EURUSD MA Cross As Series.csv&class="macro">#x27;)
class="macro">#Drop the last class="num">4 years
data             =  data.iloc[:(-class="num">24 * class="num">365 * class="num">4),:]
data.reset_index(drop=True,inplace=True)
class="macro">#Label the data
data[&class="macro">#x27;Target&class="macro">#x27;]      = data[&class="macro">#x27;Close&class="macro">#x27;].shift(-look_ahead)
data[&class="macro">#x27;MA class="num">5 Target&class="macro">#x27;]      = data[&class="macro">#x27;MA class="num">5&class="macro">#x27;].shift(-look_ahead)
data[&class="macro">#x27;MA class="num">5 Close Target&class="macro">#x27;]      = data[&class="macro">#x27;Target&class="macro">#x27;] - data[&class="macro">#x27;MA class="num">5 Target&class="macro">#x27;]
data[&class="macro">#x27;MA class="num">60 Target&class="macro">#x27;]      = data[&class="macro">#x27;MA class="num">60&class="macro">#x27;].shift(-look_ahead)
data[&class="macro">#x27;MA class="num">60 Close Target&class="macro">#x27;]      = data[&class="macro">#x27;Target&class="macro">#x27;] - data[&class="macro">#x27;MA class="num">60 Target&class="macro">#x27;]
data.dropna(inplace=True)
data.reset_index(drop=True,inplace=True)
data
class="macro">#Classical error
classical_error = []
epochs = class="num">1000
for i in np.arange(class="num">0,class="num">30):
  model = MLPRegressor(hidden_layer_sizes=(class="num">10,class="num">4),max_iter=epochs,early_stopping=False,solver=&class="macro">#x27;lbfgs&class="macro">#x27;)
  classical_error.append(np.mean(np.abs(cross_val_score(model,data.loc[:,[‘Open’,‘High’,‘Low’,‘Close’]],data.loc[:,‘Target’],cv=tscv,scoring=‘neg_mean_squared_error’))))
class="macro">#MA Cross Over error
ma_5_error = []
for i in np.arange(class="num">0,class="num">30):
  model = MLPRegressor(hidden_layer_sizes=(class="num">10,class="num">4),max_iter=epochs,early_stopping=False,solver=&class="macro">#x27;lbfgs&class="macro">#x27;)
  ma_5_error.append(np.mean(np.abs(cross_val_score(model,data.loc[:,[‘Open’,‘High’,‘Low’,‘Close’,‘MA class="num">5’]],data.loc[:,‘MA class="num">5 Target’],cv=tscv,scoring=‘neg_mean_squared_error’))))
class="macro">#New error
ma_60_error = []
for i in np.arange(class="num">0,class="num">30):
  model = MLPRegressor(hidden_layer_sizes=(class="num">10,class="num">4),max_iter=class="num">10000,early_stopping=False,solver=&class="macro">#x27;lbfgs&class="macro">#x27;)
  ma_60_error.append(np.mean(np.abs(cross_val_score(model,data.loc[:,[‘Open’,‘High’,‘Low’,‘Close’,‘MA class="num">60’]],data.loc[:,‘MA class="num">60 Target’],cv=tscv,scoring=‘neg_mean_squared_error’))))
plt.plot(classical_error)
plt.plot(ma_5_error)
plt.plot(ma_60_error)
plt.legend([&class="macro">#x27;OHLC&class="macro">#x27;,&class="macro">#x27;MA class="num">5 &class="macro">#x27;,&class="macro">#x27;MA class="num">60&class="macro">#x27;])
plt.axhline(np.mean(classical_error),class="type">class="kw">color=&class="macro">#x27;blue&class="macro">#x27;,linestyle=&class="macro">#x27;--&class="macro">#x27;)

◍ 把回归模型压成 ONNX 丢进行情终端

上面的交叉验证误差图只是开胃菜,真正能在 MT5 里跑的得是序列化后的轻量模型。这里用 Ridge 对 EURCHF 的 MA5 / MA60 以及 EURUSD 的对应收盘高度目标各训了四条回归线,输入恒为 Open、High、Low、Close 加一条均线,共 5 维特征。 训练完立即用 skl2onnx 转 ONNX,initial_types 写死成 FloatTensorType([1, 5]),target_opset=12 是为了兼顾老版本 ONNX Runtime 的兼容。四条模型分别落盘为 eurchf_ma_5_model.onnx、eurchf_ma_60_model.onnx、eurusd_ma_5_height_model.onnx、eurusd_ma_60_height_model.onnx。 外汇与贵金属杠杆高、滑点凶,这类回归预测只给方向概率,实盘前务必在策略测试器用历史 tick 重跑一遍误差分布,别直接信回测里的均值线。

MQL5 / C++
plt.axhline(np.mean(ma_5_error),class="type">class="kw">color=&class="macro">#x27;orange&class="macro">#x27;,linestyle=&class="macro">#x27;--&class="macro">#x27;)
plt.axhline(np.mean(ma_60_error),class="type">class="kw">color=&class="macro">#x27;green&class="macro">#x27;,linestyle=&class="macro">#x27;--&class="macro">#x27;)
plt.grid()
plt.ylabel(&class="macro">#x27;Cross Validated Error&class="macro">#x27;)
plt.xlabel(&class="macro">#x27;Iteration&class="macro">#x27;)
plt.title(&class="macro">#x27;Comparing Different The Error Associated With Different Targets&class="macro">#x27;)
plt.show()
class="kw">import onnx
from skl2onnx class="kw">import convert_sklearn
from skl2onnx.common.data_types class="kw">import FloatTensorType
from sklearn.neural_network class="kw">import MLPRegressor
ma_5_model = Ridge()
ma_5_model.fit(data[[&class="macro">#x27;Open&class="macro">#x27;,&class="macro">#x27;High&class="macro">#x27;,&class="macro">#x27;Low&class="macro">#x27;,&class="macro">#x27;Close&class="macro">#x27;,&class="macro">#x27;MA class="num">5&class="macro">#x27;]],data[&class="macro">#x27;MA class="num">5 Target&class="macro">#x27;])
ma_5_height_model = Ridge()
ma_5_height_model.fit(data[[&class="macro">#x27;Open&class="macro">#x27;,&class="macro">#x27;High&class="macro">#x27;,&class="macro">#x27;Low&class="macro">#x27;,&class="macro">#x27;Close&class="macro">#x27;,&class="macro">#x27;MA class="num">5&class="macro">#x27;]],data[&class="macro">#x27;MA class="num">5 Close Target&class="macro">#x27;])
ma_60_model = Ridge()
ma_60_model.fit(data[[&class="macro">#x27;Open&class="macro">#x27;,&class="macro">#x27;High&class="macro">#x27;,&class="macro">#x27;Low&class="macro">#x27;,&class="macro">#x27;Close&class="macro">#x27;,&class="macro">#x27;MA class="num">60&class="macro">#x27;]],data[&class="macro">#x27;MA class="num">60 Target&class="macro">#x27;])
ma_60_height_model = Ridge()
ma_60_height_model.fit(data[[&class="macro">#x27;Open&class="macro">#x27;,&class="macro">#x27;High&class="macro">#x27;,&class="macro">#x27;Low&class="macro">#x27;,&class="macro">#x27;Close&class="macro">#x27;,&class="macro">#x27;MA class="num">60&class="macro">#x27;]],data[&class="macro">#x27;MA class="num">60 Close Target&class="macro">#x27;])
initial_type = [(&class="macro">#x27;float_input&class="macro">#x27;, FloatTensorType([class="num">1, class="num">5]))]
ma_5_onx = convert_sklearn(ma_5_model, initial_types=initial_type, target_opset=class="num">12 )
ma_5_height_onx = convert_sklearn(ma_5_height_model, initial_types=initial_type, target_opset=class="num">12 )
ma_60_height_onx = convert_sklearn(ma_60_height_model, initial_types=initial_type, target_opset=class="num">12 )
ma_60_onx = convert_sklearn(ma_60_model, initial_types=initial_type, target_opset=class="num">12 )
onnx.save(ma_5_onx,&class="macro">#x27;eurchf_ma_5_model.onnx&class="macro">#x27;)
onnx.save(ma_60_onx,&class="macro">#x27;eurchf_ma_60_model.onnx&class="macro">#x27;)
onnx.save(ma_5_height_onx,&class="macro">#x27;eurusd_ma_5_height_model.onnx&class="macro">#x27;)
onnx.save(ma_60_height_onx,&class="macro">#x27;eurusd_ma_60_height_model.onnx&class="macro">#x27;)

MQL5中的最终更新

让我们应用新模型,看看它们能否帮助我们过滤掉市场中的虚假突破信号。我们需要进行的第一个更新是导入刚刚创建的ONNX模型。 接下来,我们需要创建一些与模型相关的新变量。 我们必须扩展初始化程序,使其现在能够设置好ONNX模型。 如果系统不再使用,我们应该释放不再需要的资源。 当我们接收到更新后的价格时,与之前相比此处唯一的重大区别在于,我们还将尝试从AI模型中获取预测结果。 我们需要在MQL5中定义一个函数,该函数负责从ONNX模型中获取预测结果。 我们最后所做的更改会影响策略选择交易的方式。现在,我们的策略不会再一味地盲目入场,而是会根据其所学到的价格与移动平均线之间的关系来执行交易。我们的交易应用程序现在具有灵活性,即使交易方向与我们认为的市场趋势相反,它也可以进行买卖操作。 请注意,这里调用了一个新函数“valid setup”,该函数仅在突破条件成立时返回“true”。 检查我们是否已突破通道。如果已突破,该函数将返回“true”,否则返回“false”。 我相信到现在为止,您应该已经对我们将在回测中指定的设置有所了解。请记住,保持这些设置的一致性非常重要,这样我们才能将盈利能力的变化与我们对交易规则所做的修改关联起来,从而进行准确分析。 图例20:我们将用于对最新交易策略进行回测的部分设置。 请回想一下,我们的模型仅训练至2019年,但是测试是从2020年开始的。因此,我们正在紧密模拟如果我们过去设计了这个系统,实际会发生的情况。 图例21:我们将用于对最新交易策略进行回测的第二批设置。 同样地,我们在所有三次测试中使用的设置都是相同的。 图例22:我们在最后一次测试中用于控制应用程序的设置。 现在,我们可以看到基于模型的突破交易应用程序在欧元兑美元货币对上的实际运行情况。请记住,在训练模型时,这些数据均未展示给模型。 图例23:我们基于模型的突破策略最终版本的实际运行情况。 从下图23中可见,我们最终成功纠正了模型自始至终存在的负收益斜率特征,现在策略开始实现盈利增长。 图例24:我们基于新模型的策略进行回测的结果。 我们的目标是提高平均利润,同时降低亏损交易的比例,当前已经做到了这一点。在首次测试中,我们的总亏损为498美元,第二次测试中为403美元,而如今已降至298美元。与此同时,首次测试的总利润为378美元,而在本次最终测试中则为341美元。显然,我们所做的修改在保持总利润几乎不变的同时,降低了总亏损。在最初的系统中,我们70%的交易都是亏损的。然而,在新系统中,只有55%的交易是亏损的。 图例25:我们基于模型的策略的详细回测结果。 [CODE] <span class="comment">//+------------------------------------------------------------------+</span> <span class="comment">//|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&n

「把通道线和ONNX模型一次性挂上MT5」

在EA初始化阶段,先把EURUSD的M30前一根K线低点读进channel_low,再用OBJ_HLINE画出Channel High与Channel Low两条水平线,方便肉眼对照价格是否触碰通道边界。外汇与贵金属杠杆高,画线只是辅助,不代表突破就一定会延续。 成交量按lot_multiple乘以SYMBOL_VOLUME_MIN计算,避免手填手数踩到券商最小交易单位。技术指标端拉了14期ATR和两条自定义周期的EMA(快/慢线均取收盘价),为后续过滤信号做准备。 AI部分直接把四个ONNX模型从内存buffer载入:ma_5、ma_5_height、ma_60、ma_60_height,输入形状固定为{1,5}、输出{1,1}。用for循环遍历这四个句柄,逐个校验INVALID_HANDLE并强行设置输入输出维度,任何一步失败就Comment报错,不静默跳过。 退出时OnDeinit里必须IndicatorRelease掉ATR和两条MA句柄,并OnnxRelease四个模型,否则MT5反复加载EA会漏内存句柄。开MT5把这段贴进 OnInit/OnDeinit,编译后看专家日志有无‘Wrong input shape’就能验证模型维度是否对齐。

MQL5 / C++
  channel_low   = iLow("EURUSD",PERIOD_M30,class="num">1);
  ObjectCreate(class="num">0,"Channel High",OBJ_HLINE,class="num">0,class="num">0,channel_high);
  ObjectCreate(class="num">0,"Channel Low",OBJ_HLINE,class="num">0,class="num">0,channel_low);
class=class="str">"cmt">//--- Our trading volums
  vol = lot_multiple * SymbolInfoDouble("EURUSD",SYMBOL_VOLUME_MIN);
class=class="str">"cmt">//--- Our technical indicators
  atr_handler   = iATR("EURUSD",PERIOD_CURRENT,class="num">14);
  ma_fast       = iMA("EURUSD",PERIOD_CURRENT,ma_f_period,class="num">0,MODE_EMA,PRICE_CLOSE);
  ma_slow       = iMA("EURUSD",PERIOD_CURRENT,ma_s_period,class="num">0,MODE_EMA,PRICE_CLOSE);
class=class="str">"cmt">//--- Setup our ONNX models
class=class="str">"cmt">//--- Define our ONNX model
  class="type">ulong input_shape [] = {class="num">1,class="num">5};
  class="type">ulong output_shape [] = {class="num">1,class="num">1};
class=class="str">"cmt">//--- Create the model
  ma_5_model = OnnxCreateFromBuffer(eurusd_ma_5_buffer,ONNX_DEFAULT);
  ma_60_model = OnnxCreateFromBuffer(eurusd_ma_60_buffer,ONNX_DEFAULT);
  ma_5_height_model = OnnxCreateFromBuffer(eurusd_ma_5_height_buffer,ONNX_DEFAULT);
  ma_60_height_model = OnnxCreateFromBuffer(eurusd_ma_60_height_buffer,ONNX_DEFAULT);
class=class="str">"cmt">//--- Store our models in a list
  class="type">long onnx_models[] = {ma_5_model,ma_5_height_model,ma_60_model,ma_60_height_model};
class=class="str">"cmt">//--- Loop over the models and set them up
  for(class="type">int i = class="num">0; i < class="num">4; i++)
   {
     if(onnx_models[i] == INVALID_HANDLE)
      {
       Comment("Failed to load AI module correctly: Invalid handle");
      }
     class=class="str">"cmt">//--- Validate I/O
     if(!OnnxSetInputShape(onnx_models[i],class="num">0,input_shape))
      {
       Comment("Failed to set input shape correctly:  Wrong input shape ",GetLastError()," Actual shape: ",OnnxGetInputCount(ma_5_model));
      }
     if(!OnnxSetOutputShape(onnx_models[i],class="num">0,output_shape))
      {
       Comment("Failed to load AI module correctly: Wrong output shape ",GetLastError()," Actual shape: ",OnnxGetOutputCount(ma_5_model));
      }
   }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert deinitialization function                                 |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnDeinit(class="kw">const class="type">int reason)
  {
class=class="str">"cmt">//--- Free the resources we don&class="macro">#x27;t need
   IndicatorRelease(atr_handler);
   IndicatorRelease(ma_fast);
   IndicatorRelease(ma_slow);
   OnnxRelease(ma_5_model);
   OnnxRelease(ma_5_height_model);
   OnnxRelease(ma_60_model);

◍ 用 OnTick 驱动 ONNX 双周期推理

EA 的主循环放在 OnTick 里,但不是每笔报价都重算。用 static datetime timestamp 记录上一根 K 线时间,只有 iTime(Symbol(),PERIOD_CURRENT,0) 变化时,才更新系统变量、跑模型预测和找形态,避免一秒几十次空转消耗 CPU。 无持仓时(PositionsTotal()==0)state 归零并调用 find_setup 扫描入场;有持仓则转去 manage_setup 做风控和移动止损。这套状态机把「找机会」和「管仓位」拆开,逻辑不容易缠死。 model_predict 是推理核心:快线模型吃 H1 的 EURUSD 开高低收加上快均线和慢均线数值,组成 vectorf 喂给 OnnxRun。注意 ma_60_model 用的是 ONNX_DEFAULT,其余三个显式指定 ONNX_DATA_TYPE_FLOAT,类型不匹配可能在 MT5 里直接抛错。 外汇与贵金属属高风险品种,ONNX 预测仅提供概率倾向,实盘前务必在策略测试器用至少 3 个月 Tick 数据回测,确认推理延迟和信号频率可接受。

MQL5 / C++
class="type">void OnTick()
  {
class=class="str">"cmt">//--- Keep track of time
   class="kw">static class="type">class="kw">datetime timestamp;
   class="type">class="kw">datetime time = iTime(Symbol(),PERIOD_CURRENT,class="num">0);
   if(timestamp != time)
     {
      class=class="str">"cmt">//--- Time Stamp
      timestamp = time;
      class=class="str">"cmt">//--- Update system variables
      update();
      class=class="str">"cmt">//--- Make a new prediction
      model_predict();
      if(PositionsTotal() == class="num">0)
        {
         state = class="num">0;
         find_setup();
        }
     }
class=class="str">"cmt">//--- If we have positions open
   if(PositionsTotal() > class="num">0)
     manage_setup();
  }

class="type">void model_predict(class="type">void)
  {
   class=class="str">"cmt">//--- Moving average inputs
   class="type">float  a = (class="type">float) ma_f[class="num">0];
   class="type">float  b = (class="type">float) ma_s[class="num">0];
   class=class="str">"cmt">//--- Price quotes
   class="type">float op = (class="type">float) iOpen("EURUSD",PERIOD_H1,class="num">0);
   class="type">float hi = (class="type">float) iHigh("EURUSD",PERIOD_H1,class="num">0);
   class="type">float lo = (class="type">float) iLow("EURUSD",PERIOD_H1,class="num">0);
   class="type">float cl = (class="type">float) iClose("EURUSD",PERIOD_H1,class="num">0);
   class=class="str">"cmt">//--- ONNX inputs
   vectorf fast_inputs = {op,hi,lo,cl,a};
   vectorf slow_inputs = {op,hi,lo,cl,b};
   Print("Fast inputs: ",fast_inputs);
   Print("Slow inputs: ",slow_inputs);
   class=class="str">"cmt">//--- Inference
   OnnxRun(ma_5_model,ONNX_DATA_TYPE_FLOAT,fast_inputs,ma_5_forecast);
   OnnxRun(ma_5_height_model,ONNX_DATA_TYPE_FLOAT,fast_inputs,ma_5_height_forecast);
   OnnxRun(ma_60_model,ONNX_DEFAULT,slow_inputs,ma_60_forecast);
   OnnxRun(ma_60_height_model,ONNX_DATA_TYPE_FLOAT,fast_inputs,ma_60_height_forecast);
  }

双模型共振下的 EURUSD 自动进出场

这段逻辑把 60 周期与 5 周期两条预测带叠加使用:当收盘价 c 同时低于两条带的上沿(ma_x_forecast[0] + ma_x_height_forecast[0]),视为双模型同判下行,触发买入;反之同判上行则触发卖出。注意这里买卖方向与「预测带」的相对位置是反的——价格低于预测带上沿反而开多,属于均值回归类假设,实盘前需在 MT5 策略测试器用 2023 年 EURUSD H1 数据回测确认信号质量。

MQL5 / C++
if((c < (ma_60_forecast[class="num">0] + ma_60_height_forecast[class="num">0])) && (c < (ma_5_forecast[class="num">0] + ma_5_height_forecast[class="num">0])))
  {
   if(last_trade_state != class="num">1)
     {
      Trade.Buy(vol,"EURUSD",ask,class="num">0,class="num">0,"Volatility Doctor");
      initial_sl = channel_low;
      last_trade_state = class="num">1;
      last_cross_over_state = current_state;
     }
  }
class=class="str">"cmt">//--- Both models are forecasting falling prices
if((c > (ma_60_forecast[class="num">0] + ma_60_height_forecast[class="num">0])) && (c > (ma_5_forecast[class="num">0] + ma_5_height_forecast[class="num">0])))
  {
   if(last_trade_state != -class="num">1)
     {
      Trade.Sell(vol,"EURUSD",bid,class="num">0,class="num">0,"Volatility Doctor");
      initial_sl = channel_high;
      last_trade_state = -class="num">1;
      last_cross_over_state = current_state;
     }
  }
  }
class=class="str">"cmt">//+---------------------------------------------------------------+
class=class="str">"cmt">//| Do we have a valid setup?                                     |
class=class="str">"cmt">//+---------------------------------------------------------------+
class="type">bool valid_setup(class="type">void)
  {
   class="kw">return(((confirmation == class="num">1) && (bias == -class="num">1) && (current_state != last_cross_over_state)) || ((confirmation == class="num">1) && (bias == class="num">1) && (current_state != last_cross_over_state)));
  }
代码逐行拆解:第 1 行判断 c 是否同时低于 60 与 5 预测带上沿;第 2–3 行进入代码块并检测 last_trade_state 不等于 1(避免重复开多);第 4 行以 ask 价买入 vol 手 EURUSD,备注“Volatility Doctor”;第 5 行把初始止损设为通道下轨 channel_low;第 6–7 行更新交易状态与交叉状态。下方对称段为双模型看多时做空,initial_sl 取 channel_high。最后 valid_setup() 要求 confirmation==1 且 bias 与方向匹配、且当前状态不同于上次交叉状态才认作有效设置。 外汇与贵金属属高杠杆品种,这套逻辑在震荡市可能频繁止损,建议先把 vol 设为 0.01 在演示账户跑两周再谈参数微调。

MQL5 / C++
if((c < (ma_60_forecast[class="num">0] + ma_60_height_forecast[class="num">0])) && (c < (ma_5_forecast[class="num">0] + ma_5_height_forecast[class="num">0])))
  {
   if(last_trade_state != class="num">1)
     {
      Trade.Buy(vol,"EURUSD",ask,class="num">0,class="num">0,"Volatility Doctor");
      initial_sl = channel_low;
      last_trade_state = class="num">1;
      last_cross_over_state = current_state;
     }
  }
class=class="str">"cmt">//--- Both models are forecasting falling prices
if((c > (ma_60_forecast[class="num">0] + ma_60_height_forecast[class="num">0])) && (c > (ma_5_forecast[class="num">0] + ma_5_height_forecast[class="num">0])))
  {
   if(last_trade_state != -class="num">1)
     {
      Trade.Sell(vol,"EURUSD",bid,class="num">0,class="num">0,"Volatility Doctor");
      initial_sl = channel_high;
      last_trade_state = -class="num">1;
      last_cross_over_state = current_state;
     }
  }
  }
class=class="str">"cmt">//+---------------------------------------------------------------+
class=class="str">"cmt">//| Do we have a valid setup?                                     |
class=class="str">"cmt">//+---------------------------------------------------------------+
class="type">bool valid_setup(class="type">void)
  {
   class="kw">return(((confirmation == class="num">1) && (bias == -class="num">1) && (current_state != last_cross_over_state)) || ((confirmation == class="num">1) && (bias == class="num">1) && (current_state != last_cross_over_state)));
  }

「记住这一条就够了」

突破行情在欧元兑美元盘面上确实可能是一天里性价比最高的交易窗口,但识别难度也摆在那:假突破和外溢噪音会吃掉多数盲目跟单的账户。我们用三版实现递进验证了这一点——从基础多时间框架通道(MTF_Channel_2.mq5,10.55 KB)到引入基准货币确认(MTF_Channel_2_EURUSD.mq5,12.79 KB),再到挂上四个 ONNX 模型做 AI 过滤(MTF_Channel_2_EURUSD_AI.mq5,16.18 KB,配套模型每个仅 0.28 KB)。 五个周期参数里只取了 5 和 60 两根均线,作者本人在评论区也承认这并非历史优化出的极值,只是另一条研究线的引子。外汇与贵金属杠杆高、滑点凶,直接把这套 EA 丢真仓等于赌参数。 真要落地,先开 MT5 把那三个 mq5 依次回测,重点看 AI 版相较基础版在 EURUSD 的回撤是否真收窄;模型文件体积小,意味着结构极简,过拟合概率低但也别神话。思路比代码值钱,剩下的调参功夫得你自己往里砸时间。

常见问题

把同一段行情分别用裸价和均线特征跑回归,对比样本外 R² 和方向命中率;均线在欧元兑美元小时图常见把命中率抬到 55% 以上时,才值得换特征。
用 onnxruntime 在终端脚本里加载 .onnx 文件,每根 K 线喂入相同预处理后的特征;注意终端里只做推理不训练,模型文件要放对数据目录。
可以,小布能接入你的 ONNX 推理结果和通道线状态,在 EURUSD 品种页直接标出双周期共振的潜在突破点,省去手动挂指标的重复劳动。
能,写一段启动脚本同时加载通道画线对象和模型推理模块即可;两个逻辑共用 OnTick 触发,避免重复订阅行情造成延迟。
进场后把止损初设在通道外侧一根 AT 外,破通道且模型反向概率超阈值才平仓;外汇高风险,参数需自己在历史里先跑一遍验证。