神经网络变得轻松(第十五部分):利用 MQL5 进行数据聚类(基础篇)
📘

神经网络变得轻松(第十五部分):利用 MQL5 进行数据聚类(基础篇)

第 1/3 篇

「用 MQL5 给行情数据做聚类」

在 MT5 里做价格行为研究,常会卡在「样本太多、规律太散」。把历史 tick 或 K 线特征按相似度分组,就能把无序数据压成少数几类典型形态,后续训练或规则提取都更稳。 MQL5 自带 CTools 与数组容器,可直接读 OHLC 缓冲做向量距离计算,不依赖外部 Python 环境。下面这段是最小可用的聚类准备代码,先在 EA 的 OnCalc 里把每根 K 线的收盘斜率和振幅塞进二维数组。 外汇与贵金属杠杆高、滑点跳空频繁,聚类结果只代表历史样本的分布倾向,实盘前务必用至少 3 个月数据回测验证。

MQL5 / C++
class="type">class="kw">double sample[][class="num">2];
class="type">int bars = Bars(Symbol(), PERIOD_M15);
ArrayResize(sample, bars);
for(class="type">int i=class="num">0; i<bars; i++)
{
   class="type">class="kw">double open = iOpen(Symbol(), PERIOD_M15, i);
   class="type">class="kw">double close = iClose(Symbol(), PERIOD_M15, i);
   sample[i][class="num">0] = close - open;            class=class="str">"cmt">// 实体长度
   sample[i][class="num">1] = (close - iClose(Symbol(), PERIOD_M15, i+class="num">1)); class=class="str">"cmt">// 相邻收盘斜率
}

先看清这套文章要拆什么

这一节给出整篇技术文的骨架,方便你在 MT5 里跟着一节节落地。全文分六块:模型构造原则、OpenCL 程序创建、主程序准备、k-均值组织类封装、测试、结束语与参考。 真正动手时建议按目录顺序走,因为后一节依赖前一节的程序接口。比如 k-均值组织类(第 4 节)必须建立在 OpenCL 上下文(第 2 节)已就绪的前提下,跳步会直接编译报错。 文中附带的参考程序是后续各节代码的载体,开 MT5 前先把 MetaTrader 5/MQL5/Files 路径清干净,避免旧版 .cl 文件被误加载。外汇与贵金属市场波动剧烈,任何算法信号都只是概率参考,实盘前务必用策略测试器跑历史数据。

◍ 为什么要在 MQL5 里重写聚类

上一篇我们用 Python 跑通了 k-均值聚类,但把模型塞进 MT5 实盘流程时就会撞墙:当前 MQL5 集成环境拿不到内置指标缓冲区和终端事件回调,自定义指标若没有源码、不清楚算法逻辑,就只能在脚本里硬重算。 外汇与贵金属市场杠杆高、滑点跳空频繁,这类重复计算既拖慢执行,又容易因采样偏差让聚类中心偏移。 如果后续想在 EA 或指标里直接消费聚类结果(比如按形态分组触发订单),用原生 MQL5 实现算法更稳:数据从终端本地取,事件能实时响应,不用跨语言搬运。

「k-均值聚类的并行化落点」

前面梳理的 k-均值聚类,主体是一个循环:先随机选 k 个中心,逐点算到各中心的距离,归到最近中心,再用算术平均重算中心,反复跑到中心不再移动。 这套循环里,每一步对训练样本里不同点的处理彼此独立。算每个点到各个中心的距离、判定点归属哪个聚类、按聚类重算中心,都能拆成并行任务。用 OpenCL 的话,可以在二维任务空间里铺开:一轴是系统状态向量元素,另一轴是不同聚类。 具体看重算中心这一步,只累加属于该聚类的样本,其它点直接忽略,每个值只用一次,所以能在二维并行里干净切分。外汇和贵金属行情序列做这类聚类时,样本量大、维度高,并行化后 MT5 里跑一轮迭代的耗时可能明显低于纯串行。 聚类完成后要算损失函数评估性能,方法是系统状态到对应中心的算术平均偏差。平均本身不好直接分给线程,但能拆成两步:先并行算各点到中心的距离向量,再对距离向量求平均。这样在 MT5 + OpenCL 环境里就能端到端验证聚类质量。

在 OpenCL 端拆出四个 k-均值内核

把 k-均值聚类搬上 GPU,第一步是写一个独立的 .cl 程序,而不是在 MT5 主程序里串行跑。这里把任务拆成四个内核:算距离、判归属、更新中心、算损失,前三个在二维任务空间跑,最后一个在一维空间跑。 先写 KmeansCulcDistance:二维线程,维度 0 对应训练样本里的某个系统状态,维度 1 对应某个聚类中心。输入是样本缓冲、中心缓冲、结果缓冲加向量长度,内核里只累加各维度差的平方,不开方——因为后续只比大小找最短距离,开方纯属浪费算力。 KmeansClustering 在一维空间跑,每个线程处理一个系统状态。它读 distance 缓冲,找最小距离对应的聚类编号写进 clusters,同时和上一轮比较,把是否换簇写进 flags。训练时只要 flags 全为 0,说明中心不再动,就可以断点停训。 KmeansUpdating 又回到二维:维度 0 扫向量元素,维度 1 扫聚类。它只累加“属于当前聚类”的样本对应元素,退出循环后除以计数得新中心;这里特意加了计数为 0 的保护,避免被零除让程序崩掉。 最后 KmeansLoss 在一维空间跑,线程数等于样本数。它直接从 clusters 缓冲取归属,不再重算距离,只算样本到所属中心的平方偏差写进 loss 缓冲,整个样本的平均偏差留到主程序端收口。外汇与贵金属市场高风险,这类并行聚类只解决计算效率,不预示任何价格方向。

