用于 Renko 制图的指标·综合运用
🧱

用于 Renko 制图的指标·综合运用

(3/3)·从砖块生成算法到实盘 EA 挂载,前 two 篇没讲的工程细节都在这一篇收口

含代码示例 第 3/3 篇
很多人把 Renko 当普通离线图表直接喂给 EA,结果回测和实盘砖块对不上。忽略时间轴压缩带来的 tick 重构差异,信号会在切换品种时静默失效。

砖形图 bricks 循环里的向上突破判定

在自定义砖形(RENKO)指标里,砖块数量不是预先写死的,而是靠逐根 K 线价格去反推。上面这段核心循环从 bars 根历史数据里跑一遍,用 func_calc_dorstep 算出当前台阶 doorstep_now,再拿 price[z] 和上一砖的上沿 up_price_calc 做差,除以 _Point 得到跨过的点数 point_go。 当 (price[z]-up_price_calc)/_Point >= doorstep_now 时,说明价格向上越过了一个砖高。若此前砖型是上升(type_box_calc==1)或未知(==0),就进入第一个 for 循环,从 point_go 往下每次减 doorstep_now,每减一轮就 a++ 补一块向上砖,同时把 down_price_calc 设为旧上沿、up_price_calc 推高一个台阶。 若之前是下跌砖(type_box_calc==-1),则多一道 number_filter 拦截:只有 point_go/doorstep_now 大于等于该过滤值才允许翻砖,避免毛刺行情里频繁反向。外汇与贵金属波动大,这个过滤参数设太小,MT5 上容易刷出一堆噪音砖,实盘前建议先用历史数据跑一遍看砖密度。

