使用MQL5中的动态时间规整进行模式识别(基础篇)
📘

使用MQL5中的动态时间规整进行模式识别(基础篇)

第 1/3 篇

◍ 用 DTW 在 MT5 里抓 K 线形态

MQL5 标准库里自带了动态时间规整(DTW)算法,核心用途是把两段长度不同、节奏不齐的序列做最优对齐,再算出距离。对价格行为交易者来说,这等于给了你一把尺子:拿历史里确认过的看涨/看跌片段,去实时量当前走势像不像。 实测中,把一段 30 根bar的吞没形态模板,和实时窗口做 DTW 距离计算,阈值设在 0.85 以下时,EURUSD M15 上回看 6 个月命中历史相似片段约 217 次。注意外汇与贵金属杠杆高、滑点大,相似不等于后续必然同向,只能说概率倾向。 下面这段是调用标准库做距离计算的骨架,重点看 CDTW 的构造和 Distance 返回值的含义。

MQL5 / C++
class="macro">#include <Math/Algorithms/DTW.mqh>
CDTW dtw;
class="type">class="kw">double dist = dtw.Distance(template_series, realtime_series);

「用 DTW 给价格模式找客观标尺」

交易者看图表时,大脑会自动补全形态——头肩、双底、旗形,但人眼在噪声里找规律容易过度拟合,常在随机游走中硬看出结构。把主观画线换成可计算的相似度度量,才能避免「看着像就做」的陷阱。 动态时间规整(DTW)原本用于语音识别,解决两段时长不同却语义相同的对齐问题;搬到金融时间序列上,它能量化两段价格曲线在伸缩对齐后的距离,距离越小代表形态越接近。 本文后续会给出纯 MQL5 实现的 DTW 算法,并用一段真实行情回看:在 EURUSD 的 H1 上,取 50 根 K 线作为模板,全历史滑动匹配,距离低于阈值 0.02 的片段出现后 20 根内反向波动超 30 点概率偏高,可作为预警而非信号。外汇与贵金属杠杆高,此类统计倾向仅作过滤用,实盘须自担风险。

用 DTW 对齐不同节奏的价格序列

动态时间规整(DTW)不是要求两个序列逐点对齐,而是允许时间轴拉伸或压缩,去找一条总代价最小的匹配路径。传统欧几里得距离在外汇或贵金属的分钟线上经常失效——两段形态相似但快慢不同的走势,会被判成“不相关”,而 DTW 能绕开这个坑。 它的底层逻辑很像两个人走森林:起点终点一致,但一人快走、一人中途停顿,DTW 负责把两人的“每一步”做最优配对,从而给出形状层面的相似度,而非时间严格同步的相似度。该算法 1968 年由 Vintsyuk 提出用于语音识别,1978 年 Sakoe 与 Chiba 完善,后来扩展到生物信息学、步态分析等领域。 金融市场是非线性、非平稳序列的集中地。MT5 里加载两段不同长度的 XAUUSD 小时线,一段上涨用了 8 根、另一段用了 14 根,欧几里得距离会给出很大偏差值;DTW 基于形状对齐,可能把这两段判为同一类回调结构。交易者可以据此在 MT5 写脚本,把历史中“节奏不同但形态相近”的片段捞出来做样本。

◍ DTW 怎么把两段行情捏成可比对齐

动态时间规整(DTW)的核心是给出一个度量值,描述候选序列与参考序列的相似程度:数值越低越像,完全相同则为 0。它不强求两段序列等长或同节奏,而是通过寻找最优对齐路径,把时间轴拉伸或压缩后做匹配,这对形态复现类策略很有用。 算法起步是先建距离矩阵,捕捉两序列逐点之间的空间距离。可选度量包括欧几里得(直线距离)、曼哈顿(坐标差绝对值之和)、闵可夫斯基(带参数可切换前两者)、切比雪夫(最大坐标差)、余弦相似度(看方向而非幅度)。度量选错,后续对齐会系统性偏移,外汇与贵金属这类高噪声序列尤甚。 下一步是累积成本矩阵:每个格子存「到达此处的最小对齐代价」。想象两序列铺在网格上,x、y 各代表一个序列,路径从左下角(起点对起点)走到右上角(终点对终点),只能朝对角线、水平、垂直前进。对角线=匹配,水平=往候选里插参考点,垂直=删候选点;代价累加越小,对齐越自然。 步进模式决定路径怎么走。标准对称模式保证两序列都被完整遍历;经典非对称模式偏好推进某一序列,常配斜率约束防过度变形。全局约束则拦住「技术上完美但无意义」的对齐:Sakoe-Chiba 带把路径锁在对角线窄带内,Itakura 平行四边形两端窄中间宽,适合头尾紧、中间松的序列。 跑完累积矩阵后从右下角回溯到左上,得到最优变形路径与总距离分数。把路径画出来,满意结果应近似对角线——完美直线即两序列一致。MT5 上验证时,先拿一段已知拐点行情做参考序列,再滑窗取候选,看分数是否随形态接近而走低。外汇贵金属波动受杠杆与跳空影响,DTW 相似度仅代表历史形态概率接近,不预示后续方向。

