Kagi 制图指标·综合运用
🔑

Kagi 制图指标·综合运用

(3/3)· 当时间轴被抽走,只剩价格本身说话,你能否读懂那把“钥匙”的转向信号?

偏理论 第 3/3 篇
很多交易者把 Kagi 图当成另类蜡烛看,仍按固定周期找买卖点,结果被无时间轴的线条绕晕。忽略时间尺度意味着你惯用的时段过滤完全失效,逆转只认价格穿透幅度。先弄清粗细线切换逻辑,再谈用法,否则只是换了个画图工具自欺。

Kagi 图数据搬运与阈值计算的底层函数

做 Kagi(卡吉)图指标时,历史时间数组的分批拷贝很容易在第二次调用时多塞一根 bar,导致绘图错位。下面这段逻辑用 bars_to_copy_time 动态减掉已拷贝量,非首次进入时先 bars_copied_time-- 再 bars_to_copy_time++,相当于用一根 bar 做重叠缓冲,保证时间序列连续不重不漏。 func_calc_dorstep 负责把「门槛」换算成整数步数:type_doorstep==0 直接取 doorstep 点数;==1 则按 price/_Point*doorstep/100 算百分比对应的点数。返回强转 int,意味着 1.5 点会被截断成 1 点,回测前得确认你的 doorstep 精度是否够用。 func_draw_kagi 的形参列表暴露了 Kagi 线分了阴线两组、阳线两组,外加各三条辅助线数组(lin1~lin3)以及时间数组,说明绘制时把反转后的粗细线、影线都拆开存了。函数开头 a=0 用作反转计数,后续每触发一次方向反转就自增,直接在 MT5 里打印 a 就能看某段行情反转了几次。 外汇与贵金属杠杆高、滑点随机,这类自定义图表仅作结构参考,实盘信号概率性成立,别当确定性依据。

MQL5 / C++
  bars_to_copy_time-=bars_copied_time; class=class="str">"cmt">// Calculate the number of bars to be copied
  if(bars_copied_time!=class="num">0) class=class="str">"cmt">// If it is not the first time the data has been copied
    {
      bars_copied_time--;
      bars_to_copy_time++;
    }
  ArrayResize(time_interim,bars_to_copy_time); class=class="str">"cmt">// Change the size of the receiving array
  result_copy=CopyTime(_Symbol,period,class="num">0,bars_to_copy_time,time_interim);
  if(result_copy!=-class="num">1) class=class="str">"cmt">// If copying to the intermediate array is successful
    {
      ArrayCopy(result_array,time_interim,bars_copied_time,class="num">0,WHOLE_ARRAY); class=class="str">"cmt">// Copy the data from the temporary array to the main one
      x=true; class=class="str">"cmt">// assign the positive answer to the function
      bars_copied_time+=result_copy; class=class="str">"cmt">// Increase the value of the processed data
    }
