在 MetaTrader 5 中使用自组织特征映射(Kohonen 映射)·进阶篇
📘

在 MetaTrader 5 中使用自组织特征映射(Kohonen 映射)·进阶篇

第 2/3 篇

◍ 自组织映射网络的初始化与训练入口

这段声明定义了一个 CSOM 类实例 KohonenMap,封装了自组织映射(SOM)常用的几个动作:Train 跑训练、Render 出图、ShowBMP 把位图贴到图表上,以及 AddVectorToTrainingSet / AddTrainPattern 往训练集里塞样本。ColToRGB 与 RGB256 负责颜色分量和整型色值互转,RGB256 里用了 r+256*g+65536*b 的线性编码,和 MT5 调色板底层一致。 OnInit 里先 MathSrand(200) 固定随机种子,保证每次加载 EA 权值初始化可复现。随后 KohonenMap.InitParameters(10000,15,20,400,400) 设定了 10000 次迭代、15×20 的神经元网格、400×400 的输出位图——这三个数直接决定训练耗时和图像分辨率,在 MT5 实盘加载前值得先在小周期回测里跑一遍看资源占用。 训练集加了 8 组 RGB 向量:纯红(255,0,0)、绿(0,128,0)、蓝(0,0,255)、深绿(0,100,0)、深蓝(0,0,139)、黄(255,255,0)、橙(255,165,0)、紫(128,0,128)。AddTrainPattern 只收三维输入,说明这个 SOM 把颜色当三维特征做聚类,不涉任何行情数据。 调用顺序很直白:加完样本就 Train(),再 Render() 生成位图,最后用 ShowPattern 把指定颜色和其文字标题画出来。外汇与贵金属图表上挂这类脚本属于高风险环境下的附加可视化,建议先在离线账户验证内存与刷新频率。

MQL5 / C++
  class=class="str">"cmt">//--- train method
  class="type">void                Train();
  class=class="str">"cmt">//--- render method
  class="type">void                Render();
  class=class="str">"cmt">//--- shows the bmp image on the chart
  class="type">void                ShowBMP(class="type">bool back);
  class=class="str">"cmt">//--- adds a vector to training set
  class="type">void                AddVectorToTrainingSet(class="type">class="kw">double &vector[]);
  class=class="str">"cmt">//--- shows the pattern title
  class="type">void                ShowPattern(class="type">class="kw">double c1,class="type">class="kw">double c2,class="type">class="kw">double c3,class="type">class="kw">string name);
  class=class="str">"cmt">//--- adds a pattern to training set
  class="type">void                AddTrainPattern(class="type">class="kw">double c1,class="type">class="kw">double c2,class="type">class="kw">double c3);
  class=class="str">"cmt">//--- returns the RGB components of the class="type">color
  class="type">void                ColToRGB(class="type">int col,class="type">int &r,class="type">int &g,class="type">int &b);
  class=class="str">"cmt">//--- returns the class="type">color by RGB components
  class="type">int                 RGB256(class="type">int r,class="type">int g,class="type">int b) const {class="kw">return(r+class="num">256*g+class="num">65536*b);    }
  class=class="str">"cmt">//--- deletes image from the chart
  class="type">void                NetDeinit();
  };
