您应该知道的 MQL5 向导技术(第 02 部分):Kohonen 映射·进阶篇
🧠

您应该知道的 MQL5 向导技术(第 02 部分):Kohonen 映射·进阶篇

(2/3)· 多数交易者误把 functor 当二维图像,却忽略了直线映射才是盯盘降噪的关键切口

含代码示例 第 2/3 篇
把自组织映射直接当成二维热力图来用的交易者不在少数,结果把 functor 维度硬凑成平面,反而丢了时序方向。Kohonen 映射对行情数据真正有用的地方,是把高维 feed 压到一维直线再学右侧未知。用错维度,模型学到的就不是市场结构而是你自己的图形偏见。

网络骨架的 C++ 类封装

在 MT5 里用面向对象方式搭神经网络,第一步是把「输入层—输出层」这对关系塞进一个哈希映射容器。下面这段类定义就是干这个的:继承 CHashMap,键是输入层指针,值是输出层指针。 构造函数里一口气 new 出输入层、输出层、键值对,并立刻 Add 进容器;同时预建三个神经元指针(i_neuron、o_neuron、best_neuron),后者通常用来在训练后存最优神经元。析构函数则负责释放 i 和 o 两层对象,避免 EA 反复加载卸载时漏内存。 开 MT5 自建 EA 时,可直接把这段类声明贴进头文件,再补一层权重矩阵就能跑最简单的感知机。外汇与贵金属波动高、滑点随机,这类模型仅作概率参考,实盘前务必用策略测试器跑足样本。

MQL5 / C++
class Cnetwork : class="kw">public CHashMap<Cinput_layer*,Coutput_layer*>
{
   class="kw">public:

   Cinput_layer       *i;
   Coutput_layer      *o;

   CKeyValuePair
   <
   Cinput_layer*,
   Coutput_layer*
   >                  *io;

   Cneuron            *i_neuron;
   Cneuron            *o_neuron;

   Cneuron            *best_neuron;

   Cnetwork()         {
                     i = new Cinput_layer();
                     o = new Coutput_layer();
                     io = new CKeyValuePair<Cinput_layer*,Coutput_layer*>(i,o);
                     Add(io);

                     i_neuron = new Cneuron();
                     o_neuron = new Cneuron();

                     best_neuron = new Cneuron();
                     };

   ~Cnetwork()        {
                     class="kw">delete i;
                     class="kw">delete o;

◍ 神经单元对象的析构与端口读写接口

这段 C++ 风格的类实现片段,把几个堆上申请的神经元指针在作用域收尾时统一释放:io、i_neuron、o_neuron、best_neuron 全部走 delete,避免 MT5 自定义指标或 EA 长时间跑下来内存泄漏。 对外暴露的虚函数才是实战里常碰的:GetInputSize 返回输入向量维度 i.size,GetOutputIndex 取 o.index,SetOutputIndex 改完再 TrySetValue 回写。GetOutputSize / SetOutputSize 同理,用 o.size 控制输出层宽度。 跑回归或特征工程时,若你发现某次前向计算后输入长度对不上,优先在 TryGetValue(i,o) 这步下断点——它负责从存储结构把 i、o 两个结构体同步进来,很多维度错乱都出在同步时机而非算法本身。外汇与贵金属行情采样频率高,这类对象频繁创建销毁,建议直接在 MT5 策略测试器里挂一段最小复现看内存曲线。

MQL5 / C++
              class="kw">delete io;
              class="kw">delete i_neuron;
              class="kw">delete o_neuron;
              class="kw">delete best_neuron;
             };

  class="kw">virtual class="type">int     GetInputSize()
              {
                TryGetValue(i,o);
                class="kw">return(i.size);
              };

  class="kw">virtual class="type">int     GetOutputIndex()
              {
                TryGetValue(i,o);
                class="kw">return(o.index);
              };