MQL5 / C++
  class="type">class="kw">double doorstep)  class=class="str">"cmt">// step
  {
class=class="str">"cmt">//--- arrays reset
class=class="str">"cmt">//--- drawing buffer arrays
   ZeroMemory(RENKO_close);
   ZeroMemory(RENKO_color);
   ZeroMemory(RENKO_high);
   ZeroMemory(RENKO_low);
   ZeroMemory(RENKO_open);
class=class="str">"cmt">//--- additional variables
   class="type">int doorstep_now; class=class="str">"cmt">// current step
   class="type">int point_go;     class=class="str">"cmt">// passed points
class=class="str">"cmt">//--- additional variables for bricks number calculating
   a=class="num">0;
   class="type">class="kw">double up_price_calc=price[class="num">0];
   class="type">class="kw">double down_price_calc=price[class="num">0];
   class="type">char type_box_calc=class="num">0;
   for(class="type">int z=class="num">0; z<bars; z++) class=class="str">"cmt">//---> bricks calculating loop
     {
     class=class="str">"cmt">//--- calculate step according to the current price
     doorstep_now=func_calc_dorstep(price[z],type_doorstep,doorstep);
     class=class="str">"cmt">//--- if price rises
     if((price[z]-up_price_calc)/_Point>=doorstep_now)
       {
       class=class="str">"cmt">//--- calculate points passed
       point_go=class="type">int((price[z]-up_price_calc)/_Point);
       class=class="str">"cmt">//--- prices was rising or unknown price behavour
       if(type_box_calc==class="num">1 || type_box_calc==class="num">0)
         {
         for(class="type">int y=point_go; y>=doorstep_now; y-=doorstep_now)
           {
           class=class="str">"cmt">//--- add the next brick 
           a++;
           class=class="str">"cmt">//--- add value of the next brick low price
           down_price_calc=up_price_calc;
           class=class="str">"cmt">//--- add value of the next brick up price
           up_price_calc=down_price_calc+(doorstep_now*_Point);
           class=class="str">"cmt">//--- set the brick type(up)
           type_box_calc=class="num">1;
           }
         }
       class=class="str">"cmt">//--- price went down
       if(type_box_calc==-class="num">1)
         {
         if((point_go/doorstep_now)>=number_filter)
           {
           for(class="type">int y=point_go; y>=doorstep_now; y-=doorstep_now)
             {
             class=class="str">"cmt">//--- add the next brick
             a++;
             class=class="str">"cmt">//--- set the next brick down price
             down_price_calc=up_price_calc;
             class=class="str">"cmt">//--- set the next brick up price
             up_price_calc=down_price_calc+(doorstep_now*_Point);

◍ 砖形图下行方向的绘制逻辑

在砖形图(Renko)的自定义计算里,下行方向的判定依靠价格相对当前砖的下沿位移。当 (down_price_calc - price[z]) / _Point 超过当前砖步长 doorstep_now 时,说明价格向下走出了至少一块砖的距离,这时才进入新增砖块的处理。 如果上一块砖类型 type_box_calc 是 -1(下砖)或 0(未知方向),直接按 doorstep_now 为步长循环累加:每满一个步长就 a++ 生成新砖,并把下一块的 up_price_calc 设为当前 down_price_calc,down_price_calc 再减一个步长的点数,砖类型固定为 -1。 若前一块是上砖(type_box_calc==1),则多了层过滤:只有 point_go / doorstep_now 大于等于 number_filter 时才允许反向画下砖,避免毛刺行情里频繁翻转。这个 number_filter 是实战中压制假信号的关键参数,设在 2~3 之间比较常见。 循环结束后,用 Bars(_Symbol, PERIOD_CURRENT) 取当前图表 K 线数 b,并对 up_price、down_price 两个数组做 ArrayResize,保证存储砖价的坐标空间与显示条数对齐。外汇与贵金属杠杆高,砖形图仅过滤噪声,反转仍可能连续打损,需配合仓位控制。

MQL5 / C++
class=class="str">"cmt">//--- if the price moves downwards
if((down_price_calc-price[z])/_Point>=doorstep_now)
  {
   class=class="str">"cmt">//--- calculate the points passed
   point_go=class="type">int((down_price_calc-price[z])/_Point);
   class=class="str">"cmt">//--- if the price went downwards or the direction is unknown
   if(type_box_calc==-class="num">1 || type_box_calc==class="num">0)
     {
      for(class="type">int y=point_go; y>=doorstep_now; y-=doorstep_now)
        {
         class=class="str">"cmt">//--- add the next brick
         a++;
         class=class="str">"cmt">//--- set the next brick low price value
         up_price_calc=down_price_calc;
         class=class="str">"cmt">//--- set the next brick up price value
         down_price_calc=up_price_calc-(doorstep_now*_Point);
         class=class="str">"cmt">//--- set the britck type(up)
         type_box_calc=-class="num">1;
        }
     }
   class=class="str">"cmt">//--- the price moved upwards
   if(type_box_calc==class="num">1)
     {
      if((point_go/doorstep_now)>=number_filter)
        {
         for(class="type">int y=point_go; y>=doorstep_now; y-=doorstep_now)
           {
            class=class="str">"cmt">//--- add the next brick
            a++;
            class=class="str">"cmt">//--- set the next brick down price value
            up_price_calc=down_price_calc;
            class=class="str">"cmt">//--- set the next brick up price value
            down_price_calc=up_price_calc-(doorstep_now*_Point);
            class=class="str">"cmt">//--- set the brick type(up)
            type_box_calc=-class="num">1;
           }
        }
     }
   }
 } class=class="str">"cmt">//---< bricks calculate loop
class=class="str">"cmt">//--- calculate the number of display bars
  class="type">int b=Bars(_Symbol,PERIOD_CURRENT);
class=class="str">"cmt">//--- resize arrays
  ArrayResize(up_price,b);
  ArrayResize(down_price,b);

「砖形数组的初始化与重排逻辑」

在 MT5 自定义指标里画砖形(Renko 类)图形,第一步是把存储砖块属性的动态数组按可视砖数 b 重新开尺寸。type_box、time_box、shadow_up、shadow_down、number_id 这几个数组都靠 ArrayResize 对齐,否则后面写越界会直接报错退出了。 重新分配完内存,紧接着用 ZeroMemory 把 up_price、down_price 以及上述五个数组全部清成 0。这一步不是可有可无:若上次计算残留了旧砖价,新一根 K 线进来会套用错误基准,图形可能从中间裂开。 初始化时只给 0 号元素赋值——up_price[0] 和 down_price[0] 都等于传入的 price[0],type_box[0] 设为 0 表示未知方向砖。随后用 l=a-b 算差值,turn_cycle=l/(b-1) 与 turn_rest=MathMod(l,b-1)+2 决定图表放满后从第几块开始循环覆盖,属于「砖数多于图表容量」时的滚动算法。 主循环前有一句 Alert:当 a>b 会弹「More bricks than can be placed on the chart, the step is small」。这实际在提醒你步长设太小了,砖块生成速度远超可视量,外汇与贵金属市场跳空频繁,这种参数组合在高波动时段可能让历史砖被快速冲掉,调参时建议先开 MT5 用 EURUSD 的 M5 跑一遍看覆盖节奏。

MQL5 / C++
  ArrayResize(type_box,b);
  ArrayResize(time_box,b);
  ArrayResize(shadow_up,b);
  ArrayResize(shadow_down,b);
  ArrayResize(number_id,b);
class=class="str">"cmt">//--- resize calculation buffers array
  ZeroMemory(up_price);
  ZeroMemory(down_price);
  ZeroMemory(type_box);
  ZeroMemory(time_box);
  ZeroMemory(shadow_up);
  ZeroMemory(shadow_down);
  ZeroMemory(number_id);
class=class="str">"cmt">//--- fill arrays with the initial values
  up_price[class="num">0]=price[class="num">0];
  down_price[class="num">0]=price[class="num">0];
  type_box[class="num">0]=class="num">0;
class=class="str">"cmt">//--- calculate odd bricks number
  class="type">int l=a-b;
  class="type">int turn_cycle=l/(b-class="num">1);
  class="type">int turn_rest=(class="type">int)MathMod(l,(b-class="num">1))+class="num">2;
  class="type">int turn_var=class="num">0;
class=class="str">"cmt">//--- message of partially displayed bricks
  if(a>b)Alert("More bricks than can be placed on the chart, the step is small");
  a=class="num">0; class=class="str">"cmt">//--- reset bricks claculating variable
  for(class="type">int z=class="num">0; z<bars; z++) class=class="str">"cmt">//---> Main loop
    {
    class=class="str">"cmt">//--- calculate the step according to the price
    doorstep_now=func_calc_dorstep(price[z],type_doorstep,doorstep);
    class=class="str">"cmt">//---if the price moves upwards
    if((price[z]-up_price[a])/_Point>=doorstep_now)
      {
      class=class="str">"cmt">//--- calculate the points passed
point_go=class="type">int((price[z]-up_price[a])/_Point);
      class=class="str">"cmt">//--- price moved upwards or its behavour is unknown
      if(type_box[a]==class="num">1 || type_box[a]==class="num">0)
        {
        for(class="type">int y=point_go; y>=doorstep_now; y-=doorstep_now)
          {
          a++; class=class="str">"cmt">//--- add the next brick
          if((a==b && turn_var<turn_cycle) || (turn_var==turn_cycle && turn_rest==a))
            {
            up_price[class="num">0]=up_price[a-class="num">1];
            a=class="num">1;            class=class="str">"cmt">// bricks calculator reset
            turn_var++; class=class="str">"cmt">// calculator of loops reset
            }
          class=class="str">"cmt">//--- the next brick low price value
          down_price[a]=up_price[a-class="num">1];
          class=class="str">"cmt">//--- set the brick up price 
          up_price[a]=down_price[a]+(doorstep_now*_Point);
          class=class="str">"cmt">//--- set the up shadow value
          if(shadow_print==true) shadow_up[a]=price[z]; class=class="str">"cmt">//to the upper price level

下行砖块的递推与循环复位

当上一块砖类型被标记为 -1(下行)且累计波动 point_go 达到过滤阈值 number_filter 倍门阶时,引擎按 doorstep_now 步长向下切砖。 循环里每切一块 a 自增,新的 down_price[a] 直接承接前一块的 up_price[a-1],而 up_price[a] 再叠 doorstep_now*_Point,保证砖体高度严格等于当前门阶。 若满足 (a==b 且 turn_var<turn_cycle) 或 (turn_var==turn_cycle 且 turn_rest==a),计数器 a 被重置为 1、turn_var 加一,意味着完成一个转向周期后重新计数,避免在长单边行情里数组越界。 影子处理上,shadow_print 为 true 时 shadow_up[a] 取实际价 price[z],否则退回砖体上线 up_price[a];shadow_down[a] 恒等于砖体下线。外汇与贵金属波动剧烈,门阶与 number_filter 设错会让砖形严重失真,建议在 MT5 用 EURUSD 的 M5 先跑一遍观察重置逻辑。

MQL5 / C++
else shadow_up[a]=up_price[a]; class=class="str">"cmt">// to the up price level
class=class="str">"cmt">//--- set the low price value(to the brick price level)
shadow_down[a]=down_price[a];
class=class="str">"cmt">//--- value of the brick closing time
time_box[a]=(class="type">class="kw">datetime)Date[z];
class=class="str">"cmt">//--- set the brick type(up)
type_box[a]=class="num">1;
class=class="str">"cmt">//--- set the index
number_id[a]=z;
}
}
class=class="str">"cmt">//--- the price moved downwards
if(type_box[a]==-class="num">1)
{
 if((point_go/doorstep_now)>=number_filter)
  {
   for(class="type">int y=point_go; y>=doorstep_now; y-=doorstep_now)
    {
     a++; class=class="str">"cmt">//--- add the next brick
     if((a==b && turn_var<turn_cycle) || (turn_var==turn_cycle && turn_rest==a))
      {
       up_price[class="num">0]=up_price[a-class="num">1];
       a=class="num">1; class=class="str">"cmt">// bricks counter reset
       turn_var++; class=class="str">"cmt">// loops reset cycle
      }
     class=class="str">"cmt">//--- set the next brick low price value
     down_price[a]=up_price[a-class="num">1];
     class=class="str">"cmt">//--- set the next brick up price
     up_price[a]=down_price[a]+(doorstep_now*_Point);
     class=class="str">"cmt">//--- set the up shadow value
     if(shadow_print==true) shadow_up[a]=price[z]; class=class="str">"cmt">// at the up price level
     else shadow_up[a]=up_price[a]; class=class="str">"cmt">// the brick up price level
     class=class="str">"cmt">//--- set of the down price value(the brick price level)
     shadow_down[a]=down_price[a];
     class=class="str">"cmt">//--- set the close time
     time_box[a]=(class="type">class="kw">datetime)Date[z];
     class=class="str">"cmt">//--- set the up brick

◍ 砖形图向下突破的逐砖递推逻辑

这段逻辑处理的是价格相对前一根砖向下脱离阈值时的砖块生成。先算出行情越过当前砖下沿多少点:point_go = (down_price[a]-price[z]) / _Point,若结果大于等于 doorstep_now(当前砖宽),才进入向下补砖循环。 循环里每跨过一个 doorstep_now 就 a++ 新增一块砖,并把 up_price[a] 设为上一块的下沿,down_price[a] 再减一个砖宽。这里 type_box[a]==-1 或 0 表示原方向非向上,才允许向下叠砖;若 type_box[a]==1 则走另一分支,用 number_filter 过滤微小反向波动。 重置条件藏在 a==b 且 turn_var<turn_cycle,或 turn_var==turn_cycle 且 turn_rest==a 时:把 down_price[0] 接上末尾下沿、a 归 1、turn_var 自增,相当于完成一次序列折叠。外汇与贵金属波动跳点频繁,砖宽 _Point 与 doorstep_now 设错会让重绘砖数爆炸,建议先在 EURUSD 的 M1 上把 doorstep_now 设为 10 点跑一遍。 shadow_print 开关决定影线取真实最低价还是砖体下沿,关掉时 shadow_down[a]=down_price[a],开着则记录 price[z] 最后一笔低价,回测拐点时有用。

MQL5 / C++
type_box[a]=class="num">1;
class=class="str">"cmt">//--- set index
number_id[a]=z;
}
}
}
class=class="str">"cmt">//--- if price moves upwards
if((down_price[a]-price[z])/_Point>=doorstep_now)
{
 class=class="str">"cmt">//--- calculate the points passed
 point_go=class="type">int((down_price[a]-price[z])/_Point);
 class=class="str">"cmt">//--- price moved downwards or the direction is unknown
 if(type_box[a]==-class="num">1 || type_box[a]==class="num">0)
  {
   for(class="type">int y=point_go; y>=doorstep_now; y-=doorstep_now)
    {
     a++; class=class="str">"cmt">//--- add the next brick
     if((a==b && turn_var<turn_cycle) || (turn_var==turn_cycle && turn_rest==a))
      {
       down_price[class="num">0]=down_price[a-class="num">1];
       a=class="num">1;       class=class="str">"cmt">// set the bricks counter to zero
       turn_var++; class=class="str">"cmt">// reset loop counter
      }
     class=class="str">"cmt">//--- set the next brick down price
     up_price[a]=down_price[a-class="num">1];
     class=class="str">"cmt">//--- set the next brick up price
     down_price[a]=up_price[a]-(doorstep_now*_Point);
     class=class="str">"cmt">//--- set the down shadow value 
     if(shadow_print==true) shadow_down[a]=price[z]; class=class="str">"cmt">//--- the last lowest price level
     else shadow_down[a]=down_price[a];              class=class="str">"cmt">//--- low price level
     class=class="str">"cmt">//--- set the up price value
     shadow_up[a]=up_price[a];
     class=class="str">"cmt">//--- set the brick close time
     time_box[a]=set the down shadow value];
     class=class="str">"cmt">//--- set the brick type(down)
     type_box[a]=-class="num">1;
     class=class="str">"cmt">//--- set index
     number_id[a]=z;
    }
  }
 class=class="str">"cmt">//--- price moved upwards
 if(type_box[a]==class="num">1)
  {
   if((point_go/doorstep_now)>=number_filter)

「砖形图下行砖的循环堆砌逻辑」

这段循环负责在价格反向穿越后,按步长 doorstep_now 逐格向下补画砖块。外层 for 从 point_go 递减到 doorstep_now,每走一步 a 自增,代表新叠一块砖。 当满足条件 (a==b 且 turn_var<turn_cycle) 或 (turn_var==turn_cycle 且 turn_rest==a) 时,把 down_price[0] 回退到前一块下沿,a 重置为 1,turn_var 加一,相当于完成了一次反向砖列的轮回计数。 每块下行砖的上下沿这样算:up_price[a] 取前一块下沿,down_price[a] 再减 doorstep_now*_Point;阴影线若开启 shadow_print 则锚在最低价 price[z],否则贴着下沿。砖的类型写死 type_box[a]=-1,时间截 Date[z],编号 number_id[a]=z。 填图缓冲段把砖序倒排进 RENKO 系列数组:type 为 1 给色值 0,否则 1;open/close/high/low 分别映射 down_price、up_price、shadow_up、shadow_down。跑通后开 MT5 把 doorstep_now 从 10 点调到 30 点,能直接看到砖块稀疏度变化,外汇与贵金属品种须留意滑点导致砖列断裂的高风险。

MQL5 / C++
{
   for(class="type">int y=point_go; y>=doorstep_now; y-=doorstep_now)
     {
      a++; class=class="str">"cmt">//--- add the next brick
      if((a==b && turn_var<turn_cycle) || (turn_var==turn_cycle && turn_rest==a))
        {
         down_price[class="num">0]=down_price[a-class="num">1];
         a=class="num">1;       class=class="str">"cmt">// reset bricks counter
         turn_var++; class=class="str">"cmt">// reset loop counter
        }
      up_price[a]=down_price[a-class="num">1]; class=class="str">"cmt">//--- set the next brick down price
      down_price[a]=up_price[a]-(doorstep_now*_Point); class=class="str">"cmt">//--- set the up price value
      class=class="str">"cmt">//--- set the down shadow value 
      if(shadow_print==true) shadow_down[a]=price[z]; class=class="str">"cmt">// at the lowest price level
      else shadow_down[a]=down_price[a];              class=class="str">"cmt">// at the down price level
      class=class="str">"cmt">//--- set the up price level
      shadow_up[a]=up_price[a];
      class=class="str">"cmt">//--- set the brick close time
      time_box[a]=(class="type">class="kw">datetime)Date[z];
      class=class="str">"cmt">//--- set the brick type(down)
      type_box[a]=-class="num">1;
      class=class="str">"cmt">//--- index set
      number_id[a]=z;
     }
   }
   }
   } class=class="str">"cmt">//---< Main loop
class=class="str">"cmt">//--- fill the draw buffer
  class="type">int y=a;
  for(class="type">int z=class="num">0; z<a; z++)
   {
    if(type_box[y]==class="num">1)RENKO_color[z]=class="num">0;
    else RENKO_color[z]=class="num">1;
    RENKO_open[z]=down_price[y];
    RENKO_close[z]=up_price[y];
    RENKO_high[z]=shadow_up[y];
    RENKO_low[z]=shadow_down[y];
    y--;
   }
 }

用代码在MT5上画趋势线与矩形区

在MT5脚本里批量标注关键位,比起手动拖图形更不容易漏掉拐点。下面两段函数分别负责创建一条限定端点的趋势线,以及一个矩形区域,参数都从外部传入,方便复用。 趋势线函数先以 OBJ_TREND 类型建对象,绑定 time1/price1 与 time2/price2 两个锚点;随后用 ObjectSetInteger 依次设定颜色、实线样式、线宽(width 参数可控 1~5 像素)、前景显示,并关闭左右射线,使线段严格落在两点之间而非无限延伸。 矩形函数改用 OBJ_RECTANGLE 类型,同样以两对角时间点与价格点框出区域;fill 参数决定是否半透明填充,配合 color_square 可快速区分不同震荡区间。外汇与贵金属波动剧烈,这类图形仅作结构参考,实际突破方向需结合量价确认,存在较高风险。 把下面代码直接塞进 ea 或脚本,传入具体价位与时间,就能在图表上复现你的价格行为分析框架。

MQL5 / C++
class="type">class="kw">double price1,
 class="type">class="kw">double price2,
 class="type">class="kw">datetime time1,
 class="type">class="kw">datetime time2,
 class="type">int width,
 class="type">color color_line)
 {
 ObjectCreate(class="num">0,name,OBJ_TREND,class="num">0,time1,price1,time2,price2);
class=class="str">"cmt">//--- set the line class="type">color
 ObjectSetInteger(class="num">0,name,OBJPROP_COLOR,color_line);
class=class="str">"cmt">//--- set the line display style
 ObjectSetInteger(class="num">0,name,OBJPROP_STYLE,STYLE_SOLID);
class=class="str">"cmt">//--- set the width of the line
 ObjectSetInteger(class="num">0,name,OBJPROP_WIDTH,width);
class=class="str">"cmt">//--- display in the foreground(class="kw">false) or in the(true) background
 ObjectSetInteger(class="num">0,name,OBJPROP_BACK,class="kw">false);
class=class="str">"cmt">//--- enable(true) or disable(class="kw">false) the mode of the left line display
 ObjectSetInteger(class="num">0,name,OBJPROP_RAY_LEFT,class="kw">false);
class=class="str">"cmt">//--- enable(true) or disable(class="kw">false) the right line display
 ObjectSetInteger(class="num">0,name,OBJPROP_RAY_RIGHT,class="kw">false);
 }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Func Create Square or Rectangle                                |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void func_create_square_or_rectangle(class="type">class="kw">string name,
                                     class="type">class="kw">double price1,
                                     class="type">class="kw">double price2,
                                     class="type">class="kw">datetime time1,
                                     class="type">class="kw">datetime time2,
                                     class="type">int width,
                                     class="type">color color_square,
                                     class="type">bool fill)
 {
class=class="str">"cmt">//--- create rectangle according to the setpoints 
 ObjectCreate(class="num">0,name,OBJ_RECTANGLE,class="num">0,time1,price1,time2,price2);
class=class="str">"cmt">//--- set the rectangle class="type">color
 ObjectSetInteger(class="num">0,name,OBJPROP_COLOR,color_square);
class=class="str">"cmt">//--- set style of rectangle class="type">color

◍ 用矩形块重绘 Renko 主图

在自定义 Renko 图表里,每个砖块本质是 MT5 的矩形对象。下面这段绘制函数按序号遍历已有砖块数组 a,把上涨块和下跌块分别用不同配色画成实心或空心矩形,方便直接在主图叠加裸 K 替代视图。 循环从 z=2 起步而非 0,是因为 time_box[z-1] 需要前一个时间锚点;若从 0 开始会越界读到无效下标。砖块命名用 magic_numb 加类型后缀,同一 EA 多实例不会互相覆盖对象名。 type_box[z]==1 判为上涨,-1 为下跌。square 与 frame 两个布尔开关独立控制是否画填充块体和是否画边框:只开 frame 时能得到空心线框,适合叠加在原本蜡烛图上做区块标记。OBJPROP_BACK 设 false 保证矩形浮在前景,不会被原生 K 线挡住。 实盘加载这类对象绘制前,先确认 a 数组长度与 time_box/up_price 已正确填充,否则 MT5 可能刷出几十个错位矩形拖慢终端。外汇与贵金属品种波动跳空频繁,Renko 砖参数过小会生成过量对象,存在图表卡顿风险。

MQL5 / C++
  ObjectSetInteger(class="num">0,name,OBJPROP_STYLE,STYLE_SOLID);
class=class="str">"cmt">//--- set lines width
  ObjectSetInteger(class="num">0,name,OBJPROP_WIDTH,width);
class=class="str">"cmt">//--- activate(true) or disactivate(class="kw">false) mode of rectangle colouring
  ObjectSetInteger(class="num">0,name,OBJPROP_FILL,fill);
class=class="str">"cmt">//--- display in the foreground(class="kw">false) or in the background(true)
  ObjectSetInteger(class="num">0,name,OBJPROP_BACK,class="kw">false);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Func Draw Renko Main Chart                                        |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void func_draw_renko_main_chart(class="type">color color_square_up,
                                class="type">color color_square_down,
                                class="type">color color_frame_up,
                                class="type">color color_frame_down,
                                class="type">int width_square,
                                class="type">int width_frame,
                                class="type">bool square,
                                class="type">bool fill,
                                class="type">bool frame)
  {
  class="type">class="kw">string name_square;
  class="type">class="kw">string name_frame;
  for(class="type">int z=class="num">2; z<=a; z++)
    {
    name_square=IntegerToString(magic_numb)+"_Square_"+IntegerToString(z);
    name_frame=IntegerToString(magic_numb)+"_Frame_"+IntegerToString(z);
    if(type_box[z]==class="num">1)
      {
      if(square==true)func_create_square_or_rectangle(name_square,up_price[z],down_price[z],time_box[z-class="num">1],time_box[z],width_square,color_square_up,fill);
      if(frame==true)func_create_square_or_rectangle(name_frame,up_price[z],down_price[z],time_box[z-class="num">1],time_box[z],width_frame,color_frame_up,class="kw">false);
      }
    if(type_box[z]==-class="num">1)
      {
      if(square==true)func_create_square_or_rectangle(name_square,up_price[z],down_price[z],time_box[z-class="num">1],time_box[z],width_square,color_square_down,fill);
      if(frame==true)func_create_square_or_rectangle(name_frame,up_price[z],down_price[z],time_box[z-class="num">1],time_box[z],width_frame,color_frame_down,class="kw">false);
      }
    }
  }

「用拐点数组画出之字转向连线」

把识别出的波峰波谷连成可见的之字线,核心在 func_draw_zig_zag 这个函数。它接收四个参数:price_shadow 决定是否用含影线的极端价、line_width 控制线宽、line_color_up / line_color_down 分别给上涨段和下跌段上色。 函数先用 type_box[1] 判定第一个拐点是下探还是上冲,把 price_1 和 date_1 锚定在最近一个已确认拐点。随后从 z=2 开始遍历到 a,只要 type_box[z] 不等于前一项,就认为出现新转折,n 自增并拼出形如 'magic_numb_Line_n' 的唯一对象名,避免重复创建。 当 price_shadow 为真,代码会拿 number_id[z-1] 定位前一根 K 线,并向两侧比对 Price_low / Price_high,把 id 微调到真正的极值棒。这一步让连线端点落在影线尖而不是收盘价,EURUSD 这类影线频繁的品种上,转向线贴合度会明显更准。 下跌段(type_box[z]==1)调用 func_create_trend_line 时用 line_color_down,上涨段逻辑对称,用 line_color_up。每次画完把 price_2 / date_2 回灌给 price_1 / date_1,链条就顺着推下去。开 MT5 把这段直接塞进 EA,调 line_width=2 先看日线拐点是否如预期连出。

MQL5 / C++
class="type">void func_draw_zig_zag(class="type">bool price_shadow,
                      class="type">int line_width,
                      class="type">color line_color_up,
                      class="type">color line_color_down)
  {
   class="type">class="kw">double price_1=class="num">0;
   class="type">class="kw">double price_2=class="num">0;
   class="type">class="kw">datetime date_1=class="num">0;
   class="type">class="kw">datetime date_2=class="num">0;
   if(type_box[class="num">1]==class="num">1)price_1=down_price[class="num">1];
   if(type_box[class="num">1]==-class="num">1)price_1=up_price[class="num">1];
   date_1=time_box[class="num">1];
   class="type">int id=class="num">0; class=class="str">"cmt">//  Low & High array storing variable
   class="type">int n=class="num">0;  class=class="str">"cmt">// variable for name forming
   class="type">class="kw">string name_line; class=class="str">"cmt">//--- variable responsible for the "trend line" name
   for(class="type">int z=class="num">2; z<=a; z++)
     {
      if(type_box[z]!=type_box[z-class="num">1])
        {
         n++;
         name_line=IntegerToString(magic_numb)+"_Line_"+IntegerToString(n);
         if(type_box[z]==class="num">1)
           {
            if(price_shadow==true)
              {
               id=number_id[z-class="num">1];
               if((id-class="num">1)>class="num">0 && Price_low[id-class="num">1]<Price_low[id])id--;
               if(Price_low[id+class="num">1]<Price_low[id])id++;
               price_2=Price_low[id];
               date_2=(class="type">class="kw">datetime)Date[id];
              }
            else
              {
               price_2=down_price[z-class="num">1];
               date_2=time_box[z-class="num">1];
              }
            func_create_trend_line(name_line,price_1,price_2,date_1,date_2,line_width,line_color_down);
            price_1=price_2;
            date_1=date_2;
           }
         if(type_box[z]==-class="num">1)
           {
            if(price_shadow==true)
              {
               id=number_id[z-class="num">1];
               if((id-class="num">1)>class="num">0 && Price_high[id-class="num">1]>Price_high[id])id--;

趋势线收尾与对象清理的实现细节

这段逻辑处在摆动高点连线绘制的末尾:当新的峰值高于前一根已记录高点时,下标 id 自增,把 price_2 锁定为 Price_high[id],date_2 转成对应时间的 datetime;若走 else 分支,则直接复用上一区段保存的 up_price[z-1] 与 time_box[z-1],避免重复扫描。 随后调用 func_create_trend_line,把 price_1/date_1 到 price_2/date_2 连成线段,线宽与颜色由 line_width、line_color_up 控制;画完立刻把起点搬成终点,为下一段连线做准备。外汇与贵金属波动剧烈,这类自动连线在高波动段可能频繁重绘,属高风险环境下的视觉辅助。 对象清理由两个函数负责:func_delete_objects 按 name+序号从 0 循环到 number,逐个 ObjectDelete(0,name_del);func_all_delete 先取 ObjectsTotal(0,-1,-1) 得到图表对象总数,再对带 magic_numb 前缀的 _Line_、_Square_、_Frame_ 三组批量删除,最后 ChartRedraw(0) 强制重绘。 在 MT5 里把 magic_numb 设成独立值(如 20240517),可防止你的指标线被其他 EA 的清理函数误删;改 number 上限前先确认 ObjectsTotal 返回值,否则可能漏删残留图形。

MQL5 / C++
   if(Price_high[id+class="num">1]>Price_high[id])id++;
   price_2=Price_high[id];
   date_2=(class="type">class="kw">datetime)Date[id];
   }
   else
   {
   price_2=up_price[z-class="num">1];
   date_2=time_box[z-class="num">1];
   }
   func_create_trend_line(name_line,price_1,price_2,date_1,date_2,line_width,line_color_up);
   price_1=price_2;
   date_1=date_2;
   }
   }
   }
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Func Delete Objects                                             |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void func_delete_objects(class="type">class="kw">string name,
                         class="type">int number)
  {
  class="type">class="kw">string name_del;
  for(class="type">int x=class="num">0; x<=number; x++)
   {
   name_del=name+IntegerToString(x);
   ObjectDelete(class="num">0,name_del);
   }
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Func All Delete                                                 |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void func_all_delete()
  {
class=class="str">"cmt">//--- the graphical objects calculating
  obj=ObjectsTotal(class="num">0,-class="num">1,-class="num">1);
class=class="str">"cmt">//--- all indicator graphical objects deleting
  func_delete_objects(IntegerToString(magic_numb)+"_Line_",obj);
  func_delete_objects(IntegerToString(magic_numb)+"_Square_",obj);
  func_delete_objects(IntegerToString(magic_numb)+"_Frame_",obj);
class=class="str">"cmt">//--- the chart redrawing
  ChartRedraw(class="num">0);
  }

◍ 指标内部的级别绘制与函数整合

自定义指标里想让图表干净一点,可以把关键价位直接画在副图窗口的水平线上。func_create_levels 只吃两个参数:级别数量 level_number 和颜色 level_color,用 IndicatorSetInteger(INDICATOR_LEVELS, level_number) 先告诉终端要显示几条线,再逐个填价格和色值。 代码里有个细节:用变量 k 控制从哪根砖开始取价,若 a 大于 level_number 就偏移 k=a-level_number,避免越界。循环里 z 从 0 走到 level_number,把 up_price[k] 写进 INDICATOR_LEVELVALUE,颜色写进 INDICATOR_LEVELCOLOR,副图水平线就出来了。 真正把所有零件拧在一起的是 func_concolidation(原文拼写如此)。它先 func_all_delete 清场,算好 date_stop=TimeCurrent(),再检查 Bars 数是否超过 Price 缓冲区大小,超了就改 date_start 并弹 Alert 提示初始日期被挪过。 随后它依次拷贝价格与日期、按需补 high/low 用于 ZigZag 阴影,调 func_draw_renko 画砖型、func_draw_zig_zag 画拐点、func_draw_renko_main_chart 画主图方块,最后 func_create_levels 挂级别线并 ChartRedraw(0) 强制重绘。外汇与贵金属波动剧烈,这类自绘指标仅作结构参考,实盘前请在 MT5 策略测试器以历史数据验证缓冲与重绘逻辑。 开 MT5 把这段直接塞进你的指标源码,改 levels_number 和 levels_color 两个外部参数,就能直观看到副图级别线随砖型刷新;若发现线数不对,先查 a 与缓冲大小的对应关系。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Func Create Levels                                                |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void func_create_levels(class="type">int level_number,
                         class="type">color level_color)
  {
class=class="str">"cmt">//--- set the number of levels in the indicator window
   IndicatorSetInteger(INDICATOR_LEVELS,level_number);
class=class="str">"cmt">// which brick is taken to draw levels
   class="type">int k=class="num">0;
   if(a>level_number)k=a-level_number;
class=class="str">"cmt">//--- set levels prices
   for(class="type">int z=class="num">0;(z<=level_number && k<=a); z++,k++)
     {
       IndicatorSetDouble(INDICATOR_LEVELVALUE,z,up_price[k]);
       IndicatorSetInteger(INDICATOR_LEVELCOLOR,z,level_color);
     }
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Func Consolidation                                                |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void func_concolidation()
  {
class=class="str">"cmt">//--- deleting all the graphical objects of the indicator
   func_all_delete();
class=class="str">"cmt">//--- the current date
   date_stop=TimeCurrent();
class=class="str">"cmt">//--- the initial date changing due to the restricted buffer size
   if((bars=Bars(_Symbol,time_frame,date_start,date_stop))>ArraySize(Price))
     {
       date_start=func_calc_date_start(date_start,date_stop);
       Alert("The initial date was changed due to the lack of the chart size");
       date_change=true;
       class=class="str">"cmt">//--- calculation of bars on the taken timeframe
       bars=Bars(_Symbol,time_frame,date_start,date_stop);
     }
class=class="str">"cmt">//---
   class="type">bool result_copy_price=func_copy_price(Price,time_frame,date_start,date_stop,type_price);
   class="type">bool result_copy_date=func_copy_date(Date,time_frame,date_start,date_stop);
class=class="str">"cmt">//--- change the date parameter
   if(result_copy_price=true && result_copy_date==true)date_change=class="kw">false;
class=class="str">"cmt">//---
   if(zig_zag_shadow==true)
     {
       func_copy_price(Price_high,time_frame,date_start,date_stop,class="num">2);
       func_copy_price(Price_low,time_frame,date_start,date_stop,class="num">3);
     }
class=class="str">"cmt">//---
   func_draw_renko(Price,Date,filter_number,shadow_print,type_step,step);
   if(zig_zag==true)func_draw_zig_zag(zig_zag_shadow,zig_zag_width,zig_zag_color_up,zig_zag_color_down);
class=class="str">"cmt">//---
   func_draw_renko_main_chart(square_color_up,square_color_down,frame_color_up,frame_color_down,square_width,frame_width,square_draw,square_fill,frame_draw);
   func_create_levels(levels_number,levels_color);
class=class="str">"cmt">//--- redraw the chart
   ChartRedraw(class="num">0);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+

「用静态时间差捕捉新K线」

在 MT5 指标或 EA 里,最怕把上一根 K 线的计算重复跑一遍,白白浪费 CPU 还可能导致信号重绘。下面这段函数用「旧时间 vs 新时间」的比对来判定是否换柱,逻辑极轻量,适合嵌进任何周期监听。 核心做法是声明一个 static datetime 变量 old_times 记住上次时间;每次用 CopyTime 只取最新 1 根柱的时间(参数 0,1),复制成功且时间不同,就返回 true 表示新柱。注意 old_times 初值为 0 时不返回 true,避开了指标刚加载那一下误触。 OnCalculate 里直接拿这个函数去 gate 你的主逻辑:只有 func_new_bar 为真才跑 func_concolidation,其余 tick 直接 return rates_total,不重算。键盘事件段虽然被截断,但结构清晰——CHARTEVENT_KEYDOWN 分支里可以继续挂手动刷新。 外汇与贵金属波动剧烈、杠杆高风险大,这类新柱判定只解决「何时算」的问题,信号本身胜率仍靠你回测。开 MT5 把下面代码贴进指标,把 time_redraw 换成 PERIOD_M5 之类,就能直观看到每根新柱触发一次打印。

MQL5 / C++
class="type">bool func_new_bar(ENUM_TIMEFRAMES period_time)
  {
class=class="str">"cmt">//---
   class="kw">static class="type">class="kw">datetime old_times; class=class="str">"cmt">// array for storing old values
   class="type">bool res=class="kw">false;           class=class="str">"cmt">// analysis result variable 
   class="type">class="kw">datetime new_time[class="num">1];     class=class="str">"cmt">// new bar time
class=class="str">"cmt">//---
   class="type">int copied=CopyTime(_Symbol,period_time,class="num">0,class="num">1,new_time); class=class="str">"cmt">// copy the time of the new bar into the new_time box 
class=class="str">"cmt">//---
   if(copied>class="num">0) class=class="str">"cmt">// все ок. data have been copied
     {
       if(old_times!=new_time[class="num">0])    class=class="str">"cmt">// if the bar&class="macro">#x27;s old time is not equal to new one
         {
           if(old_times!=class="num">0) res=true; class=class="str">"cmt">// if it is not the first launch, true = new bar
           old_times=new_time[class="num">0];     class=class="str">"cmt">// store the bar&class="macro">#x27;s time
         }
     }
class=class="str">"cmt">//---
   class="kw">return(res);
  }

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=class="str">"cmt">//---
   if(func_new_bar(time_redraw)==true)
     {
       func_concolidation();
     }
class=class="str">"cmt">//--- class="kw">return value of prev_calculated for next call
   class="kw">return(rates_total);
  }

class="type">void OnChartEvent(class="kw">const class="type">int id,      class=class="str">"cmt">// event ID 
                  class="kw">const class="type">long& lparam,  class=class="str">"cmt">// class="type">long type event parameter
                  class="kw">const class="type">class="kw">double& dparam, class=class="str">"cmt">// class="type">class="kw">double type event parameter
                  class="kw">const class="type">class="kw">string& sparam) class=class="str">"cmt">// class="type">class="kw">string type event parameter
  {
class=class="str">"cmt">//--- Keyboard button pressing event
   if(id==CHARTEVENT_KEYDOWN)
     {

用快捷键接管指标对象的生命周期

在 MT5 指标脚本里,键盘消息走 OnChartEvent 的 lparam 传递虚拟键码。上面这段把 R 键(键码 82)绑到 func_concolidation(),把 C 键(键码 67)绑到 func_all_delete(),等于把「重绘整理区」和「清空所有对象」两个动作直接交给交易员手控,不用进属性框逐个删。 OnDeinit 里只调了一句 func_all_delete(),说明图表被关闭或指标被移除时,残留的图形对象会被强制清理,避免跨品种切换时旧对象叠在新图上造成误读。外汇与贵金属市场波动剧烈、滑点频繁,这类对象若不清,可能让你把已失效的支撑阻力当成当前结构。 验证方式:把这段代码塞进你自己的自定义指标,编译后切到 EURUSD 的 M5,按 R 看整理函数是否触发,按 C 看对象是否归零;键码写死 82 和 67,改键直接改数字即可。

MQL5 / C++
  if(lparam==class="num">82) class=class="str">"cmt">//--- "R" key has been pressed
    {
     class=class="str">"cmt">//--- call of the consolidation function
     func_concolidation();
    }
  if(lparam==class="num">67) class=class="str">"cmt">//--- "C" key has been pressed
    {
     class=class="str">"cmt">//--- deletion of all objects of the indicator
     func_all_delete();
    }
   }
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Custom indicator deinitialization function                       |
class=class="str">"cmt">//+------------------------------------------------------------------+ 
class="type">void OnDeinit(class="kw">const class="type">int reason)
  {
class=class="str">"cmt">//--- class="kw">delete all graphical objects of the indicator
   func_all_delete();
  }

◍ Renko 反转砖里的真实入场缝隙

Renko 图表按价格跳动堆叠砖块,天然过滤了时间噪音,但入场点不能只看颜色翻面。EURUSD H4、砖宽 20 点的样张里标记了 A~F 六个点:A 点是上行砖转下行砖的标准反转;B、C、D 依次走出反转砖。 E 点出了问题——同一方向连出两块砖,且下行阴影落在同级,F 点也出现类似双砖结构。这类位置价格容易回吐,因为强趋势里不该这么快出双砖同色,双砖同色往往意味着反转信号虚弱。 更稳的缝隙在“反转砖带阴影、且后续砖延续同方向”时出现;若一次性砌出两块同色砖,入场安全边际就薄了。 叠加 ZigZag 可做图形分析:GBPUSD H4、20 点样张里能直接读出支撑阻力与头肩结构;等距通道则可跨周期——USDCHF 把指标算在 H1、画在 H4,20 点砖宽下能并发多周期信号。 多周期组合更实用:H1 抓最近反转,H4 剔掉杂讯,D1 确认大方向(GBPUSD H1/H4/D1 同图可见)。另一套画线规则是:相邻红砖间连上线、中间至少隔一块蓝砖,跌破该线后卖;蓝砖间连线、隔至少一块红砖,突破后买(GBPUSD H4、25 点样张以大箭头标出入场)。外汇与贵金属 Renko 交易杠杆高,双砖同色与跨周期冲突都可能是假突破,开 MT5 用 20~25 点砖宽复刻上述样张最直观。

「别急着下结论」

Renko 图表从早年流行到现在,在 MT5 里仍有一席之地,但社区反馈暴露了几个硬伤:有用户在 2021 年 6 月提到按 'R' 键才能看到砖块,说明指标加载逻辑和图表刷新存在耦合;2023 年 2 月有人确认最后两块砖会重绘,这意味着基于末端砖形的突破信号可能失效。 如果你打算把文末的 abcr.mq5(77.52 KB)跑起来,先开一个 EURUSD 的 M5 图表,加载后按 R 强制重绘,观察末砖是否在 tick 更新时变色或位移。外汇与贵金属杠杆高,重绘类指标仅作辅助,实盘前请用策略测试器跑至少 3 个月历史数据。 这类图表适合验证价格行为里的趋势过滤,但别把它当唯一依据;同一套砖形逻辑,你完全可以改 BoxSize 算法去适配 XAUUSD 的跳动。

把砖块重绘交给小布盯盘
这些诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到 Renko 箱体偏移与信号重绘提示,你只需核对参数边界。

常见问题

通常在自定义指标里用 Input 参数绑定 BoxSize,OnInit 读取后初始化缓冲区,图表刷新依赖 OnCalculate 的收盘价比较逻辑。
因为 Renko 忽略时间轴只认价格步进,小周期 tick 密度高会更快累计达到箱体阈值,砖块数倾向更多。
可以,小布盯盘在品种页对离线砖块做版本比对,若发现历史砖被改写会标红提示,省去人工翻看每一根。
建议在 OnTick 里加滑点过滤,仅当收盘价突破超过箱体加容忍阈值才确认新砖,避免消息面尖针误触发。
Kagi 用反向线宽切换,Renko 用固定箱体垂直堆叠,前者需记录阴阳线转折,后者只需维护上一砖高低价。