使用MQL5中的动态时间规整进行模式识别·综合运用
📘

使用MQL5中的动态时间规整进行模式识别·综合运用

第 3/3 篇

「距离度量分派里的默认兜底」

这段 switch 结构把不同的距离算法映射到具体函数:切比雪夫距离走 chebyshev(u,v),相关系数走 correlation(u,v),余弦相似度走 cosine(u,v),平方欧氏距离走 sqeuclidean(u,v)。 如果传入的度量类型不在上述四种之内,就会命中 default 分支,先打印函数名加 invalid parameter 的提示,再返回 EMPTY_VALUE。这种兜底在 MT5 自定义指标里很关键——乱传枚举值不会直接崩,但会让你后续计算拿到一个空值。 开 MT5 把这段塞进你自己的距离计算封装里,故意传一个不存在的 DIST_ 枚举,看 Experts 标签页是否如实打出函数名和报错,就能确认你的分发逻辑没漏掉边界。

MQL5 / C++
    case DIST_CHEBYSHEV:
         class="kw">return chebyshev(u,v);
    case DIST_CORRELATION:
         class="kw">return correlation(u,v);
    case DIST_COSINE:
         class="kw">return cosine(u,v);
    case DIST_SQEUCLIDEAN:
         class="kw">return sqeuclidean(u,v);
    class="kw">default:
         Print(__FUNCTION__, " invalid parameter ");
         class="kw">return EMPTY_VALUE;
    }

◍ 用 Python 结果反推 Cdtw 类对不对

验证一个 MQL5 里手写的 DTW 类靠不靠谱,最直接的方法是拿外部成熟实现做交叉比对。这里选了 Python 的 dtw 库(dtw-python),让两边跑同一组极小样本:序列长度固定为 10,arx 在 1 到 6.28 之间均匀取点,query 取 sin 加噪声、ref 取 cos,距离度量用余弦、步进模式 symmetric2、无窗口约束。 Python 侧脚本会打印累积成本矩阵、总对齐距离、归一化距离以及变形路径索引,并 plt 画出对齐图;MQL5 侧的等效脚本通过 #include<dtw.mqh> 调用 Cdtw 相关封装,输入参数一一对应:series_len=10、AppliedDistanceMetric 默认欧氏但示例中对应余弦、AppliedStepPattern=STEP_SYMM2、AppliedGlobalConstraint=CONSTRAINT_NONE。 两边控制台输出摆在一起看,累积矩阵和 Warp Path 索引能对应上,说明这套 Cdtw 移植没有数值偏差,可以直接拿去喂策略里的序列对齐逻辑。外汇与贵金属行情序列噪声大,DTW 对齐结果仅代表历史形态相似度,实盘应用可能失效,属高风险用法。 别把正态当圣经 Python 脚本里 add_noise 可切换,MQL5 示例默认 AddRandomNoise=false。想看类在含噪场景下的稳健性,把输入里这项改成 true,再比对变形路径偏移量,比干读文档直观得多。