  class="kw">virtual class="type">void    SetOutputIndex(const class="type">int Index)
              {
                TryGetValue(i,o);
                o.index=Index;
                TrySetValue(i,o);
              };

  class="kw">virtual class="type">int     GetOutputSize()
              {
                TryGetValue(i,o);
                class="kw">return(o.size);
              };

  class="kw">virtual class="type">void    SetOutputSize(const class="type">int Size)
              {
                TryGetValue(i,o);
                o.size=Size;

「自组织映射里的网络容器与神经元读写」

这段定义紧接前文的网络结构,给出一个 Cmap 类作为自组织映射(SOM)的容器,内部只挂了一个 Cnetwork 指针,意味着映射层不直接存权重,而是委托给具体的网络对象去管。 类里有两个静态成员:radius 作为常量是邻域半径基准,time 是动态衰减的计时变量。在 SOM 训练里,邻域大小通常随 time 增大而收缩,所以这两个值直接决定了神经元更新的空间范围与节奏。 前面的 Cnetwork 片段给出了神经元存取的虚函数实现:GetInNeuron 和 GetOutNeuron 先从 i、o 两个容器 TryGetValue 拿到当前层对象,再按 NeuronIndex 取具体神经元引用;SetInNeuron / SetOutNeuron 则反向写回。这种双层 Try 调用避免了越界崩溃,但也意味着每次读写都有容器查找开销。 在 MT5 里跑这套时,可以把 radius 初值设成网格边长的一半(例如 5×5 网格给 2.5),观察 time 步进后分类边界是否过早收敛。外汇与贵金属行情序列做 SOM 聚类本身是高风险的探索,结论只代表历史样本的可能结构,不构成方向判断。

MQL5 / C++
 o.Capacity(Size);
 TrySetValue(i,o);
 };
 
 class="kw">virtual class="type">void GetInNeuron(const class="type">int NeuronIndex)
 {
 TryGetValue(i,o);
 i.TryGetValue(NeuronIndex,i_neuron);
 };
 
 class="kw">virtual class="type">void GetOutNeuron(const class="type">int NeuronIndex)
 {
 TryGetValue(i,o);
 o.TryGetValue(NeuronIndex,o_neuron);
 };
 
 class="kw">virtual class="type">void SetInNeuron(const class="type">int NeuronIndex)
 {
 i.TrySetValue(NeuronIndex,i_neuron);
 };
 