CSOM KohonenMap;
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert initialization function                                     |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnInit()
  {
  MathSrand(class="num">200);
class=class="str">"cmt">//--- initialize net, class="num">10000 iterations will be used for training
class=class="str">"cmt">//--- the net contains 15x20 nodes, bmp image size 400x400
  KohonenMap.InitParameters(class="num">10000,class="num">15,class="num">20,class="num">400,class="num">400);
class=class="str">"cmt">//-- add RGB-components of each class="type">color from training set
  KohonenMap.AddTrainPattern(class="num">255,  class="num">0,  class="num">0);   class=class="str">"cmt">// Red
  KohonenMap.AddTrainPattern(  class="num">0,class="num">128,  class="num">0);   class=class="str">"cmt">// Green
  KohonenMap.AddTrainPattern(  class="num">0,  class="num">0,class="num">255);   class=class="str">"cmt">// Blue
  KohonenMap.AddTrainPattern(  class="num">0,class="num">100,  class="num">0);   class=class="str">"cmt">// Dark green
  KohonenMap.AddTrainPattern(  class="num">0,  class="num">0,class="num">139);   class=class="str">"cmt">// Dark blue
  KohonenMap.AddTrainPattern(class="num">255,class="num">255,  class="num">0);   class=class="str">"cmt">// Yellow
  KohonenMap.AddTrainPattern(class="num">255,class="num">165,  class="num">0);   class=class="str">"cmt">// Orange
  KohonenMap.AddTrainPattern(class="num">128,  class="num">0,class="num">128);   class=class="str">"cmt">// Purple
class=class="str">"cmt">//--- train net
  KohonenMap.Train();
class=class="str">"cmt">//--- render map to bmp
  KohonenMap.Render();
class=class="str">"cmt">//--- show patterns and titles for each class="type">color
  KohonenMap.ShowPattern(class="num">255,  class="num">0,  class="num">0,"Red");

「自组织映射里的颜色训练与渲染」

在 MT5 里用自组织映射(SOM)做颜色聚类时,KohonenMap 对象先通过 ShowPattern 把若干 RGB 向量灌进显示层:例如 (0,128,0) 标为 Green、(0,0,255) 标为 Blue、(255,165,0) 标为 Orange,共 7 种基准色参与拓扑排布。随后 ShowBMP(false) 将生成的位图直接绘到图表上,不另存文件。 初始化阶段用 InitParameters(10000,15,20,400,400) 设定训练步数 10000、15×20 的节点栅格,以及 400×400 的输出图像尺寸;HexagonalCell 与 ShowBorders 这两个 input 布尔量分别控制六边形单元与边界线的绘制开关。 针对颜色场景,CSOMWeb 类继承自 CSOM 并改写两个方法:AddTrainColor 把整型颜色值经 ColToRGB 拆成 r/g/b 三通道,塞进长度为 3 的 vector 数组再送入训练集;ShowColor 则负责在对应获胜节点上标注颜色名。下面这段是 AddTrainColor 的核心实现,逐行拆开看逻辑很直白。 别把 RGB 当归一化输入 MQL5 里 ColToRGB 出来的 r/g/b 是 0–255 整数,直接进 SOM 会让数值尺度偏粗;若你后面接外汇tick颜色编码,建议先除以 255 再做距离计算,否则高通道权重会碾压低通道。

MQL5 / C++
class="type">void CSOMWeb::AddTrainColor(class="type">int col)
  {
   class="type">class="kw">double vector[];                 class=class="str">"cmt">// 声明 class="type">class="kw">double 数组,用来装 RGB 三分量
   ArrayResize(vector,class="num">3);           class=class="str">"cmt">// 把数组长度定为 class="num">3(R、G、B)
   class="type">int r=class="num">0;                         class=class="str">"cmt">// 红通道初值
   class="type">int g=class="num">0;                         class=class="str">"cmt">// 绿通道初值
   class="type">int b=class="num">0;                         class=class="str">"cmt">// 蓝通道初值
   ColToRGB(col,r,g,b);             class=class="str">"cmt">// 把整型颜色 col 拆成 r/g/b 三个整数
   vector[class="num">0]=r;                     class=class="str">"cmt">// 第 class="num">0 维放红
   vector[class="num">1]=g;                     class=class="str">"cmt">// 第 class="num">1 维放绿
   vector[class="num">2]=b;                     class=class="str">"cmt">// 第 class="num">2 维放蓝
   AddVectorToTrainingSet(vector);  class=class="str">"cmt">// 把该向量加入 SOM 训练集
   ArrayResize(vector,class="num">0);           class=class="str">"cmt">// 释放数组内存
  }

把 132 个网页色塞进自组织映射

上面那段循环把单个颜色转成 RGB 后交给 ShowPattern 显示,真正喂给网络的原料是下面这个 web_colors 数组。它一口气列了 132 个 MQL5 预定义颜色常量,从 clrBlack 到 clrWhite 排满整张光谱,相当于给 Kohonen 映射准备了 132 个训练样本点。 外汇和贵金属图表上套这类颜色聚类时,高风险在于历史调色分布不代表未来波动结构,样本量 132 只是静态调色板规模,不暗示任何行情概率。 CSOMWeb KohonenMap 这一行才是真正声明网络实例,前面数组只是待处理的输入池。开 MT5 把 web_colors 长度改成你自己的品种色带数量,可能更容易看出聚类边界。

MQL5 / C++
  ColToRGB(col,r,g,b);
  ShowPattern(r,g,b,name);
}
class=class="str">"cmt">//--- web colors array
class="type">color web_colors[class="num">132]=
  {
   clrBlack, clrDarkGreen, clrDarkSlateGray, clrOlive, clrGreen,
   clrTeal, clrNavy, clrPurple, clrMaroon, clrIndigo, clrMidnightBlue,
   clrDarkBlue, clrDarkOliveGreen, clrSaddleBrown, clrForestGreen, clrOliveDrab,
   clrSeaGreen, clrDarkGoldenrod, clrDarkSlateBlue, clrSienna, clrMediumBlue,
   clrBrown, clrDarkTurquoise, clrDimGray, clrLightSeaGreen, clrDarkViolet,
   clrFireBrick, clrMediumVioletRed, clrMediumSeaGreen, clrChocolate, clrCrimson,
   clrSteelBlue, clrGoldenrod, clrMediumSpringGreen, clrLawnGreen, clrCadetBlue,
   clrDarkOrchid, clrYellowGreen, clrLimeGreen, clrOrangeRed, clrDarkOrange,
   clrOrange, clrGold, clrYellow, clrChartreuse, clrLime, clrSpringGreen,
   clrAqua, clrDeepSkyBlue, clrBlue, clrMagenta, clrRed, clrGray,
   clrSlateGray, clrPeru, clrBlueViolet, clrLightSlateGray, clrDeepPink,
   clrMediumTurquoise, clrDodgerBlue, clrTurquoise, clrRoyalBlue, clrSlateBlue,
   clrDarkKhaki, clrIndianRed, clrMediumOrchid, clrGreenYellow, clrMediumAquamarine,
   clrDarkSeaGreen, clrTomato, clrRosyBrown, clrOrchid, clrMediumPurple,
   clrPaleVioletRed, clrCoral, clrCornflowerBlue, clrDarkGray, clrSandyBrown,
   clrMediumSlateBlue, clrTan, clrDarkSalmon, clrBurlyWood, clrHotPink,
   clrSalmon, clrViolet, clrLightCoral, clrSkyBlue, clrLightSalmon,
   clrPlum, clrKhaki, clrLightGreen, clrAquamarine, clrSilver,
   clrLightSkyBlue, clrLightSteelBlue, clrLightBlue, clrPaleGreen, clrThistle,
   clrPowderBlue, clrPaleGoldenrod, clrPaleTurquoise, clrLightGray, clrWheat,
   clrNavajoWhite, clrMoccasin, clrLightPink, clrGainsboro, clrPeachPuff,
   clrPink, clrBisque, clrLightGoldenrod, clrBlanchedAlmond, clrLemonChiffon,
   clrBeige, clrAntiqueWhite, clrPapayaWhip, clrCornsilk, clrLightYellow,
   clrLightCyan, clrLinen, clrLavender, clrMistyRose, clrOldLace,
   clrWhiteSmoke, clrSeashell, clrIvory, clrHoneydew, clrAliceBlue,
   clrLavenderBlush, clrMintCream, clrSnow, clrWhite
  };
