Erik Nayman 的绳索指标·进阶篇
(2/3)· 接上篇概念铺垫,本篇直接落地代码实现、实盘用法与输入参数调优
新K线出现时的数组与时间序列处理
在 MT5 自定义指标里,把 time、open、high、low、close 以及十几个 Buffer_ 开头的数组全部用 ArraySetAsSeries(...,true) 置为时间序列,是后续按索引 0 取最新值的前提。最低索引 0 对应历史中最后一个已知计算值,这个设定直接决定你循环里引用 close[0] 拿到的是不是当前未收盘柱。 当 prev_calculated==0(例如断网很久后重连,历史有缺口)或 prev_rates_total>rates_total(数据大小回缩,视为失败需重算),代码会把 _tm_prev 清零。只有 _tm_prev<time[0] 即新柱线出现时才进计算分支,避免每笔 tick 都重跑全量逻辑。 若 _tm_prev>0 表示已有旧计算数据,则调用 TakeShift_byTime(Symbol(),PERIOD_CURRENT,_tm_prev) 按最后计算时间做柱线移位;返回值 copy<=0 就直接 return 0,等下一 tick 再试。copy 有效时 bars_calc=copy+1,并从索引 0 开始把 Bull/Bear/mainline/MA/calc 等缓冲全清 0.0,颜色索引统一设 5(空色不绘制),保证未收盘首柱不显示残影。外汇与贵金属波动大、跳空频繁,这种断点重算机制直接影响指标在实盘里的重绘稳定性,建议开 MT5 用 EURUSD 15 分钟图断网测试一次。
class="kw">const class="type">int &spread[]) { ArraySetAsSeries(time,true); class=class="str">"cmt">//--- 设置数组索引作为时间序列, 最低索引 class="num">0 是历史中最后已知计算值 ArraySetAsSeries(open,true); ArraySetAsSeries(high,true); ArraySetAsSeries(low,true); ArraySetAsSeries(close,true); ArraySetAsSeries(Buffer_main_bull,true); ArraySetAsSeries(Buffer_color_bull,true); ArraySetAsSeries(Buffer_main_bear,true); ArraySetAsSeries(Buffer_color_bear,true); ArraySetAsSeries(Buffer_mainline,true); ArraySetAsSeries(Buffer_mainline_color,true); ArraySetAsSeries(Buffer_MAfast,true); ArraySetAsSeries(Buffer_MAslow,true); ArraySetAsSeries(Buffer_calc,true); ArraySetAsSeries(Buffer_calc_bull,true); ArraySetAsSeries(Buffer_calc_bear,true); ArraySetAsSeries(Buffer_calcLINE,true); class=class="str">"cmt">//--- class="type">int copy=class="num">0; bars_calc=class="num">0; class=class="str">"cmt">//--- 计算指标数据 class=class="str">"cmt">//--- 当 prev_calculated = class="num">0 所有已计算柱线的所有数据需要再次计算 , =class="num">0 历史数据中有缺口则等于零, 例如, 当断网很长一段时间 if(prev_calculated==class="num">0) _tm_prev=class="num">0; class=class="str">"cmt">//---如果以前的大小大于当前大小, 它表示失败且所有数据必须重计算 if(prev_rates_total>rates_total) _tm_prev=class="num">0; if(_tm_prev<time[class="num">0]) class=class="str">"cmt">// 当新柱线出现时计算 { if(_tm_prev>class="num">0) class=class="str">"cmt">// 如果指标已经有以前计算的数据 { copy=TakeShift_byTime(Symbol(),PERIOD_CURRENT,_tm_prev); class=class="str">"cmt">// 当前品种柱线沿最后计算的柱线时间移位 if(copy<=class="num">0) class=class="str">"cmt">// 如果移位未发现, 则计算偏离并在下一次瞬时报价重复 { class="kw">return class="num">0; } bars_calc=copy+class="num">1; class=class="str">"cmt">// 设置自当前指标未收盘柱线开始计算 class=class="str">"cmt">//--- 首根未收盘柱线清零, 即, 其数据未计算 Buffer_main_bull[class="num">0]=class="num">0.0; Buffer_color_bull[class="num">0]=class="num">5; class=class="str">"cmt">// 设置颜色索引, 号码 class="num">5 对应空色, 所以不会绘制颜色 Buffer_main_bear[class="num">0]=class="num">0.0; Buffer_color_bear[class="num">0]=class="num">5; class=class="str">"cmt">// 设置颜色索引, 号码 class="num">5 对应空色, 所以不会绘制颜色 Buffer_mainline[class="num">0]=class="num">0.0; Buffer_mainline_color[class="num">0]=class="num">5; class=class="str">"cmt">// 设置颜色索引, 号码 class="num">5 对应空色, 所以不会绘制颜色 Buffer_MAfast[class="num">0]=class="num">0.0; Buffer_MAslow[class="num">0]=class="num">0.0; Buffer_calc[class="num">0]=class="num">0.0; Buffer_calc_bull[class="num">0]=class="num">0.0; Buffer_calc_bear[class="num">0]=class="num">0.0; Buffer_calcLINE[class="num">0]=class="num">0.0; } else class=class="str">"cmt">// 如果指标首次设置且数值尚未计算
◍ 缓冲区清零与价格差值计算的开端
指标初始化时先把全部绘图缓冲清零,是避免 MT5 重绘错位的硬规矩。下面这段把 9 个 buffer 逐根柱线置 0,包括多空主缓冲、均线缓冲与计算缓冲,循环里还嵌了 IsStopped() 检查,终端手动停止时立刻断循环。 计算起点用 bars_calc = Bars(Symbol(),PERIOD_CURRENT)-1,即从倒数第二根柱开始,留出 i+1 作对比基准。若 bars_calc 算出来小于 0 直接 return 0 放弃本轮;若超过 max_bars 且 max_bars 不为 0 则截断,实盘把 max_bars 设 5000 可明显降 CPU 占用。 随后按 _price 枚举切换计算相邻柱线价差:收盘、开盘、最高、最低、中位数、典型价、加权价都能选。以 PRICE_TYPICAL 为例,Buffer_calc[i]=((high[i]+low[i]+close[i])/3)-((high[i+1]+low[i+1]+close[i+1])/3),这一步是后续多空动能拆分的原始输入。外汇与贵金属波动大,此类差值对点差敏感,建议先在策略测试器用 2023 年 XAUUSD 的 M5 数据跑一遍验证缓冲逻辑。
bars_calc=Bars(Symbol(),PERIOD_CURRENT)-class="num">1; class=class="str">"cmt">// 柱线移位到总柱线数 -class="num">1 开始计算 class=class="str">"cmt">//--- 所有柱线的所有数据清零以避免绘图错误 for(class="type">int i=class="num">0;i<Bars(Symbol(),PERIOD_CURRENT) && !IsStopped(); i++) { Buffer_main_bull[i]=class="num">0.0; Buffer_main_bear[i]=class="num">0.0; Buffer_mainline[i]=class="num">0.0; Buffer_MAfast[i]=class="num">0.0; Buffer_MAslow[i]=class="num">0.0; Buffer_calc[i]=class="num">0.0; Buffer_calc_bull[i]=class="num">0.0; Buffer_calc_bear[i]=class="num">0.0; Buffer_calcLINE[i]=class="num">0.0; } class=class="str">"cmt">//--- 如果用于指标计算的柱线数量低于零, 则计算将被放弃 if(bars_calc<class="num">0) class="kw">return class="num">0; class=class="str">"cmt">//--- 限制计算 max_bars 值的柱线数量 if(bars_calc>max_bars && max_bars!=class="num">0) bars_calc=max_bars; for(class="type">int i=class="num">1; i<bars_calc && !IsStopped(); i++) { class=class="str">"cmt">//--- 根据 _price 设置, 计算两根柱线报价间的差值 class="kw">switch(_price) { case PRICE_CLOSE: Buffer_calc[i]=close[i]-close[i+class="num">1]; class="kw">break; case PRICE_OPEN: Buffer_calc[i]=open[i]-open[i+class="num">1]; class="kw">break; case PRICE_HIGH: Buffer_calc[i]=high[i]-high[i+class="num">1]; class="kw">break; case PRICE_LOW: Buffer_calc[i]=low[i]-low[i+class="num">1]; class="kw">break; case PRICE_MEDIAN: Buffer_calc[i]=((high[i]+low[i])/class="num">2)-((high[i+class="num">1]+low[i+class="num">1])/class="num">2); class="kw">break; case PRICE_TYPICAL: Buffer_calc[i]=((high[i]+low[i]+close[i])/class="num">3)-((high[i+class="num">1]+low[i+class="num">1]+close[i+class="num">1])/class="num">3); class="kw">break; case PRICE_WEIGHTED:
「逐根K线累计多空力道的循环实现」
指标在主循环里对每根未计算柱线先清空 12 个缓冲区变量,再按 _period 窗口回看历史。注意 Buffer_color_bull[i]=5 与 Buffer_color_bear[i]=5 这种初始值,是留给后续按信号强度重绘颜色的占位,MT5 里颜色索引 5 通常对应空画刷,初态不显示。
内层 for(int i2=i; i2<i+_period; i2++) 把窗口内每根 Buffer_calc[i2] 的正负分类:大于 0 累进 sum_bull 且 total_bull++,小于 0 累进 sum_bear 且 total_bear++。这一步就是把一个分析周期内多头阳线动量、空头阴线动量分别记账。
窗口走完用 Buffer_calc_bull[i]=sum_bull/total_bull 求多头单根均值力道,空头同理。若 total_bull 为 0 则该缓冲区保持 0.0,意味着该窗口内无阳线,直方图不会误画多头柱。外汇与贵金属波动跳空频繁,这种按数量归一的做法在跳空缺口段可能低估真实单向力道,实盘前建议在 EURUSD 的 M15 上用 _period=10 跑一遍验证。
下方代码片段是分类累加的核心段,直接拷进 MT5 自定义指标 OnCalculate 后半段即可观察缓冲变化。
for(class="type">int i=class="num">1; i<=bars_calc && !IsStopped(); i++) { sum_bull=class="num">0.0; total_bull=class="num">0; sum_bear=class="num">0.0; total_bear=class="num">0; Buffer_main_bull[i]=class="num">0.0; Buffer_color_bull[i]=class="num">5; Buffer_main_bear[i]=class="num">0.0; Buffer_color_bear[i]=class="num">5; Buffer_mainline[i]=class="num">0.0; Buffer_mainline_color[class="num">0]=class="num">5; Buffer_calc_bull[i]=class="num">0.0; Buffer_calc_bear[i]=class="num">0.0; Buffer_calcLINE[i]=class="num">0.0; if(i>=(rates_total-_period)) class="kw">continue; for(class="type">int i2=i; i2<i+_period; i2++) { if(Buffer_calc[i2]>class="num">0) { sum_bull+=Buffer_calc[i2]; total_bull++; } if(Buffer_calc[i2]<class="num">0) { sum_bear+=Buffer_calc[i2]; total_bear++; } } if(total_bull>class="num">0) Buffer_calc_bull[i]=sum_bull/total_bull; if(total_bear>class="num">0) Buffer_calc_bear[i]=sum_bear/total_bear; }
多空直方图的三级确认着色
指标把每一根柱线拆成多头和空头两条直方图,分别用累计点数除以柱线数量得到均值:total_bull>0 时 Buffer_main_bull[i]=sum_bull/total_bull,total_bear>0 时 Buffer_main_bear[i]=sum_bear/total_bear。这里的「活力」是柱线数量,「力度」是累计点数绝对值,「技巧」是覆盖周期内的力度/活力均值。 颜色索引不是拍脑袋,而是三个维度两两比较的结果。当 total_bull>total_bear 且 MathAbs(sum_bull)>MathAbs(sum_bear) 且 MathAbs(Buffer_main_bull[i])>MathAbs(Buffer_main_bear[i]),三个多头维度全压过空头,给 0 号色(深绿,高级别确认)。 只要任意两个多头指标胜出、一个空头指标胜出,就降一档到 1 号色(海绿,平均确认);若只有一个多头维度占优,则落到 2 号色(浅绿,低级别确认)。其余情况统一涂灰,代表买单禁止。外汇与贵金属波动剧烈,这种分级只描述概率倾向,实盘仍需自担高风险。 在 MT5 里把下面这段直接塞进 OnCalculate 的循环体,就能看见三档绿色随柱线切换;改一下比较里的 MathAbs 阈值,可以逼出更挑剔或更宽松的确认信号。
if(total_bull>class="num">0) Buffer_main_bull[i]=sum_bull/total_bull; class=class="str">"cmt">//--- 绘制空头的直方图, 用柱线的点数值除以柱线数量 if(total_bear>class="num">0) Buffer_main_bear[i]=sum_bear/total_bear; class=class="str">"cmt">//--- class="num">0 直方图颜色 clrDarkGreen - 颜色索引 "仅买入", 买单具有高级别确认 class=class="str">"cmt">//--- 确认级别 - 所有指标示意多头趋势 class=class="str">"cmt">//--- 多头活力 > 空头活力 且 多头力度 > 空头力度 且 多头技巧 > 空头技巧 class=class="str">"cmt">//--- 活力 - 柱线数量 class=class="str">"cmt">//--- 力度 - 累计柱线点数大小 class=class="str">"cmt">//--- 技巧 - 覆盖指示周期的所有柱线的力度/活力计算平均值 if(total_bull>total_bear && MathAbs(sum_bull)>MathAbs(sum_bear) && MathAbs(Buffer_main_bull[i])>MathAbs(Buffer_main_bear[i])) Buffer_color_bull[i]=class="num">0; else { class=class="str">"cmt">//--- 直方图颜色 clrMediumSeaGreen - 颜色索引 "买单允许", 买单具有平均级别确认 class=class="str">"cmt">//--- 确认级别 - 任意两个多头指标和一个空头指标的组合 if((total_bull>total_bear && MathAbs(sum_bull)>MathAbs(sum_bear) && MathAbs(Buffer_main_bull[i])<MathAbs(Buffer_main_bear[i])) || (total_bull>total_bear && MathAbs(sum_bull)<MathAbs(sum_bear) && MathAbs(Buffer_main_bull[i])>MathAbs(Buffer_main_bear[i])) || (total_bull<total_bear && MathAbs(sum_bull)>MathAbs(sum_bear) && MathAbs(Buffer_main_bull[i])>MathAbs(Buffer_main_bear[i]))) Buffer_color_bull[i]=class="num">1; else { class=class="str">"cmt">//--- 直方图颜色 clrLightGreen - 颜色索引 "买单允许", 买单具有低级别确认 class=class="str">"cmt">//--- 确认级别 - 任意一个多头指标和两个空头指标的组合 if((total_bull>total_bear && MathAbs(sum_bull)<MathAbs(sum_bear) && MathAbs(Buffer_main_bull[i])<MathAbs(Buffer_main_bear[i])) || (total_bull<total_bear && MathAbs(sum_bull)>MathAbs(sum_bear) && MathAbs(Buffer_main_bull[i])<MathAbs(Buffer_main_bear[i])) || (total_bull<total_bear && MathAbs(sum_bull)<MathAbs(sum_bear) && MathAbs(Buffer_main_bull[i])>MathAbs(Buffer_main_bear[i]))) Buffer_color_bull[i]=class="num">2; else { class=class="str">"cmt">//--- 直方图颜色 clrGray - 颜色索引 "买单禁止" class=class="str">"cmt">//--- 确认级别 - 当多头指标缺失 } } }
◍ 空头侧颜色分级的确认逻辑
这段着色代码只干一件事:根据多头与空头指标的数量差、绝对值求和差、以及主缓冲绝对值差,给空头直方图 Buffer_color_bear 打 0~3 四级标签。0 级(clrDarkRed)要求最严——total_bull<total_bear 且空头求和绝对值更大、主缓冲绝对值也更大,相当于所有指标一致看空,属高级别确认。
- 级(clrIndianRed)放宽到「任意两个空头条件搭配一个多头条件」:比如数量空方占优但主缓冲多方更大,或数量多方占优但求和空方更大等三种组合,属平均级别确认。
- 级(clrLightPink)则是「一个多头条件加两个空头条件」的逆向搭配,属低级别确认;其余情况统一归为 3 级(clrGray),即空头指标缺失、卖单禁止状态。
开 MT5 把这段塞进你自己的多指标合成脚本,改 Buffer_color_bear 的赋值色号,就能直接看出当前 K 线处在哪档做空置信度。外汇与贵金属波动剧烈,颜色分级只是概率倾向,不代表方向必现。
if(total_bull<total_bear && MathAbs(sum_bull)<MathAbs(sum_bear) && MathAbs(Buffer_main_bull[i])<MathAbs(Buffer_main_bear[i])) Buffer_color_bear[i]=class="num">0; else { if((total_bull<total_bear && MathAbs(sum_bull)<MathAbs(sum_bear) && MathAbs(Buffer_main_bull[i])>MathAbs(Buffer_main_bear[i])) || (total_bull<total_bear && MathAbs(sum_bull)>MathAbs(sum_bear) && MathAbs(Buffer_main_bull[i])<MathAbs(Buffer_main_bear[i])) || (total_bull>total_bear && MathAbs(sum_bull)<MathAbs(sum_bear) && MathAbs(Buffer_main_bull[i])<MathAbs(Buffer_main_bear[i]))) Buffer_color_bear[i]=class="num">1; else { if((total_bull<total_bear && MathAbs(sum_bull)>MathAbs(sum_bear) && MathAbs(Buffer_main_bull[i])>MathAbs(Buffer_main_bear[i])) || (total_bull>total_bear && MathAbs(sum_bull)<MathAbs(sum_bear) && MathAbs(Buffer_main_bull[i])>MathAbs(Buffer_main_bear[i])) || (total_bull>total_bear && MathAbs(sum_bull)>MathAbs(sum_bear) && MathAbs(Buffer_main_bull[i])<MathAbs(Buffer_main_bear[i]))) Buffer_color_bear[i]=class="num">2; else { Buffer_color_bear[i]=class="num">3; } } }
「主线着色与均线计算的收口逻辑」
主线 Buffer_mainline 的取值等于多空合计力度乘偏离系数,和 Buffer_calcLINE 共用同一算法,只是多了一层颜色判定。颜色索引写死为 0/1/2 三档:2 是灰(无确认),0 深绿(仅买、高级别确认),1 深红(仅卖、高级别确认)。 绿色触发要求三条件同时成立:total_bull>total_bear、绝对值 sum_bull>sum_bear、且当前柱 abs(Buffer_calc_bull[i])>abs(Buffer_calc_bear[i]);红色则三项全部反向。任一不满足就落回灰色,意味着信号未跨过「多指标共振」门槛。 均线部分先调用 ArraySetAsSeries(...,false) 关掉时间序列索引,把数组尾当作当前柱,再按 rates_total-bars_calc 起点向后推。max_bars>0 时若 i 小于 rates_total-max_bars 直接跳到边界并 continue,相当于只算最近 max_bars 根,老数据不重绘。 没拿到均线数据的柱先清 0,再按 method_MA_fast 分支选算法;MODE_SMA 只是入口,后面还有其它模式待接。外汇与贵金属波动剧烈,这种多指标确认仅降低假信号概率,实盘仍需自担高风险。
Buffer_calcLINE[i]=(Buffer_calc_bull[i]+Buffer_calc_bear[i])*line_deviation; class=class="str">"cmt">//--- 通过信号确认级别绘制填充颜色的线条 class=class="str">"cmt">// class="num">0 - clrDarkGreen - 颜色索引 "仅买入", 买单具有高级别确认 class=class="str">"cmt">// class="num">1 - clrDarkRed - 颜色索引 "仅卖出", 卖单具有高级别确认 class=class="str">"cmt">// class="num">2 - clrGray - 颜色索引 "无限制确认" if(draw_line) { class=class="str">"cmt">//--- 绘制主线值, 累计空头和多头的技巧 Buffer_mainline[i]=(Buffer_calc_bull[i]+Buffer_calc_bear[i])*line_deviation; class=class="str">"cmt">//--- 设置不确定线条颜色 (class="num">2 - clrGray) Buffer_mainline_color[i]=class="num">2; class=class="str">"cmt">//--- 设置多头线条颜色 (class="num">0 - clrDarkGreen) 当所有三个多头指标匹配 (多头 > 空头) if(total_bull>total_bear && MathAbs(sum_bull)>MathAbs(sum_bear) && MathAbs(Buffer_calc_bull[i])>MathAbs(Buffer_calc_bear[i])) Buffer_mainline_color[i]=class="num">0; class=class="str">"cmt">//--- 设置多头线条颜色 (class="num">1 - clrDarkRed) 当所有三个空头指标匹配 (空头 > 多头) if(total_bull<total_bear && MathAbs(sum_bull)<MathAbs(sum_bear) && MathAbs(Buffer_calc_bull[i])<MathAbs(Buffer_calc_bear[i])) Buffer_mainline_color[i]=class="num">1; } } class=class="str">"cmt">//--- 计算并绘制均线 if(draw_MA_fast || draw_MA_slow) { class=class="str">"cmt">//--- 禁用索引作为时间序列来转换库 MovingAverages.mqh(索引改到数组结尾的当前历史柱线) ArraySetAsSeries(Buffer_calcLINE,class="kw">false); ArraySetAsSeries(Buffer_MAfast,class="kw">false); ArraySetAsSeries(Buffer_MAslow,class="kw">false); class=class="str">"cmt">//--- 迭代所有未计算柱线。迭代 = 所有柱线 (rates_total) - 柱线从最后已知计算过的时间 _tm_prev(bars_calc) 移位 for(class="type">int i=rates_total-bars_calc; i<rates_total && !IsStopped(); i++) { class=class="str">"cmt">//--- 在参数 max_bars 里设置计算限制, 计算仅用设置在 max_bars 的数量作为所有柱线 (rates_total) 减设置数量 (max_bars) 的总差值。 if(max_bars>class="num">0?(i<(rates_total-max_bars)):class="kw">false) { class=class="str">"cmt">//--- 如果当前索引 i 低于许可数值, 我们继续允许索引 i=rates_total-max_bars; class="kw">continue; } class=class="str">"cmt">//--- 在未收到均线数据的情况下清空数组 Buffer_MAfast[i]=class="num">0.0; Buffer_MAslow[i]=class="num">0.0; class=class="str">"cmt">//--- 基于 method_MA_fast 参数选择快速均线计算方法 if(draw_MA_fast) { class="kw">switch(method_MA_fast) { case MODE_SMA: class=class="str">"cmt">// 简单平均