 class="kw">virtual class="type">void SetOutNeuron(const class="type">int NeuronIndex)
 {
 o.TrySetValue(NeuronIndex,o_neuron);
 };
};
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| | 
class=class="str">"cmt">//+------------------------------------------------------------------+
class Cmap
 {
 class="kw">public:
 
 Cnetwork *network;
 
 class="kw">static const class="type">class="kw">double radius;
 class="kw">static class="type">class="kw">double time;
 

Cmap 结构体的初始状态设定

自组织映射(SOM)封装类里,Cmap 结构体先把量化误差 QE 和拓扑误差 TE 两个代理变量摆出来,作为后续训练质量的观测锚点。QE 衡量样本被映射到最近神经元后的失真程度,TE 反映最佳匹配神经元与次佳匹配神经元在网格上的距离偏离。 构造函数 Cmap() 里给了一组冷启动默认值:network 指针新建一个 Cnetwork 实例,initialised 置 false,time 归零,QE 预设 0.50、TE 预设 5000.0,refreshed 时间戳设为 1970.01.05。这组数字不是随便填的——QE=0.50 代表未训练时中等失真水平,TE=5000.0 则故意放得极大,方便在首轮训练前用 if(TE>1000) 之类判断直接跳过有效性检查。 开 MT5 把这段结构体抄进 EA 的 include 头文件,编译后在下单前打印 QE、TE 初始值,能确认你的映射对象没被其他模块意外改写。外汇与贵金属杠杆高,任何特征工程里的静默初始化错误都可能放大实盘风险。

MQL5 / C++
class="type">class="kw">double                QE;       class=class="str">"cmt">//proxy for Quantization Error
class="type">class="kw">double                TE;       class=class="str">"cmt">//proxy for Topological Error

 class="type">class="kw">datetime              refreshed;

 class="type">bool                  initialised;

 Cmap()                {
                        network = new Cnetwork();
                        initialised=false;
                        time=class="num">0.0;
                        QE=class="num">0.50;
                        TE=class="num">5000.0;
                        refreshed=D&class="macro">#x27;class="num">1970.01.class="num">05&class="macro">#x27;;
                       };

 ~Cmap()               {

◍ 清零结构体与静态常量初始化

在 Cmap 类析构或重置逻辑里,连续调用 ZeroMemory 把多个数组成员和标志位清空,避免上一轮 tick 的残留数据污染下一轮计算。被清零的包括 initialised、time、QE、TE、refreshed,都是该类内部用于记录状态与缓冲的字段。 ZeroMemory 是 MQL5 里按字节填零的底层函数,对 double 数组会置为 0.0,对 bool 或整型标志会置为 false/0。若不在每轮重算前做这一步,跨周期回测时可能出现‘假信号’——比如 refreshed 没清零导致某根 K 线被重复处理。 类外紧接着定义了两个静态成员:radius 直接取宏 IN_RADIUS;time 则用 10000/log(IN_RADIUS) 做归一化,且用 fmax(1.0, ...) 兜底防止半径为 1 时除零。假设 IN_RADIUS=10,time 静态值约为 10000/2.3026≈4342.94,这个常量会参与后续时间衰减类的距离计算。

MQL5 / C++
ZeroMemory(initialised);
ZeroMemory(time);
ZeroMemory(QE);
ZeroMemory(TE);
ZeroMemory(refreshed);
};
};
const class="type">class="kw">double Cmap::radius=IN_RADIUS;
class="type">class="kw">double Cmap::time=class="num">10000/fmax(class="num">1.0,log(IN_RADIUS));

「训练循环里的半径与拓扑误差代理」

