在继续阅读之前,请务必单击上面的链接来学习规范。
一般规范分析
在首次阅读时可以看到开发指标的 主要需求。
- 开发过程要分阶段进行。
- 确保实现指标的最佳性能。
- 指标还拥有图形界面。
之字折线算法。 之字折线的构造算法与标准算法不同。
- 之字折线并非在局部极值形成时改变方向,而是在振荡器数值越过超买/超卖级别时才改变方向。
- 之字折线基于价格图表。 相应地,新的极值由价格数据决定。
因此,必须要注意由此产生的某些特征。
- 指标的高点/低点可能与价格的最高价/最低价不对应。 所以,当之字折线改变方向时,必须检查新价格的最高价/最低价是否比振荡器跨入超买/超卖区域 (图例 1) 更早出现。
图例 1. WPR 离开超买区域发生在箭头 1 所标记的柱线中,
然而,新的之字折线段落只绘制到箭头 2 所标记的柱线 - 由于之字折线的方向变化由振荡器定义,因此其数值随柱线形成而变化。 因此,之字折线能够改变方向,但当柱线形成后,也许方向改变会取消。 在此情况下,有必要确保指标的正确操作。
- 出于新的极值由价格图表 (按最高/最低价格) 定义,因此在柱线形成时不能取消新形成的高点/低点。 不过,之字折线可能会在具有新极值的柱线上逆转。 在此情况下,新的高点/低点会被取消 (图例 2)。
图例 2. 1 — 之字折线顶点是新柱线形成的高点。
2 — 之字折线逆转,而之前检测到的高点则被取消当然,由于 MetaTrader 5 拥有彩色具有之字折线绘图样式,会令本案例含糊不清。 它允许垂直在绘制之字折线段落时不将其顶点向左移动 (预判的最大值)。 然而,这样绘图无法为我们提供独立绘制两个之字折线段落 (垂直和相邻倾斜) 的能力。 另外,这种绘制之字折线的方式并不常见,且规范没有明确要求应用此方法。 因此,默认情况下会选择最常用的选项。
显示。 之字折线的显示有其自身的特性。
- 除了显示指标本身外,价格图表还应标记指标跨入超买 (位于柱线最高价的黄点) 和超卖 (位于柱线最低价的绿点) 区域的柱线。
- 依据之字折线顶端和底端的相互位置来揭示形态。 由之字折线段落形成的形态应有不同的颜色。 事实证明这是最大的问题。 首先,我们不仅需要在检测到形态的柱线上做标记,还要更改历史记录中若干之字折线段落的颜色。 其次,当柱线形成时,之字折线逆转和新极值可能会被取消。 因此,在计算柱线之前,我们需要清除可能已标记形态 (应返回到中性颜色) 的之字折线区域。 第三,形态 (包括相反指向的形态) 也许会重叠。 因此,在清理和绘制之字折线段落时,我们不应该破坏之前已检测到形态的颜色 (图例 3)。
图例 3. 重叠形态
我们来考察图例 3 中显示的之字折线段落。 段落 1-4 构成上行趋势形态。 这意味着,段落 1 应该是蓝色的。 但它已被纳入下行形态,因此,它被涂成红色。 当段落 6 出现时,形成另一个向上形态 (段落 3-6)。 由于之字折线段落在每根柱线计算之前应该收到它们的初始颜色,在此情况下,我们只需要清除段落 5 和 6,因为段落 3 和 4 已经涉及到另一个形态。
替代解决方案是改变每个新形态中所有段落的颜色。 然而,这会令指标在历史上的信息量较少。 所以,我们选择了第一个选项,尽管它要复杂得多。
图形界面。 图形界面的需求相对简单。 控件集合是恒定的,它不需要依据所选的振荡器而变化。 仅用到了两个数字参数 (超买/超卖水平),并且它们的设置对于所有振荡器是相同的。
在理解了任务的所有功能之后,我们可以开始开发指标。 在每个开发阶段结束时,我们在附件中设置相应的文件名。 如果您在进一步阅读中对于所添加代码的顺序难以理解的话,我们建议您在编辑器中打开相应阶段的文件并进行检查。
阶段 1 — 开发之字折线
我们在 MetaEditor 中创建新的 OscZigZagStep1 自定义指标。 添加一个外部变量以便在代码中留出变量的位置。 在事件处理程序选择窗口中,选择第一个选项 — OnCalculate(…,open,high,low,close),不需要其它的处理程序。 在显示参数窗口中创建两个缓冲区。 我们为第一个缓冲区命名 “HighLow”,类型 — 彩色箭头和两种颜色: 金色和柠檬绿色 第二个命名为 “ZigZag”,类型 — 彩色线段和三种颜色: 灰色,矢车菊蓝色和红色 (图例 4)。
图例 4. 在指标开发向导窗口中选择显示参数
由于彩色点与柱线相关,因此首先绘制它们 (更接近柱线),然后绘制之字折线更合乎逻辑。 因此,缓冲区按此顺序排列。
单击完成后,指示文件将出现在编辑器中。 首先,我们应该删除其中过多的颜色样本来调整 indicator_color1 的属性值。 那么 indicator_color1 属性的指示线应具有以下外观:
#property indicator_color1 clrGold,clrLimeGreen
应以相同的方式调整 indicator_color2 的属性 (应保留三种颜色)。
在自动创建的外部参数中查找字符串:
input int Input1;
删除它并替换为声明 WPR 指标参数的变量:
input int WPRperiod = 14; input double WPRmax = -20; input double WPRmin = -80;
下面,我们声明句柄的变量:
int h;
在 OnInit() 函数的最开始下载指标:
h=iWPR(Symbol(),Period(),WPRperiod); if(h==INVALID_HANDLE){ Print("无法加载指标"); return(INIT_FAILED); }
释放 OnDeinit() 函数中的句柄:
void OnDeinit(const int reason){ if(h!=INVALID_HANDLE){ IndicatorRelease(h); } }
使用指标开发向导时,我们已经创建了显示缓冲区,但我们还需要辅助缓冲区。 例如,我们现在需要保存振荡器数值的缓冲区。 将 indicator_buffers 加 1 (用 5 替换 4):
#property indicator_buffers 5
声明另一个保存振荡器数值缓冲区的数组:
double wpr[];
在 OnInit() 函数中指派此数组用于指标缓冲区的中间计算。 代码添加在 OnInit() 函数的最末尾:
SetIndexBuffer(4,wpr,INDICATOR_CALCULATIONS);
进入 OnCalculate() 函数,编写计算柱线计数范围的标准代码,并将 WPR 振荡器数值复制到缓冲区:
int start; if(prev_calculated==0){ start=0; } else{ start=prev_calculated-1; } if(CopyBuffer(h,0,0,rates_total-start,wpr)==-1){ return(0); }
现在,我们可以编写标准指标循环并显示振荡器跨入超买/超卖区域的点位:
for(int i=start;i<rates_total;i++){ HighLowBuffer[i]=EMPTY_VALUE; if(wpr[i]>WPRmax){ HighLowBuffer[i]=high[i]; HighLowColors[i]=0; } else if(wpr[i]<WPRmin){ HighLowBuffer[i]=low[i]; HighLowColors[i]=1; } }
在此阶段,指标可以挂载到图表。 我们挂载标准的 WPR,并确认一切顺利 (图例 5)。
图例 5. 在价格图表上显示超买/超卖区域
我们继续开发之字折线。 我们将需要几个辅助缓冲区: 一个用于当前指标方向,而另外两个保存之字折线最后顶端和底端的柱线索引:
double dir[]; // 方向 double lhb[]; // 最后的顶端柱线索引 double llb[]; // 最后的底端柱线索引
由于我们添加了三个缓冲区,我们需要在定义指标缓冲区数量的属性里提升:
#property indicator_buffers 8
通过 SetIndexBuffer() 函数来应用 OnInit() 函数中声明的新数组:
SetIndexBuffer(5,dir,INDICATOR_CALCULATIONS); SetIndexBuffer(6,lhb,INDICATOR_CALCULATIONS); SetIndexBuffer(7,llb,INDICATOR_CALCULATIONS);
此代码应附加在 OnInit() 函数中已存在的最后一次 SetIndexBuffer() 函数调用之后。
现在,我们要考虑一个重点。 若令彩色线段类型的缓冲区正常工作,请确保为其设置空值 0。 否则,将显示下面显示的指示线而非之字折线:
图例 6. 如果彩色线段类型缓冲区的值不为空,则之字折线显示不正确
若要设置空值,请在 OnInit() 函数的最末尾 ‘return’ 字符串之前添加以下字符串:
PlotIndexSetDouble(1,PLOT_EMPTY_VALUE,0);
请注意,第一个参数等于 1。 在此情况下,这是显示缓冲区群中的索引,即 1 对应于 ZigZagBuffer[] 缓冲区。
OnInit() 函数已准备就绪。 现在,我们来关注 OnCalculate() 函数并继续在标准指标循环中编写代码。
在指标循环开始时,在第一个字符串清除 HighLowBudder 缓冲区之后顺移辅助缓冲区数据:
lhb[i]=lhb[i-1]; llb[i]=llb[i-1]; dir[i]=dir[i-1];
我们在代码中为 dir[] 缓冲区设置方向,其中定义了设置跨越的超买/超卖区域:
if(wpr[i]>WPRmax){ HighLowBuffer[i]=high[i]; HighLowColors[i]=0; dir[i]=1; } else if(wpr[i]<WPRmin){ HighLowBuffer[i]=low[i]; HighLowColors[i]=1; dir[i]=-1; }
现在,在第一阶段最令人兴奋的事情是构建之字折线。 之字折线的方向已定义。 dir[] 缓冲区包含的数值为: 当抬头向上时 1,在低头向下时 -1。 此外,我们需要定义方向变化的柱线。 之字折线基准由以下切分为 4 条分支的代码组成:
if(dir[i]==1){ if(dir[i-1]==-1){ // 方向转变上行 } else{ // 上行走势延续 } } else if(dir[i]==-1){ if(dir[i-1]==1){ // 方向转变下行 } else{ // 下行走势延续 } }
我们考察之字折线方向转为上行及其随后的上行走势。 另外两条分支是对称的。
转为上行方向
1. 我们在最后一个底部到所计算的柱线范围内搜索最大价格值 (底部柱线不包括在范围之内):
if(dir[i]==1){ if(dir[i-1]==-1){ // 方向转变上行 // 搜索最大值 int hb=i; for(int j=i;j>llb[i];j--){ if(high[j]>high[hb]){ hb=j; } } //... } else{ // 上行走势延续 } }
2. 在检测到的柱线上,设置之字折线端点,在 lhb[] 缓冲区中设置柱线索引,并通过 ZigZagColor 缓冲区定义中性颜色:
ZigZagBuffer[hb]=high[hb]; lhb[i]=hb; ZigZagColors[hb]=0;
3. 重新计算此柱线时,可能会发现振荡器的值已经改变,且它不应是一个端点。 因此,我们需要删除它。 通常这是在指标循环开始时通过清除缓冲区来完成:
ZigZagBuffer[i]=0;
但在此情况下,形成的之字折线顶部与所计算的柱线错位了未知数量的柱线 (图例 1)。 因此,我们需要保存新顶点所在的柱线索引以及所计算柱线的时间:
NewDotTime=time[i]; NewDotBar=hb;
在指标全局层次上声明 NewDotTime 和 NewDotBar 变量。
4. 检查 NewDotTime 变量值是否与指标循环开始时计算的柱线相匹配。 如果匹配,删除新的之字折线端点:
if(NewDotTime==time[i]){ ZigZagBuffer[NewDotBar]=0; }
上行走势
我们来考察定义上行走势延续的代码段。 如果柱线的最高价超过之前已固定的之字折线数值,则删除旧端点并设置新端点:
if(high[i]>ZigZagBuffer[(int)lhb[i]]){ // 删除旧端点 ZigZagBuffer[(int)lhb[i]]=0; // 设置新端点 ZigZagBuffer[i]=high[i]; ZigZagColors[i]=0; lhb[i]=i; }
在指标循环之初重新计算柱线之前,将指标返回到其原始状态 (返回删除的端点):
ZigZagBuffer[(int)lhb[i]]=high[(int)lhb[i]]; ZigZagBuffer[(int)llb[i]]=low[(int)llb[i]];
确保使用零值初始化 lhb[] 和 llb[] 缓冲区,以避免指标操作之初的数组超界错误。 此外,我们需要将 NewDotTime 和 NewDotBar 变量清零。 这在计算计数范围时完成:
if(prev_calculated==0){ start=1; lhb[0]=0; llb[0]=0; NewDotTime=0; } else{ start=prev_calculated-1; }
此刻,指标开发的第一阶段已经结束。 在下面的附件中,此阶段的指标名为 OscZigZagStep1.mq5。
阶段 2 — 检测形态并涂色
若要检测形态,我们需要比较 5 个之字折线的极值端点。 建议不要在每次循环中搜索指标中的所有端点,因为这会降低指标的速度 (指标的迅捷操作是主要的规范需求)。 最好将新出现的极值保存在单独的数组中,以便可以直接且快速地访问它们。
之字折线极值点的数据将保存在结构数组中。 该结构的字段应包含: 柱线索引,数值,方向和更多布尔类型的字段。 如果极值是形态中的最后一个 (限定之字折线的颜色以便与先前识别的形态区分),则会保存’ true’。 描述结构并声明数组:
struct SZZDot{ int bar; double val; int dir; bool pat; }; SZZDot ZZDot[];
然后,在四部分代码每一个的末尾添加 AddZZDot() 函数的调用。 该函数在 ZZDot[] 数组中添加新的极值:
if(dir[i]==1){ if(dir[i-1]==-1){ //... AddZZDot(1,high[hb],hb,i); } else{ if(high[i]>ZigZagBuffer[(int)lhb[i]]){ //... AddZZDot(1,high[i],i,i); } } } else if(dir[i]==-1){ if(dir[i-1]==1){ //... AddZZDot(-1,low[lb],lb,i); } else{ if(low[i]<ZigZagBuffer[(int)llb[i]]){ //... AddZZDot(-1,low[i],i,i); } } }
将四个参数 (方向,数值,极值所在柱线索引,以及计算的柱线索引) 传递给 AddZdot() 函数。 我们稍后会考察函数本身。 使用 cnt[] 指标缓冲区保存检测到的极值数量 (AADot[] 数组中已占用元素)。 声明 cnt[] 数组:
double cnt[];
在 OnInit() 函数中,为它调用 SetIndexBuffer() 函数:
SetIndexBuffer(8,cnt,INDICATOR_CALCULATIONS);
更改定义缓冲区数量的属性的值:
#property indicator_buffers 9
在指标循环之初,顺移缓冲区最后一个值:
cnt[i]=cnt[i-1];
我们已经提到,在柱线计算期间检测到的之字折线逆转也许在下一次计算同一柱线时消失。 所以,应删除保存在数组中的极值。 不过,这种删除不是通过缩减数组而是通过降低已占用数组元素数量 (cnt[] 缓冲区) 的计数器来执行的。 这显著提高了指标的速度。
我们来看看 AddZdot() 函数:
void AddZZDot(int d,double v,int b,int i){ int c=(int)cnt[i]; if(c==0){ // 在指标启动或重新计算完毕期间 ArrayResize(ZZDot,1024); ZZDot[c].dir=d; ZZDot[c].val=v; ZZDot[c].bar=b; ZZDot[c].pat=false; cnt[i]=1; } else{ if(ZZDot[c-1].dir==d){ // 更新同向极值 ZZDot[c-1].val=v; ZZDot[c-1].bar=b; } else{ // 添加新的极值 // 必要时,将数组增加 1024 个元素块 if(c>=ArraySize(ZZDot)){ ArrayResize(ZZDot,c+1024); } // 添加新的极值 ZZDot[c].dir=d; ZZDot[c].val=v; ZZDot[c].bar=b; ZZDot[c].pat=false; cnt[i]=c+1; } } }
在指标启动或重新计算完毕期间,数组大小设置为 1024。 其初始元素具有极值参数,而极值计数器增加 1。 在后续函数调用期间,将检查数组中最后一个极值的方向。 如果它对应于调用函数的参数,则更新最后一个极值的数据。 如果方向相反,则添加新的极值。
在分析任务时,我已经解释过,在之字折线逆转期间,相反方向的最后一个极值可以移到更早的柱线 (图例 2)。 所以,在执行之字折线主代码之前,将极值 (预先已知) 设置为 ZZDot 数组最后一个占用元素的数值。 这是在指标循环结束时完成的:
if(cnt[i]>0){ int ub=(int)cnt[i]-1; if(ZZDot[ub].dir==1){ ZZDot[ub].bar=(int)lhb[i]; ZZDot[ub].val=high[(int)lhb[i]]; } else{ ZZDot[ub].bar=(int)llb[i]; ZZDot[ub].val=low[(int)llb[i]]; } }
现在,如果在计算的柱线上检测到新的极值,则其值将在 ZZDot 数组中更新。 在逆转的情况下,先前已知的极值仍然保留。
在第一次指标计算之前以及执行全部重新计算时,将 cnt[] 数组以初始元素进行初始化:
if(prev_calculated==0){ //... cnt[0]=0; } else{ start=prev_calculated-1; }
现在我们有了全部之字折线的极值数据,并且能够轻松访问它们,我们将重点放到检测形态及其涂色。 如果至少有 5 个之字折线极值,才有可能:
if(cnt[i]>=5)
计算极值数组中最后一个元素的索引:
int li=(int)cnt[i]-1;
若在此极值上没有检测到形态,我们则要设置。 我们要为之字折线返回中性颜色:
ZZDot[li].pat=false;
为之字折线的部分返回初始颜色:
for(int j=0;j<4;j++){ if(ZZDot[li-j].pat){ break; } ZigZagColors[ZZDot[li-j].bar]=0; }
注意: 一旦发现带有形态的极值,循环就结束了。
检查形态条件:
if(ZZDot[li].dir==1){ // 向上 if( ZZDot[li].val>ZZDot[li-2].val && ZZDot[li-2].val>ZZDot[li-4].val && ZZDot[li-1].val>ZZDot[li-3].val ){ ZZDot[li].pat=true; // 涂色 } } else{ // 向下 if( ZZDot[li].val<ZZDot[li-2].val && ZZDot[li-2].val<ZZDot[li-4].val && ZZDot[li-1].val<ZZDot[li-3].val ){ ZZDot[li].pat=true; // 涂色 } }
现在,我们只需要编写涂色代码。 它类似于清理。 对于上行方向:
for(int j=0;j<4;j++){ if(j!=0 && ZZDot[li-j].pat){ break; } ZigZagColors[ZZDot[li-j].bar]=1; }
与清理代码略有不同的是,当 j=0 时,不会退出循环。
指标开发的第二阶段已经完毕。 该指标如下所示:
图例 7. 第 2 阶段完毕时的指标
在下面的附件中,此阶段的指标名为 OscZigZagStep2.mq5。
阶段 3 — 添加振荡器
我们来描述一下枚举:
enum EIType{ WPR, CCI, Chaikin, RSI, Stochastic };
声明用来选择振荡器的外部变量:
input EIType Type = WPR;
添加其余振荡器的参数:
// CCI input int CCIperiod = 14; input ENUM_APPLIED_PRICE CCIprice = PRICE_TYPICAL; input double CCImax = 100; input double CCImin = -100; // Chaikin input int CHfperiod = 3; input int CHsperiod = 10; input ENUM_MA_METHOD CHmethod = MODE_EMA; input ENUM_APPLIED_VOLUME CHvolume = VOLUME_TICK; input double CHmax = 1000; input double CHmin = -1000; // RSI input int RSIperiod = 14; input ENUM_APPLIED_PRICE RSIprice = PRICE_CLOSE; input double RSImax = 70; input double RSImin = 30; // Stochastic input int STperiodK = 5; input int STperiodD = 3; input int STperiodS = 3; input ENUM_MA_METHOD STmethod = MODE_EMA; input ENUM_STO_PRICE STprice = STO_LOWHIGH; input double STmax = 80; input double STmin = 20;
声明级别的变量:
double max,min;
在 OnStart 函数之初选择一个振荡器:
switch(Type){ case WPR: max=WPRmax; min=WPRmin; h=iWPR(Symbol(),Period(),WPRperiod); break; case CCI: max=CCImax; min=CCImin; h=iCCI(Symbol(),Period(),CCIperiod,CCIprice); break; case Chaikin: max=CHmax; min=CHmin; h=iChaikin(Symbol(),Period(),CHfperiod,CHsperiod,CHmethod,CHvolume); break; case RSI: max=RSImax; min=RSImin; h=iRSI(Symbol(),Period(),RSIperiod,RSIprice); break; case Stochastic: max=STmax; min=STmin; h=iStochastic(Symbol(),Period(),STperiodK,STperiodD,STperiodS,STmethod,STprice); break; } if(h==INVALID_HANDLE){ Print("无法加载指标"); return(INIT_FAILED); }
在 OnCalculate() 函数中以 max 和 min 变量替换 WPRmax 和 WPmin 变量。
开发指标的第三阶段业已完成。 现在,我们可以在指标属性窗口中选择振荡器。 在下面的附件中,此阶段的指标名为 OscZigZagStep3.mq5。
阶段 4 — 开发图形界面
IncGUI 库用于开发图形界面。 该函数库是为系列文章《自定义图形控件》专门开发的,它包括三个部分 最新修订版本的函数库 (IncGUI_v4.mqh) 附带在《拥有图形界面的通用振荡器》一文中。 它也附带于此。 在开始图形界面开发之前,将 IncGUI_v4.mqh 文件复制到终端数据文件夹的 MQL5/Includes 目录。
我们来逐步开发图形界面。
包含函数库。 制作一份 OscZigZagStep3 指标的副本,并将函数库包含在其中:
#include <IncGUI_v4.mqh>
窗体类。 IncGUI_v4.mqh 文件包含 CFormTemplate 类,它是一类用于开发窗体的模板。 在包含函数库之后立即复制并粘贴到指标文件。 然后将它从 CFormTemplate 重命名为 CForm。
窗体属性。 在 MainProperties() 方法中设置主窗体属性:
m_Name = "Form"; m_Width = 200; m_Height = 150; m_Type = 2; m_Caption = "ZigZag on Oscillator"; m_Movable = true; m_Resizable = true; m_CloseButton = true;
- m_Name 变量是窗体名称 (构成窗体的所有图形对象的前缀)。
- m_Width 和 m_Height 变量是窗体的宽度和高度 (以像素为单位)。
- m_Type 变量是窗体类型。 如果值为 2,则窗体底部将显示关闭按钮。
- m_Caption 变量是窗体标题。
- m_Movable 变量是一个可移动窗体。 用于移动的按钮位于窗体的左上角。
- m_Resizable 变量 — 窗体可以展开/折叠。 相应的按钮位于右上角。
- m_CloseButton 变量 — 窗体可以关闭。 相应的按钮位于右上角。
控件。 创建窗体控件。 窗体将有两套框架。 一套将含有一组单选按钮,而另一套将含有两个输入字段。 在窗体类的 “public” 部分中输入以下代码:
今天的文章振荡器设计_zigzag指标公式源码分享到此就结束了,感谢您的阅读。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/81779.html