研究烛条分析技术(第四部分):形态分析器的更新和补充(基础篇)
📘

研究烛条分析技术(第四部分):形态分析器的更新和补充(基础篇)

第 1/3 篇

「形态分析器更新的模块切分」

这篇 2019 年 5 月发布的 MT5 烛条形态分析器更新,原文把重构工作拆成了主窗口、对话窗口和形态搜索三个独立模块,而不是在一个文件里堆逻辑。这种切法对后续调参和加新形态有直接好处:你改搜索算法不会动到界面创建代码。 原文给出的模块清单里,主窗口负责创建和布局,对话窗口单独处理交互参数,计算部分则重写了烛条修订与形态匹配方法。想在 MT5 里复现,先打开 MetaEditor 建一个空 EA 或脚本,按这三个块分文件写,比照搬旧版单文件结构更容易排错。 一个可验证的落点:原文发布页显示 4 850 次浏览、24 条评论,说明这套模块化思路在实盘社群里被翻过很多次。你可以现在开 MT5 的导航栏,看自己装过的指标是否也是窗口与计算分离的结构,不是的话后续维护成本会偏高。

形态分析器这版改了什么

上一阶段我们已在 MT5 里搭出能测烛条形态相关性的程序,后续又加了用长/短实体、十字星、顶纺锤等基础类型拼自定义形态的能力。最近这版形态分析器(Pattern Analyzer)不是重写,而是在那套底子上修 bug、加功能、顺手把界面也调了。 开发时直接采纳了前一篇读者反馈,比如之前建自定义形态要点好几下菜单,这次合并进更短的路径。最终能跑的版本就是本文要拆的那一个,开 MT5 把旧版覆盖后就能看到差异。 外汇和贵金属行情受杠杆与跳空影响大,这类形态相关性工具只负责把历史统计摆出来,信号失效的概率始终存在,别当确定性依据。

◍ 分析面板重构后到底改了什么

MT5 里那套形态分析工具的老界面,最碍事的是顶部横向排布的三个选卡——右上角空着一大片却塞不进更多标签,字体还小得费眼。新版把它们挪到窗口左侧竖向排列,空白区直接腾出来留给后续扩展,肉眼可见更顺手。 旧版结果表格的数据密度太低:字号小、行高挤、整体尺寸也不够。重构后这三样同步放大,长串的形态测试结果扫一眼就能定位,不用凑近盯。 时间帧和样本范围的限定也被拆开了。原来只能是「Timeframe -> Result」的死结构,现在独立出多时间帧勾选和形态单选;历史区间从「往回数 N 根烛条」改成「日期范围」手动框选,想测哪段就测哪段。 一个关键迁移是 'Trend threshold value'(趋势阈值,单位点数)从 Settings 挪到了 Analyze 和 AutoSearch 各自选项卡内,且互不干扰。结果表还删了 occurrence 列,换成更实用的 Timeframe 列。 AutoSearch 选项卡原先每次改参数都要跳去 Settings,现在绝大多数设置就地完成,日期范围也独立保存。开 MT5 把这两页都打开对照老版本,就能验证左侧选卡和阈值参数的实际落点。

「GUI 重构后计算路径怎么跑」

新版本把主窗口拆成 Analyze、AutoSearch、Setting 三个板块,CProgram::CreateGUI() 里依次挂了 CreateWindow 与三个 Setting 弹窗。Analyze 页新增的形态集、时间帧集、日期范围按钮,本质是把旧版写死的交互改成可切换 UI,计算触发点仍绑定在品种列表点击事件上。 计算现在分两条线:已有形态走第一型 GetPatternType(),生成形态走第二型(传入字符串数组)。两者末尾都调同一个 GetPatternType() 重载做结果落表,但旧版结果表行数固定为 14(对应预设形态数),新版因用户可随意勾选形态/时间帧,改用 m_total_row 计数器动态加行,这是回测脚本里最容易漏改的地方。 实测图例 9 的样例:EURUSD 在 M15、M30、H1、H2 上跑 1 根烛条形态,只勾了索引 1 和 2 两个简单烛条。算法先扫完 M15 全部生成形态,再进 M30,依此类推——若你本地历史数据不全,GetCandleType() 会直接弹“数据不足”并中断,所以开 MT5 前先确认对应品种周期的历史下载完整。 AutoSearch 选项卡里时间帧数量未知,旧版按组合数固定写行已失效,现在每个选中时间帧都要重写整组生成形态数组。想验证这套逻辑,把下面 GUI 创建骨架直接塞进你的 EA 工程,断点跟一遍 CreatePatternSet 的按钮回调即可。

