ZigZag (之字折线) 的力量(第二部分)。 接收、处理和显示数据的示例(基础篇)
◍ 用 ZigZag 抓转折:MT5 里怎么接数据画出来
ZigZag 指标在 MT5 里不是直接给你买卖信号,而是把价格里的显著折返点连成线,帮你看清波段结构。想在 EA 或脚本里用它,第一步是拿到它的缓冲数据,再决定怎么处理、怎么画。 下面这段 MQL5 演示了如何获取 ZigZag 句柄、读取最近两个折点数值并打印。实际跑的时候,把代码贴进 MT5 脚本编辑器编译,挂到 EURUSD 的 H1 图表,就能在专家日志里看到输出——外汇和贵金属波动大,ZigZag 参数敏感,不同品种回看点数要自己调,高风险。 需要注意,ZigZag 是重绘指标,越靠近当前 K 线,折点越可能偏移。所以拿它做触发条件时,建议只用已经稳定的历史折点,别拿最新未确认点下单。
class="type">int zigzag_handle = iCustom(_Symbol, PERIOD_H1, "Examples\\ZigZag", class="num">12, class="num">5, class="num">3); if(zigzag_handle == INVALID_HANDLE) { Print("Failed to create ZigZag handle"); class="kw">return; } class="type">class="kw">double zigzag_buffer[class="num">2]; if(CopyBuffer(zigzag_handle, class="num">0, class="num">0, class="num">2, zigzag_buffer) <= class="num">0) { Print("CopyBuffer failed"); class="kw">return; } Print("ZigZag[class="num">0] = ", zigzag_buffer[class="num">0], " ZigZag[class="num">1] = ", zigzag_buffer[class="num">1]);
四类价格行为指标与统计EA的分工
要把价格行为量化,第一步是定义什么是「线段」。在 MT5 里,线段通常指 ZigZag 识别出的相邻拐点之间的波动段,其大小与持续时间是后续统计的底层变量。 FrequencyChangeZZ 负责记录拐点出现的频率变化,用来观察市场节奏是否加快或放缓;SumSegmentsZZ 汇总线段的总量,适合判断一段行情里波动密集程度。 PercentageSegmentsZZ 按涨跌幅百分比给线段分类,MultiPercentageSegmentsZZ 则在此基础上做多阈值切分,能同时输出不同幅度区间的线段占比。外汇与贵金属波动受杠杆与消息影响大,这类统计仅反映历史分布,不预示下一步方向。 真正落地要靠配套 EA:它收集上述指标数据,分别按「大小」和「持续时间」计数,并在图形界面呈现。EA 处理界面时注意控件重绘开销,避免在每 tick 刷新全部标签,否则高频行情下可能卡顿。
「从修订 ZigZag 到可交易 EA 的链路」
前面已经给出过修订版 ZigZag 指标和一套接收该类指标数据的 C++ 类封装。这一节要做的,是把这些底层工具真正接上交易逻辑:先基于它们写出指标,再写一个读取 ZigZag 形态信号下单的 EA,用历史数据跑通验证。 顺带会引入 EasyAndFast 图形界面函数库的新版本,省掉自己撸窗体事件的麻烦。MT5 里新建 EA 时直接#include 这套库,就能在参数面板外再挂一个可视化统计面板。 本篇主线分三块:定义价格行为的指标原型、带 GUI 的统计型 EA、以及专门数指定区间内 ZigZag 线段根数的 EA。外汇与贵金属波动大、滑点不可控,这类统计 EA 只用于概率观察,不预示单边走势。
◍ 四个刻画价格行为的派生指标
做价格行为量化,先要把走势拆成可统计的线段。基于 ZigZag 的派生指标里,有四个直接定义价格行为特征:FrequencyChangeZZ 统计反向 ZigZag 线段的形成频率,SumSegmentsZZ 从线段集合算总和与均值,PercentageSegmentsZZ 给出线段总和的占比及相互差值,MultiPercentageSegmentsZZ 则借用 PercentageSegmentsZZ 的数值,去判断更高时间帧中若干线段的形成性质。 这四个指标代码结构都和前文 ZigZag 一致,真正要盯的只有接收数据并填充缓冲区的 FillIndicatorBuffers 主函数。 在 MT5 里把这类指标挂到 EURUSD 的 M15,观察 FrequencyChangeZZ 连续 20 根 K 线的输出,能直接看到反向线段形成节奏的稀密变化——这是手动画线很难快速量化的。外汇与贵金属杠杆高、滑点随机,这类统计只描述历史行为,后续转折仍是概率事件。
FrequencyChangeZZ 怎么把 ZigZag 线段数搬进缓冲区
FrequencyChangeZZ 的核心思路,是把 ZigZag 的线段结构换算成「每段包含多少根柱线」,再写进自己的指标缓冲区。主图挂一个普通 ZigZag 只是为了肉眼对照,真正的计算全在子窗口完成。 外部参数只有三项,且本文所有指标共用同一套:按全部可用历史计算、ZigZag 新线段的最小偏差阈值、以及取最终数据用的极值数量。调参时若改了最小偏差,子窗口里「价格放缓」的显现密度会明显变化。 函数入口先接收柱线索引 i 和时间数组 time[],随后从当前柱线时间起,尝试拷贝 1000 根柱的 ZigZag 高低缓冲与对应时间。拷贝成功才往下走,失败就重试最多 10 次——这是为了防止 MT5 在品种刚加载时返回不全。 拿到源数据后调用 GetZigZagData 重组线段,再用 SegmentsTotalBars 取出线段集合里的柱线总数,落进 segments_bars_total_buffer[i]。图表上柱状越低,往往意味着该段 ZigZag 收敛快、价格在选择方向前出现了放缓。外汇与贵金属波动剧烈,这类放缓信号仅作概率参考,实盘须自担高风险。
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">//--- 将线段集合中的柱线数量保存到指标缓冲区 segments_bars_total_buffer[i]=zz.SegmentsTotalBars(); class="kw">break; } } }
「用三条缓冲区看线段总和的强弱倾斜」
SumSegmentsZZ 在底层复用了前一示例的取数逻辑,差异只发生在缓冲区填充阶段:它分别向三个缓冲区写数据,两个对应向上与向下线段的总和,第三个存二者均值,方便直接在图表上比对多空线段的累积能量。 加载到 MT5 图表后,蓝线代表向上线段总和、红线代表向下线段总和。实测可见,当蓝线持续位于红线上方,说明向上线段总和大于向下线段总和;红线反超则情形逆转。 单向线段总和压过反向的时间越长,后续发生逆转的概率倾向升高,但这只是图表现象,能否作为价格方向的前瞻信号,仍需在策略测试器里跑实验验证,外汇与贵金属品种本身波动剧烈、风险较高。 下面这段是缓冲区填充核心:每次尝试从 ZZ 句柄拷贝 1000 根 K 线的高低点与时间,成功后调用 SumSegmentsUp / Down 算总和并写缓冲,最多重试 10 次。
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">//--- 获取线段数据 segments_up_total_buffer[i] =zz.SumSegmentsUp(); segments_dw_total_buffer[i] =zz.SumSegmentsDown(); segments_average_buffer[i] =(segments_up_total_buffer[i]+segments_dw_total_buffer[i])/class="num">2; class="kw">break; } } }