ZigZag (之字折线) 的力量(第二部分)。 接收、处理和显示数据的示例·综合运用
(3/3)· 从接收数据到图形化统计,教你用 4 个指标和 2 类 EA 榨干之字折线的信息量
在 MT5 面板里搭按钮和品种扫描表
做多品种监控面板时,GUI 初始化顺序很关键:先挂按钮、再建表格、最后补进度栏,任何一步返回 false 都会导致整个面板加载失败。下面这段初始化尾部代码就是标准套路,按钮放在窗口坐标 (375,0)、宽 50 高 70,表格列间距 2 行间距 75,进度栏贴着状态栏。 //--- 按钮 if(!CWndCreate::CreateButton(m_request,"Request",m_window,0,375,50,70)) return(false); //--- 表格 if(!CreateTable(2,75)) return(false); //--- 进度栏 if(!CWndCreate::CreateProgressBar(m_progress_bar,"Processing:",m_status_bar,0,2,3)) return(false); //--- 结束 GUI 开发 CWndEvents::CompletedGUI(); return(true); } 表格本身用 CreateTable() 定义:4 列 1 行,列宽默认 50 像素、第 0 列拉到 80 像素放品种名,表头写死为 Symbols / M5 / H1 / H8,也就是只扫这四个时间框架。开启 Zebra 行底色(clrWhiteSmoke)和自动 X/Y 拉伸后,面板能跟着窗口变宽变高,右侧留 2 像素、底部留 24 像素边距。 #define COLUMNS1_TOTAL 4 #define ROWS1_TOTAL 1 //--- 保存指向主元素的指针 m_table.MainPointer(m_window); //--- 列宽数组 int width[COLUMNS1_TOTAL]; ::ArrayInitialize(width,50); width[0]=80; //--- 沿 X 轴的列中文本偏移的数组 int text_x_offset[COLUMNS1_TOTAL]; ::ArrayInitialize(text_x_offset,7); //--- 列中的文本对齐数组 ENUM_ALIGN_MODE align[COLUMNS1_TOTAL]; ::ArrayInitialize(align,ALIGN_CENTER); align[0]=ALIGN_LEFT; //--- 属性 m_table.TableSize(COLUMNS1_TOTAL,ROWS1_TOTAL); m_table.TextAlign(align); m_table.ColumnsWidth(width); m_table.TextXOffset(text_x_offset); m_table.ShowHeaders(true); m_table.IsSortMode(true); m_table.IsZebraFormatRows(clrWhiteSmoke); m_table.AutoXResizeMode(true); m_table.AutoYResizeMode(true); m_table.AutoXResizeRightOffset(2); m_table.AutoYResizeBottomOffset(24); //--- 创建控件 if(!m_table.CreateTable(x_gap,y_gap)) return(false); //--- 标题 string headers[]={"Symbols","M5","H1","H8"}; for(uint i=0; i<m_table.ColumnsTotal(); i++) m_table.SetHeaderText(i,headers[i]); //--- 将对象添加到公共对象组数组 CWndContainer::AddToElementsArray(0,m_table); return(true); 品种过滤交给 CheckFilterText(),它按字符串规则筛掉不符合条件的 symbol,再进表。外汇和贵金属波动大、滑点频繁,这种面板只解决「看」的问题,信号真假还得你人工复核,别直接当入场依据。
if(!CWndCreate::CreateButton(m_request,"Request",m_window,class="num">0,class="num">375,class="num">50,class="num">70)) class="kw">return(class="kw">false); if(!CreateTable(class="num">2,class="num">75)) class="kw">return(class="kw">false); if(!CWndCreate::CreateProgressBar(m_progress_bar,"Processing:",m_status_bar,class="num">0,class="num">2,class="num">3)) class="kw">return(class="kw">false); CWndEvents::CompletedGUI(); class="kw">return(true); } class="type">bool CProgram::CreateTable(class="kw">const class="type">int x_gap,class="kw">const class="type">int y_gap) { class="macro">#define COLUMNS1_TOTAL class="num">4 class="macro">#define ROWS1_TOTAL class="num">1 m_table.MainPointer(m_window); class="type">int width[COLUMNS1_TOTAL]; ::ArrayInitialize(width,class="num">50); width[class="num">0]=class="num">80; class="type">int text_x_offset[COLUMNS1_TOTAL]; ::ArrayInitialize(text_x_offset,class="num">7); ENUM_ALIGN_MODE align[COLUMNS1_TOTAL]; ::ArrayInitialize(align,ALIGN_CENTER); align[class="num">0]=ALIGN_LEFT; m_table.TableSize(COLUMNS1_TOTAL,ROWS1_TOTAL); m_table.TextAlign(align); m_table.ColumnsWidth(width); m_table.TextXOffset(text_x_offset); m_table.ShowHeaders(true); m_table.IsSortMode(true); m_table.IsZebraFormatRows(clrWhiteSmoke); m_table.AutoXResizeMode(true); m_table.AutoYResizeMode(true); m_table.AutoXResizeRightOffset(class="num">2); m_table.AutoYResizeBottomOffset(class="num">24); if(!m_table.CreateTable(x_gap,y_gap)) class="kw">return(class="kw">false); class="type">class="kw">string headers[]={"Symbols","M5","H1","H8"}; for(class="type">uint i=class="num">0; i<m_table.ColumnsTotal(); i++) m_table.SetHeaderText(i,headers[i]); CWndContainer::AddToElementsArray(class="num">0,m_table); class="kw">return(true); } class CProgram : class="kw">public CWndCreate { class="kw">private: class="type">bool CheckFilterText(class="kw">const class="type">class="kw">string symbol_name); }; class="type">bool CProgram::CheckFilterText(class="kw">const class="type">class="kw">string symbol_name) {
◍ 用过滤器筛出可交易外汇品种
在 MT5 面板工具里做品种扫描时,先靠一个文本过滤器决定哪些符号进入后续列表。过滤器没按下、或输入框为空,函数直接返回 true,等于不过滤全放行;一旦有逗号分隔的关键词,就按子串匹配品种名,命中即标记 check 并跳出循环。 下面的 GetSymbols 方法负责真正拉取品种。它先调用 SymbolsTotal(false) 拿到当前终端可见品种总数,再逐个用 SymbolName(i,false) 取名字,并顺手 SymbolSelect(symbol_name,false) 把该品种从市场观察窗口隐藏,避免界面被刷屏。 只保留纯外汇品种:用 SymbolInfoInteger 查 SYMBOL_TRADE_CALC_MODE,不等于 SYMBOL_CALC_MODE_FOREX 的直接 continue;同时剔掉 SYMBOL_TRADE_MODE 为 DISABLED 的禁止交易品种。最后过一遍 CheckFilterText 的名称过滤,剩下的才写进 m_symbols 数组供后面跑回测或盯盘用。 外汇与贵金属杠杆高、点差跳变频繁,这类自动隐藏和过滤逻辑能降低误选冷门或禁交易品种的概率,但终端品种权限仍受券商限制,实际可交易集可能少于代码筛出的数量。
class="type">bool check=class="kw">false; class=class="str">"cmt">//--- 如果启用了品种名称过滤器 if(!m_filter.IsPressed()) class="kw">return(true); class=class="str">"cmt">//--- 如果输入了文本 class="type">class="kw">string text=m_filter.GetValue(); if(text=="") class="kw">return(true); class=class="str">"cmt">//--- 切分为子串 class="type">class="kw">string elements[]; class="type">class="kw">ushort sep=::StringGetCharacter(",",class="num">0); ::StringSplit(text,sep,elements); class=class="str">"cmt">//--- 匹配检查 class="type">int elements_total=::ArraySize(elements); for(class="type">int e=class="num">0; e<elements_total; e++) { class=class="str">"cmt">//--- 删除边外空白 ::StringTrimLeft(elements[e]); ::StringTrimRight(elements[e]); class=class="str">"cmt">//--- 如果检测到匹配 if(::StringFind(symbol_name,elements[e])>-class="num">1) { check=true; class="kw">break; } } class=class="str">"cmt">//--- 结果 class="kw">return(check); } class CProgram : class="kw">public CWndCreate { class="kw">private: class=class="str">"cmt">//--- 品种数组 class="type">class="kw">string m_symbols[]; class=class="str">"cmt">//--- class="kw">private: class=class="str">"cmt">//--- 获取品种 class="type">void GetSymbols(class="type">void); }; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 获取品种 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CProgram::GetSymbols(class="type">void) { class=class="str">"cmt">//--- 进度 m_progress_bar.LabelText("Get symbols..."); m_progress_bar.Update(class="num">0,class="num">1); class=class="str">"cmt">//--- 清楚品种数组 ::ArrayFree(m_symbols); class=class="str">"cmt">//--- 收集外汇品种的数组 class="type">int symbols_total=::SymbolsTotal(class="kw">false); for(class="type">int i=class="num">0; i<symbols_total; i++) { class=class="str">"cmt">//--- 获取品种名称 class="type">class="kw">string symbol_name=::SymbolName(i,class="kw">false); class=class="str">"cmt">//--- 在市场观察窗口中隐藏它 ::SymbolSelect(symbol_name,class="kw">false); class=class="str">"cmt">//--- 如果这不是外汇代码,则转到下一个 if(::SymbolInfoInteger(symbol_name,SYMBOL_TRADE_CALC_MODE)!=SYMBOL_CALC_MODE_FOREX) class="kw">continue; class=class="str">"cmt">//--- 如果禁止交易,则转到下一个 if(::SymbolInfoInteger(symbol_name,SYMBOL_TRADE_MODE)==SYMBOL_TRADE_MODE_DISABLED) class="kw">continue; class=class="str">"cmt">//--- 按照过滤器检查品种 if(!CheckFilterText(symbol_name)) class="kw">continue; class=class="str">"cmt">//--- 将品种保存到数组
「动态装载品种与等待指标计算就绪」
在面板初始化阶段,把外部传入的 symbol_name 追加进 m_symbols 数组:先取原长度 array_size,用 ArrayResize 预留 1000 个冗余单元避免频繁重分配,再把新名写进末位。若数组为空,则把当前图表 _Symbol 作为保底默认值塞进去,防止后续遍历空数组出错。 品种收集完后,用 SymbolSelect(m_symbols[i], true) 逐个把它们在市场观察窗口置为可见,这样后续 iCustom 取数才不会因品种未订阅而返回空值。 GetSymbolsData 里调的是自定义指标 PercentageSegmentsZZ.ex5,路径写死在 ::Indicators\Custom\ZigZag\ 下,iCustom 第 4~6 参分别是 0、0、5,buffer_index 取 2 号缓冲。 这里有个实战坑:Bars(symbol,period,start_time,end_time) 算出的 bars_total 可能大于指标已算出的 bars_calculated,此时若不等待直接 CopyBuffer 会丢数据。代码用 Sleep(100) 轮询,直到 bars_calculated>=bars_total 才跳出,拷贝窗口精确到起止日期。外汇与贵金属行情跳空频繁,这种等待机制能降低自定义 ZigZag 类指标回测时前段样本缺失的概率,但高频切换品种仍属高风险操作。
class="type">int array_size=::ArraySize(m_symbols); ::ArrayResize(m_symbols,array_size+class="num">1,class="num">1000); m_symbols[array_size]=symbol_name; class=class="str">"cmt">//--- 如果数组为空,则将当前品种设置为默认值 class="type">int array_size=::ArraySize(m_symbols); if(array_size<class="num">1) { ::ArrayResize(m_symbols,array_size+class="num">1); m_symbols[array_size]=_Symbol; } class=class="str">"cmt">//--- 在市场观察窗口中显示 class="type">int selected_symbols_total=::ArraySize(m_symbols); for(class="type">int i=class="num">0; i<selected_symbols_total; i++) ::SymbolSelect(m_symbols[i],true); } class CProgram : class="kw">public CWndCreate { class="kw">private: class=class="str">"cmt">//--- 获取品种数据 class="type">class="kw">double GetSymbolsData(class="kw">const class="type">class="kw">string symbol,class="kw">const ENUM_TIMEFRAMES period); }; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 获取品种数据 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">class="kw">double CProgram::GetSymbolsData(class="kw">const class="type">class="kw">string symbol,class="kw">const ENUM_TIMEFRAMES period) { class="type">class="kw">double result =class="num">0.0; class="type">int buffer_index =class="num">2; class=class="str">"cmt">//--- 获取指标句柄 class="type">class="kw">string path ="::Indicators\\Custom\\ZigZag\\PercentageSegmentsZZ.ex5"; class="type">int handle =::iCustom(symbol,period,path,class="num">0,class="num">0,class="num">5); if(handle!=INVALID_HANDLE) { class=class="str">"cmt">//--- 复制指定范围内的数据 class="type">class="kw">double data[]; class="type">class="kw">datetime start_time =m_from_date.SelectedDate(); class="type">class="kw">datetime end_time =m_to_date.SelectedDate(); class=class="str">"cmt">//--- 指定范围内的柱线数量 class="type">int bars_total=::Bars(symbol,period,start_time,end_time); class=class="str">"cmt">//--- 指定范围内的柱线数量 class="type">int bars_calculated=::BarsCalculated(handle); if(bars_calculated<bars_total) { class="kw">while(true) { ::Sleep(class="num">100); bars_calculated=::BarsCalculated(handle); if(bars_calculated>=bars_total) class="kw">break; } } class=class="str">"cmt">//--- 获取数据 class="type">int copied=::CopyBuffer(handle,buffer_index,start_time,end_time,data); if(copied<class="num">1) { class="kw">while(true) {
指标缓冲读取与超阈占比统计
这段逻辑先处理指标数据拉取失败时的兜底:若 CopyBuffer 在重试后拿到的数据量仍不足 bars_total,就靠 Sleep(100) 做 100 毫秒级退让再补一次;当 total<1 直接带着空 result 退出,避免后面除零。 核心统计在标红块里:用 for 循环扫完 data 数组,凡是 data[k] 大于 m_level.GetValue() 返回的阈值就 counter++,最后 result = (counter/total)*100,得出该品种在当前周期里「突破阈值」的柱线百分比。外汇与贵金属波动随机,这个百分比只反映历史样本分布,不预示后续方向。 表格刷新由 RebuildingTables 负责,先 DeleteAllRows 清空,再按 m_symbols 数量从 1 开始 AddRow;SetData 则对每一行调用 GetSymbolsData 取数,用 m_table.SetValue(column_index,r,string(value),2,true) 写两位小数并触发重绘。开 MT5 把 m_level 设成 ATR 倍数,就能直接看到哪些品种近期异动概率偏高。
::Sleep(class="num">100); copied=::CopyBuffer(handle,buffer_index,start_time,end_time,data); if(copied>=bars_total) class="kw">break; class=class="str">"cmt">//--- 如果没有收到数据则退出 class="type">int total=::ArraySize(data); if(total<class="num">1) class="kw">return(result); class=class="str">"cmt">//--- 计算重复次数 class="type">int counter=class="num">0; for(class="type">int k=class="num">0; k<total; k++) { if(data[k]>(class="type">class="kw">double)m_level.GetValue()) counter++; } class=class="str">"cmt">//--- 百分比 result=((class="type">class="kw">double)counter/(class="type">class="kw">double)total)*class="num">100; ::IndicatorRelease(handle); class="kw">return(result); class="type">void CProgram::RebuildingTables(class="type">void) { m_table.DeleteAllRows(); class="type">int symbols_total=::ArraySize(m_symbols); for(class="type">int i=class="num">1; i<symbols_total; i++) m_table.AddRow(i); } class="type">void CProgram::SetData(class="kw">const class="type">int column_index,class="kw">const ENUM_TIMEFRAMES period) { for(class="type">uint r=class="num">0; r<(class="type">uint)m_table.RowsTotal(); r++) { class="type">class="kw">double value=GetSymbolsData(m_symbols[r],period); m_table.SetValue(column_index,r,class="type">class="kw">string(value),class="num">2,true); m_table.Update(); } }
◍ 多周期数据落表的进度与填充逻辑
在 MT5 面板程序里,把多品种多周期数据塞进表格不能一把梭,得先让进度条接管界面反馈。CProgram::GetData 的调用顺序是 StartProgress → GetSymbols → SetDataToTable → EndProgress,其中 SetDataToTable 负责真正的表结构重建与写值。 SetDataToTable 先调 RebuildingTable 清掉旧结构,再写死表头为 Symbols / M5 / H1 / H8 四列,并把第 1 列以后的列数据类型设为 TYPE_DOUBLE。第一列用 m_table.SetValue(0,r,m_symbols[r],0,true) 逐行填品种名,随后 m_table.Update(true) 强制重绘。 剩余三列分别由 SetData(1,PERIOD_M5)、SetData(2,PERIOD_H1)、SetData(3,PERIOD_H8) 填充,周期名通过 GetPeriodName 截取 EnumToString(period) 从第 7 个字符起的子串得到,比如 PERIOD_M5 会变成 M5。 进度反馈分两层:外层 GetData 用 StartProgress / EndProgress 包住整体;内层循环里 m_progress_bar.LabelText 拼出 "Data preparation ["+品种+","+周期名+"]...",再用 Update(r,总行数) 推进百分比。外汇与贵金属行情多周期抓取受点差和报价中断影响,实盘跑这套填充逻辑时滑点风险偏高,建议在策略测试器用历史数据先验证表结构无误。
class=class="str">"cmt">//--- 进度 m_progress_bar.LabelText("Data preparation ["+m_symbols[r]+","+GetPeriodName(period)+"]..."); m_progress_bar.Update(r,m_table.RowsTotal()); } } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 返回周期字符串对应的值 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">class="kw">string CProgram::GetPeriodName(class="kw">const ENUM_TIMEFRAMES period) { class="kw">return(::StringSubstr(::EnumToString(period),class="num">7)); } class CProgram : class="kw">public CWndCreate { class="kw">private: class=class="str">"cmt">//--- 在表格中填充数据 class="type">void SetDataToTable(class="type">void); }; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 在表格中填充数据 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CProgram::SetDataToTable(class="type">void) { class=class="str">"cmt">//--- 进度 m_progress_bar.LabelText("Data preparation..."); m_progress_bar.Update(class="num">0,class="num">1); class=class="str">"cmt">//--- 重新构建表格 RebuildingTable(); class=class="str">"cmt">//--- 标题 class="type">class="kw">string headers[]={"Symbols","M5","H1","H8"}; for(class="type">uint i=class="num">0; i<m_table.ColumnsTotal(); i++) m_table.SetHeaderText(i,headers[i]); for(class="type">uint i=class="num">1; i<m_table.ColumnsTotal(); i++) m_table.DataType(i,TYPE_DOUBLE); class=class="str">"cmt">//--- 在第一列里设置数值 for(class="type">uint r=class="num">0; r<(class="type">uint)m_table.RowsTotal(); r++) m_table.SetValue(class="num">0,r,m_symbols[r],class="num">0,true); class=class="str">"cmt">//--- 显示表格 m_table.Update(true); class=class="str">"cmt">//--- 用数据填充剩余的列 SetData(class="num">1,PERIOD_M5); SetData(class="num">2,PERIOD_H1); SetData(class="num">3,PERIOD_H8); } class CProgram : class="kw">public CWndCreate { class="kw">private: class=class="str">"cmt">//--- 获取数据 class="type">void GetData(class="type">void); class=class="str">"cmt">//--- 进度(class="num">1)开始,(class="num">2)结束 class="type">void StartProgress(class="type">void); class="type">void EndProgress(class="type">void); }; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 获取数据 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CProgram::GetData(class="type">void) { class=class="str">"cmt">//--- 进度开始 StartProgress(); class=class="str">"cmt">//--- 获取品种列表 GetSymbols(); class=class="str">"cmt">//--- 在表格中填充数据 SetDataToTable(); class=class="str">"cmt">//--- 进度结束
「进度条与事件响应的衔接写法」
在 MT5 面板程序里,长按按钮触发数据拉取前,先用 StartProgress 把进度栏亮出来,能避免界面假死被用户误判为崩了。它的实现很直白:写标签 "Please wait...",把进度初值设成 0 和 1,Show 之后立刻 Redraw 强制重绘。 EndProgress 负责收尾,Hide 掉进度栏只是第一步,还要把按钮的 MouseFocus 置 false 并 Update(true),否则按钮会卡在悬停态。这两段配对使用,界面状态才闭环。 事件分发集中在 OnEvent:CHARTEVENT_CUSTOM+ON_CLICK_BUTTON 里比对 lparam 和 m_request.Id(),命中才跑 GetData;表格排序则走 ON_SORT_DATA 分支,只认 m_table.Id()。这种按控件 ID 分流的写法,在多控件面板里不容易串事件。 表格配色可用 CColors 做渐变填充,声明 color out_colors[] 接收输出。外汇与贵金属行情波动剧烈,这类面板仅作辅助观察,实盘决策须自行承担高风险。
class="type">void CProgram::StartProgress(class="type">void) { m_progress_bar.LabelText("Please wait..."); m_progress_bar.Update(class="num">0,class="num">1); m_progress_bar.Show(); m_chart.Redraw(); } class="type">void CProgram::EndProgress(class="type">void) { class=class="str">"cmt">//--- 隐藏进度栏 m_progress_bar.Hide(); class=class="str">"cmt">//--- 更新按钮 m_request.MouseFocus(class="kw">false); m_request.Update(true); m_chart.Redraw(); } class="type">void CProgram::OnEvent(class="kw">const class="type">int id,class="kw">const class="type">long &lparam,class="kw">const class="type">class="kw">double &dparam,class="kw">const class="type">class="kw">string &sparam) { class=class="str">"cmt">//--- 按下按钮事件 if(id==CHARTEVENT_CUSTOM+ON_CLICK_BUTTON) { if(lparam==m_request.Id()) { class=class="str">"cmt">//--- 获取数据 GetData(); class="kw">return; } class=class="str">"cmt">//--- class="kw">return; } class=class="str">"cmt">//--- 表格排序事件 if(id==CHARTEVENT_CUSTOM+ON_SORT_DATA) { if(lparam==m_table.Id()) { m_table.Update(true); class="kw">return; } class=class="str">"cmt">//--- class="kw">return; } }
用三色渐变给表格数值上色
在 MT5 面板里把数字直接染成热力图,比盯着裸数字看相关性快得多。下面这段逻辑先抓出表格里的最大和最小值,再映射成一条从番茄红到矢车菊蓝的渐变带。 核心做法是用两层循环扫遍所有单元格(列从 1 起跳,跳过表头列;行从 0 开始)。max 初值设 0、min 初值设 100,靠 fmax / fmin 实时刷新边界,所以表格里只要出现大于 0 或小于 100 的值就会被正确捕获。 边界拿到后先用 floor 向下取整,再算 range = int(max-min)+1,这个 +1 保证了极值本身也能落进渐变数组下标。Gradient 按 range 长度生成 out_colors,之后第二轮循环里用「当前值减 min」当索引取色,写回 BackColor。 外汇与贵金属行情跳动快,这种热力表若挂在实时信号面板上,颜色突变可能预示某周期相关性反转,但样本外仍可能失效,只看图不做仓位判断。开 MT5 把下面代码丢进你的 CTable 派生类,改一下 colors 数组就能换配色方案。
class=class="str">"cmt">//--- 三色渐变 class="type">class="kw">color colors[class="num">3]={clrTomato,clrWhite,clrCornflowerBlue}; class=class="str">"cmt">//--- 查找最低和最高表格数值 class="type">class="kw">double max =class="num">0; class="type">class="kw">double min =class="num">100; for(class="type">uint c=class="num">1; c<(class="type">uint)m_table.ColumnsTotal(); c++) { for(class="type">uint r=class="num">0; r<(class="type">uint)m_table.RowsTotal(); r++) { max =::fmax(max,(class="type">class="kw">double)m_table.GetValue(c,r)); min =::fmin(min,(class="type">class="kw">double)m_table.GetValue(c,r)); } } class=class="str">"cmt">//--- 调整到最接近的较低整数 max =::floor(max); min =::floor(min); class=class="str">"cmt">//--- 获得范围 class="type">int range =class="type">int(max-min)+class="num">1; class=class="str">"cmt">//--- 获得颜色渐变数组 clr.Gradient(colors,out_colors,range); class=class="str">"cmt">//--- 设置单元格的背景颜色 for(class="type">uint c=class="num">1; c<(class="type">uint)m_table.ColumnsTotal(); c++) { for(class="type">uint r=class="num">0; r<(class="type">uint)m_table.RowsTotal(); r++) { class="type">int index=(class="type">int)m_table.GetValue(c,r)-(class="type">int)min; m_table.BackColor(c,r,out_colors[index],true); } } class=class="str">"cmt">//--- 更新表格 m_table.Update(); }
◍ 统计各价位区间内的线段密度
在上一版 EA 基础上改出双表结构:第一张表只放品种清单,第二张表两列,分别是「点数区间」与「该区间内的线段数」。点「请求」拉品种,「计算」才在指定时间窗内抓 ZigZag 数据并落表。 核心差异在第二张表。CProgram::GetIndicatorData() 先连 ExactZZ_Plus 指标,再按起止日期拷贝线段;CProgram::GetLevels() 负责按步长切分区间,逻辑是先取最大线段点数,再循环填数组到顶。 填表由 CProgram::SetDataToTable2() 完成:先判第一张表有无高亮品种,无则退并写日志;有则取数据、调上述两法,再逐区间累加计数器。 实测 M5 上 EURUSD 自 2010 年至今、步长 100 点(五位报价),线段总数 302145,最大线段约 2400 点。0–100 点区间最密,数量随区间放大而递减。外汇与贵金属波动剧烈,此类统计仅描述历史分布,不预示后续线段规模。
class CProgram : class="kw">public CWndCreate { class="kw">private: class=class="str">"cmt">//--- 获取指标数据 class="type">void GetIndicatorData(class="kw">const class="type">class="kw">string symbol,class="kw">const ENUM_TIMEFRAMES period); }; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 获取指标数据 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CProgram::GetIndicatorData(class="kw">const class="type">class="kw">string symbol,class="kw">const ENUM_TIMEFRAMES period) { class=class="str">"cmt">//--- 获取指标句柄 class="type">class="kw">string path ="::Indicators\\Custom\\ZigZag\\ExactZZ_Plus.ex5"; class="type">int handle =::iCustom(symbol,period,path,class="num">0,class="num">0); if(handle!=INVALID_HANDLE) { class=class="str">"cmt">//--- 复制指定范围内的数据 class="type">class="kw">datetime start_time =m_from_date.SelectedDate(); class="type">class="kw">datetime end_time =m_to_date.SelectedDate(); m_zz.GetZigZagData(handle,class="num">2,class="num">3,symbol,period,start_time,end_time); class=class="str">"cmt">//--- 在状态栏中显示数据 class="type">class="kw">string text="["+symbol+","+(class="type">class="kw">string)GetPeriodName(period)+"] - Segments total: "+(class="type">class="kw">string)m_zz.SegmentsTotal(); m_status_bar.SetValue(class="num">0,text); m_status_bar.GetItemPointer(class="num">0).Update(true); } class=class="str">"cmt">//--- 释放指标 ::IndicatorRelease(handle); } class CProgram : class="kw">public CWndCreate { class="kw">private: class=class="str">"cmt">//--- 数组范围 class="type">int m_levels_array[]; class=class="str">"cmt">//--- class="kw">private: class=class="str">"cmt">//--- 获取级别 class="type">void GetLevels(class="type">void); }; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 获取级别 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CProgram::GetLevels(class="type">void) { class=class="str">"cmt">//--- 释放数组 ::ArrayFree(m_levels_array); class=class="str">"cmt">//--- 获取最大线段大小 class="type">int max_value=class="type">int(m_zz.LargestSegment()/m_symbol.Point()); class=class="str">"cmt">//--- 用级别填充数组 class="type">int counter_levels=class="num">0;
「表格联动下的分形段长统计」
左侧表格没选中品种时,SetDataToTable2 会直接 Print 提示并 return,不会往下跑指标抓取。这一道闸能避免空 symbol 调用指标接口导致的运行时报错。 核心逻辑是先拿选中行的 symbol 名传给 m_symbol,再调 GetIndicatorData 与 GetLevels 把级别数组 m_levels_array 填满;之后用双层循环统计之字转向(ZigZag)每段点数落在区间的频次。 统计段用 m_zz.SegmentSize(s)/m_symbol.Point() 把段长换算成点数,再判断 size 是否落在 m_levels_array[i] 与 m_levels_array[i+1] 之间,命中就把第二列计数 +1。若你的 ZZ 深度参数不同,段数分布会明显偏移,建议在 MT5 里改 Input 后重跑观察。 循环里每步都更新 m_progress_bar,标签动态显示当前级别如 'Get data [50]...',行数多时你能直接看到卡在哪一级。外汇与贵金属波动剧烈,段长统计仅反映历史形态概率,不构成方向判断。
class="kw">while(true) { class="type">int size=::ArraySize(m_levels_array); ::ArrayResize(m_levels_array,size+class="num">1); m_levels_array[size]=counter_levels; class=class="str">"cmt">//--- if(counter_levels>max_value) class="kw">break; class=class="str">"cmt">//--- counter_levels+=(class="type">int)m_step.GetValue(); } } class CProgram : class="kw">public CWndCreate { class="kw">private: class=class="str">"cmt">//--- 用数据填写第二张表格 class="type">void SetDataToTable2(class="type">void); }; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 用数据填写第二张表格 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CProgram::SetDataToTable2(class="type">void) { class=class="str">"cmt">//--- 如果不是高亮显示得行,则退出 if(m_table1.SelectedItem()==WRONG_VALUE) { ::Print(__FUNCTION__," > Select a symbol in the table on the left!"); class="kw">return; } class=class="str">"cmt">//--- 进度开始 StartProgress(); class=class="str">"cmt">//--- 隐藏表格 m_table2.Hide(); class=class="str">"cmt">//--- 从第一张表格中获取品种 class="type">class="kw">string symbol=m_table1.GetValue(class="num">0,m_table1.SelectedItem()); m_symbol.Name(symbol); class=class="str">"cmt">//--- 获取指标数据 GetIndicatorData(symbol,_Period); class=class="str">"cmt">//--- 获取级别 GetLevels(); class=class="str">"cmt">//--- 重新构建表格 RebuildingTable2(); class=class="str">"cmt">//--- 在第一列中设置范围 for(class="type">uint r=class="num">0; r<(class="type">uint)m_table2.RowsTotal(); r++) m_table2.SetValue(class="num">0,r,(class="type">class="kw">string)m_levels_array[r],class="num">0); class=class="str">"cmt">//--- 获取第二列的值 class="type">int items_total=::ArraySize(m_levels_array); class="type">int segments_total=m_zz.SegmentsTotal(); for(class="type">int i=class="num">0; i<items_total-class="num">1; i++) { class=class="str">"cmt">//--- 进度 m_progress_bar.LabelText("Get data ["+(class="type">class="kw">string)m_levels_array[i]+"]..."); m_progress_bar.Update(i,m_table2.RowsTotal()); class=class="str">"cmt">//--- for(class="type">int s=class="num">0; s<segments_total; s++) { class="type">int size=class="type">int(m_zz.SegmentSize(s)/m_symbol.Point()); if(size>m_levels_array[i] && size<m_levels_array[i+class="num">1]) { class="type">int value=(class="type">int)m_table2.GetValue(class="num">1,i)+class="num">1; m_table2.SetValue(class="num">1,i,(class="type">class="kw">string)value,class="num">0); } } } class=class="str">"cmt">//--- 显示表格 m_table2.Update(true); class=class="str">"cmt">//--- 结束进度 EndProgress(); }
按持续柱数给线段分桶
线段持续时间(以柱线计)本身就是一种分组维度。把上一版 EA 复制一份,再往 GUI 加一张两列表格:第一列是柱线数量,第二列是该持续时间内出现的线段数,就能直接看形态在时间轴上的分布密度。 操作流程是:先点「请求」拿品种列表,在第一张表选品种,点「计算」出第二张表的范围数据;要填第三张表,得在第二张表里高亮某一行范围,程序才按这个范围统计线段持续时间。高亮行会触发 ON_CLICK_LIST_ITEM 事件,这里用第二张表 ID 来捕获。 换品种或重算时,必须清掉第三张表旧数据,否则前后数据混在一起容易误读。实盘挂 EA 后,用 USD 系货币对测:GBPUSD 从 2018 年起、步长 100 拉范围,高亮第二表「200」那行,得到范围 200–300 内共 1922 个线段。第三表展开看,持续 10 根柱线的线段只有 11 个——这种长尾分布是肉眼调参前先要心里有数的。 外汇和贵金属波动受杠杆与消息面影响大,此类统计只反映历史样本,实盘信号概率会随 regime 漂移,别直接当进场依据。 下面这段 CProgram::SetDataToTable3 是填第三表的核心。逐行看:类里先声明私有方法 SetDataToTable3;方法开头若第二表没高亮行就打印提示并 return;StartProgress 起进度条,Hide 隐藏表格避免闪烁;selected_row_index 取高亮行号,黄底行 selected_range 从表头 0 列拿范围值。 RebuildingTable3 重建表结构,随后第一列填 1、2、3… 序号;segments_total 取 zigzag 线段总数,外层按行、内层按线段遍历。绿底判断是重点:线段 size 落在 selected_range 到 range+步长之间、且 bars 等于当前行号+1 时,才把第二列对应行计数 +1。最后 Update 显示、EndProgress 收尾。
<span class="keyword">class</span> CProgram : <span class="keyword">class="kw">public</span> CWndCreate { <span class="keyword">class="kw">private</span>: <span class="comment">class=class="str">"cmt">//--- 用数据填充表格三</span> <span class="keyword">class="type">void</span> SetDataToTable3(<span class="keyword">class="type">void</span>); }; <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">void</span> CProgram::SetDataToTable3(<span class="keyword">class="type">void</span>) { <span class="comment">class=class="str">"cmt">//--- 如果不是高亮显示得行,则退出</span> <span class="keyword">if</span>(m_table2.SelectedItem()==<span class="macro">WRONG_VALUE</span>) { ::<span class="functions">Print</span>(<span class="keyword">__FUNCTION__</span>,<span class="class="type">class="kw">string">" > Select a range in the table on the left!"</span>); <span class="keyword">class="kw">return</span>; } <span class="comment">class=class="str">"cmt">//--- 进度开始</span> StartProgress(); <span class="comment">class=class="str">"cmt">//--- 隐藏表格</span> m_table3.Hide(); <span class="comment">class=class="str">"cmt">//--- 获取高亮显示的行</span> <span class="keyword">class="type">int</span> selected_row_index=m_table2.SelectedItem(); <span class="comment">class=class="str">"cmt">//--- 范围</span> <span style="background-class="type">class="kw">color:rgb(class="num">255, class="num">242, class="num">153);"><span class="keyword">class="type">int</span> selected_range=(<span class="keyword">class="type">int</span>)m_table2.GetValue(<span class="number">class="num">0</span>,selected_row_index);</span> <span class="comment">class=class="str">"cmt">//--- 重新构建表格</span> RebuildingTable3(); <span class="comment">class=class="str">"cmt">//--- 在第一列中设置数值</span> <span class="keyword">for</span>(<span class="keyword">class="type">uint</span> r=<span class="number">class="num">0</span>; r<(<span class="keyword">class="type">uint</span>)m_table3.RowsTotal(); r++) m_table3.SetValue(<span class="number">class="num">0</span>,r,(<span class="keyword">class="type">class="kw">string</span>)(r+<span class="number">class="num">1</span>),<span class="number">class="num">0</span>); <span class="comment">class=class="str">"cmt">//--- 获取第二列的值</span> <span class="keyword">class="type">int</span> segments_total=m_zz.SegmentsTotal(); <span class="keyword">for</span>(<span class="keyword">class="type">uint</span> r=<span class="number">class="num">0</span>; r<(<span class="keyword">class="type">uint</span>)m_table3.RowsTotal(); r++) { <span class="comment">class=class="str">"cmt">//--- 进度</span> m_progress_bar.LabelText(<span class="class="type">class="kw">string">"Get data ["</span>+(<span class="keyword">class="type">class="kw">string</span>)r+<span class="class="type">class="kw">string">"]..."</span>); m_progress_bar.Update(r,m_table3.RowsTotal()); <span class="comment">class=class="str">"cmt">//---</span> <span class="keyword">for</span>(<span class="keyword">class="type">int</span> s=<span class="number">class="num">0</span>; s<segments_total; s++) { <span class="keyword">class="type">int</span> size =<span class="keyword">class="type">int</span>(m_zz.SegmentSize(s)/m_symbol.<span class="predefines">Point</span>()); <span class="keyword">class="type">int</span> bars =m_zz.SegmentBars(s); <span class="comment">class=class="str">"cmt">//---</span> <span style="background-class="type">class="kw">color:rgb(class="num">216, class="num">232, class="num">194);"> <span class="keyword">if</span>(size>selected_range && size<selected_range+(<span class="keyword">class="type">int</span>)m_step.GetValue() && bars==r+<span class="number">class="num">1</span>)</span> { <span class="keyword">class="type">int</span> value=(<span class="keyword">class="type">int</span>)m_table3.GetValue(<span class="number">class="num">1</span>,r)+<span class="number">class="num">1</span>; m_table3.SetValue(<span class="number">class="num">1</span>,r,(<span class="keyword">class="type">class="kw">string</span>)value,<span class="number">class="num">0</span>); } } } <span class="comment">class=class="str">"cmt">//--- 显示表格</span> m_table3.Update(<span class="macro">true</span>); <span class="comment">class=class="str">"cmt">//--- 进度结束</span> EndProgress(); }
◍ 表格联动的事件路由怎么写
在 MT5 自定义面板里,多表格联动靠的是图表自定义事件。单击第二张表(m_table2)的某一行时,程序通过 lparam == m_table2.Id() 判断来源,随即调用 SetDataToTable3() 刷新第三张表,避免误触其他控件。 点「请求」按钮(m_request)会重抓第一张表数据,并立即清空第二、第三张表:m_table2.DeleteAllRows(true) 与 m_table3.DeleteAllRows(true)。点「计算」按钮(m_calculate)只刷第二张表并清掉第三张,保留已请求的原始数据。 外汇与贵金属行情跳动快,这种「按来源清旧数据」的逻辑能降低界面显示错位的概率,但高频点击仍可能带来渲染延迟,属正常现象。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 事件处理器 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CProgram::OnEvent(class="kw">const class="type">int id,class="kw">const class="type">long &lparam,class="kw">const class="type">class="kw">double &dparam,class="kw">const class="type">class="kw">string &sparam) { ... class=class="str">"cmt">//--- 单击行的事件 if(id==CHARTEVENT_CUSTOM+ON_CLICK_LIST_ITEM) { class=class="str">"cmt">//--- 单击表格行 if(lparam==m_table2.Id()) { class=class="str">"cmt">//--- 获取第三张表格的数据 SetDataToTable3(); class="kw">return; } class=class="str">"cmt">//--- class="kw">return; } ... } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 事件处理器 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CProgram::OnEvent(class="kw">const class="type">int id,class="kw">const class="type">long &lparam,class="kw">const class="type">class="kw">double &dparam,class="kw">const class="type">class="kw">string &sparam) { class=class="str">"cmt">//--- 单击按钮的事件 if(id==CHARTEVENT_CUSTOM+ON_CLICK_BUTTON) { class=class="str">"cmt">//--- 点击请求按钮 if(lparam==m_request.Id()) { class=class="str">"cmt">//--- 获取第一张表格的数据 SetDataToTable1(); class=class="str">"cmt">//--- 从表格中删除不相关的数据 m_table2.DeleteAllRows(true); m_table3.DeleteAllRows(true); class="kw">return; } class=class="str">"cmt">//--- 单击计算按钮 if(lparam==m_calculate.Id()) { class=class="str">"cmt">//--- 获取第二张表格的数据 SetDataToTable2(); class=class="str">"cmt">//--- 从表格中删除不相关的数据 m_table3.DeleteAllRows(true); } class=class="str">"cmt">//--- class="kw">return; } ... }
「图表品种切换时别重建界面」
MT5 面板若挂了十几个控件,每次切品种或周期都 Destroy 再 Create,初始化整组 GUI 的耗时可能卡掉 200~500ms,频繁操作时体验明显发涩。 其实图表品种 / 周期变更触发的是 REASON_CHARTCHANGE,界面本身没必要拆了重来。只要在程序主类里留一个字段记最后一次逆初始化原因,就能判断是不是单纯换图。 逆初始化时只在这种原因下跳过销毁,其余情况(比如卸载 EA、改参数)照常清界面;创建 GUI 的方法开头查一下这个字段,若是图表变更直接 return true,控件原样保留。 下面这段是核心写法:m_last_deinit_reason 存原因,OnDeinitEvent 里赋值并条件销毁,CreateGUI 里提前拦截。外汇和贵金属行情跳动快,这类细节能减少界面闪烁,但 EA 逻辑错误仍可能导致宕机,属高风险操作。
class CProgram : class="kw">public CWndCreate { class="kw">private: class=class="str">"cmt">//--- 逆初始化的最终原因 class="type">int m_last_deinit_reason; }; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 构造函数 | class=class="str">"cmt">//+------------------------------------------------------------------+ CProgram::CProgram(class="type">void) : m_last_deinit_reason(WRONG_VALUE) { } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 逆初始化 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CProgram::OnDeinitEvent(class="kw">const class="type">int reason) { class=class="str">"cmt">//--- 记住最后的逆初始化原因 m_last_deinit_reason=reason; class=class="str">"cmt">//--- 如果原因与变更品种和周期无关,则删除 GUI if(reason!=REASON_CHARTCHANGE) { CWndEvents::Destroy(); } } 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(m_last_deinit_reason==REASON_CHARTCHANGE) class="kw">return(true); ... class="kw">return(true); }
把 ZigZag 数据接进多周期统计
论坛上常有人说 ZigZag 不能拿来出信号,这其实是个误解。撇开信号不谈,单就刻画价格行为性质而言,很少有指标能像它一样给出这么密的结构信息——只要你能把折线数据抓出来做二次统计。 前文已经把取数工具铺好了,这一节直接看怎么把高周期 ZigZag 和当前周期分段接起来。下方代码来自 MultiPercentageSegmentsZZ 指标里的缓冲区填充函数,它从更高时间框架拉取 1000 根 K 线范围内的折线缓冲,再按 0、1、2 三段分别取当前周期数据。 [CODE] //+------------------------------------------------------------------+
| // | 填入指标缓冲区 |
|---|
//+------------------------------------------------------------------+ void FillIndicatorBuffers(const int i,const int total,const datetime &time[]) { int index=total-i-1; int copy_total=1000; int h_buff=2,l_buff=3; datetime start_time_in =NULL; datetime stop_time_in =NULL; //--- 从更高时间段获取源数据 datetime stop_time=time[i]-(PeriodSeconds(HigherTimeframe)*copy_total); CopyBuffer(zz_handle_htf,2,time[i],stop_time,h_zz_buffer_temp); CopyBuffer(zz_handle_htf,3,time[i],stop_time,l_zz_buffer_temp); CopyTime(_Symbol,HigherTimeframe,time[i],stop_time,t_zz_buffer_temp); //--- 从更高的时间框架中获取最终数据 zz_higher_tf.GetZigZagData(h_zz_buffer_temp,l_zz_buffer_temp,t_zz_buffer_temp); double htf_value=zz_higher_tf.PercentSumSegmentsDifference(); //--- 第一分段数据 zz_higher_tf.SegmentTimes(zz_handle_current,h_buff,l_buff,_Symbol,HigherTimeframe,_Period,0,start_time_in,stop_time_in); zz_current0.GetZigZagData(zz_handle_current,h_buff,l_buff,_Symbol,_Period,start_time_in,stop_time_in); //--- 第二段数据 zz_higher_tf.SegmentTimes(zz_handle_current,h_buff,l_buff,_Symbol,HigherTimeframe,_Period,1,start_time_in,stop_time_in); zz_current1.GetZigZagData(zz_handle_current,h_buff,l_buff,_Symbol,_Period,start_time_in,stop_time_in); //--- 第三分段数据 zz_higher_tf.SegmentTimes(zz_handle_current,h_buff,l_buff,_Symbol,HigherTimeframe,_Period,2,start_time_in,stop_time_in); zz_current2.GetZigZagData(zz_handle_current,h_buff,l_buff,_Symbol,_Period,start_time_in,stop_time_in); 逐行拆一下:h_buff=2、l_buff=3 是高周期 ZigZag 指标里高/低拐点对应的缓冲区序号;stop_time 用 PeriodSeconds(HigherTimeframe)*1000 反推起点,确保拉满 1000 根高周期 K 线。CopyBuffer 把高周期折线的高低缓冲和 CopyTime 的时间数组暂存,交给 GetZigZagData 解析出线段集合,PercentSumSegmentsDifference 算出的 htf_value 就是高周期线段百分比差值。后面三段 SegmentTimes 依次用分段索引 0/1/2 切出当前周期对应时段,再喂给 zz_current0~2 做各自取数。 随文给了一套可直接丢进 MT5 的文件:FrequencyChangeZZ 算反向线段形成频率,SumSegmentsZZ 算线段总和与均值,PercentageSegmentsZZ 和 MultiPercentageSegmentsZZ 做百分比差值,四个 EA(TestZZ_05、ZZ_Scanner_01~03)分别验不同维度的线段扫描。外汇和贵金属波动大、杠杆高,用这些工具做统计倾向辅助判断即可,别当成下单依据。 下一篇会接着演示怎么用上面这套取数接口榨出更多衍生数据,比如跨价格区间的线段分布。先把 ZZ_Scanner_02 挂上黄金 M15,看不同价格带里线段密度差多少,比空谈参数直观得多。
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,h_buff,l_buff,_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); zz_current1.GetZigZagData(zz_handle_current,h_buff,l_buff,_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,h_buff,l_buff,_Symbol,_Period,start_time_in,stop_time_in);
◍ 画得少,看得清
这段收尾逻辑只处理两件事:当前未走完的条形图直接沿用高 timeframe 取值与三段 ZigZag 的百分比差值;一旦较高周期冒出新的 bar,就把上一根已定稿的数据整体前移一格,避免历史重绘时图形抖动。 具体看,i<total-1 时每根都实时写 buffer_zz_higher_tf 与三个 segment 差值;else 分支里靠 new_bar_time 是否等于 t_zz_buffer_temp[0] 判断换柱,i>2 才做前移,少了前两根的缓冲就可能越界。 实盘接这思路,MT5 里把 indicator 的 buffers 数设成 4(1 个高周期 + 3 个分段差),周期选 H1 加载、ZigZag 深度分别 12/24/48,肉眼能看出多周期共振段明显变稀疏。外汇与贵金属杠杆高,信号只是概率倾向,验证时先用策略测试器跑半年 tick 数据再上真仓。
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(); } }