MQL5 / C++
<span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span>
<span class="comment">class=class="str">"cmt">//| 创建程序的图形界面                                                    |</span>
<span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span>
<span class="keyword">class="type">bool</span> CProgram::CreateGUI(<span class="keyword">class="type">void</span>)
&nbsp;&nbsp;{
<span class="comment">class=class="str">"cmt">//--- 创建面板</span>
&nbsp;&nbsp; <span class="keyword">if</span>(!CreateWindow(<span class="class="type">class="kw">string">"Pattern Analyzer"</span>))
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">class="kw">return</span>(<span class="keyword">class="kw">false</span>);
<span class="comment">class=class="str">"cmt">//--- 创建对话窗口</span>
&nbsp;&nbsp; <span class="keyword">if</span>(!CreateWindowSetting1(<span class="class="type">class="kw">string">"Settings"</span>))
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">class="kw">return</span>(<span class="keyword">class="kw">false</span>);
<span class="comment">class=class="str">"cmt">//--- 创建对话窗口</span>
&nbsp;&nbsp; <span class="keyword">if</span>(!CreateWindowSetting2(<span class="class="type">class="kw">string">"Date range settings"</span>))
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">class="kw">return</span>(<span class="keyword">class="kw">false</span>);
<span class="comment">class=class="str">"cmt">//--- 创建对话窗口</span>
&nbsp;&nbsp; <span class="keyword">if</span>(!CreateWindowSetting3(<span class="class="type">class="kw">string">"Date range settings"</span>))
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">class="kw">return</span>(<span class="keyword">class="kw">false</span>);
<span class="comment">class=class="str">"cmt">//--- 完成 GUI 创建</span>
&nbsp;&nbsp; CWndEvents::CompletedGUI();
&nbsp;&nbsp; <span class="keyword">class="kw">return</span>(<span class="keyword">true</span>);
&nbsp;&nbsp;}
<span class="comment">class=class="str">"cmt">//+-----------------------------------------------------------------</span>
<span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span>
<span class="comment">class=class="str">"cmt">//| “Analyze”选卡  &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;   &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; |</span>
<span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span>
<span class="comment">class=class="str">"cmt">//--- 创建形态集的按钮</span>
&nbsp;&nbsp; <span class="keyword">if</span>(!CreatePatternSet(m_patterns,<span class="number">class="num">10</span>,<span class="number">class="num">10</span>))
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">class="kw">return</span>(<span class="keyword">class="kw">false</span>);
<span class="comment">class=class="str">"cmt">//--- 时间帧标题</span>
&nbsp;&nbsp; <span class="keyword">if</span>(!CreateTFLabel(m_text_labels[<span class="number">class="num">1</span>],<span class="number">class="num">10</span>,<span class="number">class="num">100</span>,<span class="number">class="num">0</span>))

面板两页的控件布局坐标

这套自动搜索面板分了两个选卡,每个选卡里的控件都用固定像素坐标堆出来,任何一步创建失败就直接 return(false) 中断初始化。 第一页从 x=10 起手:时间帧集合按钮放在 (10,125),品种过滤框在 (10,180),日期范围在 (280,180),盈利阈值输入框宽 100 高 100 落在 (400,180),品种表与结果表则都从 y=225 开始,分别占 x=10 和 x=120。 第二页(AutoSearch 选卡)结构类似但控件索引加了 1:双按钮在 (200,50),三按钮在 (10,50),时间帧标题在 (10,100),时间帧集合在 (10,125),其余过滤、日期、阈值、表格坐标与第一页一致,仅传入 tab_index=1。 打开 MT5 把这段初始化逻辑粘进你的 CAppDialog 派生类,改几个坐标数就能重排面板——外汇与贵金属品种波动大,面板回测参数请先在模拟盘验证。

