构建K线图趋势约束模型(第一部分):针对EA和技术指标·进阶篇
(2/3)· 当M1里的假突破淹没你的均线交叉,D1的阴阳才是那道该立的篱笆
「双均线交叉落点的缓冲区填充逻辑」
这段处理的核心是:先确认两条均线句柄都已算出足够柱数,再把它们的缓冲区和当前品种的高低价数组按时间倒序排好,避免后面索引错位。 BarsCalculated 返回小于等于 0 就直接 return(0),CopyBuffer / CopyLow / CopyHigh 失败则回退到 rates_total,这是防止指标在历史数据不足时崩掉的基础防线。 主循环从 limit-1 往下跑到 0,并用 MathMin(PLOT_MAXIMUM_BARS_BACK-1, rates_total-1-OMIT_OLDEST_BARS) 跳过最老的一段 K 线——实测若 OMIT_OLDEST_BARS 设得过小,在 EURUSD 的 M1 上加载容易触发 Array out of range。 交叉判定很直白:当 MA[i] 上穿 MA2[i](且前一柱是下穿状态),把 Buffer1[i] 标在当根 K 线最低价;反之下穿则把 Buffer2[i] 标在最高价。没触发时写 EMPTY_VALUE,MT5 上就不会画出散点。 把这段代码贴进 MetaEditor 编译后,你能在副图看到均线金叉/死叉对应的高低点标记,外汇与贵金属波动大,此类信号仅作概率参考,实盘请自担高风险。
ArrayInitialize(Buffer2, EMPTY_VALUE); } else limit++; if(BarsCalculated(MA_handle) <= class="num">0) class="kw">return(class="num">0); if(CopyBuffer(MA_handle, class="num">0, class="num">0, rates_total, MA) <= class="num">0) class="kw">return(rates_total); ArraySetAsSeries(MA, true); if(BarsCalculated(MA_handle2) <= class="num">0) class="kw">return(class="num">0); if(CopyBuffer(MA_handle2, class="num">0, class="num">0, rates_total, MA2) <= class="num">0) class="kw">return(rates_total); ArraySetAsSeries(MA2, true); if(CopyLow(Symbol(), PERIOD_CURRENT, class="num">0, rates_total, Low) <= class="num">0) class="kw">return(rates_total); ArraySetAsSeries(Low, true); if(CopyHigh(Symbol(), PERIOD_CURRENT, class="num">0, rates_total, High) <= class="num">0) class="kw">return(rates_total); ArraySetAsSeries(High, true); class=class="str">"cmt">//--- main loop for(class="type">int i = limit-class="num">1; i >= class="num">0; i--) { if (i >= MathMin(PLOT_MAXIMUM_BARS_BACK-class="num">1, rates_total-class="num">1-OMIT_OLDEST_BARS)) class="kw">continue; class=class="str">"cmt">//omit some old rates to prevent "Array out of range" or slow calculation class=class="str">"cmt">//Indicator Buffer class="num">1 if(MA[i] > MA2[i] && MA[i+class="num">1] < MA2[i+class="num">1] class=class="str">"cmt">//Moving Average crosses above Moving Average ) { Buffer1[i] = Low[i]; class=class="str">"cmt">//Set indicator value at Candlestick Low } else { Buffer1[i] = EMPTY_VALUE; } class=class="str">"cmt">//Indicator Buffer class="num">2 if(MA[i] < MA2[i] && MA[i+class="num">1] > MA2[i+class="num">1] class=class="str">"cmt">//Moving Average crosses below Moving Average ) { Buffer2[i] = High[i]; class=class="str">"cmt">//Set indicator value at Candlestick High } else { Buffer2[i] = EMPTY_VALUE; } } class="kw">return(rates_total); } class=class="str">"cmt">//copy the code to meta editor to compile it
◍ 用日线当筛子过滤逆势信号
把高时间框架趋势当成筛子,逻辑和谷物分离一样:只放行与日线方向一致的信号,逆势噪音直接漏掉。具体落地时,用前一根 M1 收盘价去比前一根 D1 开盘价(D1 开盘近似前一天收盘),M1 收 ≥ D1 开判多头,≤ 则判空头;日线多头只给买信号,空头只给卖信号。 选 M1 收盘价而不是买价卖价或当日收盘,是因为按这套箭头风格,后者在图表上画不出所需提示。贴着 D1 开与低周期收的关系走,指标箭头和策略意图才能对齐,决策链路更短。 回测片段显示约束接入后信号分布明显收敛:总信号 28 个,卖出 27、买入 1,其中虚假逆势 3 个、正确 25 个。和未加约束的表比,真信号留住了,假信号被削掉,EA 实盘过滤更干净。外汇与贵金属波动剧烈,高杠杆下仍可能连续止损,须自测周期匹配度。 下面这段是 D1 多头条件的核心判断,顺带看缓冲赋值怎么画箭头: if(Close[1+barshift_M1[i]] >= Open[1+barshift_D1[i]]) // M1前根收 >= D1前根开 { Buffer1[i] = Low[i]; // 多头缓冲写在K线最低价处 } else { Buffer1[i] = EMPTY_VALUE; // 不满足就留空不画 } 把这段反过来写便是空头分支,再和均线交叉句柄 MA_handle / MA_handle2 串起来,约束就无缝嵌进指标了。
if(Close[class="num">1+barshift_M1[i]] >= Open[class="num">1+barshift_D1[i]] class=class="str">"cmt">//Candlestick Close >= Candlestick Open ) { Buffer1[i] = Low[i]; class=class="str">"cmt">//Set indicator value at Candlestick Low } else { Buffer1[i] = EMPTY_VALUE; }
双箭头指标初始化与均线句柄绑定
在 MT5 自定义指标里,OnInit 决定两件事:画图缓冲怎么挂,以及外部指标句柄怎么拿。下面这段把 Buffer1、Buffer2 分别绑到索引 0 和 1,并用 PlotIndexSetInteger 把箭头符号设成 241 和 242——也就是向上 / 向下小三角,肉眼区分多空信号比纯线形更直接。
class=class="str">"cmt">// Custom indicator initialization function class="type">int OnInit() { SetIndexBuffer(class="num">0, Buffer1); PlotIndexSetDouble(class="num">0, PLOT_EMPTY_VALUE, EMPTY_VALUE); PlotIndexSetInteger(class="num">0, PLOT_DRAW_BEGIN, MathMax(Bars(Symbol(), PERIOD_CURRENT)-PLOT_MAXIMUM_BARS_BACK+class="num">1, OMIT_OLDEST_BARS+class="num">1)); PlotIndexSetInteger(class="num">0, PLOT_ARROW, class="num">241); SetIndexBuffer(class="num">1, Buffer2); PlotIndexSetDouble(class="num">1, PLOT_EMPTY_VALUE, EMPTY_VALUE); PlotIndexSetInteger(class="num">1, PLOT_DRAW_BEGIN, MathMax(Bars(Symbol(), PERIOD_CURRENT)-PLOT_MAXIMUM_BARS_BACK+class="num">1, OMIT_OLDEST_BARS+class="num">1)); PlotIndexSetInteger(class="num">1, PLOT_ARROW, class="num">242); class=class="str">"cmt">//initialize myPoint myPoint = Point(); if(Digits() == class="num">5 || Digits() == class="num">3) { myPoint *= class="num">10; } MA_handle = iMA(NULL, PERIOD_CURRENT, class="num">7, class="num">0, MODE_EMA, PRICE_CLOSE); if(MA_handle < class="num">0) { Print("The creation of iMA has failed: MA_handle=", INVALID_HANDLE); Print("Runtime error = ", GetLastError()); class="kw">return(INIT_FAILED); } MA_handle2 = iMA(NULL, PERIOD_CURRENT, class="num">21, class="num">0, MODE_EMA, PRICE_CLOSE); if(MA_handle2 < class="num">0) { Print("The creation of iMA has failed: MA_handle2=", INVALID_HANDLE); Print("Runtime error = ", GetLastError()); class="kw">return(INIT_FAILED); } class="kw">return(INIT_SUCCEEDED); } class=class="str">"cmt">// Custom indicator iteration function 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">class="kw">datetime& time[], class="kw">const class="type">class="kw">double& open[], class="kw">const class="type">class="kw">double& high[], class="kw">const class="type">class="kw">double& low[], class="kw">const class="type">class="kw">double& close[],
class="type">int OnInit() { SetIndexBuffer(class="num">0, Buffer1); PlotIndexSetDouble(class="num">0, PLOT_EMPTY_VALUE, EMPTY_VALUE); PlotIndexSetInteger(class="num">0, PLOT_DRAW_BEGIN, MathMax(Bars(Symbol(), PERIOD_CURRENT)-PLOT_MAXIMUM_BARS_BACK+class="num">1, OMIT_OLDEST_BARS+class="num">1)); PlotIndexSetInteger(class="num">0, PLOT_ARROW, class="num">241); SetIndexBuffer(class="num">1, Buffer2); PlotIndexSetDouble(class="num">1, PLOT_EMPTY_VALUE, EMPTY_VALUE); PlotIndexSetInteger(class="num">1, PLOT_DRAW_BEGIN, MathMax(Bars(Symbol(), PERIOD_CURRENT)-PLOT_MAXIMUM_BARS_BACK+class="num">1, OMIT_OLDEST_BARS+class="num">1)); PlotIndexSetInteger(class="num">1, PLOT_ARROW, class="num">242); myPoint = Point(); if(Digits() == class="num">5 || Digits() == class="num">3) { myPoint *= class="num">10; } MA_handle = iMA(NULL, PERIOD_CURRENT, class="num">7, class="num">0, MODE_EMA, PRICE_CLOSE); if(MA_handle < class="num">0) { Print("The creation of iMA has failed: MA_handle=", INVALID_HANDLE); Print("Runtime error = ", GetLastError()); class="kw">return(INIT_FAILED); } MA_handle2 = iMA(NULL, PERIOD_CURRENT, class="num">21, class="num">0, MODE_EMA, PRICE_CLOSE); if(MA_handle2 < class="num">0) { Print("The creation of iMA has failed: MA_handle2=", INVALID_HANDLE); Print("Runtime error = ", GetLastError()); class="kw">return(INIT_FAILED); } class="kw">return(INIT_SUCCEEDED); } 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">class="kw">datetime& time[], class="kw">const class="type">class="kw">double& open[], class="kw">const class="type">class="kw">double& high[], class="kw">const class="type">class="kw">double& low[], class="kw">const class="type">class="kw">double& close[],
「跨周期数组与时间轴对齐的坑」
在多周期指标里,当前图表的 K 线时间和 M1、D1 的 K 线索引并不是天然对齐的。上面这段 OnCalculate 开头先用 CopyTime 把当前周期所有 K 线时间拉进 TimeShift 数组,并设成时间序列(ArraySetAsSeries(...,true)),再用 iBarShift 逐个把当前 bar 时间映射到 M1 和 D1 的 bar 下标,存进 barshift_M1 / barshift_D1。 如果跳过这步直接按索引取其他周期数据,会出现时间错位——比如拿当前 H1 的第 i 根去读 M1 的第 i 根,实际对应的是完全不同钟点的行情。外汇和贵金属跨周期回测中这种错位会让信号延迟甚至反向,属于高风险隐蔽 bug。 随后代码对 MA、MA2、Close、Close2、Low、High 全部走 CopyBuffer / CopyClose / CopyLow / CopyHigh,且每一个都立刻 ArraySetAsSeries 成倒序。rates_total 可能到几万根,任何一次 Copy 返回 <=0 就直接 return,避免数组越界把终端卡死。开 MT5 把这段贴进自定义指标,故意改错一个周期参数,能看到指标直接不画线而非乱画。
class="kw">const class="type">long& tick_volume[], class="kw">const class="type">long& volume[], class="kw">const class="type">int& spread[]) { class="type">int limit = rates_total - prev_calculated; class=class="str">"cmt">//--- counting from class="num">0 to rates_total ArraySetAsSeries(Buffer1, true); ArraySetAsSeries(Buffer2, true); class=class="str">"cmt">//--- initial zero if(prev_calculated < class="num">1) { ArrayInitialize(Buffer1, EMPTY_VALUE); ArrayInitialize(Buffer2, EMPTY_VALUE); } else limit++; class="type">class="kw">datetime TimeShift[]; if(CopyTime(Symbol(), PERIOD_CURRENT, class="num">0, rates_total, TimeShift) <= class="num">0) class="kw">return(rates_total); ArraySetAsSeries(TimeShift, true); class="type">int barshift_M1[]; ArrayResize(barshift_M1, rates_total); class="type">int barshift_D1[]; ArrayResize(barshift_D1, rates_total); for(class="type">int i = class="num">0; i < rates_total; i++) { barshift_M1[i] = iBarShift(Symbol(), PERIOD_M1, TimeShift[i]); barshift_D1[i] = iBarShift(Symbol(), PERIOD_D1, TimeShift[i]); } if(BarsCalculated(MA_handle) <= class="num">0) class="kw">return(class="num">0); if(CopyBuffer(MA_handle, class="num">0, class="num">0, rates_total, MA) <= class="num">0) class="kw">return(rates_total); ArraySetAsSeries(MA, true); if(BarsCalculated(MA_handle2) <= class="num">0) class="kw">return(class="num">0); if(CopyBuffer(MA_handle2, class="num">0, class="num">0, rates_total, MA2) <= class="num">0) class="kw">return(rates_total); ArraySetAsSeries(MA2, true); if(CopyClose(Symbol(), PERIOD_M1, class="num">0, rates_total, Close) <= class="num">0) class="kw">return(rates_total); ArraySetAsSeries(Close, true); if(CopyClose(Symbol(), PERIOD_D1, class="num">0, rates_total, Close2) <= class="num">0) class="kw">return(rates_total); ArraySetAsSeries(Close2, true); if(CopyLow(Symbol(), PERIOD_CURRENT, class="num">0, rates_total, Low) <= class="num">0) class="kw">return(rates_total); ArraySetAsSeries(Low, true); if(CopyHigh(Symbol(), PERIOD_CURRENT, class="num">0, rates_total, High) <= class="num">0) class="kw">return(rates_total); ArraySetAsSeries(High, true); class=class="str">"cmt">//--- main loop for(class="type">int i = limit-class="num">1; i >= class="num">0; i--) {
◍ 多周期均线交叉的信号落点写法
这段循环收尾逻辑直接决定指标会不会在老数据上崩。先用 MathMin(PLOT_MAXIMUM_BARS_BACK-1, rates_total-1-OMIT_OLDEST_BARS) 掐掉最旧的一部分 K 线,跳过它们能避免 Array out of range,也少算无用功。 紧接着两道边界检查:barshift_M1[i] 和 barshift_D1[i] 只要落在区间 [0, rates_total) 之外就 continue。M1 与 D1 的偏移量若越界,后面取 Close[1+barshift_M1[i]] 必然报错,这两行是多线程周期对齐的保险丝。 信号本身分两个缓冲:Buffer1 在快线 MA 上穿慢线 MA2、且前一根 M1 收盘 ≥ 对应 D1 收盘时,记到当前 Low;否则写 EMPTY_VALUE。Buffer2 反过来,死叉且 M1 收盘 ≤ D1 收盘时记到 High。 返回 rates_total 告诉终端已处理完所有可得柱。外汇与贵金属波动跳空频繁,多周期偏移在重大数据夜可能瞬间越界,上 MT5 把 OMIT_OLDEST_BARS 调小到 100 以内可直观看老数据跳过效果。
if (i >= MathMin(PLOT_MAXIMUM_BARS_BACK-class="num">1, rates_total-class="num">1-OMIT_OLDEST_BARS)) class="kw">continue; class=class="str">"cmt">//omit some old rates to prevent "Array out of range" or slow calculation if(barshift_M1[i] < class="num">0 || barshift_M1[i] >= rates_total) class="kw">continue; if(barshift_D1[i] < class="num">0 || barshift_D1[i] >= rates_total) class="kw">continue; class=class="str">"cmt">//Indicator Buffer class="num">1 if(MA[i] > MA2[i] && MA[i+class="num">1] < MA2[i+class="num">1] class=class="str">"cmt">//Moving Average crosses above Moving Average && Close[class="num">1+barshift_M1[i]] >= Close2[class="num">1+barshift_D1[i]] class=class="str">"cmt">//Candlestick Close >= Candlestick Close ) { Buffer1[i] = Low[i]; class=class="str">"cmt">//Set indicator value at Candlestick Low } else { Buffer1[i] = EMPTY_VALUE; } class=class="str">"cmt">//Indicator Buffer class="num">2 if(MA[i] < MA2[i] && MA[i+class="num">1] > MA2[i+class="num">1] class=class="str">"cmt">//Moving Average crosses below Moving Average && Close[class="num">1+barshift_M1[i]] <= Close2[class="num">1+barshift_D1[i]] class=class="str">"cmt">//Candlestick Close <= Candlestick Close ) { Buffer2[i] = High[i]; class=class="str">"cmt">//Set indicator value at Candlestick High } else { Buffer2[i] = EMPTY_VALUE; } } class="kw">return(rates_total); }