CSOMWeb KohonenMap;

◍ 自组织映射的初始化与归一化细节

这段初始化函数把 Kohonen 网络的训练前置工作一次性铺开:先用 MathSrand(200) 固定随机种子,保证每次跑 EA 时权重扰动可复现;随后 KohonenMap.InitParameters(10000,50,50,500,500) 设定 10000 次迭代、50x50 的神经元网格,输出位图尺寸 500x500。 训练集由 web_colors 数组灌入,ArraySize 拿到总数后逐个 AddTrainColor,再调 Train() 和 Render()。如果你在 MT5 里改了种子或迭代次数,颜色聚类图的边界会明显不同,这属于高随机性过程,外汇与贵金属行情建模时须警惕过拟合风险。 CSOMFood 类继承了 CSOM,并重写了 Train、Render 与 ShowPattern。关键在归一化:它用 ArrayResize 建了长度为 3 的 m_max_values / m_min_values,对训练集每个通道(j=0,1,2)扫描求极值,Print 出如 '0 m_min_value=0.0 m_max_value=255.0' 这类日志。 渲染时权重要先减最小值再除极差,乘 255 转成 RGB 整数:r=int(255*(w0-min0)/(max0-min0))。若跳过这步,神经元权重直接当颜色会溢出或发灰,图谱失去区分度。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert initialization function                                 |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnInit()
  {
   MathSrand(class="num">200);
   class="type">int total_web_colors=ArraySize(web_colors);
class=class="str">"cmt">//--- initialize net, class="num">10000 iterations will be used for training
class=class="str">"cmt">//--- the net contains 15x20 nodes, bmp image size 400x400
   KohonenMap.InitParameters(class="num">10000,class="num">50,class="num">50,class="num">500,class="num">500);
class=class="str">"cmt">//-- add all web colors to training set
   for(class="type">int i=class="num">0; i<total_web_colors; i++)
     {
      KohonenMap.AddTrainColor(web_colors[i]);
     }
class=class="str">"cmt">//--- train net
   KohonenMap.Train();
class=class="str">"cmt">//--- render map to bmp
   KohonenMap.Render();
class=class="str">"cmt">//--- show patterns and titles for each class="type">color
   for(class="type">int i=class="num">0; i<total_web_colors; i++)
     {
      KohonenMap.ShowColor(web_colors[i],ColorToString(web_colors[i],true));
     }
class=class="str">"cmt">//--- show bmp image on the chart
   KohonenMap.ShowBMP(false);
   }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| CSOMFood class                                                  |
