您应当知道的 MQL5 向导技术(第 13 部分):智能信号类 DBSCAN(基础篇)
📘

您应当知道的 MQL5 向导技术(第 13 部分):智能信号类 DBSCAN(基础篇)

第 1/2 篇

用 DBSCAN 给行情密度做聚类

MQL5 向导里的智能信号类支持直接挂 DBSCAN 聚类算法,用来把历史价格点按密度自动分群,而不是靠人工画支撑阻力。对外汇和贵金属这类高波动品种,密度聚类可能比固定通道更贴合当下的无序震荡。 DBSCAN 核心是两个参数:邻域半径 eps 和最小样本数 minPts。eps 太小会把连续行情切碎成噪声,太大则把不同波段揉成一团;minPts 偏低容易误判 outlier 为簇,偏高则可能漏掉短促的突破密度区。 在 MT5 里新建智能信号时选 DBSCAN,回看 EURUSD H1 近 3 个月数据,默认 eps=0.0010、minPts=5 的配置常把窄幅整理识别为单一簇,而把每次 20 点以上的快速拉伸拆成独立簇。想验证就自己调 eps 到 0.0006 看簇边界变化。

「用 DBSCAN 给 wizard 信号类松绑」

MQL5 向导把开平仓、仅在新柱线触发决策这类琐碎逻辑封装进专家类,让交易者能把一个未成型思路直接拼成 EA 去做初步验证。它最实用的地方,是让你在同一种流程里横向比对不同方法,而不必为每个想法重写框架。 前面两篇我们试过聚合聚类和 k-均值,二者都要求事先指定聚类数量 k——这等于默认你已摸透数据集。DBSCAN 换了个路数:只吃两个参数,ε(点位间最大空间距离)和最小点位数,聚类数量反而成了输出项。 这一改动在探索陌生行情样本时很关键。它不仅能自适应分出几堆,还能暴露你原本“以为有 k 类”的偏见是否站得住脚。公开对比图里,k-均值按 x/y 轴硬切地域,DBSCAN 多带一条密度维度,只要内部接近度不够,轴域内再密也不算同一簇,分组会甩出最佳拟合区。 本篇就基于向导搭三个不同数据集接入 DBSCAN 的信号类,优调买卖触发。重申一遍:外汇/贵金属杠杆风险极高,这些类只作策略筛选与回测用,离真仓实盘差得远,独立验证是你的事。

◍ 从超市到社交网络看密度聚类

拿三个生活场景先拆开 K-均值和 DBSCAN 的底层差异。超市老板若用 K-均值,得预先假定客户类型数固定(比如 2 类),随之产生 2 个平均质心对应的补货现金流,往往不如多而小的支出好管理;换 DBSCAN,以购物日期在日历上的相隔时间当 ε,按密度切分客群,库存支出更可控。 城市规划者评估住宅限额时,K-均值先拿现有行政区数当聚类数,再按早晚高峰平均交通量加权;DBSCAN 不预设行政区数量,只用路线每公里车流密度做 ε,把高密度路线聚到一起,即便跨物理区域也归为同一“行政区类型”便于配置。 社交媒体上用户自组群组,K-均值难定合适聚类数;DBSCAN 用设定周期内的互动次数指导 ε,把交互密度归零的归为噪声。 DBSCAN 在无监督下对噪声和异常值更友好。最小点数参数不像 ε 那样敏感,本质只是设“噪声”阈值——任何没落进指定聚类的点都算噪声。外汇与贵金属市场用这类密度聚类做形态识别时,样本外失效概率不低,属高风险尝试,建议先用 MT5 历史数据回测。

把 DBSCAN 塞进 MT5 信号类的实操路径

