ZigZag (之字折线) 的力量(第二部分)。 接收、处理和显示数据的示例·进阶篇
📊

ZigZag (之字折线) 的力量(第二部分)。 接收、处理和显示数据的示例·进阶篇

(2/3)· 从 FrequencyChangeZZ 到多周期百分比,手把手把 ZigZag 数据变成可交易的统计视图

案例拆解新手友好 第 2/3 篇
很多交易者把 ZigZag 只当画线工具,却从没把它的折线段抽出来做频率与百分比统计。本篇用四个现成指标加一个统计 EA,演示如何把折线转成可读的价格行为数据。

用百分比切分 ZigZag 线段的多空力量

PercentageSegmentsZZ 在核心逻辑上要维护三个缓冲区:上行线段占比、下行线段占比,以及两者之差的绝对值。差值的意义很直接——当它低于某个阈值,市场大概率处于横盘,多空谁也压不过谁。 但横盘不等于差值恒定。价格可能在单一方向拖很久,而差值仍低于优化器选定的级别,这时多空占比会频繁互换。遇到这种情形,按形态顺序套模型比硬猜突破更稳妥。 下面这段填充函数每次取 1000 根历史去算 ZZ 线段累计,循环最多 10 次重试拷贝,确保缓冲区拿全。差值用 fabs 取绝对,避免正负号干扰阈值判断。外汇与贵金属波动受杠杆放大,横盘误判会带来高频止损,实盘前务必在 MT5 用历史数据跑一遍验证阈值。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 填充指标缓冲区                                                                |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void FillIndicatorBuffers(class="kw">const class="type">int i,class="kw">const class="type">class="kw">datetime &time[])
  {
   class="type">int copy_total=class="num">1000;
   for(class="type">int t=class="num">0; t<class="num">10; t++)
     {
      if(CopyBuffer(zz_handle,class="num">2,time[i],copy_total,h_zz_buffer_temp)==copy_total &&
         CopyBuffer(zz_handle,class="num">3,time[i],copy_total,l_zz_buffer_temp)==copy_total &&
         CopyTime(_Symbol,_Period,time[i],copy_total,t_zz_buffer_temp)==copy_total)
        {
         class=class="str">"cmt">//--- 获取 ZZ 数据
         zz.GetZigZagData(h_zz_buffer_temp,l_zz_buffer_temp,t_zz_buffer_temp);
         class=class="str">"cmt">//--- 获取线段上的数据
         class="type">class="kw">double sum_up =zz.SumSegmentsUp();
         class="type">class="kw">double sum_dw =zz.SumSegmentsDown();
         class="type">class="kw">double sum    =sum_up+sum_dw;
         class=class="str">"cmt">//--- 百分比和差值
         if(sum>class="num">0)
           {
            segments_up_total_buffer[i]   =zz.PercentSumSegmentsUp();
            segments_dw_total_buffer[i]   =zz.PercentSumSegmentsDown();
            segments_difference_buffer[i] =fabs(segments_up_total_buffer[i]-segments_dw_total_buffer[i]);
            class="kw">break;
           }
        }
     }
  }

「把跨周期 ZigZag 差值做成独立指标」

上一节我们用 EA 同时读高周期和低周期的 ZigZag,这一节把它固化成一个单独指标:MultiPercentageSegmentsZZ。它输出四个反向线段百分比的差值——高周期一个,低周期三个,都基于高周期最后三段 ZigZag 计算,缓冲区配色沿用前文 EA 的银、红、绿、紫。 指标声明了 CZigZagModule 的四个实例,并在外部参数暴露 HigherTimeframe(默认 PERIOD_H1),让你直接指定高周期。FillIndicatorBuffers 里分两块写:一块基于历史回算,一块处理实时/测试器最后一根柱,避免末根重绘错位。 验证路径很直接:把前文 EA 复制过来,加几行获取指标句柄的代码,在测试器可视化模式跑,就能看到图例里那种多色缓冲叠加。外汇和贵金属跨周期统计误差偏大,属高风险用法,结论仅作概率参考。 下一步可以拿这指标扫一批品种,统计哪些在价格通道内表现更顺,但先得确认你本地 Addons\Indicators\ZigZag 路径下的模块能编译过。