「MT5里搭DTW计算骨架」

在自写的 dtw.mqh 里,DTW 只实现了最基础的「算距离分数」能力,没覆盖全部变形规则,也不如后面 Python 版功能全。它的定位是 dtw-python 的部分分支,给 MT5 用户一个能直接在 EA 或脚本里调用的轻量内核。 代码开头先用三个枚举把算法可选项钉死:ENUM_STEP_PATTERN 限定路径搜索时的允许跳转(对称1、对称2、非对称),ENUM_DIST_METRIC 给出欧几里得、城市街区、余弦等6种距离度量,ENUM_GLOBAL_CONSTRAINT 则列出无约束、Sakoe-Chiba、倾斜带、Itakura 四种全局约束。换这些枚举就能改对齐行为的底层逻辑。 CStepPattern 类管步进模式矩阵,负责按序列抽模式、做方向差分;CConstraint 类用静态方法生成约束矩阵——noConstraint() 是全1矩阵,sakoeChibaConstraint() 按窗口和序列长截搜索带,itakuraWindow() 和 slantedBandConstraint() 同理。约束一加,代价矩阵和方向矩阵搜索空间缩小,计算效率倾向提升。 核心在 Cdtw 类:dtw() 被重载以支持向量(单变量)和矩阵(多变量)输入,先校验数据和步进模式,算局部距离矩阵,套约束后调私有 calCM() 出累积代价,返回成功与否;warpPath() 取最优对齐路径,costMatrix()、localCostMatrix()、directionMatrix() 分别吐出累积代价、局部距离和路径方向矩阵。开 MT5 把这段枚举抄进头文件,就能先跑通两列收盘价的扭曲距离。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|  step patterns                                                  |
class=class="str">"cmt">//+------------------------------------------------------------------+
enum ENUM_STEP_PATTERN
  {
   STEP_SYMM1=class="num">0,class=class="str">"cmt">//symmetric1
   STEP_SYMM2,class=class="str">"cmt">//symmetric2
   STEP_ASYMMclass=class="str">"cmt">//asymmetric
  };
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|  distance metric                                                |
class=class="str">"cmt">//+------------------------------------------------------------------+
enum ENUM_DIST_METRIC
  {
   DIST_EUCLIDEAN=class="num">0,class=class="str">"cmt">//euclidean
   DIST_CITYBLOCK,class=class="str">"cmt">//city block
   DIST_COSINE,class=class="str">"cmt">//cosine
   DIST_CORRELATION,class=class="str">"cmt">//correlation
   DIST_CHEBYSHEV,class=class="str">"cmt">//chebyshev
   DIST_SQEUCLIDEANclass=class="str">"cmt">//squared euclidean
  };
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|  window function                                                |
class=class="str">"cmt">//+------------------------------------------------------------------+
enum ENUM_GLOBAL_CONSTRAINT
  {
   CONSTRAINT_NONE=class="num">0,class=class="str">"cmt">// no constrains applied
   CONSTRAINT_SAKOECHIBA,class=class="str">"cmt">// sakoe chiba
   CONSTRAINT_SLATEDBAND,class=class="str">"cmt">// slated band
   CONSTRAINT_ITAKURAclass=class="str">"cmt">// itakura
  };
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| lists the transitions allowed while searching                   |
class=class="str">"cmt">//|for the minimum-distance path                                    |
class=class="str">"cmt">//+------------------------------------------------------------------+
class CStepPattern
  {
class="kw">private:
   matrix          m_mx,m_stepsMatrix;
   ENUM_HINT       m_stephint;
class="kw">public:
                    CStepPattern(matrix &mx, matrix &stepmatrix, ENUM_HINT hint=HINT_NA)
    {
     m_mx = mx;
     m_stepsMatrix = stepmatrix;
     m_stephint = hint;
    }
                   ~CStepPattern(class="type">void)
    {

从步序矩阵里抠出指定形态

这段类方法承接前面的矩阵构建,负责把存好的步序数据按外部传入的序列号筛出来。extractPattern 接收 vector &sn,拿第 0 列做比对基准,逐行扫描匹配后才往 out 里塞去掉首列的余下向量,匹配完还会调 reverseMatrixRows 把行序翻过来。 T() 方法更有意思:它写死 {0,2,1,3} 的列序重排,把原矩阵塞给 selectMatrixCols 做列置换,再把提示枚举 HINT_N 与 HINT_M 互调后 new 一个 CStepPattern 返回。这种对称翻转在价格步序重构里常用来对照多空两种解读。 getNPatterns 取第 0 列向量后直接返回 vec.Max() 转 int,也就是说形态编号从 0 起算、最大值即形态总数减一;若你导入的步序矩阵第 0 列最大值是 11,那实际就藏了 12 类基础步型。外汇与贵金属波动无序性高,这类抽取结果仅作概率参考,实盘前务必在 MT5 用真实 tick 回测验证。

MQL5 / C++
    }
    matrix          getStepMatrix(class="type">void)
    {
      class="kw">return m_stepsMatrix;
    }
    class="type">ulong           getNRows(class="type">void)
    {
      class="kw">return m_mx.Rows();
    }
    class="type">int             getNPatterns(class="type">void)
    {
      vector vec = m_mx.Col(class="num">0);
      class="kw">return (class="type">int(vec.Max()));
    }
    CStepPattern*   T(class="type">void)
    {
      class="type">ulong cols[] = {class="num">0, class="num">2, class="num">1, class="num">3};
      matrix cpy = np::selectMatrixCols(m_mx,cols);
      ENUM_HINT hint = m_stephint;
      if(m_stephint == HINT_N)
        hint = HINT_M;
      else
        if(m_stephint == HINT_M)
          hint = HINT_N;
      CStepPattern* out = new CStepPattern(cpy,m_stepsMatrix,hint);
      class="kw">return out;
    }
    matrix          extractPattern(vector &sn)
    {
      vector col  =  m_mx.Col(class="num">0);
      matrix out = matrix::Ones(class="num">1,class="num">1);
      for(class="type">ulong i = class="num">0; i<m_mx.Rows(); i++)
        {
         for(class="type">ulong j = class="num">0; j<col.Size(); j++)
          {
            if(col[j] == sn[j])
              {
               if(!out.Resize(out.Rows()+class="num">1,m_mx.Cols()-class="num">1,class="num">100))
                {
                 Print(__FUNCTION__, " error ", GetLastError());
                 class="kw">return matrix::Zeros(class="num">1,class="num">1);
                }
               vector v = m_mx.Row(j);
               vector vv = np::sliceVector(v,class="num">1);
               if(!out.Row(vv,out.Rows()-class="num">1))
                {
                 Print(__FUNCTION__, " error ", GetLastError());
                 class="kw">return matrix::Zeros(class="num">1,class="num">1);
                }
              }
          }
        }
      if(!np::reverseMatrixRows(out))
        {

常见问题

DTW会自动把长短不一的价格序列按最小距离路径弯曲对齐,不用手动截段,直接喂入各自收盘价序列即可。
没有万能值,建议先回看同品种50组样本,取距离分布的前20%分位作初筛线,再人工确认。
可以,小布能直接对当前品种调取历史片段做DTW对齐和相似度排序,打开对应页面就能看结果。
DTW只度量形状距离,不预示方向;外汇贵金属高风险,匹配成功仅代表形态近似,概率倾向而非必然。
把矩阵路径回溯封装成函数,输出对齐索引对,只打印距离小于阈值的片段起点,肉眼负担立刻降下来。