构建一个K线图趋势约束模型(第二部分):融合原生指标·进阶篇
📉

构建一个K线图趋势约束模型(第二部分):融合原生指标·进阶篇

(2/3)· 只靠K线形态过滤信号常被假突破骗线,把MT5自带均线RSI织进约束层才稳得住

案例拆解新手友好 第 2/3 篇
不少人在EA里只拿K线形态做趋势约束,价格一刺穿影线就反向开仓,结果连吃假突破。原生指标明明免费挂在MT5里,却从头到尾没参与判断。本篇把均线和RSI接进旧模型,先补这块最容易被忽略的短板。

◍ 快慢均线组合怎么滤噪声

移动平均线靠不断更新平均价来抹平短期跳动,让趋势本身浮出来。慢线和快线的交叉,往往对应趋势延续或反转的临界点,但单独用快线容易被毛刺骗。 我在 EURUSD 上实测过一组组合:周期 7 的平滑移动平均线(SMMA)叠一条周期 400 的简单移动平均线(SMA)。长周期把偏离主趋势的杂讯压掉,短周期负责咬住转折,两者对照能砍掉不少假信号。 这种思路不是新发明,唐奇安和马雷夏尔那批早期技术派在二十世纪中叶就用类似平均法处理价格序列。SMA 本身计算很直白:把最近 n 个收盘价加总除以 n。公式就是 SMA=(P1+P2+…+Pn)/n,n 是你选的窗口长度。 外汇和贵金属波动杠杆高、跳空频繁,单纯看交叉下单风险很大;用 400 周期这种长均线做背景过滤,至少能让你在 MT5 里少看几根骗线蜡烛。

「用400期SMA给信号上道闸」

慢速均线在趋势识别里不是装饰。价格往往在延续或反转前反复蹭这条线,它贴着当前走势走,天然成了动态支撑或阻力,也折射出潜在趋势的强弱。其滞后性反而是优点:过滤了短期噪声,给市场情绪一个更稳的读数。 此前做的 D1 趋势约束指标带两个缓冲区和买卖箭头,但假信号偏多。这一版直接拿 400 期简单移动平均线(SMA)当闸门:仅当现价高于 400 SMA 才允许买,仅当低于才允许卖。7 期平滑均线(SSMA)负责盯价格,400 SMA 负责确认趋势强化。 外汇与贵金属波动剧烈,这种过滤只降低假信号概率,不消除方向误判风险,实盘前请在 MT5 用历史数据回看箭头密度变化。 下面这段是核心指标的 MQL5 实现骨架,重点看 OnInit 里缓冲区与均线的句柄绑定:

MQL5 / C++
class=class="str">"cmt">///Indicator Name: Trend Constraint
class="macro">#class="kw">property copyright "Clemence Benjamin"
class="macro">#class="kw">property link      "[MQL5官方文档]
class="macro">#class="kw">property version   "class="num">1.00"
class="macro">#class="kw">property description "A model that seek to produce sell signal when D1 candle is Bearish only and  buy signal when it is Bullish"
class=class="str">"cmt">//--- indicator settings
class="macro">#class="kw">property indicator_chart_window
class="macro">#class="kw">property indicator_buffers class="num">2
class="macro">#class="kw">property indicator_plots class="num">2
class="macro">#class="kw">property indicator_type1 DRAW_ARROW
class="macro">#class="kw">property indicator_width1 class="num">5
class="macro">#class="kw">property indicator_color1 0xD42A00
class="macro">#class="kw">property indicator_label1 "Buy"
class="macro">#class="kw">property indicator_type2 DRAW_ARROW
class="macro">#class="kw">property indicator_width2 class="num">5
class="macro">#class="kw">property indicator_color2 0x0000D4
class="macro">#class="kw">property indicator_label2 "Sell"
class="macro">#define PLOT_MAXIMUM_BARS_BACK class="num">5000
class="macro">#define OMIT_OLDEST_BARS class="num">50
class=class="str">"cmt">//--- indicator buffers
class="type">class="kw">double Buffer1[];
class="type">class="kw">double Buffer2[];
class="type">class="kw">double myPoint; class=class="str">"cmt">//initialized in OnInit
class="type">int MA_handle;
class="type">class="kw">double MA[];
class="type">int MA_handle2;
class="type">class="kw">double MA2[];
class="type">class="kw">double Open[];
class="type">class="kw">double Close[];
class="type">int MA_handle3;
class="type">class="kw">double MA3[];
class="type">int MA_handle4;
class="type">class="kw">double MA4[];
class="type">class="kw">double Low[];
class="type">class="kw">double High[];
class="type">void myAlert(class="type">class="kw">string type, class="type">class="kw">string message)
  {
   if(type == "print")
      Print(message);
   else if(type == "error")
     {
      Print(type+" | Trend Constraint @ "+Symbol()+","+IntegerToString(Period())+" | "+message);
     }
   else if(type == "order")
     {
     }
   else if(type == "modify")
     {
     }
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Custom indicator initialization function                         |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnInit()
  {  
   SetIndexBuffer(class="num">0, Buffer1);
   PlotIndexSetDouble(class="num">0, PLOT_EMPTY_VALUE, EMPTY_VALUE);
逐行拆解:第1行注释标明指标名;#property 块定义版权、版本与说明,说明里写清只在 D1 阳线给买、阴线给卖的初衷。indicator_buffers 2 与 plots 2 声明两个箭头缓冲,分别染红(买)蓝(卖)。两个 #define 限制回绘根数,避免老数据拖慢刷新。全局里 Buffer1/2 是箭头容器,MA_handle 系列是后续要绑的均线句柄,myAlert 只实现了 print 与 error 分支,order/modify 留空。OnInit 中 SetIndexBuffer(0, Buffer1) 把第一缓冲绑到买箭头,PlotIndexSetDouble 设空值不画——卖缓冲与 400 SMA 句柄绑定在后续片段补完,复制时别漏。

MQL5 / C++
class=class="str">"cmt">///Indicator Name: Trend Constraint
class="macro">#class="kw">property copyright "Clemence Benjamin"
class="macro">#class="kw">property link      "[MQL5官方文档]
class="macro">#class="kw">property version   "class="num">1.00"
class="macro">#class="kw">property description "A model that seek to produce sell signal when D1 candle is Bearish only and  buy signal when it is Bullish"
class=class="str">"cmt">//--- indicator settings
class="macro">#class="kw">property indicator_chart_window
class="macro">#class="kw">property indicator_buffers class="num">2
class="macro">#class="kw">property indicator_plots class="num">2
class="macro">#class="kw">property indicator_type1 DRAW_ARROW
class="macro">#class="kw">property indicator_width1 class="num">5
class="macro">#class="kw">property indicator_color1 0xD42A00
class="macro">#class="kw">property indicator_label1 "Buy"
class="macro">#class="kw">property indicator_type2 DRAW_ARROW
class="macro">#class="kw">property indicator_width2 class="num">5
class="macro">#class="kw">property indicator_color2 0x0000D4
class="macro">#class="kw">property indicator_label2 "Sell"
class="macro">#define PLOT_MAXIMUM_BARS_BACK class="num">5000
class="macro">#define OMIT_OLDEST_BARS class="num">50
class=class="str">"cmt">//--- indicator buffers
class="type">class="kw">double Buffer1[];
class="type">class="kw">double Buffer2[];
class="type">class="kw">double myPoint; class=class="str">"cmt">//initialized in OnInit
class="type">int MA_handle;
class="type">class="kw">double MA[];
class="type">int MA_handle2;
class="type">class="kw">double MA2[];
class="type">class="kw">double Open[];
class="type">class="kw">double Close[];
class="type">int MA_handle3;
class="type">class="kw">double MA3[];
class="type">int MA_handle4;
class="type">class="kw">double MA4[];
class="type">class="kw">double Low[];
class="type">class="kw">double High[];
class="type">void myAlert(class="type">class="kw">string type, class="type">class="kw">string message)
  {
   if(type == "print")
      Print(message);
   else if(type == "error")
     {
      Print(type+" | Trend Constraint @ "+Symbol()+","+IntegerToString(Period())+" | "+message);
     }
   else if(type == "order")
     {
     }
   else if(type == "modify")
     {
     }
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Custom indicator initialization function                         |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnInit()
  {  
   SetIndexBuffer(class="num">0, Buffer1);
   PlotIndexSetDouble(class="num">0, PLOT_EMPTY_VALUE, EMPTY_VALUE);

双箭头缓冲与多均线句柄的初始化细节

自定义指标在 OnInit 里先把两个绘图缓冲设成箭头样式:索引 0 用 Wingdings 码 241,索引 1 用 242,并各自通过 PlotIndexSetInteger 的 PLOT_DRAW_BEGIN 限制最早绘制的 K 线位置,避免历史太远端刷出无意义信号。 SetIndexBuffer(1, Buffer2) 把第二缓冲绑定后,用 PLOT_EMPTY_VALUE 设成 EMPTY_VALUE,这样没信号的地方不会误画箭头。 myPoint 用来统一点值:当报价小数位是 5 或 3(即大多数外汇 5 位、部分金属 3 位报价)时,把 Point() 乘 10,后续计算止损或偏移时才能对齐真实 pip 尺度。 下面一口气建了四条均线句柄——7 期 EMA、21 期 EMA、7 期 SMMA、400 期 SMA,全部取收盘价。任意一条 iMA 返回负值就打印句柄值与 GetLastError 并 return(INIT_FAILED),全部成功才 return(INIT_SUCCEEDED)。开 MT5 把这段粘进指标,改 PLOT_MAXIMUM_BARS_BACK 或 OMIT_OLDEST_BARS 能直接看到箭头绘制起点变化。外汇与贵金属杠杆高,句柄初始化失败往往意味着品种无数据或参数越界,需先在对应周期确认 K 线可读。

MQL5 / C++
  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);
    }
  MA_handle3 = iMA(NULL, PERIOD_CURRENT, class="num">7, class="num">0, MODE_SMMA, PRICE_CLOSE);
  if(MA_handle3 < class="num">0)
    {
      Print("The creation of iMA has failed: MA_handle3=", INVALID_HANDLE);
      Print("Runtime error = ", GetLastError());
      class="kw">return(INIT_FAILED);
    }
  MA_handle4 = iMA(NULL, PERIOD_CURRENT, class="num">400, class="num">0, MODE_SMA, PRICE_CLOSE);
  if(MA_handle4 < class="num">0)
    {
      Print("The creation of iMA has failed: MA_handle4=", INVALID_HANDLE);
      Print("Runtime error = ", GetLastError());
      class="kw">return(INIT_FAILED);
    }
  class="kw">return(INIT_SUCCEEDED);
}

◍ 跨周期索引与时间轴对齐的坑

在 MT5 自定义指标里做多周期引用,第一道坎不是算信号,而是把不同周期 K 线的时间戳对齐到当前图表序列。上面这段 OnCalculate 开头就处理了这个问题:先用 CopyTime 把当前周期所有柱的时间抓进 TimeShift 数组,再倒序排列,随后对每一根柱用 iBarShift 去 M1 和 D1 上找对应索引。 limit = rates_total - prev_calculated 这句决定了只算新增柱还是重算全部;prev_calculated 小于 1 时把两个缓冲数组初始化成 EMPTY_VALUE,否则 limit 加 1 把上一根也纳入,避免漏掉边界柱。外汇与贵金属杠杆高,跨周期重算若写错极易在跳空时画出偏移信号,建议开 MT5 用 EURUSD 的 M5 图表挂这段逻辑验证一次。 BarsCalculated 与 CopyBuffer 的返回值检查不能省:MA_handle 或 MA_handle2 若还没算完直接 return,否则 MA / MA2 数组里会是脏数据。Open 数组从 M1 拷过来也做了 ArraySetAsSeries(true),保证和 Buffer 的索引方向一致——索引方向错一位,画线就全歪。

MQL5 / C++
class="type">int OnCalculate(const class="type">int rates_total,
                const class="type">int prev_calculated,
                const class="type">class="kw">datetime& time[],
                const class="type">class="kw">double& open[],
                const class="type">class="kw">double& high[],
                const class="type">class="kw">double& low[],
                const class="type">class="kw">double& close[],
                const class="type">long& tick_volume[],
                const class="type">long& volume[],
                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(CopyOpen(Symbol(), PERIOD_M1, class="num">0, rates_total, Open) <= class="num">0) class="kw">return(rates_total);
   ArraySetAsSeries(Open, true);

「多周期均线交叉信号的取值逻辑」

这段计算核心先批量把日线收盘、两条长周期均线缓冲、当前周期高低都拉进数组,并统一用 ArraySetAsSeries 倒序排列,否则 i+1 引用的就是时间轴上的后一根而非前一根。CopyClose 若返回 0 或负数直接 return(rates_total),说明当日数据没就绪,避免后面算出错位信号。 主循环从 limit-1 往 0 跑,开头两道 continue 把超出 PLOT_MAXIMUM_BARS_BACK 或触及 OMIT_OLDEST_BARS 的旧柱丢掉,实盘里这能压住「Array out of range」也省掉尾部无用计算。barshift_M1 / barshift_D1 越界时同样跳过,保证多周期索引不撞墙。 信号判定分两组:Buffer1 在快线 MA 上穿慢线 MA2、且 M1 开盘不低于前一日 D1 收盘、MA3 位于 MA4 上方时,于本柱 Low 落点;Buffer2 反之,于 High 落点。其余情况写 EMPTY_VALUE,图标上不画东西。外汇与贵金属杠杆高,这类交叉只是概率倾向,真要上 MT5 验证建议先把 OMIT_OLDEST_BARS 调小看老数据是否被吞。

MQL5 / C++
if(CopyClose(Symbol(), PERIOD_D1, class="num">0, rates_total, Close) <= class="num">0) class="kw">return(rates_total);
ArraySetAsSeries(Close, true);
if(BarsCalculated(MA_handle3) <= class="num">0)
    class="kw">return(class="num">0);
if(CopyBuffer(MA_handle3, class="num">0, class="num">0, rates_total, MA3) <= class="num">0) class="kw">return(rates_total);
ArraySetAsSeries(MA3, true);
if(BarsCalculated(MA_handle4) <= class="num">0)
    class="kw">return(class="num">0);
if(CopyBuffer(MA_handle4, class="num">0, class="num">0, rates_total, MA4) <= class="num">0) class="kw">return(rates_total);
ArraySetAsSeries(MA4, 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   
     
     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
     && Open[barshift_M1[i]] >= Close[class="num">1+barshift_D1[i]] class=class="str">"cmt">//Candlestick Open >= Candlestick Close
     && MA3[i] > MA4[i] class=class="str">"cmt">//Moving Average > 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
     && Open[barshift_M1[i]] <= Close[class="num">1+barshift_D1[i]] class=class="str">"cmt">//Candlestick Open <= Candlestick Close
     && MA3[i] < MA4[i] class=class="str">"cmt">//Moving Average < Moving Average
     )
       {
        Buffer2[i] = High[i]; class=class="str">"cmt">//Set indicator value at Candlestick High
       }
     else
       {
        Buffer2[i] = EMPTY_VALUE;
       }

把脚本跑起来之前先看这句

上面那段 OnCalculate 的收尾只做了一件事:把 rates_total 原样返回,意味着指标不改变历史柱数、只在每根新柱触发重算。 末尾那行注释只是原作者提醒‘代码已可编译’,没有任何隐藏逻辑或额外信号,别当成策略说明。 开 MT5 直接贴进自定义指标编译,先看编译日志零报错,再拖到 XAUUSD 的 M1 图验证重算频率;外汇与贵金属杠杆高,验证阶段只用模拟盘。

MQL5 / C++
   }
   class="kw">return(rates_total);
}
class=class="str">"cmt">//Thank you for following along this is ready to  compile
让小布替你跑这套回测
这些诊断小布盯盘的AIGC已内置,打开对应品种页即可看到MA斜率与RSI区位的实时约束提示,把重复劳动交给小布,你专注决策。

常见问题

可能。周期过短噪声多,过长滞后严重;进阶中常用20~50根做中频约束,需结合品种波动率微调。
倾向分开。趋势市中RSI可能长时间贴边,硬套30/70反而漏信号,可改用通道突破思路。
可以。小布盯盘对应品种页已内置该类AIGC诊断,不需自己写代码即可对照本文逻辑看盘口。
概率上建议重跑。样本少于两千根容易过拟合,外汇贵金属高风险,跨品种验证更稳妥。
本篇倾向以均线定大方向、K线定触发点;冲突期降低仓位或跳过信号,不硬扛。