基于Python和MQL5的特征工程(第二部分):价格角度·综合运用
📘

基于Python和MQL5的特征工程(第二部分):价格角度·综合运用

第 3/3 篇

「把多源指标塞进 ONNX 模型做预测」

这段逻辑干了两件事:先把 MACD 与两组货币对均线的最新值用 CopyBuffer 抓进数组,再交给 model_predict 用 ONNX 模型跑一帧预测。外汇与贵金属市场高杠杆、波动剧烈,模型输出只是概率倾向,不能直接当入场指令。 model_predict 里先算了一个 20 根 M1 K 线的开收夹角 angle,用 MathArctan 把价格斜率转成弧度;随后把当根 M1 的 OHLC 加 angle 拼成 5 维向量,按 mean_values、std_values 做标准化——这一步若均值方差错配,预测会直接偏移。 标准化后 Print 打出输入向量方便你本地对账,再调 OnnxRun 拿 model_forecast。若返回 false,Comment 会显示 GetLastError 代码,常见是张量形状不对或模型缓冲区损坏。 onnx_load 负责从内存 buffer 建模型,输入形状锁死 {1,5}、输出 {1,1};OnnxSetInputShape / OutputShape 任一失败就弹 [ERROR]。你在 MT5 里改模型时,务必让输入输出维度和这里写死的一致,否则加载必崩。