class=class="str">"cmt">//+------------------------------------------------------------------+
class CSOMFood : class="kw">public CSOM
  {
class="kw">protected:
   class="type">class="kw">double         m_max_values[];
   class="type">class="kw">double         m_min_values[];
class="kw">public:
   class="type">void           Train();
   class="type">void           Render();
   class="type">void           ShowPattern(class="type">class="kw">double c1,class="type">class="kw">double c2,class="type">class="kw">double c3,class="type">class="kw">string name);
   };
class=class="str">"cmt">//--- find minimal and maximal values of the training set
   ArrayResize(m_max_values,class="num">3);
   ArrayResize(m_min_values,class="num">3);
   for(class="type">int j=class="num">0; j<class="num">3; j++)
     {
      class="type">class="kw">double maxv=m_training_sets_array[class="num">3+j];
      class="type">class="kw">double minv=m_training_sets_array[class="num">3+j];
      for(class="type">int i=class="num">1; i<m_total_training_sets; i++)
        {
         class="type">class="kw">double v=m_training_sets_array[class="num">3*i+j];
         if(v>maxv) {maxv=v;}
         if(v<minv) {minv=v;}
        }
      m_max_values[j]=maxv;
      m_min_values[j]=minv;
      Print(j,"m_min_value=",m_min_values[j],"m_max_value=",m_max_values[j]);
     }
class=class="str">"cmt">// class="type">int r = class="type">int(m_som_nodes[ind].GetWeight(class="num">0));
class=class="str">"cmt">// class="type">int g = class="type">int(m_som_nodes[ind].GetWeight(class="num">1));
class=class="str">"cmt">// class="type">int b = class="type">int(m_som_nodes[ind].GetWeight(class="num">2));
class="type">int r=class="type">int ((class="num">255*(m_som_nodes[ind].GetWeight(class="num">0)-m_min_values[class="num">0])/(m_max_values[class="num">0]-m_min_values[class="num">0])));

「自组织映射里的权重着色与训练数据加载」