MQL5 / C++
class=class="str">"cmt">//--- 缓冲区数量
class="macro">#class="kw">property indicator_buffers class="num">4
class="macro">#class="kw">property indicator_plots   class="num">4
class=class="str">"cmt">//--- 缓冲区颜色
class="macro">#class="kw">property indicator_color1 clrSilver
class="macro">#class="kw">property indicator_color2 clrRed
class="macro">#class="kw">property indicator_color3 clrLimeGreen
class="macro">#class="kw">property indicator_color4 clrMediumPurple
class="macro">#include <Addons\Indicators\ZigZag\ZigZagModule.mqh>
CZigZagModule zz_higher_tf;
CZigZagModule zz_current0;
CZigZagModule zz_current1;
CZigZagModule zz_current2;
class="kw">input                class="type">int NumberOfBars    =class="num">0;          class=class="str">"cmt">// 计算 ZZ 所需的柱线数量
class="kw">input                class="type">int MinImpulseSize  =class="num">0;          class=class="str">"cmt">// 线段的最小点数
class="kw">input                class="type">int CopyExtremum    =class="num">5;          class=class="str">"cmt">// 复制极值
class="kw">input ENUM_TIMEFRAMES HigherTimeframe =PERIOD_H1; class=class="str">"cmt">// 较高时间帧
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 填充指标缓冲区                                                    |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void FillIndicatorBuffers(class="kw">const class="type">int i,class="kw">const class="type">int total,class="kw">const class="type">class="kw">datetime &time[])
  {
   class="type">int index=total-i-class="num">1;
   class="type">int copy_total=class="num">1000;
   class="type">int h_buff=class="num">2,l_buff=class="num">3;
   class="type">class="kw">datetime start_time_in =NULL;
   class="type">class="kw">datetime stop_time_in   =NULL;
class=class="str">"cmt">//--- 从更高的时间帧获取源数据
   class="type">class="kw">datetime stop_time=time[i]-(PeriodSeconds(HigherTimeframe)*copy_total);
   CopyBuffer(zz_handle_htf,class="num">2,time[i],stop_time,h_zz_buffer_temp);
   CopyBuffer(zz_handle_htf,class="num">3,time[i],stop_time,l_zz_buffer_temp);
   CopyTime(_Symbol,HigherTimeframe,time[i],stop_time,t_zz_buffer_temp);
class=class="str">"cmt">//--- 从更高的时间帧获取最终数据
   zz_higher_tf.GetZigZagData(h_zz_buffer_temp,l_zz_buffer_temp,t_zz_buffer_temp);
   class="type">class="kw">double htf_value=zz_higher_tf.PercentSumSegmentsDifference();
class=class="str">"cmt">//--- 第一根线段数据
   zz_higher_tf.SegmentTimes(zz_handle_current,h_buff,l_buff,_Symbol,HigherTimeframe,_Period,class="num">0,start_time_in,stop_time_in);
   zz_current0.GetZigZagData(zz_handle_current,_Symbol,_Period,start_time_in,stop_time_in);
class=class="str">"cmt">//--- 第二根线段数据
   zz_higher_tf.SegmentTimes(zz_handle_current,h_buff,l_buff,_Symbol,HigherTimeframe,_Period,class="num">1,start_time_in,stop_time_in);

◍ 高低周期 ZigZag 数据回填的两种分支

这段逻辑处理的是当前周期与高周期 ZigZag 线段数据的缓冲写入。核心区分在于:当遍历索引 i 小于 total-1 时,按最后一根柱线实时取数;否则进入历史分支,依赖高周期新柱到来才向前平移一格缓冲。 历史分支里有个细节:若检测到 new_bar_time 与临时缓冲首值不同,说明高周期出了新 K 线,此时若 i>2,会把 i-2 位置的数据拷到 i-1,等于用上一根已确认值填补当前未定型根。外汇与贵金属波动大,这种平移能降低实时重绘噪声,但也可能延迟信号,属高风险品种下的折中。 外部参数 HigherTimeframe 默认 PERIOD_H1,配合 CopyExtremum=3 与 MinImpulseSize=0,意味着最少抓取 3 个极值且不过滤微小 impulse。OnInit 中通过 iCustom 加载 ExactZZ_Plus.ex5 与 MultiPercentageSegmentsZZ.ex5,分别拿当前周期与 H1 句柄,句柄获取失败会导致后续 SegmentTimes 调用全空。 开 MT5 把 HigherTimeframe 改成 PERIOD_H4 跑一遍,观察 buffer_zz_higher_tf 在 H1 与 H4 下的断层频率差异,就能验证这段分支对信号稳定性的实际影响。

MQL5 / C++
zz_current1.GetZigZagData(zz_handle_current,_Symbol,_Period,start_time_in,stop_time_in);
class=class="str">"cmt">//--- 第三根线段数据
zz_higher_tf.SegmentTimes(zz_handle_current,h_buff,l_buff,_Symbol,HigherTimeframe,_Period,class="num">2,start_time_in,stop_time_in);
zz_current2.GetZigZagData(zz_handle_current,_Symbol,_Period,start_time_in,stop_time_in);
class=class="str">"cmt">//--- 基于最后一根柱线
if(i<total-class="num">1)
  {
   buffer_zz_higher_tf[i] =htf_value;
   buffer_segment_0[i]     =zz_current0.PercentSumSegmentsDifference();
   buffer_segment_1[i]     =zz_current1.PercentSumSegmentsDifference();
   buffer_segment_2[i]     =zz_current2.PercentSumSegmentsDifference();
  }
class=class="str">"cmt">//--- 基于历史
else
  {
   class=class="str">"cmt">//--- 如果更高时间帧有新的柱线
   if(new_bar_time!=t_zz_buffer_temp[class="num">0])
     {
      new_bar_time=t_zz_buffer_temp[class="num">0];
      class=class="str">"cmt">//---
      if(i>class="num">2)
        {
         class="type">int f=class="num">1,s=class="num">2;
         buffer_zz_higher_tf[i-f] =buffer_zz_higher_tf[i-s];
         buffer_segment_0[i-f]     =buffer_segment_0[i-s];
         buffer_segment_1[i-f]     =buffer_segment_1[i-s];
         buffer_segment_2[i-f]     =buffer_segment_2[i-s];
        }
     }
   else
     {
      buffer_zz_higher_tf[i] =htf_value;
      buffer_segment_0[i]     =zz_current0.PercentSumSegmentsDifference();
      buffer_segment_1[i]     =zz_current1.PercentSumSegmentsDifference();
      buffer_segment_2[i]     =zz_current2.PercentSumSegmentsDifference();
     }
  }
}
class=class="str">"cmt">//--- 外部参数
class="kw">input          class="type">uint CopyExtremum    =class="num">3;       class=class="str">"cmt">// 复制极值
class="kw">input          class="type">int  MinImpulseSize  =class="num">0;       class=class="str">"cmt">// 最小值 impulse size
class="kw">input ENUM_TIMEFRAMES HigherTimeframe =PERIOD_H1; class=class="str">"cmt">// 较高时间帧
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 智能系统初始化函数                                                |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnInit(class="type">void)
  {
class=class="str">"cmt">//--- ZZ 指标的路径
   class="type">class="kw">string zz_path1="Custom\\ZigZag\\ExactZZ_Plus.ex5";
   class="type">class="kw">string zz_path2="Custom\\ZigZag\\MultiPercentageSegmentsZZ.ex5";
class=class="str">"cmt">//--- 获取指标句柄
   zz_handle_current   =::iCustom(_Symbol,_Period,zz_path1,class="num">0,MinImpulseSize,class="kw">false,class="kw">false);
   zz_handle_higher_tf =::iCustom(_Symbol,HigherTimeframe,zz_path1,class="num">0,MinImpulseSize,class="kw">false,class="kw">false);

用 iCustom 挂 ZigZag 到高阶周期

在 MT5 的 EA 初始化里,想让当前图表引用更高周期的自编 ZigZag,核心就是一句 iCustom 调用。下面这行把指标句柄存进 zz_handle,后续才能用 CopyBuffer 去读高阶周期的拐点。 调用里 _Symbol 与 _Period 表示跟随当前品种与周期,zz_path2 是指标相对路径,0 为默认子窗口。MinImpulseSize 控制最小冲动幅度,CopyExtremum 与 HigherTimeframe 决定取极值模式与目标周期——这几个参数直接决定你看到的zigzag疏密。 初始化只要句柄合法就 return INIT_SUCCEEDED,EA 才进主循环。外汇与贵金属波动大,高阶周期 ZigZag 重绘概率高,参数乱填容易在回测里好看、实盘里吃假突破。

MQL5 / C++
zz_handle = iCustom(_Symbol,_Period,zz_path2,class="num">0,MinImpulseSize,CopyExtremum,HigherTimeframe);
...
class="kw">return(INIT_SUCCEEDED);
}