用 MQL5 向导搭智能系统,底层依赖 Expert 类(位于 include\Expert\Expert.mqh),它再继承 ExpertBase 派生出信号、尾随、资金三个子类。信号类管买卖决策,尾随类管止损跟随幅度,资金类管保证金占用比例——这是我们挂 DBSCAN 聚类的落点。 DBSCAN 的核心输入是 ε(邻域半径)和最小点数。原文以公开论文源码为底,重写 Euclidean 距离函数,因为 MQL5 标准库未在 Loss/Regression 里提供它;向量点也支持余弦相似度等替代度量,可自己换。 RegionQuery 按 ε 阈值捞邻点,ExpandCluster 判断是否够核心点(≥m_min_points)来发聚类 ID,不够则标 -1 当噪声。主 DBSCAN 函数遍历全点、递增聚类 ID 并调用上述两者。 数据准备由智能基类接手,但理想做法是用目标经纪商真实报价测试——从他们服务器拉尽量长的 tick,否则聚类对行情的刻画可能偏样。外汇与贵金属杠杆高,回测漂亮不等于实盘稳健。 DBSCAN 随数据集膨胀有内存压力,且统一 ε 对密度不均的行情不一定合理。变体如 HDBSCAN 只依赖最小点数、免 ε,泛化更好,后续可另聊。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Function for Euclidean Distance between points                    |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">class="kw">double CSignalDBSCAN::Distance(Spoint &A, Spoint &B)
{  class="type">class="kw">double _d = class="num">0.0;
   for(class="type">int i = class="num">0; i < class="type">int(fmin(A.key.Size(), B.key.Size())); i++)
   {  _d += pow(A.key[i] - B.key[i], class="num">2.0);
   }
   _d = sqrt(_d);
   class="kw">return(_d);
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Function that returns neighbouring points for an input point &P[]|
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void CSignalDBSCAN::RegionQuery(Spoint &P[], class="type">int Index, CArrayInt &Neighbours)
{  Neighbours.Resize(class="num">0);
   class="type">int _size = ArraySize(P);
   for(class="type">int i = class="num">0; i < _size; i++)
   {  if(i == Index)
      {  class="kw">continue;
      }
      else if(Distance(P[i], P[Index]) <= m_epsilon)
      {  Neighbours.Resize(Neighbours.Total() + class="num">1);
         Neighbours.Add(i);
      }
   }
   P[Index].visited = true;
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Function that expands cluster for identified cluster IDs          |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CSignalDBSCAN::ExpandCluster(Spoint &SetOfPoints[], class="type">int Index, class="type">int ClusterID)
{  CArrayInt _seeds;
   RegionQuery(SetOfPoints, Index, _seeds);
   if(_seeds.Total() < m_min_points) class=class="str">"cmt">// no core point
   {  SetOfPoints[Index].cluster_id = -class="num">1;
      class="kw">return(false);
   }
   else
   {  SetOfPoints[Index].cluster_id = ClusterID;
      for(class="type">int ii = class="num">0; ii < _seeds.Total(); ii++)
      {  class="type">int _current_p = _seeds[ii];
         CArrayInt _result;
         RegionQuery(SetOfPoints, _current_p, _result);
         if(_result.Total() > m_min_points)
         {  for(class="type">int i = class="num">0; i < _result.Total(); i++)
            {  class="type">int _result_p = _result[i];
               if(SetOfPoints[_result_p].cluster_id == -class="num">1)

常见问题

可用 DBSCAN 对近期价格点做密度聚类,密集区样本点互相可达、形成簇,稀疏区被标记为噪声,借此区分震荡与突破概率。
把 DBSCAN 封装成独立信号类挂进向导,参数外置,后续只调 eps 和 minPts 就能换品种换周期,不必重写主体。
可以,小布盯盘已内置密度诊断,打开对应品种页即可看到当前行情簇分布与噪声标记,不用自己跑代码。
eps 过大全归为一簇、过小全变噪声;minPts 偏低噪声少但簇虚,偏高噪声多漏信号,建议从品种平均波幅反推初值。
卡在向导接口对齐与数组传参,先拿单品种单周期跑通再扩,外汇贵金属波动大记得加风控限定。