MQL5 / C++
   class="kw">return(class="kw">false);
class=class="str">"cmt">//--- 创建时间帧集合的按钮
   if(!CreateTimeframeSet(m_timeframes,class="num">10,class="num">125,class="num">0))
      class="kw">return(class="kw">false);
class=class="str">"cmt">//--- 品种过滤搜索窗口 
   if(!CreateSymbolsFilter(m_symb_filter1,m_request1,class="num">10,class="num">180,class="num">0))
      class="kw">return(class="kw">false);
class=class="str">"cmt">//--- 创建日期范围选择按钮
   if(!CreateDateRange(m_request3,class="num">280,class="num">180,class="num">0))
      class="kw">return(class="kw">false);
class=class="str">"cmt">//--- 为盈利阈值创建输入字段
   if(!CreateThresholdValue(m_threshold1,class="num">400,class="num">180,class="num">100,class="num">0))
      class="kw">return(class="kw">false);
class=class="str">"cmt">//--- 创建品种表格
   if(!CreateSymbTable(m_symb_table1,class="num">10,class="num">225,class="num">0))
      class="kw">return(class="kw">false);
class=class="str">"cmt">//--- 创建结果表格
   if(!CreateTable1(m_table1,class="num">120,class="num">225,class="num">0))
      class="kw">return(class="kw">false);
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| “AutoSearch" 选卡                                                                           |
class=class="str">"cmt">//+------------------------------------------------------------------+
   if(!CreateTFLabel(m_text_labels[class="num">4],class="num">10,class="num">10,class="num">1))
      class="kw">return(class="kw">false);
class=class="str">"cmt">//--- 按钮
   if(!CreateDualButton(m_buttons[class="num">6],m_buttons[class="num">7],class="num">200,class="num">50))
      class="kw">return(class="kw">false);
   if(!CreateTripleButton(m_buttons[class="num">8],m_buttons[class="num">9],m_buttons[class="num">10],class="num">10,class="num">50))
      class="kw">return(class="kw">false);
class=class="str">"cmt">//--- 时间帧标题
   if(!CreateTFLabel(m_text_labels[class="num">5],class="num">10,class="num">100,class="num">1))
      class="kw">return(class="kw">false);
class=class="str">"cmt">//--- 创建时间帧集合的按钮
   if(!CreateTimeframeSet(m_timeframes1,class="num">10,class="num">125,class="num">1))
      class="kw">return(class="kw">false);
class=class="str">"cmt">//--- 编辑字段
   if(!CreateSymbolsFilter(m_symb_filter2,m_request2,class="num">10,class="num">180,class="num">1))
      class="kw">return(class="kw">false);
class=class="str">"cmt">//--- 创建日期范围选择按钮
   if(!CreateDateRange(m_request4,class="num">280,class="num">180,class="num">1))
      class="kw">return(class="kw">false);
class=class="str">"cmt">//--- 为盈利阈值创建输入字段
   if(!CreateThresholdValue(m_threshold2,class="num">400,class="num">180,class="num">100,class="num">1))
      class="kw">return(class="kw">false);
class=class="str">"cmt">//--- 创建品种表格
   if(!CreateSymbTable(m_symb_table2,class="num">10,class="num">225,class="num">1))
      class="kw">return(class="kw">false);
class=class="str">"cmt">//--- 创建结果表格
   if(!CreateTable2(m_table2,class="num">120,class="num">225,class="num">1))

◍ 在 Settings 选卡里铺开烛条与系数控件

这段逻辑跑在 GUI 的 Settings 选卡初始化里,任何一步 Create 失败就直接 return(false),整个面板不会显示。它依次把 6 种价格行为学常用烛条以图片按钮形式摆出来:Long 在 (10,10)、Short 在 (104,10)、Spinning top 在 (198,10)、Doji 在 (292,10)、Marubozu 在 (386,10)、Hammer 在 (480,10),间距固定 94 像素,方便交易者一眼点选形态。 文本标签只建了两个锚点:m_text_labels[0] 在 (10,140)、m_text_labels[3] 在 (300,140),说明界面左侧和右侧各留了一块说明文字区。 系数编辑框 K1/K2/K3 分别落在 (10,180)、(100,180)、(200,180),默认值 1、0.5、0.25——这三个数直接决定后续形态识别里实体与影线比例的宽松度,调小会更倾向捕获更多变异形态。 语言选项 m_lang_setting 放在 (10,240),默认 2;列表视图从 (300,180) 起。开 MT5 把这套坐标和默认值抄进你的面板类,改 K 值前先想清楚是要严判还是广撒网。外汇与贵金属波动剧烈,这类形态识别仅作概率参考,实盘须控仓。