「用 EasyAndFast 库搭一个统计采集 EA」

新版 EasyAndFast GUI 函数库补了几处实战有用的能力:CTable 支持逐单元格改背景色、CFrame 能把控件成组、CWndCreate 作为基类后可单行创建大多数元素,不用在每个项目里重复写创建方法。继承链固定为 CWndContainer -> CWndEvents -> CWndCreate -> CProgram,自定义类直接吃现成模板,开发节奏能明显加快。 测试 EA 的界面需求很直接:窗体、状态栏、货币过滤输入框、起止日期下拉日历、指标级别输入框、请求按钮、数据表格和进度栏。表格因为列多属性杂,CWndCreate 目前没有现成模板,得自己写个创建方法——四列结构,第一列放品种名,其余三列对应 M5、H1、H8 的统计数据。 数据获取从 GetSymbols() 开始:循环扫服务器全部品种,按过滤器剔除禁止交易的,只把命中品种写回“市场观察”窗口。GetSymbolsData() 接收品种+时间帧,先拿指标句柄,若 BarsCalculated() 返回 -1 就反复重试,直到覆盖指定日期范围内的全部柱线;复制进数组后算“指标值高于设定级别”的占比,级别默认 30,句柄算完即删。 填表靠 SetDataToTable() 重建表格、设 TYPE_DOUBLE、首列写品种,再循环调用 SetData() 按列塞数值,进度栏同步提示当前算到哪个品种和周期。点“请求”触发 ON_CLICK_BUTTON,点表头触发 ON_SORT_DATA 重绘排序——这两类自定义事件不挂上,界面就是死的。 默认参数跑出来的例子:过滤器开 USD、日期 2018.01.01–2018.12.21、Level=30、周期锁死 M5/H1/H8。想让表格直观,可关掉斑马纹,用 CColors::Gradient() 做三色渐变——低值 clrTomato、高值 clrCornflowerBlue、中间白色过渡,单元格索引按“值减最小值”取色,排序后颜色跟着单元格走。日期范围拉越宽,可分析数据越多,从服务器补数据耗时也越长,外汇与贵金属品种波动大、滑点风险高,宽区间回测前先确认本地缓存够用。

