ZigZag (之字折线) 的力量(第二部分)。 接收、处理和显示数据的示例·进阶篇
(2/3)· 从 FrequencyChangeZZ 到多周期百分比,手把手把 ZigZag 数据变成可交易的统计视图
用百分比切分 ZigZag 线段的多空力量
PercentageSegmentsZZ 在核心逻辑上要维护三个缓冲区:上行线段占比、下行线段占比,以及两者之差的绝对值。差值的意义很直接——当它低于某个阈值,市场大概率处于横盘,多空谁也压不过谁。 但横盘不等于差值恒定。价格可能在单一方向拖很久,而差值仍低于优化器选定的级别,这时多空占比会频繁互换。遇到这种情形,按形态顺序套模型比硬猜突破更稳妥。 下面这段填充函数每次取 1000 根历史去算 ZZ 线段累计,循环最多 10 次重试拷贝,确保缓冲区拿全。差值用 fabs 取绝对,避免正负号干扰阈值判断。外汇与贵金属波动受杠杆放大,横盘误判会带来高频止损,实盘前务必在 MT5 用历史数据跑一遍验证阈值。
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 路径下的模块能编译过。
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 下的断层频率差异,就能验证这段分支对信号稳定性的实际影响。
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 重绘概率高,参数乱填容易在回测里好看、实盘里吃假突破。
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、中间白色过渡,单元格索引按“值减最小值”取色,排序后颜色跟着单元格走。日期范围拉越宽,可分析数据越多,从服务器补数据耗时也越长,外汇与贵金属品种波动大、滑点风险高,宽区间回测前先确认本地缓存够用。
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 再验证。
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);