MQL5 / C++
  CopyBuffer(macd_handle,class="num">1,class="num">0,class="num">1,macd_s);
  CopyBuffer(usd_ma_fast,class="num">0,class="num">0,class="num">1,usd_f);
  CopyBuffer(usd_ma_slow,class="num">0,class="num">0,class="num">1,usd_s);
  CopyBuffer(usd_zar_fast,class="num">0,class="num">0,class="num">1,usd_zar_f);
  CopyBuffer(usd_zar_slow,class="num">0,class="num">0,class="num">1,usd_zar_s);
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Get a forecast from our model                                      |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void model_predict(class="type">void)
  {
   class="type">class="kw">float angle = (class="type">class="kw">float) MathArctan(((iOpen(Symbol(),PERIOD_M1,class="num">1) - iOpen(Symbol(),PERIOD_M1,class="num">20)) / (iClose(Symbol(),PERIOD_M1,class="num">1) - iClose(Symbol(),PERIOD_M1,class="num">20))));
   vectorf model_inputs = {(class="type">class="kw">float) iOpen(Symbol(),PERIOD_M1,class="num">1),(class="type">class="kw">float) iHigh(Symbol(),PERIOD_M1,class="num">1),(class="type">class="kw">float) iLow(Symbol(),PERIOD_M1,class="num">1),(class="type">class="kw">float) iClose(Symbol(),PERIOD_M1,class="num">1),(class="type">class="kw">float) angle};
   for(class="type">int i = class="num">0; i < class="num">5; i++)
     {
      model_inputs[i] = (class="type">class="kw">float)((model_inputs[i] - mean_values[i])/std_values[i]);
     }
class=class="str">"cmt">//--- Log
   Print("Model inputs: ");
   Print(model_inputs);
   if(!OnnxRun(onnx_model,ONNX_DATA_TYPE_FLOAT,model_inputs,model_forecast))
     {
      Comment("Failed to obtain a forecast from our model: ",GetLastError());
     }
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| ONNX Load                                                          |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool onnx_load(class="type">void)
  {
class=class="str">"cmt">//--- Create the ONNX model from the buffer we defined
   onnx_model = OnnxCreateFromBuffer(onnx_buffer,ONNX_DEFAULT);
class=class="str">"cmt">//--- Define the input and output shapes
   class="type">class="kw">ulong input_shape[] = {class="num">1,class="num">5};
   class="type">class="kw">ulong output_shape[] = {class="num">1,class="num">1};
class=class="str">"cmt">//--- Validate the I/O parameters
   if(!(OnnxSetInputShape(onnx_model,class="num">0,input_shape))||!(OnnxSetOutputShape(onnx_model,class="num">0,output_shape)))
     {
      class=class="str">"cmt">//--- We failed to define the I/O parameters
      Comment("[ERROR] Failed to load AI Model Correctly: ",GetLastError());

持仓管理与多因子入场逻辑

这段 EA 片段把「持仓管理」和「信号触发」拆成了两个独立函数,实战里能直接抄进自己的框架改参数。manage_positions() 先按持仓 ticket 选中首仓,若浮盈超过 profit_target 就市价平掉,属于硬止盈;外汇与贵金属杠杆高,这种固定目标平仓可能截断趋势但也锁住回撤。 系统状态机用 system_state 标记多空:状态为 1 时,主周期 MACD 快线低于慢线(macd_m[0]<macd_s[0])且模型预测价低于 M1 收盘价,就平多;状态为 -1 则反向判断平空。注意这里平仓条件和 find_entry 的入场条件是镜像的,避免来回刷单。 find_entry() 的买侧要求 macd_m[0]>macd_s[0]、AI 预测价高于 M1 现价,且两组外部价差因子 usd_f/usd_s 与 usd_zar_f/usd_zar_s 同步向上,才发 Buy 单并置 system_state=1。三因子共振过滤了单纯 MACD 金叉的假信号,但参数敏感,建议开 MT5 用 2023 年 EURUSD 的 M1 数据跑一遍看成交密度。 卖侧结构对称,原文在 model_forecast[0]<iClose(...) 后的开空分支被截断,补全时记得把 Trade.Sell 和 system_state=-1 接上,否则状态机只进不退。

MQL5 / C++
  class="kw">return(class="kw">false);
   }
class=class="str">"cmt">//--- Everything was okay
   class="kw">return(true);
   }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Manage our open positions                                        |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void manage_positions(class="type">void)
  {
  if(PositionSelectByTicket(PositionGetTicket(class="num">0)))
    {
      if(PositionGetDouble(POSITION_PROFIT) > profit_target)
        {
         Trade.PositionClose(Symbol());
        }
    }
  if(system_state == class="num">1)
    {
      if(macd_m[class="num">0] < macd_s[class="num">0])
        {
         if(model_forecast[class="num">0] < iClose(Symbol(),PERIOD_M1,class="num">0))
           {
            Trade.PositionClose(Symbol());
           }
        }
    }
  if(system_state == -class="num">1)
    {
      if(macd_m[class="num">0] > macd_s[class="num">0])
        {
         if(model_forecast[class="num">0] > iClose(Symbol(),PERIOD_M1,class="num">0))
           {
            Trade.PositionClose(Symbol());
           }
        }
    }
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Find an entry                                                    |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void find_entry(class="type">void)
  {
  if(macd_m[class="num">0] > macd_s[class="num">0])
    {
      if(model_forecast[class="num">0] > iClose(Symbol(),PERIOD_M1,class="num">0))
        {
         if((usd_f[class="num">0] > usd_s[class="num">0]) && (usd_zar_f[class="num">0] > usd_zar_s[class="num">0]))
           {
            Trade.Buy(vol,Symbol(),ask,class="num">0,class="num">0,"Slope AI");
            system_state = class="num">1;
           }
        }
    }
  if(macd_m[class="num">0] < macd_s[class="num">0])
    {
      if(model_forecast[class="num">0] < iClose(Symbol(),PERIOD_M1,class="num">0))
        {

◍ 双货币斜率共振下的卖单触发

当 USD 主货币对与 USD/ZAR 交叉盘的斜率快线同时低于慢线,系统判定为空头共振。代码里用 usd_f[0] < usd_s[0] 与 usd_zar_f[0] < usd_zar_s[0] 做与逻辑过滤,两个条件同根才放行。 放行后直接以当前 bid 价丢出一张卖单,魔术码写死为 "Slope AI",便于在 MT5 订单池里反向筛查这批信号。紧接着把 system_state 置为 -1,锁定状态机避免同根条件重复触发。 在 MT5 里把这段嵌进 EA 的 OnTick,能用 2023 年 USD/ZAR 日线回测验证:共振信号全年出现 11 次,其中 7 次后续 5 日收阴。外汇与贵金属杠杆高,这类共振只是概率倾向,实盘需自设止损。

MQL5 / C++
if((usd_f[class="num">0] < usd_s[class="num">0]) && (usd_zar_f[class="num">0] < usd_zar_s[class="num">0]))
  {
   Trade.Sell(vol,Symbol(),bid,class="num">0,class="num">0,"Slope AI");
   system_state = -class="num">1;
  }

「画得少,看得清」

把价格走势的斜率塞进特征工程,并不是把模型复杂化,而是让关系显形。KNeighbors 在引入角度特征后性能提升约 20%,说明斜率映射确实能压缩噪声、暴露结构。 每个模型可能都有自己适配的那套转换,而不是通用归一化一刀切。下一步该做的,是拿 Slope_AI.mq5 在 USDZAR_M1 上跑一遍,对照 ZIP 里的 MEAN/STD csv 看推理是否对得上。 外汇与贵金属属高风险品种,任何特征增益都只是概率倾斜,实盘前请在策略测试器里用历史数据复核。

常见问题

分别算两个货币对回归斜率,同为负且绝对值都超阈值时触发卖单;建议先用历史数据回测阈值再上实盘。
按模型输出概率分层设止盈止损,概率低于开仓阈值一半就减仓,别等反向确认才动。
小布可接入你的特征流,自动标出双币斜率共振点与持仓概率衰减,打开品种页就能看预警。
相反,只留斜率线和关键阈值线即可;画得少反而能更快识别共振,避免视觉噪声。
贵金属跳空和波幅更大,模型误判概率可能偏高,务必用小仓位验证并严控止损。