神经元训练本质是在输出层调整已有 functor 权重,并按竞争学习补入新神经元。权重调整比率里,最敏感的是迭代次数:每迭代一次就重算一个更小的半径(这里叫 functor 误差,本质是用欧氏距离量的邻域半径),迭代越多该值越小,网络贴合度倾向更好。学习速率要从接近 1 逐渐衰减到 0,否则后期会过冲。 拓扑误差在 Kohonen 映射里是衡量输出层离长期目标远近的关键量。原文用 functor 误差充当它的代理——保留更多输出层神经元,就倾向更接近目标映射。 下面这段训练函数把上述逻辑落到了代码。初始化时 TE 置 0、迭代计数和 functor_error 累加器清 0,学习速率取 m_training_rate。 while 循环跑满 m_training_iterations 次:每次用 GetTrainingRadius 拿当前半径,遍历输出神经元算欧氏 functor 误差,小于半径才累加误差并依重映射半径调权重;每轮学习速率按 exp(-iteration/总迭代) 衰减。循环后把训练样本写进新输出神经元(扩容输出层),若有匹配则 functor_error 求平均并乘 IN_RADIUS 赋给 Map.TE。 开 MT5 把 IN_RADIUS 和 m_training_iterations 调小一档,观察 Map.TE 的收敛速度变化,能直观验证迭代次数对拓扑贴合的敏感度。外汇与贵金属市场波动剧烈,此类自组织映射仅作概率性辅助,实盘需警惕模型过拟合高风险。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                                  |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void CSignalKM::NetworkTrain(Cmap &Map,Cneuron &TrainNeuron)
  {
   Map.TE=class="num">0.0;                     class=class="str">"cmt">// 拓扑误差代理清零
   class="type">int _iteration=class="num">0;               class=class="str">"cmt">// 迭代计数器
   class="type">class="kw">double _training_rate=m_training_rate; class=class="str">"cmt">// 学习速率初始值(接近1)
   class="type">int _err=class="num">0;                     class=class="str">"cmt">// 匹配神经元计数
   class="type">class="kw">double _functor_error=class="num">0.0;      class=class="str">"cmt">// functor误差累加
   while(_iteration<m_training_iterations) class=class="str">"cmt">// 跑满设定迭代次数
     {
      class="type">class="kw">double _current_radius=GetTrainingRadius(Map,_iteration); class=class="str">"cmt">// 当前邻域半径随迭代缩小
      for(class="type">int i=class="num">0; i<=Map.network.GetOutputIndex(); i++) class=class="str">"cmt">// 遍历已有输出神经元
        {
         Map.network.GetOutNeuron(i);
         class="type">class="kw">double _error = EuclideanFunctor(TrainNeuron,Map.network.o_neuron); class=class="str">"cmt">// 算欧氏functor误差
         if(_error<_current_radius) class=class="str">"cmt">// 在半径内才调整
           {
            _functor_error+=(_error);
            _err++;
            class="type">class="kw">double _remapped_radius = GetRemappedRadius(_error, _current_radius);
            SetWeights(TrainNeuron,Map.network.o_neuron,_remapped_radius,_training_rate); class=class="str">"cmt">// 按重映射半径调权重
            Map.network.SetOutNeuron(i);
           }
        }
      _iteration++;
      _training_rate=_training_rate*exp(-(class="type">class="kw">double)_iteration/m_training_iterations); class=class="str">"cmt">// 学习速率指数衰减到0
     }
   class="type">int
   _size=Map.network.GetOutputSize(),
   _index=Map.network.GetOutputIndex();
   Map.network.SetOutputIndex(_index+class="num">1); class=class="str">"cmt">// 新增一个输出神经元位置
   if(_index+class="num">1>=_size)
     {
      Map.network.SetOutputSize(_size+OUT_BUFFER); class=class="str">"cmt">// 容量不足则扩容
     }
   Map.network.GetOutNeuron(_index+class="num">1);
   for(class="type">int w=class="num">0; w<IN_WIDTH; w++) class=class="str">"cmt">// 拷贝训练神经元输入 functor 到新神经元
     {
      Map.network.o_neuron.fd.Set(w,TrainNeuron.fd.Get(w));
     }
   for(class="type">int l=class="num">0; l<OUT_LENGTH; l++) class=class="str">"cmt">// 拷贝输出 functor
     {
      Map.network.o_neuron.fr.Set(l,TrainNeuron.fr.Get(l));
     }
   Map.network.SetOutNeuron(_index+class="num">1);
   if(_err>class="num">0) class=class="str">"cmt">// 有匹配才计算平均functor误差并赋给TE
     {
      _functor_error/=_err;
      Map.TE=_functor_error*IN_RADIUS;
     }
  }
把重复训练交给小布
这些 Kohonen 单层映射的迭代训练和距离计算,小布盯盘的 AIGC 已内置,打开对应品种页即可看到降维后的神经元权重分布,你只管读形态做决策。

常见问题

它只有单层 functor 神经元且全部直连 feed 层,不互相传递权重影响,靠每次训练迭代自组织,而非多层反向传播。
价格序列的未知侧 functor 本质是时序单向延展,压成直线能保留方向性,二维网格容易把右侧未知强行空间化导致误读。
可以,小布已内置对应品种的映射权重视图,省去自己写信号文件里 Dimension 类的过程,打开页内诊断即可。
任意时刻左侧已知收盘价序列作 feed,右侧待演化行情作 functor,维数由你分类方式定,贵金属与外汇均属高风险品类需谨慎对待概率判断。
训练后每个 functor 神经元权重维度固定,距离近的神经元代表相似市场状态,可用于聚类当下行情倾向。