class=class="str">"cmt">//---
  class="kw">return(x);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Func Calculate Doorstep                                                          |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int func_calc_dorstep(class="type">class="kw">double price)
  {
   class="type">class="kw">double x=class="num">0; class=class="str">"cmt">// Variable for answer
   if(type_doorstep==class="num">0) class=class="str">"cmt">// If the calculation is to be performed in points
     {
      x=doorstep;
     }
   if(type_doorstep==class="num">1) class=class="str">"cmt">// If the calculation is to be performed in percentage
     {
      x=price/_Point*doorstep/class="num">100;
     }
   class="kw">return((class="type">int)x);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Func Draw Kagi                                                                   |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void func_draw_kagi(class="type">class="kw">double &array_input[],
                   class="type">class="kw">double &arr_yin_1[],
                   class="type">class="kw">double &arr_yin_2[],
                   class="type">class="kw">double &arr_yin_lin1[],
                   class="type">class="kw">double &arr_yin_lin2[],
                   class="type">class="kw">double &arr_yin_lin3[],
                   class="type">class="kw">double &arr_yang_1[],
                   class="type">class="kw">double &arr_yang_2[],
                   class="type">class="kw">double &arr_yang_lin1[],
                   class="type">class="kw">double &arr_yang_lin2[],
                   class="type">class="kw">double &arr_yang_lin3[],
                   class="type">class="kw">double &arr_time[])
  {
class=class="str">"cmt">//---
  a=class="num">0; class=class="str">"cmt">// Variable for the chart construction fixing the number of chart reversals

「阴阳线缓冲区的初始化与首段判定」

在自定义指标里处理阴阳线(Yin/Yang)之前,要先声明方向跟踪变量:line_move 记上一根价格方向(1 涨 / -1 跌),line_gauge 记线型粗细(1 粗阳 / -1 细阴),price_down 与 price_up 初值分别设为 -99999 和 99999,用来承接反转价位。 辅助数组 yin_int_1、yin_int_2、lin_yin 与 yang_int_1、yang_int_2、lin_yang 需在 ArrayResize 时按 bars_copied 长度展开;时间类数组则按 bars_copied_time 展开,否则写入会越界。 两个 for 循环都把缓冲区与临时数组统一赋 -1,MT5 里 -1 代表「不绘制」,这样未触发线段的柱不会被误连。 主循环首段用 (price_1-price_2)/_Point > func_calc_dorstep(price_2) 且 line_move==0 来识别第一条细阴线:满足条件就把 price_1、price_2 写入 yin_int_1[a]、yin_int_2[a],line_move 与 line_gauge 置 -1,并用 arr_time[z] 记录 time_change[a]。开 MT5 把这段贴进 OnInit 后的预处理块,改 func_calc_dorstep 的阈值参数,能直接看到细阴起点捕捉的灵敏度变化。

MQL5 / C++
class="type">char line_move=class="num">0; class=class="str">"cmt">// Previous price direction class="num">1-up, -class="num">1-down
class="type">char line_gauge=class="num">0; class=class="str">"cmt">// Previous look of the line class="num">1-thick yang, -class="num">1-thin yin
class="type">class="kw">double price_1=class="num">0,price_2=class="num">0; class=class="str">"cmt">// Auxiliary variables for defining the price movement
class="type">class="kw">double price_down=-class="num">99999,price_up=class="num">99999; class=class="str">"cmt">// Auxiliary variables for storing the reversal price values
price_1=array_input[class="num">0];
class=class="str">"cmt">//--- auxiliary arrays for the initial data storing before the reversal(transferring to the buffers)
class="type">class="kw">double yin_int_1[];
class="type">class="kw">double yin_int_2[];
class="type">class="kw">double lin_yin[];
class="type">class="kw">double yang_int_1[];
class="type">class="kw">double yang_int_2[];
class="type">class="kw">double lin_yang[];
class=class="str">"cmt">//--- change the sizes of dynamic arrays
ArrayResize(yin_int_1,bars_copied);
ArrayResize(yin_int_2,bars_copied);
ArrayResize(yang_int_1,bars_copied);
ArrayResize(yang_int_2,bars_copied);
ArrayResize(lin_yin,bars_copied);
ArrayResize(lin_yang,bars_copied);
class=class="str">"cmt">//--- time data storing arrays
ArrayResize(time_change,bars_copied_time);
ArrayResize(time_line,bars_copied_time); class=class="str">"cmt">// Look of the line Yin = class="num">0 or Yang = class="num">1
ArrayResize(time_change_price,bars_copied_time);
ArrayResize(time_central_price,bars_copied_time);
class=class="str">"cmt">//--- assign -class="num">1 (not displayed) value to the transferred buffers
for(class="type">int z=class="num">0; z<bars_copied; z++)
  {
   arr_yin_1[z]=-class="num">1;
   arr_yin_2[z]=-class="num">1;
   arr_yin_lin1[z]=-class="num">1;
   arr_yin_lin2[z]=-class="num">1;
   arr_yin_lin3[z]=-class="num">1;
   arr_yang_1[z]=-class="num">1;
   arr_yang_2[z]=-class="num">1;
   arr_yang_lin1[z]=-class="num">1;
   arr_yang_lin2[z]=-class="num">1;
   arr_yang_lin3[z]=-class="num">1;
  }
class=class="str">"cmt">//--- equate -class="num">1 (not displayed) value to the arrays
for(class="type">int z=class="num">0; z<bars_copied; z++)
  {
   yin_int_1[z]=-class="num">1;
   yin_int_2[z]=-class="num">1;
   lin_yin[z]=-class="num">1;
   yang_int_1[z]=-class="num">1;
   yang_int_2[z]=-class="num">1;
   lin_yang[z]=-class="num">1;
   time_change[z]=-class="num">1;
   time_line[z]=-class="num">1;
   time_change_price[z]=-class="num">1;
   time_central_price[z]=-class="num">1;
  }
class=class="str">"cmt">//--- function&class="macro">#x27;s main loop
for(class="type">int z=class="num">0; z<bars_copied; z++)
  {
   price_2=array_input[z];
   class=class="str">"cmt">//--- first, let&class="macro">#x27;s define the initial market direction
   class=class="str">"cmt">//--- first THIN DESCENDING line
   if(((price_1-price_2)/_Point>func_calc_dorstep(price_2)) && line_move==class="num">0)
     {
      yin_int_1[a]=price_1;
      yin_int_2[a]=price_2;
      line_move=-class="num">1;
      line_gauge=-class="num">1;
      price_1=price_2;
      time_change[a]=(class="type">class="kw">datetime)arr_time[z];
      time_line[a]=class="num">0;
     }

◍ 下降段里粗细线的分歧处理

在 K 线反转图(Kagi)的绘制逻辑里,价格向下推进时,系统要先看前一段是细线还是粗线,再决定这一笔怎么落。代码里用 line_move 和 line_gauge 两个状态位锁死方向:line_move=-1 表示当前处于下行,line_gauge=-1 是细线、=1 是粗线。 如果是细线下行,只要价格相对上一节点的跳动超过 func_calc_dorstep(price_2) 算出的阈值,或者 kagi_type==0 且价差为正,就直接把 yin_int_2[a] 记为新价、保持细线状态。这段没有「肩部」判断,因为细线本身不承载反转肩位。 粗线下行就复杂了:同样满足下行触发条件后,还要比 price_2 和 price_down(下方肩部价)。若 price_2 < price_down,说明粗线跌穿了下肩,此时要同时写 yin_int_1[a]=price_down、yin_int_2[a]=price_2、yang_int_2[a]=price_down,并把线态翻成细线(line_gauge=-1),还额外记了 time_central_price[a]=price_down 留痕。 若没跌穿下肩(price_2>=price_down),粗线继续粗着下行,只更新 yang_int_2[a] 和新价,line_gauge 保持 1。你在 MT5 里把 kagi_type 切到 0 跑一遍 EURUSD 的 M5,能直接看到细线分支被频繁触发、粗线穿肩才翻细的差异。外汇与贵金属杠杆高,这类图只帮你看清结构,不预示涨跌。

MQL5 / C++
   class=class="str">"cmt">//--- first THICK ASCENDING line
   if(((price_1-price_2)/_Point<-func_calc_dorstep(price_2)) && line_move==class="num">0)
      {
       yang_int_1[a]=price_1;
       yang_int_2[a]=price_2;
       line_move=class="num">1;
       line_gauge=class="num">1;
       price_1=price_2;
       time_change[a]=(class="type">class="kw">datetime)arr_time[z];
       time_line[a]=class="num">1;
       }
   class=class="str">"cmt">//--- price moves DOWN
   class=class="str">"cmt">//--- if the price moved DOWN before that, the line is THIN
   if(line_move==-class="num">1 && line_gauge==-class="num">1)
      {
       if(((price_1-price_2)/_Point>func_calc_dorstep(price_2)) || (kagi_type==class="num">0 && (price_1-price_2)/_Point>class="num">0))
         {
         yin_int_2[a]=price_2;
         line_move=-class="num">1;
         line_gauge=-class="num">1;
         price_1=price_2;
         time_change[a]=(class="type">class="kw">datetime)arr_time[z];
         time_line[a]=class="num">0;
         }
      }
   class=class="str">"cmt">//--- if the price moved DOWN before that, the line is THICK
   if(line_move==-class="num">1 && line_gauge==class="num">1)
      {
       if(((price_1-price_2)/_Point>func_calc_dorstep(price_2)) || (kagi_type==class="num">0 && (price_1-price_2)/_Point>class="num">0))
         {
         if(price_2<price_down) class=class="str">"cmt">// If the thick line crossed the lower shoulder when moving downwards
            {
             yin_int_1[a]=price_down;
             yin_int_2[a]=price_2;
             yang_int_2[a]=price_down;
             line_move=-class="num">1;
             line_gauge=-class="num">1;
             price_1=price_2;
             time_change[a]=(class="type">class="kw">datetime)arr_time[z];
             time_central_price[a]=price_down;
             time_line[a]=class="num">0;
            }
         else class=class="str">"cmt">//if(price_2>=price_down) // If the thick line has not crossed the lower shoulder when moving downwards
            {
             yang_int_2[a]=price_2;
             line_move=-class="num">1;
             line_gauge=class="num">1;
             price_1=price_2;
             time_change[a]=(class="type">class="kw">datetime)arr_time[z];
             time_line[a]=class="num">1;
            }
         }

上行后转跌的阴阳线判定分支

当价格此前处于上行状态(line_move==1)时,算法根据当前线宽状态分两条路径处理向下的反转。线宽为细线(line_gauge==-1)时,只要 (price_1-price_2)/_Point 超过 func_calc_dorstep(price_2) 的动态步长,就记一次转向:把 price_1、price_2 存入阴线区间数组,线状态翻为下行细线,并将 price_up 锚定在 price_1。 若为粗线(line_gauge==1)向下击穿,则进一步看是否跌破 lower shoulder(price_down)。跌破时,阴线区间取 price_down 到 price_2,阳线区间取 price_1 到 price_down,并记录 time_central_price 为 price_down;未跌破时阳线区间直接取 price_1 到 price_2,线宽保持粗线状态(line_gauge 仍为 1),time_line 标记置 1。 这套分支直接决定后续肩位与中枢价的绘制逻辑。外汇与贵金属波动大、滑点频繁,动态步长阈值若设得过小,可能在 5 分钟图上一天触发数十次假转向,建议开 MT5 用历史数据打印 a 的增量验证。

MQL5 / C++
      class=class="str">"cmt">//--- if the price has moved UPWARDS before that, the line is THIN
      if(line_move==class="num">1 && line_gauge==-class="num">1)
        {
         if((price_1-price_2)/_Point>func_calc_dorstep(price_2))
           {
            a++;
            yin_int_1[a]=price_1;
            yin_int_2[a]=price_2;
            lin_yin[a]=price_1;
            line_move=-class="num">1;
            line_gauge=-class="num">1;
            price_up=price_1;
            price_1=price_2;
            time_change[a]=(class="type">class="kw">datetime)arr_time[z];
            time_line[a]=class="num">0;
            time_change_price[a]=lin_yin[a];
           }
        }
      class=class="str">"cmt">//--- if the price has moved UPWARDS before that, the line is THICK
      if(line_move==class="num">1 && line_gauge==class="num">1)
        {
         if((price_1-price_2)/_Point>func_calc_dorstep(price_2))
           {
            a++;
            if(price_2<price_down) class=class="str">"cmt">// If the thick line has crossed the lower shoulder when moving downwards
              {
               yin_int_1[a]=price_down;
               yin_int_2[a]=price_2;
               yang_int_1[a]=price_1;
               yang_int_2[a]=price_down;
               lin_yang[a]=price_1;
               line_move=-class="num">1;
               line_gauge=-class="num">1;
               price_up=price_1;
               price_1=price_2;
               time_change[a]=(class="type">class="kw">datetime)arr_time[z];
               time_line[a]=class="num">0;
               time_change_price[a]=lin_yang[a];
               time_central_price[a]=price_down;
              }
            elseclass=class="str">"cmt">//if(price_2>=price_down) // If the thick line has not crossed the lower shoulder when moving downwards
              {
               yang_int_1[a]=price_1;
               yang_int_2[a]=price_2;
               lin_yang[a]=price_1;
               line_move=-class="num">1;
               line_gauge=class="num">1;
               price_up=price_1;
               price_1=price_2;
               time_change[a]=(class="type">class="kw">datetime)arr_time[z];
               time_line[a]=class="num">1;
               time_change_price[a]=lin_yang[a];
              }

「价格上行时 K 线粗细的翻转判定」

这段逻辑处理价格向上穿透时的 K agi 线状态切换,核心看 line_move 与 line_gauge 两个标志位的组合。line_move=1 表示此前方向为上行,line_gauge=1 为粗线、-1 为细线,触发条件都依赖 (price_1-price_2)/_Point 小于负向阈值或 kagi_type==0 时的简单反向。 当原状态是上行粗线,且价格向上越过 func_calc_dorstep 计算的步长,就直接把 yang_int_2 记为 price_2,方向和时间线标志维持不变,属于同向延续。 若原状态是上行细线,还要比 price_up 这个「上肩」位:没越过时补一段 yin_int_2=price_up 再接 yang_int_1=price_up、yang_int_2=price_2,线转为粗;越过了则只记 yin_int_2=price_2,细线继续,time_line 置 0。 原状态为下行粗线时,价格上行突破步长会先 a++ 开新段,再填 yang_int_1=price_1、yang_int_2=price_2,意味着趋势真正反身。外汇与贵金属波动跳点大,这类反转阈值用 _Point 而非固定点值,回测时建议打印 func_calc_dorstep 返回值验证不同品种表现。

MQL5 / C++
   }
   }
   class=class="str">"cmt">//--- the price moves UP
   class=class="str">"cmt">//--- if the price has moved UPWARDS before that, the line is THICK
   if(line_move==class="num">1 && line_gauge==class="num">1)
     {
      if(((price_1-price_2)/_Point<-func_calc_dorstep(price_2)) || (kagi_type==class="num">0 && (price_1-price_2)/_Point<class="num">0))
        {
         yang_int_2[a]=price_2;
         line_move=class="num">1;
         line_gauge=class="num">1;
         price_1=price_2;
         time_change[a]=(class="type">class="kw">datetime)arr_time[z];
         time_line[a]=class="num">1;
        }
     }
   class=class="str">"cmt">//--- if the price has moved UPWARDS before that, the line is THIN
   if(line_move==class="num">1 && line_gauge==-class="num">1)
     {
      if(((price_1-price_2)/_Point<-func_calc_dorstep(price_2)) || (kagi_type==class="num">0 && (price_1-price_2)/_Point<class="num">0))
        {
         if(price_2>price_up) class=class="str">"cmt">// If the thin line has not crossed the upper shoulder when moving upwards
           {
            yin_int_2[a]=price_up;
            yang_int_1[a]=price_up;
            yang_int_2[a]=price_2;
            line_move=class="num">1;
            line_gauge=class="num">1;
            price_1=price_2;
            time_change[a]=(class="type">class="kw">datetime)arr_time[z];
            time_central_price[a]=price_up;
            time_line[a]=class="num">1;
           }
         elseclass=class="str">"cmt">//if(price_2<=price_up) // If the thin line has not crossed the upper shoulder when moving upwards
           {
            yin_int_2[a]=price_2;
            line_move=class="num">1;
            line_gauge=-class="num">1;
            price_1=price_2;
            time_change[a]=(class="type">class="kw">datetime)arr_time[z];
            time_line[a]=class="num">0;
           }
        }
     }
   class=class="str">"cmt">//--- if the price has moved DOWNWARDS before that, the line is THICK
   if(line_move==-class="num">1 && line_gauge==class="num">1)
     {
      if((price_1-price_2)/_Point<-func_calc_dorstep(price_2))
        {
         a++;
         yang_int_1[a]=price_1;
         yang_int_2[a]=price_2;

◍ 阴线细通道上破时的状态翻转

当价格此前处于向下移动的细线(line_move==-1 且 line_gauge==-1)状态,若当前 tick 相对前一价的跌幅超过 func_calc_dorstep(price_2) 动态步长,才触发通道切换逻辑;否则维持原细线结构不重绘。 若上破时 price_2 已高于 price_up(即细阴线向上穿越了上肩),代码将 yin_int_1/a 置为 price_1、yin_int_2 与 yang_int_1 都锚在 price_up、yang_int_2 取 price_2,并把 line_move 与 line_gauge 同时翻为 1,意味着细线转成粗阳线,time_line[a] 记 1 且补了 time_central_price[a]=price_up。 未触及上肩的情形(price_2<=price_up)则只更新 yin_int_1/2 与 lin_yin,line_gauge 保持 -1、time_line[a] 写 0,说明仍属细线但方向已转上,缓冲区不会标记成实体穿越。 主循环末尾用 y=a 暂存、yin/yang 初值 1,再按 z 从 0 到 a 把 yin_int_1[y]、yin_int_2[y] 拷进 arr_yin_1/2 绘制缓冲——开 MT5 把这段接进自定义指标,能在 EURUSD 15M 上直观看细线翻转是否滞后于肩位。外汇与贵金属波动带跳空,这类通道判定在高波动时段可能频繁误触,仅作结构参考。

MQL5 / C++
lin_yang[a]=price_1;
line_move=class="num">1;
line_gauge=class="num">1;
price_down=price_1;
price_1=price_2;
time_change[a]=(class="type">class="kw">datetime)arr_time[z];
time_line[a]=class="num">1;
time_change_price[a]=lin_yang[a];
 }
 }
class=class="str">"cmt">//--- if the price has moved DOWNWARDS before that, the line is THIN
if(line_move==-class="num">1 && line_gauge==-class="num">1)
 {
 if((price_1-price_2)/_Point<-func_calc_dorstep(price_2))
  {
  a++;
  if(price_2>price_up) class=class="str">"cmt">// If the thin line has crossed the upper shoulder when moving upwards
   {
   yin_int_1[a]=price_1;
   yin_int_2[a]=price_up;
   yang_int_1[a]=price_up;
   yang_int_2[a]=price_2;
   lin_yin[a]=price_1;
   line_move=class="num">1;
   line_gauge=class="num">1;
   price_down=price_1;
   price_1=price_2;
   time_change[a]=(class="type">class="kw">datetime)arr_time[z];
   time_line[a]=class="num">1;
   time_change_price[a]=lin_yin[a];
   time_central_price[a]=price_up;
   }
  else class=class="str">"cmt">//if(price_2<=price_up) // If the thin line has not crossed the upper shoulder when moving upwards
   {
   yin_int_1[a]=price_1;
   yin_int_2[a]=price_2;
   lin_yin[a]=price_1;
   line_move=class="num">1;
   line_gauge=-class="num">1;
   price_down=price_1;
   price_1=price_2;
   time_change[a]=(class="type">class="kw">datetime)arr_time[z];
   time_line[a]=class="num">0;
   time_change_price[a]=lin_yin[a];
   }
  }
 }
 }
class=class="str">"cmt">//--- function&class="macro">#x27;s main loop
class=class="str">"cmt">//--- assign actual values to drawing buffers
 class="type">uint y=a;
class=class="str">"cmt">//--- auxiliary variables for storing data on filling the current buffer
 class="type">char yin=class="num">1;
 class="type">char yang=class="num">1;
 for(class="type">uint z=class="num">0; z<=a; z++)
  {
  arr_yin_1[z]=yin_int_1[y];
  arr_yin_2[z]=yin_int_2[y];

阴线阳线分组落点与趋势线生成入口

这段逻辑在做一件事:把逐根扫描得到的阴阳线数据,按连续计数 yin / yang 的余数塞进三个并行数组,并在计数到 3 时归位为 1,形成 1-2-3 循环的缓冲带。

MQL5 / C++
class="kw">switch(yin)
  {
   case class="num">1:
     {
      arr_yin_lin1[z]=lin_yin[y];
      arr_yin_lin1[z+class="num">1]=lin_yin[y];
      yin++;
     }
   break;
   case class="num">2:
     {
      arr_yin_lin2[z]=lin_yin[y];
      arr_yin_lin2[z+class="num">1]=lin_yin[y];
      yin++;
     }
   break;
   case class="num">3:
     {
      arr_yin_lin3[z]=lin_yin[y];
      arr_yin_lin3[z+class="num">1]=lin_yin[y];
      yin=class="num">1;
     }
   break;
  }
arr_yang_1[z]=yang_int_1[y];
arr_yang_2[z]=yang_int_2[y];
class="kw">switch(yang)
  {
   case class="num">1:
     {
      arr_yang_lin1[z]=lin_yang[y];
      arr_yang_lin1[z+class="num">1]=lin_yang[y];
      yang++;
     }
   break;
   case class="num">2:
     {
      arr_yang_lin2[z]=lin_yang[y];
      arr_yang_lin2[z+class="num">1]=lin_yang[y];
      yang++;
     }
   break;
   case class="num">3:
     {
      arr_yang_lin3[z]=lin_yang[y];
      arr_yang_lin3[z+class="num">1]=lin_yang[y];
      yang=class="num">1;
     }
   break;
  }
y--;
}
逐行看:switch(yin) 按当前阴线连续编号分流,case 1/2 把 lin_yin[y] 同时写进 z 和 z+1 两个槽位,等于给线段前后端点赋同值,yin 自增;case 3 写完第三个数组后把 yin 重置为 1,避免溢出。阳线部分 arr_yang_1 / arr_yang_2 先存整数特征,再走和阴线同构的 switch(yang) 三路分发。 末尾 y-- 是把扫描游标往前挪一根,配合外层循环从最近 K 线向历史回推。外汇与贵金属波动下,这种 1-2-3 分组若直接拿来画通道,假突破概率偏高,开 MT5 把 lin_yin / lin_yang 的来源打印出来核对再上图。 下面 func_create_trend_line 的签名已经露头,接收 name、price1/price2、time1/time2 四个参数,显然是拿上面填好的数组端点去真正下指令画趋势线。

MQL5 / C++
class="kw">switch(yin)
  {
   case class="num">1:
     {
      arr_yin_lin1[z]=lin_yin[y];
      arr_yin_lin1[z+class="num">1]=lin_yin[y];
      yin++;
     }
   break;
   case class="num">2:
     {
      arr_yin_lin2[z]=lin_yin[y];
      arr_yin_lin2[z+class="num">1]=lin_yin[y];
      yin++;
     }
   break;
   case class="num">3:
     {
      arr_yin_lin3[z]=lin_yin[y];
      arr_yin_lin3[z+class="num">1]=lin_yin[y];
      yin=class="num">1;
     }
   break;
  }
arr_yang_1[z]=yang_int_1[y];
arr_yang_2[z]=yang_int_2[y];
class="kw">switch(yang)
  {
   case class="num">1:
     {
      arr_yang_lin1[z]=lin_yang[y];
      arr_yang_lin1[z+class="num">1]=lin_yang[y];
      yang++;
     }
   break;
   case class="num">2:
     {
      arr_yang_lin2[z]=lin_yang[y];
      arr_yang_lin2[z+class="num">1]=lin_yang[y];
      yang++;
     }
   break;
   case class="num">3:
     {
      arr_yang_lin3[z]=lin_yang[y];
      arr_yang_lin3[z+class="num">1]=lin_yang[y];
      yang=class="num">1;
     }
   break;
  }
y--;
}
class=class="str">"cmt">//---
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Func Object Create Trend Line                                     |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void func_create_trend_line(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,

「K线肩线在图表上的落地画法」

上面这段函数负责把 Kagi(卡吉图)的肩线直接画到 MT5 主图。先封装了一个 func_create_trend_line,用 OBJ_TREND 对象在两时间点之间拉直线,并逐一设定颜色、线型、宽度与前后延伸开关。 注意 OBJPROP_RAY_LEFT 和 OBJPROP_RAY_RIGHT 都设成 false,意味着线段只在两个肩点之间显示,不会向左或向右无限延长——这跟常见的射线支撑阻力画法不同,回测时不容易误判虚碰。 func_kagi_main_chart 里用 for(uint z=1; z<=a; z++) 遍历肩点数组。当 central_price[z]==-1 表示本肩未被穿过,若前一个肩尾 type_line_end[z-1]==0 且下一价 price[z+1] 有效,就画一段阴线(yin);若前一个肩尾为 1 则画阳线(yang)。名称里拼了 magic_numb 和序号 z,避免重复对象名被 MT5 覆盖。 实盘接贵金属或外汇前先确认 width_yin_main / color_yin_main 已在外部定义,这类品种波动跳点大,线宽设 1 在 1 分钟图上可能看不清,建议先开 MT5 用 EURUSD 的 M5 跑一遍验证对象数量。

MQL5 / C++
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 line width
  ObjectSetInteger(class="num">0,name,OBJPROP_WIDTH,width);
class=class="str">"cmt">//--- display in the foreground(false) or background(true)
  ObjectSetInteger(class="num">0,name,OBJPROP_BACK,false);
class=class="str">"cmt">//--- enable(true) or disable(false) the mode of continuing the line display to the left
  ObjectSetInteger(class="num">0,name,OBJPROP_RAY_LEFT,false);
class=class="str">"cmt">//--- enable(true) or disable(false) the mode of continuing the line display to the right
  ObjectSetInteger(class="num">0,name,OBJPROP_RAY_RIGHT,false);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Func Kagi Main Chart                                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void func_kagi_main_chart(class="type">class="kw">double &price[],      class=class="str">"cmt">// Shoulder prices array
                          class="type">class="kw">double &central_price[], class=class="str">"cmt">// Array of the prices of passing through the shoulders
                          class="type">class="kw">datetime &time[],      class=class="str">"cmt">// Current location time array([-class="num">1] - start of shoulder)
                          class="type">char &type_line_end[])  class=class="str">"cmt">// Line type by the start of shoulder formation
  {
class=class="str">"cmt">//--- start of the loop
  for(class="type">uint z=class="num">1; z<=a; z++)
    {
    class=class="str">"cmt">//--- check for the pass conditions(no pass)
    if(central_price[z]==-class="num">1)
      {
      if(type_line_end[z-class="num">1]==class="num">0 && price[z+class="num">1]!=-class="num">1)
        {
        func_create_trend_line(IntegerToString(magic_numb)+"_trend_yin_v"+IntegerToString(z),
                                price[z],price[z+class="num">1],time[z],time[z],width_yin_main,color_yin_main);
        }
      if(type_line_end[z-class="num">1]==class="num">1 && price[z+class="num">1]!=-class="num">1)
        {
        func_create_trend_line(IntegerToString(magic_numb)+"_trend_yang_v"+IntegerToString(z),

◍ 阴阳线主图趋势段的成对绘制逻辑

这段绘制分支处理的是「穿透已成立」情形下的主图线段生成。当 type_line_end[z-1]==0 且下一档价格 price[z+1] 不等于 -1 时,先画一条以 central_price[z] 到 price[z] 为端点的阴线趋势线,再画 central_price[z] 到 price[z+1] 的阳线趋势线,两条线共用 time[z] 起止时间。 若前一段类型标记为 1,则端点互换:阴线趋势线接 price[z+1]、阳线趋势线接 price[z],依旧锚定在同一个 time[z] 上。这种对称写法保证了阴阳段在视觉上关于中枢价居中。 横向线段部分单独判断:type_line_end[z-1]==0 就用阴线样式画 price[z] 到 price[z]、时间跨 time[z-1]~time[z] 的横线;等于 1 则换阳线样式。实盘里把 width_yin_main / color_yin_main 调粗一点,穿透区的边界会更容易在 MT5 主图一眼认出。外汇与贵金属波动大,这类画线仅作结构参考,不代表方向确定性。

MQL5 / C++
else class=class="str">"cmt">//--- check for the pass conditions(pass is present)
  {
   if(type_line_end[z-class="num">1]==class="num">0 && price[z+class="num">1]!=-class="num">1)
     {
      func_create_trend_line(IntegerToString(magic_numb)+"_trend_yin_v"+IntegerToString(z),
                    central_price[z],price[z],time[z],time[z],width_yin_main,color_yin_main);
      func_create_trend_line(IntegerToString(magic_numb)+"_trend_yang_v"+IntegerToString(z),
                    central_price[z],price[z+class="num">1],time[z],time[z],width_yang_main,color_yang_main);
     }
   if(type_line_end[z-class="num">1]==class="num">1 && price[z+class="num">1]!=-class="num">1)
     {
      func_create_trend_line(IntegerToString(magic_numb)+"_trend_yin_v"+IntegerToString(z),
                    central_price[z],price[z+class="num">1],time[z],time[z],width_yin_main,color_yin_main);
      func_create_trend_line(IntegerToString(magic_numb)+"_trend_yang_v"+IntegerToString(z),
                    central_price[z],price[z],time[z],time[z],width_yang_main,color_yang_main);
     }
  }
class=class="str">"cmt">//--- check for the pass conditions(pass is present)
class=class="str">"cmt">//--- draw the horizontals
if(type_line_end[z-class="num">1]==class="num">0)
  {
   func_create_trend_line(IntegerToString(magic_numb)+"_trend_h"+IntegerToString(z),
                price[z],price[z],time[z-class="num">1],time[z],width_yin_main,color_yin_main);
  }
if(type_line_end[z-class="num">1]==class="num">1)
  {
   func_create_trend_line(IntegerToString(magic_numb)+"_trend_h"+IntegerToString(z),
                price[z],price[z],time[z-class="num">1],time[z],width_yang_main,color_yang_main);
  }
class=class="str">"cmt">//--- draw the horizontals

在主图批量画出时间切换线与标签

下面这段函数负责把 K 线形态发生时间切换的位置,直接画到 MT5 主图上。它由两个独立开关控制:time_change_print 管竖线,label_print 管右侧价格箭头标签,两者可以只开一个。 竖线部分用 OBJ_VLINE 逐根创建,循环上限是变量 a(代表已记录的时间切换次数)。每条线名用 magic_numb 加序号拼接,避免多 EA 同图时对象名冲突;颜色逻辑看 time_line[z] 是 0 还是 1,分别取 time_color_first / time_color_second,关掉 time_change_color 时统一用 first 色。 标签部分用 OBJ_ARROW_RIGHT_PRICE,锚点是 time_change[numb_time] 的时间与 time_change_price[z] 的价格。注意 numb_time 的取值:当 kagi_main_chart 为 true 时等于 z,否则减 1,这对应主图是否含第 0 根 K 线的偏移差异。 线宽写死为 1、样式 SOLID、置顶显示(OBJPROP_BACK=false),RAY 参数接 time_separate_windows 布尔值,决定竖线是否穿透下方子窗口。外汇与贵金属杠杆高,图形仅作结构参考,实盘须自行验证。

MQL5 / C++
class="type">void func_label_main_chart(class="type">bool label_print,
                          class="type">color label_color,
                          class="type">bool time_change_print,
                          class="type">bool time_change_color,
                          class="type">color time_color_first,
                          class="type">color time_color_second)
  {
  if(time_change_print==true)
    {
      for(class="type">uint z=class="num">1; z<=a; z++)
        {
         class="type">class="kw">string name=IntegerToString(magic_numb)+"_time_2_"+IntegerToString(z);
         class=class="str">"cmt">//--- create an object of a vertical line type
         ObjectCreate(class="num">0,name,OBJ_VLINE,class="num">0,time_change[z],class="num">0);
         class=class="str">"cmt">//--- set the line class="type">color
         class="type">color color_line=clrBlack;
         if(time_change_color==true)
           {
            if(time_line[z]==class="num">0)color_line=time_color_first;
            if(time_line[z]==class="num">1)color_line=time_color_second;
           }
         else color_line=time_color_first;
         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 line width
         ObjectSetInteger(class="num">0,name,OBJPROP_WIDTH,class="num">1);
         class=class="str">"cmt">//--- display on the foreground(false) or background(true)
         ObjectSetInteger(class="num">0,name,OBJPROP_BACK,false);
         class=class="str">"cmt">//--- enable(true) or disable(false) the line display mode in the chart subwindows
         ObjectSetInteger(class="num">0,name,OBJPROP_RAY,time_separate_windows);
        }
    }
  if(label_print==true)
    {
      for(class="type">uint z=class="num">1; z<=a; z++)
        {
         class="type">class="kw">string name=IntegerToString(magic_numb)+"_label_2_"+IntegerToString(z);
         class="type">uint numb_time;
         if(kagi_main_chart==true)numb_time=z;
         else numb_time=z-class="num">1;
         class=class="str">"cmt">//--- create a label type object
         ObjectCreate(class="num">0,name,OBJ_ARROW_RIGHT_PRICE,class="num">0,time_change[numb_time],time_change_price[z]);
         class=class="str">"cmt">//--- set the label class="type">color

「价格标签与水平线的图形属性落地」

在 MT5 自定义指标里,把价格标签画到主图只是第一步,真正决定可读性的往往是那几行 ObjectSetInteger 的属性赋值。上面这段把颜色、线型、宽度和前后景都钉死了:颜色用 label_color 变量接管,线型强制 STYLE_SOLID,宽度写死为 1,OBJPROP_BACK 设 false 代表标签浮在前景不被 K 线盖住。 func_label_indicator_window 这个函数把『是否画价签』和『是否画水平位』拆成两个布尔开关,再靠 levels_type_draw 决定反转点绘制还是等距全区间绘制,levels_color_change 控制线色是否随状态切换。循环里用 z<number 做边界保护,name 拼法为 magic_numb+"_label_1_"+z,避免多实例对象名撞车。 实盘接这段时,先把 OBJPROP_WIDTH 从 1 改成 2 看黄金 M15 是否更醒目;外汇与贵金属波动大、滑点凶,改完请在模拟盘验证,前景标签遮挡也可能让你误判影线刺穿。

MQL5 / C++
ObjectSetInteger(class="num">0,name,OBJPROP_COLOR,label_color);
class=class="str">"cmt">//--- set the edging line style
ObjectSetInteger(class="num">0,name,OBJPROP_STYLE,STYLE_SOLID);
class=class="str">"cmt">//--- set the label size
ObjectSetInteger(class="num">0,name,OBJPROP_WIDTH,class="num">1);
class=class="str">"cmt">//--- display on the foreground(false) or background(true)
ObjectSetInteger(class="num">0,name,OBJPROP_BACK,false);
}
   }
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Func Label Indicator Window                                      |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void func_label_indicator_window(class="type">bool label_print,      class=class="str">"cmt">// Draw price labels
                                 class="type">bool levels_print,      class=class="str">"cmt">// Draw levels
                                 class="type">char levels_type_draw,  class=class="str">"cmt">// Type of drawing the levels by reversals or at an equal distance of the entire price range
                                 class="type">char levels_color_change) class=class="str">"cmt">// Change line class="type">color
  {
   class="type">uint number=a;
   if(label_print==true)
     {
      for(class="type">uint z=class="num">0; z<=label_1_number; z++)
        {
         if(z<number)
           {
            class="type">class="kw">string name=IntegerToString(magic_numb)+"_label_1_"+IntegerToString(z);
            class=class="str">"cmt">//--- create label type object
            ObjectCreate(class="num">0,name,OBJ_ARROW_RIGHT_PRICE,ChartWindowFind(),(class="type">class="kw">datetime)Time[(bars_copied_time-z-class="num">2)],time_change_price[number-z]);
            class=class="str">"cmt">//--- set the label class="type">color
            ObjectSetInteger(class="num">0,name,OBJPROP_COLOR,label_1_color);
            class=class="str">"cmt">//--- set the style of the edging line
            ObjectSetInteger(class="num">0,name,OBJPROP_STYLE,STYLE_SOLID);
            class=class="str">"cmt">//--- set the label size
            ObjectSetInteger(class="num">0,name,OBJPROP_WIDTH,class="num">1);
            class=class="str">"cmt">//--- display on the foreground(false) or background(true)
            ObjectSetInteger(class="num">0,name,OBJPROP_BACK,false);
           }
        }
     }
   if(levels_print==true)
     {

◍ 均分区间与变色逻辑的两种画法

指标绘制水平位时有两套路径:levels_type_draw 为 0 时按已有价格数组 time_change_price 倒序贴位,为 1 时则在 Price 数组的高低点之间做算术均分。后者先取 max_price=Price[ArrayMaximum(Price)]、min_price 用 ArrayMinimum 错位取值,再除以 levels_number 得到每格步长并 NormalizeDouble 到当前品种精度。 变色分支由 levels_change_color 控制:等于 0 按 z 的奇偶交替两色,等于 1 依据 time_line 标记取色,等于 2 则全用主色。外汇与贵金属波动剧烈,这类静态位仅作参考,破位概率随消息面放大。 下面这段是类型 0 的核心循环,可直接拷进 MT5 自定义指标 OnInit 后段验证:

MQL5 / C++
if(levels_type_draw==class="num">0)
  {
   for(class="type">uint z=class="num">0; z<=levels_number; z++)
     {
      if(z<number)
        {
         IndicatorSetDouble(INDICATOR_LEVELVALUE,z,time_change_price[number-z]);
         if(levels_change_color==class="num">0)
           {
            class="type">class="kw">double numb_even=z;
            if(MathMod(numb_even,class="num">2)==class="num">0)
              {
               IndicatorSetInteger(INDICATOR_LEVELCOLOR,z,levels_first_color);
              }
            if(MathMod(numb_even,class="num">2)!=class="num">0)
              {
               IndicatorSetInteger(INDICATOR_LEVELCOLOR,z,levels_second_color);
              }
           }
         if(levels_change_color==class="num">1)
           {
            if(time_line[number-z]==class="num">0)IndicatorSetInteger(INDICATOR_LEVELCOLOR,z,levels_first_color);
            if(time_line[number-z]==class="num">1)IndicatorSetInteger(INDICATOR_LEVELCOLOR,z,levels_second_color);
           }
         if(levels_change_color==class="num">2)
           {
            IndicatorSetInteger(INDICATOR_LEVELCOLOR,z,levels_first_color);
           }
        }
     }
  }
if(levels_type_draw==class="num">1)
  {
   class="type">class="kw">double max_price=Price[ArrayMaximum(Price)];
   class="type">class="kw">double min_price=Price[ArrayMinimum(Price,class="num">1,ArrayMinimum(Price)-class="num">1)];
   class="type">class="kw">double number_difference=(max_price-min_price)/levels_number;
   NormalizeDouble(number_difference,_Digits);
   for(class="type">uint z=class="num">0; z<=levels_number; z++)
     {
      IndicatorSetDouble(INDICATOR_LEVELVALUE,z,(min_price+(z*number_difference)));

用静态数组锁死多周期新K线判定

指标里最怕重复触发:同一根K线被OnCalculate跑好几遍,画线、报警、统计全乱套。下面这套 func_new_bar 用静态数组 old_Times[22] 记住每个周期上一根K线的开盘时间,新时间对不上就返回 false,只对真新 bar 放行。 删除旧对象也得批量来。func_delete_objects 按 name+序号拼出对象名,从 0 循环到 number 调 ObjectDelete,避免图表残留上一轮画的线。 func_new_bar 里 switch 把 PERIOD_M1 到 PERIOD_H3 映射到数组下标 0–13(共14个常见周期),其余周期下标继续往后排,总容量 22 足够覆盖 MT5 全部内建周期。你在 EA 里直接抄这个函数,能省掉自己写时间比对的麻烦。 外汇与贵金属杠杆高、滑点大,这类新K线判定只解决时序问题,不预示任何涨跌概率。

MQL5 / C++
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="type">bool func_new_bar(ENUM_TIMEFRAMES period_time)
  {
class=class="str">"cmt">//----
   class="kw">static class="type">class="kw">datetime old_Times[class="num">22];class=class="str">"cmt">// array for storing old values
   class="type">bool res=false;                class=class="str">"cmt">// analysis result variable  
   class="type">int   i=class="num">0;                     class=class="str">"cmt">// old_Times[] array cell index    
   class="type">class="kw">datetime new_Time[class="num">1];          class=class="str">"cmt">// new bar time
   class="kw">switch(period_time)
     {
      case PERIOD_M1:   i= class="num">0; break;
      case PERIOD_M2:   i= class="num">1; break;
      case PERIOD_M3:   i= class="num">2; break;
      case PERIOD_M4:   i= class="num">3; break;
      case PERIOD_M5:   i= class="num">4; break;
      case PERIOD_M6:   i= class="num">5; break;
      case PERIOD_M10: i= class="num">6; break;
      case PERIOD_M12: i= class="num">7; break;
      case PERIOD_M15: i= class="num">8; break;
      case PERIOD_M20: i= class="num">9; break;
      case PERIOD_M30: i=class="num">10; break;
      case PERIOD_H1:   i=class="num">11; break;
      case PERIOD_H2:   i=class="num">12; break;
      case PERIOD_H3:   i=class="num">13; break;

「用周期索引和时间戳锁新K线」

这段逻辑把 MT5 各周期映射成整型下标:H4 对应 14,H6 到 15,H8 到 16,H12 到 17,日线 18,周线 19,月线 20,当前周期 21。用数组 old_Times[i] 记住每个周期上一根 K 线的开盘时间,下次轮询时拿 CopyTime 取最新一根的时间做比对。 CopyTime(_Symbol, period_time, 0, 1, new_Time) 只拉 1 根 Bar 的时间,开销极低;若 new_Time[0] 与 old_Times[i] 不同且 old_Times[i] 非 0,说明该周期出了新 Bar,返回 true。首次运行 old_Times[i]==0 时不报新 Bar,避免指标加载瞬间误触发。 func_consolidation 里先 TimeCurrent 记结束时间,然后按 magic_numb 前缀批量删掉 _label_2_、_trend_yang_v 等七类图形对象,再调 func_copy_history 把指定时段行情塞进主缓冲区。外汇与贵金属波动受杠杆和跳空影响,这类新 Bar 检测在实盘只代表概率性信号,不代表方向确认,请先在 MT5 策略测试器跑通再上真仓。

MQL5 / C++
   case PERIOD_H4:  i=class="num">14; break;
   case PERIOD_H6:  i=class="num">15; break;
   case PERIOD_H8:  i=class="num">16; break;
   case PERIOD_H12: i=class="num">17; break;
   case PERIOD_D1:  i=class="num">18; break;
   case PERIOD_W1:  i=class="num">19; break;
   case PERIOD_MN1: i=class="num">20; break;
   case PERIOD_CURRENT: i=class="num">21; break;
   }
  class=class="str">"cmt">// copy the time of the last bar to new_Time[class="num">0] cell
  class="type">int copied=CopyTime(_Symbol,period_time,class="num">0,class="num">1,new_Time);

  if(copied>class="num">0) class=class="str">"cmt">// all is well. Data has been copied
    {
    if(old_Times[i]!=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[i]!=class="num">0) res=true;     class=class="str">"cmt">// if it is not the first launch, true = new bar
      old_Times[i]=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=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Func Consolidation                                                |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void func_consolidation()
  {
class=class="str">"cmt">//--- date of construction end
  stop_data=TimeCurrent();
class=class="str">"cmt">//--- deleting all graphical objects belonging to the indicator
  func_delete_objects(IntegerToString(magic_numb)+"_label_2_",ObjectsTotal(class="num">0,-class="num">1,-class="num">1));
  func_delete_objects(IntegerToString(magic_numb)+"_label_1_",ObjectsTotal(class="num">0,-class="num">1,-class="num">1));
  func_delete_objects(IntegerToString(magic_numb)+"_time_2_",ObjectsTotal(class="num">0,-class="num">1,-class="num">1));
  func_delete_objects(IntegerToString(magic_numb)+"_trend_yin_v",ObjectsTotal(class="num">0,-class="num">1,-class="num">1));
  func_delete_objects(IntegerToString(magic_numb)+"_trend_yang_v",ObjectsTotal(class="num">0,-class="num">1,-class="num">1));
  func_delete_objects(IntegerToString(magic_numb)+"_trend_h",ObjectsTotal(class="num">0,-class="num">1,-class="num">1));
  func_delete_objects(IntegerToString(magic_numb)+"_trend_h",ObjectsTotal(class="num">0,-class="num">1,-class="num">1));
class=class="str">"cmt">//--- copy price data to the main buffer
  copy_history=func_copy_history(Price,start_data,stop_data);
class=class="str">"cmt">//--- display information about the error when copying price data

◍ Kagi 指标的重绘与键盘触发逻辑

这段代码片段展示了 Kagi 图表指标在 MT5 里的核心回调骨架:数据拷贝失败就弹 Alert,随后调用一系列 func_draw_* 把阴阳线缓冲画到独立窗口或主图,最后用 ChartRedraw(0) 强制重绘当前图表。 OnCalculate 里只做一件事——func_new_bar(period_to_redraw) 返回 true 时才跑 func_consolidation(),其余情况直接 return(rates_total),意味着指标不会每 tick 重算,只在设定的重绘周期出现新 bar 时刷新,能省下不少 CPU。 OnChartEvent 监听键盘事件,当 lparam==82(即按下 R 键)会手动调用 func_consolidation() 重新聚合 Kagi 线。实盘里你可以改这个数字绑定其他键,比如把 82 换成 70 用 F 键触发,方便盯盘时快速重画而不等新 bar。 外汇与贵金属波动剧烈,这类自定义指标仅作价格行为辅助参考,信号失效概率不低,上手前务必在策略测试器用历史数据验证重绘逻辑是否卡顿。

MQL5 / C++
if(copy_history==false)Alert("Error of copy history Price");
class=class="str">"cmt">//--- copy time data to the main buffer
copy_time=func_copy_time(Time,start_data,stop_data);
class=class="str">"cmt">//--- display a notification of the error occurred class="kw">while copying time data
if(copy_time==false)Alert("Error of copy history Time");
class=class="str">"cmt">//--- construct Kagi chart in the indicator window
func_draw_kagi(Price,YinBuffer1,YinBuffer2,Yin1Buffer,Yin2Buffer,Yin3Buffer,
              YangBuffer1,YangBuffer2,Yang1Buffer,Yang2Buffer,Yang3Buffer,Time);
class=class="str">"cmt">//--- draw labels on the main chart
func_label_main_chart(label_2,label_2_color,time_line_draw,time_line_change_color,time_first_color,time_second_color);
class=class="str">"cmt">//--- draw labels on the indicator chart
func_label_indicator_window(label_1,levels_on_off,levels_type,levels_change_color);
class=class="str">"cmt">//--- construct Kagi chart in the main window
if(kagi_main_chart==true)func_kagi_main_chart(time_change_price,time_central_price,time_change,time_line);
class=class="str">"cmt">//--- redraw the chart
ChartRedraw(class="num">0);
class=class="str">"cmt">//---
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Custom indicator iteration function                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnCalculate(const class="type">int rates_total,
                const class="type">int prev_calculated,
                const class="type">int begin,
                const class="type">class="kw">double &price[])
  {
class=class="str">"cmt">//---
   if(func_new_bar(period_to_redraw)==true)
     {
      func_consolidation();
     }
class=class="str">"cmt">//--- class="kw">return value of prev_calculated for next call
   class="kw">return(rates_total);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| OnChartEvent                                                      |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnChartEvent(const class="type">int id,      class=class="str">"cmt">// event ID
                  const class="type">long& lparam,  class=class="str">"cmt">// class="type">long type event parameter
                  const class="type">class="kw">double& dparam, class=class="str">"cmt">// class="type">class="kw">double type event parameter
                  const class="type">class="kw">string& sparam) class=class="str">"cmt">// class="type">class="kw">string type event parameter
  {
   if(id==CHARTEVENT_KEYDOWN) class=class="str">"cmt">// Keyboard button pressing event
     {
      if(lparam==class="num">82) class=class="str">"cmt">// "R" key has been pressed
        {
         func_consolidation();
        }
     }
  }

指标卸载时的图形对象清理

指标从图表移除或参数重载时,若不在 OnDeinit 里主动清场,旧标签和趋势线会残留在 MT5 图上,造成视觉干扰甚至后续对象 ID 冲突。 上面这段处理函数用 magic_numb 拼出前缀,分批调用 func_delete_objects 删掉两类标签(_label_1_ / _label_2_)、时间标记(_time_2_)以及阴阳柱与水平趋势对象(_trend_yin_v / _trend_yang_v / _trend_h)。每次删除都传入 ObjectsTotal(0,-1,-1),即当前图表全部对象数作为遍历上限。 注意代码里 _trend_h 被连续删了两次,属冗余调用,实际跑起来无副作用但浪费一次遍历。清完之后用 ChartRedraw(0) 强制重绘零号图表,让残留立即消失。开 MT5 挂上带这套逻辑的脚本,切换周期就能看到对象随卸载同步清空。

MQL5 / C++
class="type">void OnDeinit(const class="type">int reason)
  {
class=class="str">"cmt">//--- class="kw">delete all graphical objects belonging to the indicator
   func_delete_objects(IntegerToString(magic_numb)+"_label_2_",ObjectsTotal(class="num">0,-class="num">1,-class="num">1));
   func_delete_objects(IntegerToString(magic_numb)+"_label_1_",ObjectsTotal(class="num">0,-class="num">1,-class="num">1));
   func_delete_objects(IntegerToString(magic_numb)+"_time_2_",ObjectsTotal(class="num">0,-class="num">1,-class="num">1));
   func_delete_objects(IntegerToString(magic_numb)+"_trend_yin_v",ObjectsTotal(class="num">0,-class="num">1,-class="num">1));
   func_delete_objects(IntegerToString(magic_numb)+"_trend_yang_v",ObjectsTotal(class="num">0,-class="num">1,-class="num">1));
   func_delete_objects(IntegerToString(magic_numb)+"_trend_h",ObjectsTotal(class="num">0,-class="num">1,-class="num">1));
   func_delete_objects(IntegerToString(magic_numb)+"_trend_h",ObjectsTotal(class="num">0,-class="num">1,-class="num">1));
class=class="str">"cmt">//--- redraw the chart
   ChartRedraw(class="num">0);
  }

「Kagi 图上的三类实战切入法」

Kagi 图天然过滤噪音,所以不少价格行为策略直接以阴阳线转换或结构突破为触发条件。最直白的玩法:阳线翻阴线做空,阴线翻阳线做多。EURUSD M30、反转阈值 5 点的图上,这类信号在一段行情里给出过 4 次标记,第 1 个多单在 1.3518 进场,后续最高摸至约 1.3560,单日吞下 42 点;第 3 个多单 1.3538 进场后走到 1.3695,一天半累计 157 点——这都是理论极值,实盘回撤后收益会缩水,但方向胜率值得在 MT5 里复验。 第二种偏冷门:顺走势线或通道交易,核心观察点是“肩”和“腰”的连续位移。经验上,连续 7–10 次抬肩或降腰后,反转概率明显走高。GBPUSD H4、25 点阈值图里,肩线连抬七次后走出近 150 点跌幅,大约是前段上升幅度(约 300 点)的一半。 第三种用价格标签突破:当现价超过前一价格标签做多、跌破则做空。GBPUSD H4、30 点修订的图上,红色箭头标出的位置就是击穿旧标签的瞬间。时间标签在这里只当趋势方向指示器——阳线时段和阴线时段用不同着色,蓝标签居上、红标签居下往往对应多头主导,IBM H4 百分图里这个分布就很典型。 外汇与贵金属杠杆高,上述信号仅是概率倾向,开仓前请用 MT5 的 Kagi 模板自调阈值跑一遍近三月数据。

◍ 把工具请下神坛

Kagi 图在实盘里更适合当辅助滤噪层,而不是单独扛信号的圣杯;它把价格波动压成阴阳线转折,帮你看清趋势惯性,但反转阈值设多少直接决定噪声吞掉多少真动作。 作者 2015-09-27 在论坛回帖里改过一次代码:指标可同时按高价和低价构建,且只在指标窗口内绘制;第一根蜡烛向上收盘就从低画到高(阳),向下收盘则反向(阴),之后每根收盘才补画,距离不够反转门阶就跳过该根。 外汇和贵金属杠杆高、滑点狠,这类制图指标只是视图重构,不替你管仓位;真要验证,把 bkcv.mq5 拖进 MT5 把反转阈值从默认点值改成 ATR 百分比,跑一轮 EURUSD 的 M15 看成交密度变化,比盯着神化工具实在。

把反转阈值交给小布算
不同品种该设多少点反转门槛、粗细线切换是否偏离常态,这些诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到,你只管判断方向。

常见问题

点数图以固定点数格记录涨跌,Kagi 图用线条粗细区分高低点突破状态且忽略时间,仅以价格穿透量决定反向列,二者对“无意义波动”的过滤逻辑不同。
股票价格绝对区间大、价差结构不同,百分比能自适应价位;外汇点值稳定,点数直观且不易受报价精度干扰,属历史惯例与品种特性折中。
只要回撤未触达预设反转量,线条在同一列继续延伸,细微折返被隐藏,这是设计目的而非 bug,用来剔除噪音保留主趋势。
可以,小布盯盘内置了基于波动特征的反转门槛推算,并标注当前粗细线分布是否异常,省去手动回测多周期的成本。
外汇贵金属杠杆高、跳空频繁,Kagi 忽略时间可能在重大数据后跳过整段行情,信号滞后概率偏大,仅作辅助而非单独依据。