您应当知道的 MQL5 向导技术(第 60 部分):推理学习(Wasserstein-VAE),配合移动平均线和随机振荡器形态·综合运用
🧠

您应当知道的 MQL5 向导技术(第 60 部分):推理学习(Wasserstein-VAE),配合移动平均线和随机振荡器形态·综合运用

(3/3)· 从 EURUSD 训好的模型到 EURJPY 的轻量复用,VAE 编码器如何把多品种知识压成易存文件

案例拆解 第 3/3 篇

不少交易者以为在 EURUSD 上训好的形态模型换个品种就能直接跑,结果噪声放大、回撤失控。推理学习不是简单复制权重,而是用编码器把已有知识压缩后再适配新市场。这篇收尾把前面监督与强化的铺垫落到跨品种迁移的实操边界。

在 MT5 里用正态方程跑线性回归映射

做推理模型时,我们只从潜在层取回归函数,把缺失输入映射回来,而不是像早先那样把训好的网络导成 ONNX。原因很直接:我们关心的是已经训完的 VAE 输入数据集,手里只剩特征数据,问题就变成基于特征推状态、动作和奖励。 初始化智能系统时要准备配对的特征码、状态码、动作码、奖励码数据集,拿来训一个线性回归模型。之后任何新特征点都能先映射成编码,再在同一模型里反推状态、动作、奖励——这步连接拟合走的是无监督。 MQL5 实现里,类用 m_coefficients / m_intercept 存一维系数,m_coefficients_2d / m_intercept_2d 存二维,矩阵代数批处理提速,单输出多输出都覆盖。Fit 用正态方程 (X'X)^-1 X'y 直接解,乖离时在特征里加一列 1;Fit2d 专用化靠矩阵运算同批处理多个输出。预测走点积,内存预先分配结果向量。 我们加载 EURUSD 日线 2023.01.01–2025.01.01 共两年数据,80% 训练 20% 测试,VAE 出编码后再拟合线性回归。前向漫游测试约在 2025.01.01 前五月,我们拉长到 2024.07.01–2025.01.01 共 6 个月:10 种形态里只有 1、2、5 过测,细看似乎仅形态-1 和形态-5 在两年窗口内有推理能力。状态绑 0–1、动作同区间、奖励 -1 到 +1,但部署时网络输出常溢出绑定,本文不归一化,只提醒生产环境要留意。外汇与贵金属属高风险,这类映射仅作概率参考。

MQL5 / C++
<span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span>
<span class="comment">class=class="str">"cmt">// Linear Regressor(unchanged from previous implementation)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; |</span>
<span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span>
<span class="keyword">class</span> LinearRegressor
{
<span class="keyword">class="kw">private</span>:
&nbsp;&nbsp; <span class="keyword">vector</span> m_coefficients;
&nbsp;&nbsp; <span class="keyword">class="type">class="kw">double</span> m_intercept;
&nbsp;&nbsp; <span class="keyword">matrix</span> m_coefficients_2d;
&nbsp;&nbsp; <span class="keyword">vector</span> m_intercept_2d;
<span class="keyword">class="kw">public</span>:
&nbsp;&nbsp; <span class="keyword">class="type">void</span> Fit(<span class="keyword">const</span> <span class="keyword">matrix</span> &amp;X, <span class="keyword">const</span> <span class="keyword">vector</span> &amp;y)
&nbsp;&nbsp; {&nbsp;&nbsp;<span class="keyword">class="type">int</span> n = (<span class="keyword">class="type">int</span>)X.Rows();
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">class="type">int</span> p = (<span class="keyword">class="type">int</span>)X.Cols();
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">matrix</span> X_with_bias(n, p + <span class="number">class="num">1</span>);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">for</span>(<span class="keyword">class="type">int</span> i = <span class="number">class="num">0</span>; i &lt; n; i++)
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{&nbsp;&nbsp;<span class="keyword">for</span>(<span class="keyword">class="type">int</span> j = <span class="number">class="num">0</span>; j &lt; p; j++)
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;X_with_bias[i][j] = X[i][j];
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; X_with_bias[i][p] = <span class="number">class="num">1.0</span>;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">matrix</span> Xt = X_with_bias.Transpose();
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">matrix</span> XtX = Xt.MatMul(X_with_bias);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">matrix</span> XtX_inv = XtX.Inv();
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">vector</span> y_col = y;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;y_col.Resize(n, <span class="number">class="num">1</span>);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">vector</span> beta = XtX_inv.MatMul(Xt.MatMul(y_col));
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;m_coefficients = beta;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;m_coefficients.Resize(p);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;m_intercept = beta[p];
&nbsp;&nbsp; }
&nbsp;&nbsp; <span class="keyword">class="type">void</span> Fit2d(<span class="keyword">const</span> <span class="keyword">matrix</span> &amp;X, <span class="keyword">const</span> <span class="keyword">matrix</span> &amp;Y)
&nbsp;&nbsp; {&nbsp;&nbsp;<span class="keyword">class="type">int</span> n = (<span class="keyword">class="type">int</span>)X.Rows();&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">class=class="str">"cmt">// Number of samples</span>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">class="type">int</span> p = (<span class="keyword">class="type">int</span>)X.Cols();&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">class=class="str">"cmt">// Number of input features</span>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">class="type">int</span> k = (<span class="keyword">class="type">int</span>)Y.Cols();&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">class=class="str">"cmt">// Number of output encodings</span>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">class=class="str">"cmt">// Add bias term(column of 1s) to X</span>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">matrix</span> X_with_bias(n, p + <span class="number">class="num">1</span>);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">for</span>(<span class="keyword">class="type">int</span> i = <span class="number">class="num">0</span>; i &lt; n; i++)
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{&nbsp;&nbsp;<span class="keyword">for</span>(<span class="keyword">class="type">int</span> j = <span class="number">class="num">0</span>; j &lt; p; j++)
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;X_with_bias[i][j] = X[i][j];
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; X_with_bias[i][p] = <span class="number">class="num">1.0</span>;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">class=class="str">"cmt">// Calculate coefficients using normal equation: (X&class="macro">#x27;X)^-class="num">1 X&class="macro">#x27;Y</span>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">matrix</span> Xt = X_with_bias.Transpose();
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">matrix</span> XtX = Xt.MatMul(X_with_bias);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">matrix</span> XtX_inv = XtX.Inv();

◍ 系数拆解与多路预测器的接线方式

这段实现把最小二乘求出的 beta 矩阵拆成两部分:前 p 行是各特征的权重,最后一行是截距。代码里用两个循环分别填进 m_coefficients_2d[j][d] 和 m_intercept_2d[d],维度对齐靠 p(输入特征数)和 k(编码输出数),少一个都会让后续 Predict2d 越界。 Predict2d 的算法很直白:对每个输出维度 d 先取截距,再跑 j 从 0 到 p-1 累加系数乘输入。这种扁平双重循环在 p=20、k=8 的常见编码规模下,单次推断耗时通常在微秒级,MT5 策略里直接嵌不会有明显延迟。 WassersteinVAEPredictors 类只是把四个 LinearRegressor 实例打包:feature 用 Fit2d 吃二维编码,state/action/reward 用一维 Fit。FitPredictors 一次性训完会把 m_predictors_trained 置真,否则 PredictFromFeatures 会 Print 报错并直接 return,不会静默出脏数。 开 MT5 把这段粘进 EA 的 include 区,故意把 m_predictors_trained 改成 false 再调 PredictFromFeatures,能在日志里确认报错路径;接真实行情前先拿历史 bar 的 features 矩阵跑一遍 Fit2d 看 k 维输出是否稳定。

MQL5 / C++
  matrix beta = XtX_inv.MatMul(Xt.MatMul(Y));
  class=class="str">"cmt">// Split coefficients and intercept
  m_coefficients_2d.Resize(p, k);  class=class="str">"cmt">// Coefficients for each output encodings
  m_intercept_2d.Resize(k);        class=class="str">"cmt">// Intercept for each input feature
  for(class="type">int j = class="num">0; j < p; j++)
  {  for(class="type">int d = class="num">0; d < k; d++)
     {  m_coefficients_2d[j][d] = beta[j][d];
     }
  }
  for(class="type">int d = class="num">0; d < k; d++)
  {  m_intercept_2d[d] = beta[p][d];
  }
  }
  class="type">class="kw">double Predict(const vector &x)
  {  class="kw">return m_intercept + m_coefficients.Dot(x);
  }
  vector Predict2d(const vector &X) const
  {  class="type">int p = (class="type">int)X.Size();      class=class="str">"cmt">// Number of input features
     class="type">int k = (class="type">int)m_intercept_2d.Size(); class=class="str">"cmt">// Number of output encodings
     vector predictions(k);      class=class="str">"cmt">// vector to store predictions
     for(class="type">int d = class="num">0; d < k; d++)
     {  class=class="str">"cmt">// Initialize with intercept for this output dimension
        predictions[d] = m_intercept_2d[d];
        class=class="str">"cmt">// Add contribution from each feature
        for(class="type">int j = class="num">0; j < p; j++)
        {  predictions[d] += m_coefficients_2d[j][d] * X[j];
        }
     }
     class="kw">return predictions;
  }
};
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">// Wasserstein VAE Predictors Implementation(unchanged)               |
class=class="str">"cmt">//+------------------------------------------------------------------+
class WassersteinVAEPredictors
{
class="kw">private:
  LinearRegressor m_feature_predictor;
  LinearRegressor m_state_predictor;
  LinearRegressor m_action_predictor;
  LinearRegressor m_reward_predictor;
  class="type">bool m_predictors_trained;
class="kw">public:
  WassersteinVAEPredictors() : m_predictors_trained(false) {}
  class="type">void FitPredictors(const matrix &features,
                     const vector &states,
                     const vector &actions,
                     const vector &rewards,
                     const matrix &encodings)
  {  m_feature_predictor.Fit2d(features, encodings);
     m_state_predictor.Fit(encodings, states);
     m_action_predictor.Fit(encodings, actions);
     m_reward_predictor.Fit(encodings, rewards);
     m_predictors_trained = true;
  }
  class="type">void PredictFromFeatures(const vector &y, vector &z)
  {  if(!m_predictors_trained)
     {  Print("Error: Predictors not trained yet");
        class="kw">return;

「推理前向如何把隐变量转成多空信号」

WVAE 信号类的 Infer 方法承担推理前向:先把行情特征塞进对应周期的 VAE 实例,再经编码、预测两步拿到 state / action / reward 三个标量,最后按持仓方向做偏置裁剪。 代码里 Index 只接 1、2、5 三种值,分别对应 m_vae_1 / m_vae_2 / m_vae_5;若预测器未训练会直接 Print 报错并返回,不会往下跑。这意味着你挂 EA 前必须确认对应周期模型已 fit 完,否则推理向量全为 0。 多头分支里,state 大于 0.5 才保留:先减 0.5 再乘 2.0 做区间拉伸,若 action 小于 0 则 state 强制归零。空头对称处理,state 小于 0.5 时减 0.5 乘 -2.0,action 大于 0 同样清零。 返回的 _inference 是长度 3 的 vector,下标 0 为处理后的 state、1 为 action、2 为 reward。外汇与贵金属波动剧烈、杠杆风险高,这套推理输出只代表模型倾向,实盘前务必在 MT5 策略测试器用历史数据核验各周期命中率。

MQL5 / C++
vector CSignal_WVAE::Infer(class="type">int Index, class="type">ENUM_POSITION_TYPE T)
{  vectorf _f = Get(Index, m_time.GetData(X()), m_close, m_ma, m_ma_lag, m_sto);
   vector _features;
   _features.Init(_f.Size());
   _features.Fill(class="num">0.0);
   for(class="type">int i = class="num">0; i < class="type">int(_f.Size()); i++)
   {  _features[i] = _f[i];
   }
class=class="str">"cmt">// Make a prediction
   vector _encodings;
   _encodings.Init(__ENCODINGS);
   _encodings.Fill(class="num">0.0);
   class="type">class="kw">double _state = class="num">0.0, _action = class="num">0.0, _reward = class="num">0.0;
   if(Index == class="num">1)
   {  m_vae_1.PredictFromFeatures(_features, _encodings);
      m_vae_1.PredictFromEncodings(_encodings, _state, _action, _reward);
   }
   else if(Index == class="num">2)
   {  m_vae_2.PredictFromFeatures(_features, _encodings);
      m_vae_2.PredictFromEncodings(_encodings, _state, _action, _reward);
   }
   else if(Index == class="num">5)
   {  m_vae_5.PredictFromFeatures(_features, _encodings);
      m_vae_5.PredictFromEncodings(_encodings, _state, _action, _reward);
   }
   vector _inference;
   _inference.Init(class="num">3);
   _inference[class="num">0] = _state;
   _inference[class="num">1] = _action;
   _inference[class="num">2] = _reward;
   class=class="str">"cmt">//
   if(T == POSITION_TYPE_BUY)
   {  if(_state > class="num">0.5)
      {  _inference[class="num">0] -= class="num">0.5;
         _inference[class="num">0] *= class="num">2.0;
         if(_action < class="num">0.0)
         {  _inference[class="num">0] = class="num">0.0;
         }
      }
      else
      {  _inference[class="num">0] = class="num">0.0;
      }
   }
   else if(T == POSITION_TYPE_SELL)
   {  if(_state < class="num">0.5)
      {  _inference[class="num">0] -= class="num">0.5;
         _inference[class="num">0] *= -class="num">2.0;
         if(_action > class="num">0.0)
         {  _inference[class="num">0] = class="num">0.0;
         }
      }
      else
      {  _inference[class="num">0] = class="num">0.0;
      }
   }
   class="kw">return(_inference);
}

一点提醒

把移动平均线和随机振荡器喂给监督学习只是起点,实盘里强化学习会接着滚动推进,但两类知识目前各存各的,缺一个能统一收纳的层。推理学习不一定立刻落地,却可能补上这块拼图,它不重复前面两种训练方式,而是尝试把已学形态压缩成可复用的判断。 附带文件里 wz_60.mq5 仅 7.11 KB,SignalWZ_60.mqh 有 633.05 KB,三个 VAE 模型各 144.49 KB,在 MT5 用向导汇编时头文件必须齐,模型文件对 EA 运行并非必需。外汇与贵金属波动剧烈,这类实验性系统只适合在策略测试器里验证,实盘前先想清楚样本外失效的概率。

把跨品种诊断交给小布
这些诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到 MA 与随机振荡器形态在推理迁移后的拟合偏差,你专注判断要不要重训。

常见问题

它用编码器把已训练模型在监督与强化阶段学到的形态知识压缩,降低跨品种复用时的存储与噪声映射成本,迁移效果取决于品种波动结构相似度,可能而非必然改善。
可以,小布盯盘对应品种页内置了形态拟合偏差与白噪声水平的诊断,省去手动比对历史映射的重复劳动,但决策仍由交易者做。
同时交易多品种的风险回报方案较脆弱,计算与过拟合成本虽因 GPU 与云下降,但环境漂移会让单一模型在部分品种上快速失效,概率上不如分训加推理迁移稳。
指参考旧历史相似案例映射当前展开事件时,通过编码器过滤掉无解释力的随机扰动,让迁移知识更聚焦形态结构,外汇贵金属属高风险市场仍可能失效。