MQL5 / C++
class="kw">return(class="kw">false);
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| ”Settings“ 选卡                                                                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//--- 创建烛条设置
   if(!CreateCandle(m_pictures[class="num">0],m_buttons[class="num">0],m_candle_names[class="num">0],"Long",class="num">10,class="num">10,"Images\\EasyAndFastGUI\\Candles\\class="type">long.bmp"))
      class="kw">return(class="kw">false);
   if(!CreateCandle(m_pictures[class="num">1],m_buttons[class="num">1],m_candle_names[class="num">1],"Short",class="num">104,class="num">10,"Images\\EasyAndFastGUI\\Candles\\class="type">class="kw">short.bmp"))
      class="kw">return(class="kw">false);
   if(!CreateCandle(m_pictures[class="num">2],m_buttons[class="num">2],m_candle_names[class="num">2],"Spinning top",class="num">198,class="num">10,"Images\\EasyAndFastGUI\\Candles\\spin.bmp"))
      class="kw">return(class="kw">false);
   if(!CreateCandle(m_pictures[class="num">3],m_buttons[class="num">3],m_candle_names[class="num">3],"Doji",class="num">292,class="num">10,"Images\\EasyAndFastGUI\\Candles\\doji.bmp"))
      class="kw">return(class="kw">false);
   if(!CreateCandle(m_pictures[class="num">4],m_buttons[class="num">4],m_candle_names[class="num">4],"Marubozu",class="num">386,class="num">10,"Images\\EasyAndFastGUI\\Candles\\maribozu.bmp"))
      class="kw">return(class="kw">false);
   if(!CreateCandle(m_pictures[class="num">5],m_buttons[class="num">5],m_candle_names[class="num">5],"Hammer",class="num">480,class="num">10,"Images\\EasyAndFastGUI\\Candles\\hammer.bmp"))
      class="kw">return(class="kw">false);
class=class="str">"cmt">//--- 文本标签
   if(!CreateTextLabel(m_text_labels[class="num">0],class="num">10,class="num">140))
      class="kw">return(class="kw">false);
   if(!CreateTextLabel(m_text_labels[class="num">3],class="num">300,class="num">140))
      class="kw">return(class="kw">false);
class=class="str">"cmt">//--- 编辑字段
   if(!CreateCoef(m_coef1,class="num">10,class="num">180,"K1",class="num">1))
      class="kw">return(class="kw">false);
   if(!CreateCoef(m_coef2,class="num">100,class="num">180,"K2",class="num">0.5))
      class="kw">return(class="kw">false);
   if(!CreateCoef(m_coef3,class="num">200,class="num">180,"K3",class="num">0.25))
      class="kw">return(class="kw">false);
   if(!CreateLanguageSetting(m_lang_setting,class="num">10,class="num">240,class="num">2))
      class="kw">return(class="kw">false);
class=class="str">"cmt">//--- 列表视图
   if(!CreateListView(class="num">300,class="num">180))
      class="kw">return(class="kw">false);
class=class="str">"cmt">//---

常见问题

这版把形态分析器按功能拆成了独立模块,旧版耦合在一起的代码大概率要改调用方式,建议对照新模块接口重写接入部分。
面板重构后计算路径和控件布局都变了,重点看 Settings 选卡里的烛条与系数控件,参数位置挪动后别照老习惯填。
小布可以读取你的品种页配置,自动套用新版形态分析器的系数与布局,你只需确认生成的诊断结论是否符合预期。
重构后计算改为事件触发式路径,空闲不重算,实测同品种刷新耗时倾向更低,不用担心卡顿。
两页控件坐标已在文档里按 X/Y 标好,直接抄坐标值布局即可,别凭肉眼拖,容易偏几像素导致错位。