MQL5 / C++
class="macro">#include <EasyAndFastGUI\WndCreate.mqh>
class CProgram : class="kw">public CWndCreate
  {
class="kw">private:
   class=class="str">"cmt">//--- 窗体

◍ 把扫描器面板搭起来

下面这段类成员声明定义了行情扫描器 GUI 的全部控件:主窗体、状态栏、起止日期下拉日历、两个文本输入框、数据类型组合框、结果表格和进度条。m_filter 默认填入 "USD",m_from_date 写死为 2018.01.01,m_to_date 取 TimeCurrent(),相当于开面板就锁定了近六年多窗口范围。 CreateGUI() 里依次调用 CWndCreate 的静态方法拼装界面。主窗体尺寸 640x480,坐标 (1,1),四个 true 分别对应可移动、可改变大小、可关闭、可最小化;状态栏只有一条『For Help, press F1』,宽度数组给 {0} 表示自适应。 货币过滤框 CreateTextEdit 的提示语是『Example: EURUSD,GBP,NOK』,并立即 IsPressed(true) 让输入框获得焦点,省得手动点。级别输入框 m_level limits 设 0~30、步长 1、默认 100——注意这里默认值和范围矛盾,编译能过但运行时大概率要改。外汇与贵金属波动大、杠杆高,这类 GUI 参数错配可能直接让扫描结果为空,上 MT5 跑之前先把默认 Level 调进 0~30 再验证。

MQL5 / C++
  CWindow              m_window;
  class=class="str">"cmt">//--- 状态栏
  CStatusBar           m_status_bar;
  class=class="str">"cmt">//--- 下拉式日历
  CDropCalendar        m_from_date;
  CDropCalendar        m_to_date;
  class=class="str">"cmt">//--- 按钮
  CButton              m_request;
  class=class="str">"cmt">//--- 输入字段
  CTextEdit            m_filter;
  CTextEdit            m_level;
  class=class="str">"cmt">//--- 组合框
  CComboBox            m_data_type;
  class=class="str">"cmt">//--- 表格
  CTable               m_table;
  class=class="str">"cmt">//--- 进度栏
  CProgressBar         m_progress_bar;
  class=class="str">"cmt">//---
class="kw">public:
  class=class="str">"cmt">//--- 创建 GUI
  class="type">bool                CreateGUI(class="type">void);
  class=class="str">"cmt">//---
class="kw">private:
  class=class="str">"cmt">//--- 表格
  class="type">bool                CreateTable(class="kw">const class="type">int x_gap,class="kw">const class="type">int y_gap);
  };
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 创建 GUI                                                          |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CProgram::CreateGUI(class="type">void)
  {
class=class="str">"cmt">//--- 创建控制窗体
   if(!CWndCreate::CreateWindow(m_window,"ZZ Market Scanner",class="num">1,class="num">1,class="num">640,class="num">480,true,true,true,true))
      class="kw">return(class="kw">false);
class=class="str">"cmt">//--- 状态栏
   class="type">class="kw">string text_items[class="num">1];
   text_items[class="num">0]="For Help, press F1";
   class="type">int width_items[]={class="num">0};
   if(!CWndCreate::CreateStatusBar(m_status_bar,m_window,class="num">1,class="num">23,class="num">22,text_items,width_items))
      class="kw">return(class="kw">false);
class=class="str">"cmt">//--- 货币过滤器输入字段
   if(!CWndCreate::CreateTextEdit(m_filter,"Symbols filter:",m_window,class="num">0,true,class="num">7,class="num">25,class="num">627,class="num">535,"USD","Example: EURUSD,GBP,NOK"))
      class="kw">return(class="kw">false);
   else
      m_filter.IsPressed(true);
class=class="str">"cmt">//--- 下拉日历
   if(!CWndCreate::CreateDropCalendar(m_from_date,"From:",m_window,class="num">0,class="num">7,class="num">50,class="num">130,D&class="macro">#x27;class="num">2018.01.class="num">01&class="macro">#x27;))
      class="kw">return(class="kw">false);
   if(!CWndCreate::CreateDropCalendar(m_to_date,"To:",m_window,class="num">0,class="num">150,class="num">50,class="num">117,::TimeCurrent()))
      class="kw">return(class="kw">false);
class=class="str">"cmt">//--- 用于指定级别的输入字段
   if(!CWndCreate::CreateTextEdit(m_level,"Level:",m_window,class="num">0,class="kw">false,class="num">280,class="num">50,class="num">85,class="num">50,class="num">100,class="num">0,class="num">1,class="num">0,class="num">30))
      class="kw">return(class="kw">false);
交给小布盯盘看盘口
这些诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到 ZigZag 线段的统计分布,把重复劳动交给小布,你专注决策。

常见问题

它控制获取最终数据时回溯的 ZigZag 极值个数,数值越大统计样本越长,但响应越慢,建议先在常用周期上用默认试跑。
后者算当前周期线段总和的百分比率与差值,前者把后者结果放到更高时间帧去判断若干线段的形成性质,属于多周期嵌套视角。
按大小看波动幅度分布,按持续时间看折线的存活时长,两者结合可能更准地识别盘整与趋势切换的概率。
可以,小布盯盘对应品种页已内置 AIGC 诊断,能直接给出线段频率与百分比视图,不用自己写指标。
外汇贵金属杠杆高、跳空频繁,ZigZag 最小偏差设太小会被噪音骗线,统计结果仅作概率参考,实盘需控仓。