把 SOM 节点的第二、第三维权重映射到 0–255 的 RGB 色阶,是可视化聚类地图的常见做法。下面两行把节点 ind 的权重按各维度最小最大值归一化后乘 255 强转整型,分别作为绿(g)和蓝(b)通道,配合其他维度就能在 BMP 上看出节点分布。 int g=int ((255*(m_som_nodes[ind].GetWeight(1)-m_min_values[1])/(m_max_values[1]-m_min_values[1]))); int b=int ((255*(m_som_nodes[ind].GetWeight(2)-m_min_values[2])/(m_max_values[2]-m_min_values[2]))); 训练集不限于行情,也可以用任意多维样本。上面那张食物表有 24 行,字段是蛋白质 / 碳水 / 脂肪占比,例如牛排三项为 20.9 / 0.0 / 7.9,巴西坚果是 15.5 / 2.9 / 68.3——脂肪维度拉满的节点会和黄油(1.0 / 0.0 / 81.0)在地图上靠近。 初始化函数里先 MathSrand(200) 固定随机种子,再从 DataFileName 载入训练数据;若 LoadTrainDataFromFile 返回失败直接 Print 报错并 return(1)。成功后依次 Train()、Render()、ShowTrainPatterns()、ShowBMP(false) 把自组织地图画到图表上。 鸢尾花数据集是前 8 行样本,四个形态字段加品种标签 setosa,花萼长宽为 5.1/3.5 到 4.6/3.4 之间,花瓣长宽都极小(1.3–1.7 / 0.2–0.4),这类低维稠密样本适合拿来验证 KohonenMap 的聚类分离效果。开 MT5 把这两份 csv 格式数据塞进同款 CSOM 类,改一行种子数就能对比地图稳定性。

MQL5 / C++
class="type">int g=class="type">int ((class="num">255*(m_som_nodes[ind].GetWeight(class="num">1)-m_min_values[class="num">1])/(m_max_values[class="num">1]-m_min_values[class="num">1])));
class="type">int b=class="type">int ((class="num">255*(m_som_nodes[ind].GetWeight(class="num">2)-m_min_values[class="num">2])/(m_max_values[class="num">2]-m_min_values[class="num">2])));
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert initialization function                                     |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnInit()
  {
   MathSrand(class="num">200);
class=class="str">"cmt">//--- load patterns from file
   if(!KohonenMap.LoadTrainDataFromFile(DataFileName))
     {
      Print("Error in loading data for training.");
      class="kw">return(class="num">1);
     }
class=class="str">"cmt">//--- train net
   KohonenMap.Train();
class=class="str">"cmt">//--- render map
   KohonenMap.Render();
class=class="str">"cmt">//--- show patterns from training set
   KohonenMap.ShowTrainPatterns();
class=class="str">"cmt">//--- show bmp on the chart
   KohonenMap.ShowBMP(false);
   class="kw">return(class="num">0);
  }

用鸢尾花样本验证特征聚类边界

上面这组样本是经典鸢尾花数据集的前 50 行 setosa 与后 2 行 versicolor,每行四个浮点特征依次是花萼长、花萼宽、花瓣长、花瓣宽,末尾为类别标签。setosa 的花瓣长集中在 1.0~1.9 之间,而 versicolor 前两行直接跳到 4.5 和 4.7,边界极其清晰。 在 MT5 里如果你用 CSVMultiClassifier 类做监督训练,把这类带标签的纯文本丢进去,能直观看到线性核在小样本上的分离表现。外汇和贵金属行情虽不是这种静态特征,但用类似思路做状态切片时,高风险品种也容易出现这种“断崖式”特征跃迁。 拿这两行 versicolor 当探针:当某个维度数值从 1.x 突增至 4.x,分类器置信度往往瞬间翻转。开 MT5 自己喂几行试试,比看任何说明书都来得直接。

常见问题

用随机小数值配合输入数据归一化到[0,1],能让神经元权重均匀铺开,降低陷入局部聚类的概率。
先确认训练迭代次数是否过少,再检查学习率衰减设置;增加轮次并放慢衰减,边界通常会更清晰。
可以,小布能直接读取品种页上的特征分布并标出异常簇,你不用自己写渲染代码就能判断边界是否合理。
部分重叠属正常现象,因本身特征有交叉;若完全不分说明归一化或邻域半径参数有误,需重调。
有关,132色以上建议降低每帧重绘频率或分批加载权重,否则CPU占用偏高会导致MT客户端响应变慢。