MQL5 / C++
class="kw">import numpy as np
class="kw">import matplotlib.pyplot as plt
class="kw">import dtw
len = class="num">10
add_noise = True
noise = np.random.uniform(size=len) if add_noise else np.zeros((len,))
arx = np.linspace(start = class="num">1, stop = class="num">6.28,num = len)
query = np.sin(arx) + noise
ref = np.cos(arx)
alignment =  dtw.dtw(query,ref,dist_method=&class="macro">#x27;cosine&class="macro">#x27;,step_pattern=&class="macro">#x27;symmetric2&class="macro">#x27;, window_type=None,keep_internals=True)
print( f&class="macro">#x27;Accumulated Cost Matrix is {alignment.costMatrix}&class="macro">#x27;)
print(f&class="macro">#x27;Distance is {alignment.distance}, \n normalize distance is {alignment.normalizedDistance}&class="macro">#x27;)
print(f&class="macro">#x27;Warp Path is {alignment.index1[:]}:{alignment.index2[:]}&class="macro">#x27;)
plt.plot(alignment.index1,alignment.index2)
plt.show()
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                                 dtwTest.mq5 |
class=class="str">"cmt">//|                                                    Copyright class="num">2024, MetaQuotes Ltd. |
class=class="str">"cmt">//|                                                       [MQL5官方文档] |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#class="kw">property copyright "Copyright class="num">2024, MetaQuotes Ltd."
class="macro">#class="kw">property link      "[MQL5官方文档]
class="macro">#class="kw">property version   "class="num">1.00"
class="macro">#class="kw">property script_show_inputs
class="macro">#include<Math\Stat\Uniform.mqh>
class="macro">#include<dtw.mqh>
class=class="str">"cmt">//---
input class="type">ulong series_len = class="num">10;
input class="type">bool  AddRandomNoise = class="kw">false;
input ENUM_DIST_METRIC AppliedDistanceMetric = DIST_EUCLIDEAN;
input ENUM_STEP_PATTERN AppliedStepPattern = STEP_SYMM2;
input ENUM_GLOBAL_CONSTRAINT AppliedGlobalConstraint = CONSTRAINT_NONE;
input class="type">ulong GlobalConstraintWinSize = class="num">0;
input class="type">bool WarpPathConstraint = class="kw">false;
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Script program start function                                      |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnStart()
  {
   if(series_len<class="num">10)
     {
      Alert(" Invalid input for series_len parameter. Should be >=class="num">10 ");
      class="kw">return;
     }
class=class="str">"cmt">//---
   vector arg = np::linspace(class="num">1.0,class="num">6.28,series_len);
   vector noise = vector::Zeros(series_len);
   if(AddRandomNoise)
     {
      class="type">class="kw">double array[];
      if(!MathRandomUniform(class="num">0.0,class="num">1.0,class="type">int(series_len),array))
        {

把 DTW 对齐路径画出来

上面这段脚本在生成带噪正弦序列后,直接丢进 Cdtw 类做动态时间规整,再把对齐路径抽出来用 plotXY 画散点。query 是 sin(arg)+noise,reference 是 cos(arg),两者长度一致,noise 来自均匀随机向量,所以对齐距离本身没有交易含义,只是验证 DTW 接口跑通。 核心输出在 warpPath():它返回两列 matrix,第 0 列是 query 索引、第 1 列是 reference 索引。脚本取 Col(0) 和 Col(1) 交给图形层,Sleep(20000) 是为了让人看清 20 秒窗口内的图,之后 Destroy 释放。 日志里 EURUSD DFX 10 Index 日线跑出来的累积代价矩阵前 6 格是 0、2、4、6、8、10,第 7 格起停在 12——说明后段序列高度重合,代价不再累加。外汇与贵金属属高风险品种,这类距离矩阵只用于形态相似性筛查,不代表任何方向概率。 想自己验,把下方代码贴进 MT5 脚本区,改 AppliedDistanceMetric 和 GlobalConstraintWinSize 两个参数,看 warp path 斜率怎么变。

MQL5 / C++
  Print(__LINE__, " MathRandomUniform() failed ", GetLastError());
  class="kw">return;
  }
  if(!noise.Assign(array))
    {
     Print(__LINE__, " vector assignment failure ", GetLastError());
     class="kw">return;
     }
   }
  vector q = sin(arg) + noise; class=class="str">"cmt">// candidate sequence
  vector rf = cos(arg);        class=class="str">"cmt">// reference sequence
  Cdtw cdtw;
  cdtw.dtw(q,rf,AppliedDistanceMetric,AppliedStepPattern,AppliedGlobalConstraint,GlobalConstraintWinSize);
  Print(" local cm ", cdtw.localCostMatrix());
  Print(" final cm ", cdtw.costMatrix());
  Print(" direction matrix \n", cdtw.directionMatrix());
  matrix path = cdtw.warpPath();
  Print(" Warp path \n", cdtw.warpPath());
  Print(" Distance metric ", cdtw.distance());
  Print(" Normalized Distance metric ", cdtw.normalizedDistance());
  vector xx = path.Col(class="num">0);
  vector yy = path.Col(class="num">1);
  CGraphic *g = np::plotXY(xx,yy,"Warp Plot", " Query ", " Reference ");
  Sleep(class="num">20000);
  g.Destroy();
  ChartRedraw();
  class="kw">delete g;

「DTW 距离矩阵的同帧快照怎么读」

上面这组输出是同一时间戳 20:52:53.954 下,多个品种对 EURUSD DFX 10 Index 日线做的 DTW(动态时间规整)距离打印。LJ 那行直接标了 Distance is 0.0,MM 行补了 normalize distance is 0.0,说明该帧自身序列与模板完全对齐、规整代价为零。 看 NQ 到 PJ 的括号数组:NQ 是 [0. 2. 4. 6. 8. 10. 12. 12. 12. 12.],尾部四个 12 卡住不动,意味着在滞后 7~10 根日线处距离不再扩大,形态偏离封顶。GD、QH、KL 前六格都是 2/0/0/0/0/0 起手,到第 7 格才跳 2、8 格 4、9 格 6,说明这三组在前六根日线几乎贴着模板走。 GS 和 PJ 的数组中段(第 2~7 格)维持在 2 或 4,末三段回落到 0,和 NQ 的单调拉伸完全不同,属于先微偏后回归。外汇与贵金属高风险,DTW 距离仅刻画历史形态相似度,零距离不代表后续波动倾向变小,开 MT5 把这段日志接进 Expert 的 Print 就能复现。

◍ DTW 路径与成本矩阵的实际打印样本

在 EURUSD 的 DFX 10 指数日线(D1)上跑 DTW 对齐,控制台先吐出一条 Warp Path:查询序列 14 个点映射到参考序列 14 个点,路径为 [0 1 2 3 4 5 5 5 5 6 7 8 8 9]:[0 0 0 0 0 1 2 3 4 5 6 7 8 9]。可以看到查询侧第 5~8 点连续压在参考侧第 0 点,说明那段局部形态被算法判定为「拉伸对齐」,并非一一对应。 随后 dtwTest 在多个前缀标签(NE/LH/FR/HE/RL)下重复打印 local cm 与对齐代价向量 [0,2,2,2,2,2,2,0,0,0]。这说明前 6 个桶代价恒为 2、末 3 个桶为 0,中间没有渐变,代价分布呈两段式而非平滑衰减。 DF 标签下出现了 2.220446049250313e-16 这种机器 epsilon 级数值,本质就是 0,但被浮点误差显式写出;ND 标签则把同样位置归整为纯 0。如果你在 MT5 里接这套日志,建议对 DF 类输出做 fabs(x)<1e-12 的归零处理,否则后续阈值判断可能踩坑。外汇与贵金属品种波动受杠杆与事件驱动影响,这类形态对齐结论仅作概率参考,实盘仍属高风险。

DTW 对齐日志里的累积矩阵信号

在 EURUSD DFX 10 Index 的 D1 周期上跑 dtwTest,终端会在 20:56:48.971 起的一毫秒内连续吐出多组前缀标签(FR、RS、OH、ML、JR 等)的调试行,每一行都挂着同一个时间戳和同一组标的参数,说明 DTW 内部在极短时间内完成了多次路径与代价矩阵的刷新。 FR 行给出的向量是 [2,0,0,0,0,0,2.22e-16,2,2,2],其中第 7 位的 2.220446049250313e-16 是双精度浮点的机器 epsilon 量级,基本可视为 0,意味着该维度上两段序列的扭曲代价在数值上完全贴合。 RS 与 OH 行输出 [0,2,2,2,2,2,2,0,0,0],而 ML 行的 final cm 显示累积矩阵已经从 0 线性爬到 12: [0,2,4,6,8,10,12,12,12,12],后续 JR/JH/JN/JD 四行完全复读这一最终累积序列,表明对齐路径在第 7 个节点后封口,不再产生新增代价。 这种日志形态说明 DTW 在该标的 D1 上倾向于前 6 根 K 线承担全部失真成本,后 4 根仅做平移重叠;外汇与贵金属品种波动跳变大,此类序列对齐结果仅反映历史形态距离,实盘应用仍属高风险,建议直接把上面这组调用丢进 MT5 专家日志比对自己的品种表现。

「DTW 比对日志里的方向矩阵信号」

在 EURUSD DFX 10 Index 的 D1 周期上跑 dtwTest,控制台同一毫秒(20:56:48.972)吐出了多组前缀标记。EI、CP、EH 三组的首元素分别是 2、4、6,后面紧跟一串 2.22e-16 量级的极小值,说明这几路窗口的距离累积在尾部几乎归零,形态贴合度倾向极高。 IN 与 HS 两组的数组前半段是 6 和 2/4 的整数序列,末三位才是 4.44e-16,反推它们匹配的是另一段刚性偏移路径,和 EI 系不是同一类形变。 MO 行直接标注 direction matrix,QK 行给出首值 -2147483648 加一列 1,这是 32 位有符号整型下溢的典型占位,意味着方向回溯矩阵在边界初始化时用了极小哨兵值。 GN 行 [2,0,0,…] 表明匹配起点锁在第 2 个索引,其余归零,实盘验证时可直接在 MT5 专家日志里过滤 GN 前缀看锚点是否随品种切换漂移。外汇与贵金属属高风险品种,上述 DTW 贴合仅代表历史形态概率接近,不代表后续必然重演。

◍ DTW 对齐结果里的品种差异

在 EURUSD DFX 10 Index 的 D1 周期上跑 dtwTest,同一时间戳 20:56:48.972 吐出了多个品种的对齐向量。QQ、CK、MR 三个前缀返回的数组全是 [2,0,0,0,0,0,0,0,0,0],说明它们与参考序列的扭曲路径起点一致、后续无偏移。 OE 的数组为 [2,0,0,1,1,1,1,1,1,1,1],从索引 3 开始连续 8 个位置均为 1,表明该品种在中段与参考序列发生了系统性步移。HO、MF、CH、LN 则在索引 1 处就出现 2,且后续零星分布 0/1/2,对齐形态明显更碎。 PK 行只打印了 'Warp path' 而未给出数值数组,HM 行给出了 [[9,9] 的截断路径点——意味着二者走的是路径回溯逻辑,不是直接输出对齐计数。 外汇与贵金属属高杠杆品种,DFX 指数对齐结论仅反映历史形态相似度,实盘映射倾向失效,请用 MT5 自带策略测试器复跑上述 dtwTest 调用以核对本地结果。

DTW 对齐路径与距离度量的实测输出

在 MT5 专家日志里跑 dtwTest(EURUSD DFX 10 Index, D1),同一时间戳 20:56:48.972 吐出了 14 组对齐坐标,从 [8,8] 一路递减到 [0,0],说明两条序列在日线尺度上被强制弯折到等长端点。 这些 [i,j] 对是动态时间规整的核心:横轴为参考序列下标、纵轴为待测序列下标,越靠近 [0,0] 代表尾部越同步。外汇与贵金属这类高波动品种做 DTW 前务必清醒,序列错位可能只是噪声,并非真实领先滞后关系。 末尾两行才是关键判据:Distance metric 4.440892098500626e-16,Normalized Distance metric 2.2204460492503132e-17。数值量级落在双精度浮点舍入误差区间,意味着这两个 D1 序列在该窗口内几乎完全重合。 开 MT5 把这段日志复现一遍,若你的 normalized 距离也压到 1e-17 附近,就可以怀疑样本区间存在复制式报价,而非市场自发波动。

MQL5 / C++
GJ      class="num">0    class="num">20:class="num">56:class="num">48.972  dtwTest(EURUSD DFX class="num">10 Index,D1)          [class="num">8,class="num">8]
HS      class="num">0    class="num">20:class="num">56:class="num">48.972  dtwTest(EURUSD DFX class="num">10 Index,D1)          [class="num">8,class="num">7]
DK      class="num">0    class="num">20:class="num">56:class="num">48.972  dtwTest(EURUSD DFX class="num">10 Index,D1)          [class="num">7,class="num">6]
HP      class="num">0    class="num">20:class="num">56:class="num">48.972  dtwTest(EURUSD DFX class="num">10 Index,D1)          [class="num">6,class="num">5]
QI      class="num">0    class="num">20:class="num">56:class="num">48.972  dtwTest(EURUSD DFX class="num">10 Index,D1)          [class="num">6,class="num">4]
GQ      class="num">0    class="num">20:class="num">56:class="num">48.972  dtwTest(EURUSD DFX class="num">10 Index,D1)          [class="num">5,class="num">3]
RN      class="num">0    class="num">20:class="num">56:class="num">48.972  dtwTest(EURUSD DFX class="num">10 Index,D1)          [class="num">5,class="num">2]
MG      class="num">0    class="num">20:class="num">56:class="num">48.972  dtwTest(EURUSD DFX class="num">10 Index,D1)          [class="num">5,class="num">1]
CO      class="num">0    class="num">20:class="num">56:class="num">48.972  dtwTest(EURUSD DFX class="num">10 Index,D1)          [class="num">4,class="num">0]
LD      class="num">0    class="num">20:class="num">56:class="num">48.972  dtwTest(EURUSD DFX class="num">10 Index,D1)          [class="num">3,class="num">0]
EL      class="num">0    class="num">20:class="num">56:class="num">48.972  dtwTest(EURUSD DFX class="num">10 Index,D1)          [class="num">2,class="num">0]
JE      class="num">0    class="num">20:class="num">56:class="num">48.972  dtwTest(EURUSD DFX class="num">10 Index,D1)          [class="num">1,class="num">0]
DO      class="num">0    class="num">20:class="num">56:class="num">48.972  dtwTest(EURUSD DFX class="num">10 Index,D1)          [class="num">0,class="num">0]
LD      class="num">0    class="num">20:class="num">56:class="num">48.972  dtwTest(EURUSD DFX class="num">10 Index,D1)          Distance metric class="num">4.440892098500626e-16
NR      class="num">0    class="num">20:class="num">56:class="num">48.972  dtwTest(EURUSD DFX class="num">10 Index,D1)          Normalized Distance metric class="num">2.2204460492503132e-17

「用 DTW 在 MT5 里搜历史价格模式」

动态时间规整(DTW)的价值在于:它能把两条时间轴不对齐的序列弯折后比距离,所以同一类价格形态即使拉伸、压缩了,也可能被认出来。对自动化策略来说,这意味着你可以拿一段自定义的对数收益率片段,去历史数据里翻相似结构,而不是死磕固定 K 线数。 回测环节里,DTW 还能横向对齐不同策略的权益曲线,看谁在某种市况下更稳;或者把交易信号按形态聚类,挑出重复出现的高概率环境再微调参数。但外汇和贵金属波动大、跳空多,DTW 只是辅助,绝非万能,新手很容易被参数坑。 下面这段 MQL5 脚本就是干这事用的:它调用自定义指标 LogReturns.ex5 算对数收益率,然后把你给的 Pattern 字符串逐窗滑动,用 DTW 比距离,小于阈值就打印时间戳。 //+------------------------------------------------------------------+

//dtwPatternSearch.mq5
//Copyright 2024, MetaQuotes Ltd.
//[MQL5官方文档]

//+------------------------------------------------------------------+ // 版权与链接声明,标识脚本归属与版本 #property copyright "Copyright 2024, MetaQuotes Ltd." #property link "[MQL5官方文档] #property version "1.00" // 把自定义指标 LogReturns.ex5 作为资源编译进脚本 #resource "\Indicators\LogReturns.ex5" // 运行前弹出输入参数框 #property script_show_inputs // 引入 DTW 算法库与错误描述库 #include<dtw.mqh> #include<ErrorDescription.mqh> // 枚举:选收盘价、中位价还是典型价 enum ENUM_PRICE { CLOSE=0,//close price MEDIAN,//median price TYPICAL//typical price }; // 枚举:价格转成百分比差还是对数差 enum ENUM_TRANSFORM_TYPE { PERCENT_DIFF=0,//percent difference LOG_DIFF//log difference }; //--- input parameters // 要搜的模式,默认四个对数收益率值 input string Pattern = "0.0469,0.0093,0.0697,-0.0699"; // 交易品种,默认 BTCUSD input string SymbolName="BTCUSD"; // 搜索起始日 2024.06.01 input datetime SearchStartDate=D'2024.06.01'; // 搜索结束日 2018.04.22(注意早于起始日,需自测逻辑) input datetime SearchStopDate=D'2018.04.22'; // 归一化距离阈值,小于此值算匹配 input double NormalizedDistanceThreshold=0.01; // 数据周期:日线 input ENUM_TIMEFRAMES TimeFrame=PERIOD_D1; // 距离度量:欧氏 input ENUM_DIST_METRIC AppliedDistanceMetric = DIST_EUCLIDEAN; // 步进模式:对称2 input ENUM_STEP_PATTERN AppliedStepPattern = STEP_SYMM2; // 全局约束:无 input ENUM_GLOBAL_CONSTRAINT AppliedGlobalConstraint = CONSTRAINT_NONE; // 约束窗口大小 input ulong GlobalConstraintWinSize = 0; // 应用价格类型:收盘 input ENUM_PRICE AppliedPrice=CLOSE; // 变换类型:对数差 input ENUM_TRANSFORM_TYPE AppliedTransform=LOG_DIFF; // 滞后值 input int Lag = 1; 脚本里 NormalizedDistanceThreshold 默认才 0.01,约束设成 CONSTRAINT_NONE,实测同样一段模式换全局窗口或距离度量,匹配次数会差很远。别把正态当圣经 DTW 最容易出虚假匹配:你以为找到了重复形态,其实只是约束太松把噪声对齐了。跑之前先把全局约束和步进模式手动试几轮,再信计数结果。 开 MT5 把上面脚本和资源指标放好,改 SymbolName 为自己的品种,调阈值从 0.05 往下压,就能直观看到‘历史里这段形态到底出现过几次’。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                        dtwPatternSearch.mq5 |
class=class="str">"cmt">//|                                     Copyright class="num">2024, MetaQuotes Ltd. |
class=class="str">"cmt">//|                                         [MQL5官方文档] |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#class="kw">property copyright "Copyright class="num">2024, MetaQuotes Ltd."
class="macro">#class="kw">property link      "[MQL5官方文档]
class="macro">#class="kw">property version   "class="num">1.00"
class="macro">#resource "\Indicators\LogReturns.ex5"
class="macro">#class="kw">property script_show_inputs
class="macro">#include<dtw.mqh>
class="macro">#include<ErrorDescription.mqh>
enum ENUM_PRICE
  {
   CLOSE=class="num">0,class=class="str">"cmt">//close price
   MEDIAN,class=class="str">"cmt">//median price
   TYPICALclass=class="str">"cmt">//typical price
  };
enum ENUM_TRANSFORM_TYPE
  {
   PERCENT_DIFF=class="num">0,class=class="str">"cmt">//percent difference
   LOG_DIFFclass=class="str">"cmt">//log difference
  };
class=class="str">"cmt">//--- input parameters
input class="type">class="kw">string  Pattern = "class="num">0.0469,class="num">0.0093,class="num">0.0697,-class="num">0.0699";
input class="type">class="kw">string  SymbolName="BTCUSD";
input class="type">class="kw">datetime SearchStartDate=D&class="macro">#x27;class="num">2024.06.class="num">01&class="macro">#x27;;
input class="type">class="kw">datetime SearchStopDate=D&class="macro">#x27;class="num">2018.04.class="num">22&class="macro">#x27;;
input class="type">class="kw">double  NormalizedDistanceThreshold=class="num">0.01;
input ENUM_TIMEFRAMES      TimeFrame=PERIOD_D1;
input ENUM_DIST_METRIC AppliedDistanceMetric = DIST_EUCLIDEAN;
input ENUM_STEP_PATTERN AppliedStepPattern = STEP_SYMM2;
input ENUM_GLOBAL_CONSTRAINT AppliedGlobalConstraint = CONSTRAINT_NONE;
input class="type">ulong GlobalConstraintWinSize = class="num">0;
input ENUM_PRICE AppliedPrice=CLOSE;
input ENUM_TRANSFORM_TYPE AppliedTransform=LOG_DIFF;
input class="type">int Lag = class="num">1;
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Script program start function                                      |
class=class="str">"cmt">//+------------------------------------------------------------------+

◍ 用 DTW 在 BTCUSD 日线里捞历史相似段

这段 OnStart 把形态字符串按逗号拆成数组,少于 3 个点直接弹窗退出,避免后续向量维度不够导致 DTW 报错。拆完后用 StringToDouble 转成 vector,再去加载自写的 LogReturns.ex5 指标句柄,拿历史对数收益率缓冲。 搜索靠 iCustom 挂 ::Indicators\LogReturns.ex5,若返回 INVALID_HANDLE 就打印错误并 return,这一步卡死了数据源。随后 CopyIndicatorBuffer 按起止日期把序列拷进 searchBuffer,任何加载失败也都直接退出。 核心循环用 np::sliceVector 每次切出与模板等长的子段,跑 cdtw.dtw 算归一化距离;小于 NormalizedDistanceThreshold 就计数并打印对应 K 线时间。日志里实跑样本:BTCUSD 的 D1 周期,距离阈值设 0.01、度量用 DIST_EUCLIDEAN,最终在终端输出 Reference pattern found N times,外汇与贵金属同理但波动更跳,阈值可能要放宽。 想复现就改 Pattern 字符串和 SearchStartDate/StopDate,把 NormalizedDistanceThreshold 从 0.01 往上试,看计数器怎么变——这是验证形态匹配灵敏度的直接办法。

MQL5 / C++
class="type">void OnStart()
  {
class=class="str">"cmt">//---
   class="type">class="kw">string pattern_values[];
class=class="str">"cmt">//---
   class="type">int len = StringSplit(Pattern,StringGetCharacter(",",class="num">0),pattern_values);
   if(pattern_values[len-class="num">1]=="")
       len--;
class=class="str">"cmt">//---
   if(len<class="num">3)
     {
       Alert("Pattern sequence is inadequately defined");
       class="kw">return;
     }
class=class="str">"cmt">//---
   vector pattern(len);
   for(class="type">ulong i = class="num">0; i<pattern.Size(); i++)
       pattern[i]=StringToDouble(pattern_values[i]);
class=class="str">"cmt">//---set prices handle
   class="type">int handle = INVALID_HANDLE;
   handle=iCustom(SymbolName!=""?SymbolName:NULL,TimeFrame,"::Indicators\\LogReturns.ex5",AppliedPrice,AppliedTransform,class="num">1);
   if(handle==INVALID_HANDLE)
     {
       Print("invalid handle ",ErrorDescription(GetLastError()));
       class="kw">return;
     }
class=class="str">"cmt">//---
   vector searchBuffer;
   if(!searchBuffer.CopyIndicatorBuffer(handle,class="num">0,SearchStartDate,SearchStopDate))
     {
       Print("History loading error ",ErrorDescription(GetLastError()));
       class="kw">return;
     }
class=class="str">"cmt">//---
   class="type">ulong stop = searchBuffer.Size()-pattern.Size();
   vector subv;
   Cdtw cdtw;
   class="type">ulong counter=class="num">0;
   for(class="type">ulong i = class="num">0; i<stop; i++)
     {
       subv = np::sliceVector(searchBuffer,i,i+pattern.Size());
       if(!cdtw.dtw(subv,pattern,AppliedDistanceMetric,AppliedStepPattern,AppliedGlobalConstraint,GlobalConstraintWinSize))
         {
          Print(" dtw failed ");
          class="kw">return;
         }
       if(cdtw.normalizedDistance()<NormalizedDistanceThreshold)
         {
          counter++;
          Print(" pattern found  ", class="type">class="kw">datetime(SearchStopDate+(PeriodSeconds(TimeFrame)*(i+pattern.Size()-class="num">1))));
         }
     }
class=class="str">"cmt">//---
   Print(" SearchBuffer size ", searchBuffer.Size());
   Print(" Reference pattern found ", counter, " times.");
  }

DTW 在 BTCUSD 日线里的命中日志

把 DTW 形态搜索跑在 BTCUSD 的 D1 周期上,引擎在 22:00:09.211 至 22:00:09.250 这 39 毫秒内扫完了 2197 根 K 线组成的缓冲区,约束模式为 STEP_SYMM2 且未加额外限制(CONSTRAINT_NONE),窗口大小为 0 表示按完整参考段比对。 日志里打出 7 次参考形态命中,时间锚点分别是 2018.04.25、2019.04.28、2019.07.01、2020.04.27、2020.10.26、2021.02.06 与 2024.01.29。这些日期并非随机散布,大多落在每年 4 月下旬到 7 月初,以及 10 月到次年 2 月之间,说明该对称形态在 BTC 季度转换窗口出现的概率倾向更高。 外汇与贵金属品种上直接套用这套参数要谨慎,加密与传统盘面的波动结构差异大,高风险特征明显;建议先在 MT5 用下面这段日志输出核对你自己品种的 SearchBuffer size 与命中次数,再决定是否收窄 WinSize。

MQL5 / C++
ND       class="num">0    class="num">22:class="num">00:class="num">09.211  dtwPatternSearch(BTCUSD,D1)  STEP_SYMM2
CN       class="num">0    class="num">22:class="num">00:class="num">09.211  dtwPatternSearch(BTCUSD,D1)  CONSTRAINT_NONE
HG       class="num">0    class="num">22:class="num">00:class="num">09.211  dtwPatternSearch(BTCUSD,D1)  WinSize class="num">0
OO       class="num">0    class="num">22:class="num">00:class="num">09.211  dtwPatternSearch(BTCUSD,D1)  pattern found  class="num">2018.04.class="num">25 class="num">00:class="num">00:class="num">00
NJ       class="num">0    class="num">22:class="num">00:class="num">09.221  dtwPatternSearch(BTCUSD,D1)  pattern found  class="num">2019.04.class="num">28 class="num">00:class="num">00:class="num">00
KD       class="num">0    class="num">22:class="num">00:class="num">09.222  dtwPatternSearch(BTCUSD,D1)  pattern found  class="num">2019.07.class="num">01 class="num">00:class="num">00:class="num">00
QO       class="num">0    class="num">22:class="num">00:class="num">09.230  dtwPatternSearch(BTCUSD,D1)  pattern found  class="num">2020.04.class="num">27 class="num">00:class="num">00:class="num">00
II       class="num">0    class="num">22:class="num">00:class="num">09.234  dtwPatternSearch(BTCUSD,D1)  pattern found  class="num">2020.10.class="num">26 class="num">00:class="num">00:class="num">00
PD       class="num">0    class="num">22:class="num">00:class="num">09.237  dtwPatternSearch(BTCUSD,D1)  pattern found  class="num">2021.02.class="num">06 class="num">00:class="num">00:class="num">00
RN       class="num">0    class="num">22:class="num">00:class="num">09.250  dtwPatternSearch(BTCUSD,D1)  pattern found  class="num">2024.01.class="num">29 class="num">00:class="num">00:class="num">00
DH       class="num">0    class="num">22:class="num">00:class="num">09.250  dtwPatternSearch(BTCUSD,D1)  SearchBuffer size class="num">2197
PQ       class="num">0    class="num">22:class="num">00:class="num">09.250  dtwPatternSearch(BTCUSD,D1)  Reference pattern found class="num">7 times.

「画得少,看得清」

DTW 把两条序列按最小累积距离弯折对齐,代价是 O(n²) 以上的比对量;在 MT5 里跑 dwTest.mq5 对照 1 分钟 EURUSD 与 XAUUSD 各 500 根 bar,单组对齐耗时往往超过 200 ms,实时刷新型 EA 得先降采样。 噪声是另一道坎:LogReturns.mq5 算出的对数收益率里,单根异常 bar 能把变形路径带偏,出现形似突破、实则无预测力的假阳性。贵金属与外汇杠杆高,这类误报若直接进单,回撤可能超出承受。 真要落地,就打开 dtwPatternSearch.mq5,把样本窗口缩到 100~150 根,配合 np.mqh 里的平滑函数先滤一道,再让小布盯盘替你扫多品种。算法只是放大镜,看得清不等于一定对。

常见问题

当两条序列长度差异超出预设窗口时,系统会回退到固定惩罚项的兜底距离,避免对齐崩溃;你可手动调窗口参数控制容差。
把同一组序列分别丢进 Python 的 dtw 库和你的 Cdtw 类,比对最终距离与对齐路径索引;不一致就查边界与惩罚项实现。
可以,小布能读取你的对齐日志自动渲染路径与成本矩阵图,省去手敲绘图代码,打开品种页即可调用。
看对角线附近的低值带即为最优对齐区,偏离越远成本越高;同帧快照锁定某一时刻可对比多序列相对偏移。
累积矩阵是路径成本逐步叠加的结果,突增点说明局部形变剧烈;若末端值远超样本均值,该模式匹配可信度偏低。