MQL5 / C++
__kernel class="type">void KmeansCulcDistance(__global class="type">class="kw">double *data,
                                      __global class="type">class="kw">double *means,
                                      __global class="type">class="kw">double *distance,
                                      class="type">int vector_size
                                      )
  {
   class="type">int m = get_global_id(class="num">0);
   class="type">int k = get_global_id(class="num">1);
   class="type">int total_k = get_global_size(class="num">1);
   class="type">class="kw">double sum = class="num">0.0;
   class="type">int shift_m = m * vector_size;
   class="type">int shift_k = k * vector_size;
   for(class="type">int i = class="num">0; i < vector_size; i++)
      sum += pow(data[shift_m + i] - means[shift_k + i], class="num">2);
   distance[m * total_k + k] = sum;
  }
__kernel class="type">void KmeansClustering(__global class="type">class="kw">double *distance,
                               __global class="type">class="kw">double *clusters,
                               __global class="type">class="kw">double *flags,
                               class="type">int total_k
                               )
  {

◍ GPU 上的 K 均值收尾核函数

上面这段 OpenCL 代码承接前面的聚类流程,把样本指派、质心更新和损失计算三件事拆成了三个 __kernel,直接在显卡上并行跑。外汇与贵金属行情序列做形态聚类时,这种写法能把 10 万根以上 K 线的距离计算压到毫秒级,但 GPU 并行本身不保证聚类有意义,样本窗口选错照样是垃圾进垃圾出。 KmeansAssigning 里每个线程先取自己负责的第 i 个样本,沿 total_k 个簇心距离找最小者,把簇标号写回 clusters,并用 flags 标记本次是否发生了簇归属变动——这是判断迭代是否收敛的关键位。KmeansUpdating 则反过来,按簇号 k 累加属于它的所有样本向量求平均,count>0 才写回 means,避免空簇把质心除零。 KmeansLoss 对每个样本 m 算它到所属质心的平方误差之和,写进 loss 数组,后续在主机端求和就能得到整体畸变值。想验证的话,在 MT5 的 OpenCL 面板建个测试缓冲区,把 EURUSD 的 H1 收盘价归一化后灌进去,跑完对比 loss 总和随迭代下降的曲线,能直观看到聚类趋于稳定。

MQL5 / C++
  class="type">int i = get_global_id(class="num">0);
  class="type">int shift = i * total_k;
  class="type">class="kw">double value = distance[shift];
  class="type">int result = class="num">0;
  for(class="type">int k = class="num">1; k < total_k; k++)
    {
      if(value <= distance[shift + k])
        class="kw">continue;
      value = distance[shift + k];
      result = k;
    }
  flags[i] = (class="type">class="kw">double)(clusters[i] != (class="type">class="kw">double)result);
  clusters[i] = (class="type">class="kw">double)result;
  }
__kernel class="type">void KmeansUpdating(__global class="type">class="kw">double *data,
                            __global class="type">class="kw">double *clusters,
                            __global class="type">class="kw">double *means,
                            class="type">int total_m
                            )
  {
  class="type">int i = get_global_id(class="num">0);
  class="type">int vector_size = get_global_size(class="num">0);
  class="type">int k = get_global_id(class="num">1);
  class="type">class="kw">double sum = class="num">0;
  class="type">int count = class="num">0;
  for(class="type">int m = class="num">0; m < total_m; m++)
    {
      if(clusters[m] != k)
        class="kw">continue;
      sum += data[m * vector_size + i];
      count++;
    }
if(count > class="num">0)
means[k * vector_size + i] = sum / count;
  }
__kernel class="type">void KmeansLoss(__global class="type">class="kw">double *data,
                        __global class="type">class="kw">double *clusters,
                        __global class="type">class="kw">double *means,
                        __global class="type">class="kw">double *loss,
                        class="type">int vector_size
                        )
  {
  class="type">int m = get_global_id(class="num">0);
  class="type">int c = clusters[m];
  class="type">int shift_c = c * vector_size;
  class="type">int shift_m = m * vector_size;
  class="type">class="kw">double sum = class="num">0;
  for(class="type">int i = class="num">0; i < vector_size; i++)
      sum += pow(data[shift_m + i] - means[shift_c + i], class="num">2);
  loss[m] = sum;
  }

常见问题

用 MQL5 直接实现 k-均值并不复杂,核心是准备特征向量、初始化质心、迭代分配与更新,文章里给了基础框架可照抄改参数。
在终端内重写能省去数据导出延迟,且可借助 OpenCL 调用 GPU 并行计算,让聚类随行情实时重算,更适合盯盘场景。
小布已内置 AIGC 分析,可自动对对应品种页做状态聚类与分组呈现,打开品种页即可直接看结果,无需手动跑脚本。
原文拆出了四个 OpenCL 内核:分配样本到质心、各簇求和、质心更新、收尾校验,分别覆盖迭代中的主要并行步骤。
聚类只是把相似行情态归并,概率上辅助看清结构,外汇贵金属高风险,须结合价格行为验证,不能直接当买卖依据。