交易货币篮子时可用的形态第二部分·进阶篇
(2/3)·震荡指标只是起点,跟随趋势的组合指标藏着篮子交易里被忽略的入场信号
「指标卸载时的退出原因分类」
这段 MQL5 代码展示了自定义指标在 OnDeinit 里如何根据 reason 参数区分终止场景,并用 PrintFormat 把对应文案打到日志。 reason 的枚举覆盖了从主动移除到环境变化的 11 种情况:REASON_PROGRAM 代表指标调用 ExpertRemove() 自终止;REASON_INITFAILED 说明 OnInit 返回了非零值;REASON_CLOSE 是终端关闭;REASON_ACCOUNT 是账户切换。 交易品种或周期变动会触发 REASON_CHARTCHANGE,图表被关是 REASON_CHARTCLOSE,改输入参数是 REASON_PARAMETERS,重新编译对应 REASON_RECOMPILE,从图表删掉是 REASON_REMOVE,套用新模板则是 REASON_TEMPLATE,其余归为 default 的「其它原因」。 GetMA 函数用最简滑动求和除以 period 算均线,并用 NormalizeDouble 按 _Digits 舍入;下方 pair 数组列了 7 个 EUR 交叉盘(EURUSD、EURJPY、EURCHF、EURGBP、EURNZD、EURCAD、EURAUD),bDirect 全为 true,可作为批量加载基准。外汇与贵金属波动剧烈,这类退出日志仅用于排查,不构成任何方向判断。
}
}
else
{
ind[class="num">0]= GetValue(class="num">0);
ma[class="num">0] = GetMA(ind, class="num">0, maperiod, _Digits);
}
class=class="str">"cmt">//--- 返回 prev_calculated 的值用于下一次调用
class="kw">return(rates_total);
}
class="type">void OnDeinit(class="kw">const class="type">int reason)
{
class="type">class="kw">string text;
class="kw">switch(reason)
{
case REASON_PROGRAM:
text="指标通过调用 ExpertRemove() 函数终止运行";break;
case REASON_INITFAILED:
text="这个数值表示 OnInit() 处理函数 "+__FILE__+" 返回了非零数值";break;
case REASON_CLOSE:
text="终端已经被关闭"; break;
case REASON_ACCOUNT:
text="账户已经改变";break;
case REASON_CHARTCHANGE:
text="交易品种或者时段已经改变";break;
case REASON_CHARTCLOSE:
text="图表被关闭";break;
case REASON_PARAMETERS:
text="输入参数已经改变";break;
case REASON_RECOMPILE:
text="程序 "+__FILE__+" 被重新编译";break;
case REASON_REMOVE:
text="程序 "+__FILE__+" 被从图表上删除";break;
case REASON_TEMPLATE:
text="图表上应用了新的模板";break;
class="kw">default:text="其它原因";
}
PrintFormat("%s",text);
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">class="kw">double GetMA(class="kw">const class="type">class="kw">double& arr[], class="type">int index , class="type">int period, class="type">int digit) {
class="type">class="kw">double m = class="num">0;
for (class="type">int j = class="num">0; j < period; j++) m += arr[index + j];
m /= period;
class="kw">return (NormalizeDouble(m,digit));
}
class="type">class="kw">string pair[]={"EURUSD", "EURJPY", "EURCHF", "EURGBP", "EURNZD", "EURCAD", "EURAUD"};
class="type">bool bDirect[]={true,true,true,true,true,true,true};用 ZigZag 拆解美元指数摆动幅度
把 iUniZigZagPriceSW 指标挂到美元指数图上,之字转向会画成一条蓝色粗线。我们的目的是把每段 ZigZag 折线的长度按大小排序,借此摸清楚美元指数在历史样本里的「摆动幅度」分布。 原指标在第 10 个订单分时才锁定最大可能的片段长度,把它当 100% 基准,就能反推出 1% 步长对应的绝对价格,以及每个百分位区间里出现过多少次对应大小的片段。跑完把数组落盘,再用表格软件画图即可。 下表是文件开头的一部分(片段长度单位:指数点): 数量 片段长度 片段数量
- 0.01193 2975
- 0.02387 850
- 0.0358 197
- 0.04773 54
- 0.05967 17
再往后的区间基本被 0 填满了,说明超大摆动极少。 顺着趋势进场时,若把 ZigZag 套在货币篮子指数组合上,某段折线在价格轴上的投影超过某个阈值,就要警惕反转或假突破概率上升。以这批数据看,「关键」片段长度宜设在 0.03 以内,具体可用统计方法按你的风险偏好重算。外汇与贵金属杠杆高,这类阈值只作过滤、不构成单边依据。 上面用的是全历史样本;想抓近期节奏,把统计窗口缩到年、季或月再跑一遍就行。同样套路能铺到所有货币篮子和多数周期,算法不复杂,但别只靠这一个信号,一定要等别的确认。ZigZag 类统计对趋势指标好用,对振荡指标没意义。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| iUniZigZagSW.mq5 | class=class="str">"cmt">//| Copyright class="num">2016, MetaQuotes Software Corp. | class=class="str">"cmt">//| [MQL5官方文档] | class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#class="kw">property copyright "Copyright class="num">2016, MetaQuotes Software Corp." class="macro">#class="kw">property link "[MQL5官方文档] class="macro">#class="kw">property version "class="num">1.00" class="macro">#class="kw">property indicator_separate_window class="macro">#class="kw">property indicator_buffers class="num">6 class="macro">#class="kw">property indicator_plots class="num">3 class=class="str">"cmt">//--- 最高点绘图 class="macro">#class="kw">property indicator_label1 "High" class="macro">#class="kw">property indicator_type1 DRAW_LINE class="macro">#class="kw">property indicator_color1 clrGreen class="macro">#class="kw">property indicator_style1 STYLE_SOLID class="macro">#class="kw">property indicator_width1 class="num">1 class=class="str">"cmt">//--- 最低点绘图 class="macro">#class="kw">property indicator_label2 "Low" class="macro">#class="kw">property indicator_type2 DRAW_LINE class="macro">#class="kw">property indicator_color2 clrGreen class="macro">#class="kw">property indicator_style2 STYLE_SOLID class="macro">#class="kw">property indicator_width2 class="num">1 class=class="str">"cmt">//--- 之字转向绘图 class="macro">#class="kw">property indicator_label3 "ZigZag" class="macro">#class="kw">property indicator_type3 DRAW_SECTION class="macro">#class="kw">property indicator_color3 clrRed class="macro">#class="kw">property indicator_style3 STYLE_SOLID class="macro">#class="kw">property indicator_width3 class="num">1 class=class="str">"cmt">//--- 方向绘图 class="macro">#class="kw">property indicator_label4 "Direction" class="macro">#class="kw">property indicator_type4 DRAW_LINE class="macro">#class="kw">property indicator_style4 STYLE_SOLID class="macro">#class="kw">property indicator_width4 class="num">1 class=class="str">"cmt">//--- LastHighBar(最近的高点柱形) 绘图 class="macro">#class="kw">property indicator_label5 "LastHighBar" class="macro">#class="kw">property indicator_type5 DRAW_LINE class="macro">#class="kw">property indicator_style5 STYLE_SOLID class="macro">#class="kw">property indicator_width5 class="num">1 class=class="str">"cmt">//--- LastLowBar(最近的低点柱形) 绘图 class="macro">#class="kw">property indicator_label6 "LastLowBar" class="macro">#class="kw">property indicator_type6 DRAW_LINE class="macro">#class="kw">property indicator_style6 STYLE_SOLID class="macro">#class="kw">property indicator_width6 class="num">1
◍ 把方向判定接进 ZigZag 骨架
这一段干的事很直接:给 ZigZag 指标挂两套方向判定逻辑,用枚举切,不写死在代码里。Dir_NBars 走 N 根 K 线转折判定,Dir_CCI 走 14 周期 CCI 的典型价方向,输入参数里 CCIPeriod 默认 14、ZZPeriod 默认 14,都是可以直接在 MT5 参数面板改的数。 高亮的那行 input string name="index-usd-zz.txt" 是给外部数据文件留的接口,黄金或欧美盘你换品种时改这个文件名就行,别动其它逻辑。int h 那个变量在当前片段里没用上,是预留句柄,先别管。 OnInit 里按 DirSelect 分支 new 出对应方向对象,dir.CheckHandle() 失败就弹窗“指标 2 下载错误”并返回 INIT_FAILED——说明依赖的 CSorceData 等头文件必须先在 ZigZag 目录下就位,否则指标加载直接挂。 缓冲区映射把 0~2 号设为 INDICATOR_DATA(画出来的高低点和 ZigZag 线),3~5 号是 CALCULATIONS(方向、末高末低 Bar 索引),只内部算不显示。你开 MT5 把这段塞进自定义指标 OnInit,编译过且不出 Alert,就证明头文件路径对了。
class="macro">#include <ZigZag\CSorceData.mqh> class="macro">#include <ZigZag\CZZDirection.mqh> class="macro">#include <ZigZag\CZZDraw.mqh> class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| | class=class="str">"cmt">//+------------------------------------------------------------------+ enum EDirection { Dir_NBars=class="num">0, Dir_CCI=class="num">1 }; class=class="str">"cmt">//--- 输入参数 class="kw">input EDirection DirSelect=Dir_NBars; class="kw">input class="type">int CCIPeriod = class="num">14; class="kw">input ENUM_APPLIED_PRICE CCIPrice = PRICE_TYPICAL; class="kw">input class="type">int ZZPeriod=class="num">14; class="kw">input class="type">class="kw">string name="index-usd-zz.txt"; CZZDirection*dir; CZZDraw*zz; class=class="str">"cmt">//--- 指标缓冲区 class="type">class="kw">double HighBuffer[]; class="type">class="kw">double LowBuffer[]; class="type">class="kw">double ZigZagBuffer[]; class="type">class="kw">double DirectionBuffer[]; class="type">class="kw">double LastHighBarBuffer[]; class="type">class="kw">double LastLowBarBuffer[]; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 自定义指标初始化函数 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int h; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int OnInit() { class="kw">switch(DirSelect) { case Dir_NBars: dir=new CNBars(ZZPeriod); break; case Dir_CCI: dir=new CCCIDir(CCIPeriod,CCIPrice); break; } if(!dir.CheckHandle()) { Alert("指标 class="num">2 下载错误"); class="kw">return(INIT_FAILED); } zz=new CSimpleDraw(); class=class="str">"cmt">//--- 指标缓冲区映射 SetIndexBuffer(class="num">0,HighBuffer,INDICATOR_DATA); SetIndexBuffer(class="num">1,LowBuffer,INDICATOR_DATA); SetIndexBuffer(class="num">2,ZigZagBuffer,INDICATOR_DATA); SetIndexBuffer(class="num">3,DirectionBuffer,INDICATOR_CALCULATIONS); SetIndexBuffer(class="num">4,LastHighBarBuffer,INDICATOR_CALCULATIONS); SetIndexBuffer(class="num">5,LastLowBarBuffer,INDICATOR_CALCULATIONS);
「指标析构与逐根 K 线的计算回路」
退出指标时先判断两个动态对象指针是否还活着,再分别 delete。dir 和 zz 若用 CheckPointer 返回 POINTER_DYNAMIC 才释放,能避免重复析构导致的 MT5 日志报错。 OnCalculate 里用 prev_calculated 控制起点:首根 K 线走 start=0,后续只从 prev_calculated-1 补算一根,这是标准增量计算写法,能把 EURUSD 这类品种在 M15 上的重绘开销压到最低。 代码把 price 同时写进 HighBuffer 和 LowBuffer 做占位,再交给 dir.Calculate 算方向、zz.Calculate 算之字形。rv 为 0 直接 return(0) 中断,说明方向缓冲区没就绪时绝不画 ZigZag。 开 MT5 把这段塞进自定义指标,把 zz 的回调深度参数从默认 12 调到 8,黄金 H1 上的拐点会密一倍但假突破也更多,属于高风险品种的高频噪音,自己跑一遍对比。
class="type">int OnCalculate(class="kw">const class="type">int rates_total, class="kw">const class="type">int prev_calculated, class="kw">const class="type">int begin, class="kw">const class="type">class="kw">double &price[] ) { class="type">int start; if(prev_calculated==class="num">0) { start=class="num">0; } else { start=prev_calculated-class="num">1; } for(class="type">int i=start;i<rates_total;i++) { HighBuffer[i]=price[i]; LowBuffer[i]=price[i]; } class="type">int rv; rv=dir.Calculate(rates_total, prev_calculated, HighBuffer, LowBuffer, DirectionBuffer); if(rv==class="num">0)class="kw">return(class="num">0); zz.Calculate(rates_total, prev_calculated, HighBuffer, LowBuffer, DirectionBuffer, LastHighBarBuffer, LastLowBarBuffer, ZigZagBuffer);
用 ZigZag 拐点距做百分百位数分布
当指标计数为 10 时,这段代码会扫描整段 ZigZagBuffer,先抓出全部有效拐点中的最高价与最低价,两者差值作为总振幅 lg。若某品种 ZigZag 极值分别为 1.05000 与 1.10000,则 lg=0.05000,除以 100 后得到每 1% 档位步长 0.00050。 随后它把相邻拐点间的绝对距离逐一比对 100 个档位阈值,落入哪一档就在 count[] 对应位置加一,本质是统计「任意两拐点间距小于某百分比」的频次。外汇与贵金属杠杆高、跳空频繁,这种分布仅反映历史形态,不预示后市方向。 最后循环把 0~99 档的档位值与计数通过 PrintFormat 打到日志,并写入文件句柄 h,FileClose 收尾。你可在 MT5 策略测试器跑完看日志,把 count 最高的档位当作该品种 ZigZag 常见折返密度的参考,再调 EA 的回调入场阈值。
if(ind<= class="num">10) ind++; if(ind == class="num">10) { class="type">class="kw">double mx=class="num">0,mn=class="num">1000000; class="type">class="kw">double lg; for(class="type">int i=class="num">0;i<rates_total;i++) { if(ZigZagBuffer[i]==class="num">0 || ZigZagBuffer[i]==EMPTY_VALUE) class="kw">continue; if(ZigZagBuffer[i] > mx) mx = ZigZagBuffer[i]; if(ZigZagBuffer[i] < mn) mn = ZigZagBuffer[i]; } lg=mx-mn; PrintFormat("Min index: %.05f Max index: %.05f Length: %.05f",mn,mx,lg); lg/=class="num">100; class="type">class="kw">double levels[class="num">100]; class="type">int count[class="num">100]; ArrayInitialize(count,class="num">0); for(class="type">int i=class="num">1; i<class="num">101; i++) levels[i-class="num">1]=NormalizeDouble(lg*i,_Digits); mn=class="num">0; for(class="type">int i=class="num">0;i<rates_total;i++) { if(ZigZagBuffer[i]==class="num">0 || ZigZagBuffer[i]==EMPTY_VALUE) class="kw">continue; if(mn==class="num">0) mn=ZigZagBuffer[i]; else { lg=MathAbs(mn-ZigZagBuffer[i]); for(class="type">int j=class="num">0; j<class="num">100; j++) { if(lg<levels[j]) { count[j]++; break; } } mn=ZigZagBuffer[i]; } } for(class="type">int i=class="num">0; i<class="num">100; i++) { PrintFormat("%d level: %.05f count: %d",i,levels[i],count[i]); FileWrite(h,i,levels[i],count[i]); } FileClose(h); } class="kw">return(rates_total); }