开发先进的 ICT 交易系统:在订单块指标中实现信号·进阶篇
📘

开发先进的 ICT 交易系统:在订单块指标中实现信号·进阶篇

第 2/3 篇

◍ 用深度数据在第三根K线卡订单块

这套逻辑和之前那版订单块识别一脉相承,但把循环扫描换成了直接盯死第三根烛形(索引 3)做判定。整体步骤没变:验条件、找最近的关联烛形、给结构赋值、推进数组,只是路径更短、更省算力。 起步先卡数据底线:buy_volume 与 sell_volume 两个数组都得至少有 5 个元素,否则历史深度不够,识别容易失真;同时 use_market_book 必须为 true,也就是市场深度(DOM)功能得开着,不然量价判断无从谈起。 成交量突变的阈值是写死的 1.4 倍比率。索引 3 的买量若同时大于索引 2、4 的买量和卖量乘 1.4,就点亮 case_book;卖量对称判定同理。这一步把「第三根K线出现量级失衡」从肉眼活变成了可回测规则。 主体大小用 closeArray[3] - openArray[3] 算(看跌时取反),再配合前两根与当前K线的收阳/收阴关系,以及高低价相对位置,去确认结构是否成立。随后调 FindFurthestAlcista(Time[3], 20),在左侧 20 根里捞最远的看涨烛形当参考;返回索引大于 0 才继续,避免边界失效。 外汇与贵金属杠杆高、滑点凶,DOM 失衡只代表当时盘口概率倾斜,实盘触发订单块后价格未必按预期回流,请用 MT5 策略测试器先跑历史段验证。

MQL5 / C++
OrderBlocks newVela_Order_block_Book_bajista;
OrderBlocks newVela_Order_block_Book;
if(ArraySize(buy_volume) >= class="num">5 && ArraySize(sell_volume) >= class="num">5 && use_market_book == true)
class="type">bool case_book = class="kw">false;
class="type">class="kw">double ratio = class="num">1.4;
if(buy_volume[class="num">3] > buy_volume[class="num">4] * ratio && buy_volume[class="num">3] > buy_volume[class="num">2] * ratio &&
   buy_volume[class="num">3] > sell_volume[class="num">4] * ratio && buy_volume[class="num">3] > sell_volume[class="num">2] * ratio)
{
    case_book = true;
}
if(sell_volume[class="num">3] > buy_volume[class="num">4]*ratio && sell_volume[class="num">3] > buy_volume[class="num">2]*ratio &&
sell_volume[class="num">3] > sell_volume[class="num">4]*ratio && sell_volume[class="num">3] > sell_volume[class="num">2]*ratio)
{
case_book = true;
}
class="type">class="kw">double body_tree = closeArray[class="num">3] - openArray[class="num">3];
class="type">class="kw">double body_tree = openArray[class="num">3] - closeArray[class="num">3];
if(lowArray[class="num">2] > ((body_tree * class="num">0.5) + openArray[class="num">3]) && highArray[class="num">3] < closeArray[class="num">2] &&
    closeArray[class="num">3] > openArray[class="num">3] && closeArray[class="num">2] > openArray[class="num">2] && closeArray[class="num">1] > openArray[class="num">1])
if(highArray[class="num">2] < (openArray[class="num">3]-(body_tree * class="num">0.5)) && lowArray[class="num">3] > closeArray[class="num">2] &&
            closeArray[class="num">3] < openArray[class="num">3] && closeArray[class="num">2] < openArray[class="num">2] && closeArray[class="num">1] < openArray[class="num">1])
class="type">int furthestAlcista = FindFurthestAlcista(Time[class="num">3], class="num">20);
if(furthestAlcista > class="num">0)
Print("Case Book Found");
class="type">class="kw">datetime time1 = Time[furthestAlcista];
class="type">class="kw">double price2 = openArray[furthestAlcista];
class="type">class="kw">double price1 = lowArray[furthestAlcista];
class=class="str">"cmt">//Assign the above variables to the structure
newVela_Order_block_Book.price1 = price1;
newVela_Order_block_Book.time1 = time1;
newVela_Order_block_Book.price2 = price2;
newVela_Order_block_Book.mitigated = class="kw">false;
newVela_Order_block_Book.name = "Bullish Order Block Book " + TimeToString(newVela_Order_block_Book.time1);
AddIndexToArray_alcistas(newVela_Order_block_Book);
Print("Case Book Found");
class="type">class="kw">datetime time1 = Time[furthestBajista];
class="type">class="kw">double price1 = closeArray[furthestBajista];
class="type">class="kw">double price2 = lowArray[furthestBajista];
class=class="str">"cmt">//Assign the above variables to the structure
newVela_Order_block_Book_bajista.price1 = price1;
newVela_Order_block_Book_bajista.time1 = time1;
newVela_Order_block_Book_bajista.price2 = price2;
newVela_Order_block_Book_bajista.mitigated = class="kw">false;
newVela_Order_block_Book_bajista.name = "Order Block Bajista Book " + TimeToString(newVela_Order_block_Book_bajista.time1);
AddIndexToArray_bajistas(newVela_Order_block_Book_bajista);
if(ArraySize(buy_volume) >= class="num">5 && ArraySize(sell_volume) >= class="num">5 && use_market_book == true)
  {

用市场深度锚定订单块

做空订单块的判定先把市场深度(Market Book)的成交量数组拉进来。代码里设定 ratio=1.4,要求索引 3 的卖量同时大于买量[4]、买量[2]、卖量[4]、卖量[2] 各自乘以 1.4,才把 case_book 置真,这意味着第 3 根 K 线(从右数,索引 0 为最新)的主动卖压须显著碾压相邻两侧。 实体差 body_tree 取 openArray[3]-closeArray[3],即第 3 根阴线的绝对实体长度。后续约束 highArray[2] 要低于 openArray[3] 减去实体一半,且 lowArray[3] 高于 closeArray[2],再叠加第 3、2、1 根均为阴线收盘,才进入 FindFurthestBajista 回溯前 20 根内最远空头蜡烛。 若回溯返回的索引大于 0,便在终端打印 Case Book Found,并把那根最远空头 K 的收盘、最低、时间写进 newVela_Order_block_Book_bajista 结构,mitigated 标 false 后入空头数组。外汇与贵金属杠杆高,这类订单块仅代表机构被动挂单可能的残留区,回测命中后价格仍可能不回调。 多头一侧镜像处理:buy_volume[3] 需大于两侧买卖量乘 1.4,body_tree 改为 closeArray[3]-openArray[3],且 lowArray[2] 高于 openArray[3] 加半实体、highArray[3] 低于 closeArray[2],第 3、2、1 根全阳线才触发多头订单块登记。开 MT5 把这段逻辑接上真实 tick 级深度,能直接观察 1.4 倍阈值在 EURUSD 的 M5 上过滤掉多少噪声块。

MQL5 / C++
  class="type">bool case_book = class="kw">false;
  class="type">class="kw">double ratio = class="num">1.4;
  if(sell_volume[class="num">3] > buy_volume[class="num">4]*ratio && sell_volume[class="num">3] > buy_volume[class="num">2]*ratio &&
      sell_volume[class="num">3] > sell_volume[class="num">4]*ratio && sell_volume[class="num">3] > sell_volume[class="num">2]*ratio)
    {
      case_book = true;
    }
  class="type">class="kw">double body_tree =  openArray[class="num">3] - closeArray[class="num">3];
  if(highArray[class="num">2] < (openArray[class="num">3]-(body_tree * class="num">0.5)) && lowArray[class="num">3] > closeArray[class="num">2] &&
      closeArray[class="num">3] < openArray[class="num">3] && closeArray[class="num">2] < openArray[class="num">2] && closeArray[class="num">1] < openArray[class="num">1])
    {
      class="type">int furthestBajista = FindFurthestBajista(Time[class="num">3],class="num">20); class=class="str">"cmt">//We call the "FindFurthestAlcista" function to find out if there are bullish candlesticks before "one candle"
      if(furthestBajista  > class="num">0) class=class="str">"cmt">// Whether or not there is a furthest Bullish candle, it will be greater than class="num">0 since if there is none, the previous candlestick returns to "one candle".
        {
         Print("Case Book Found");
         class="type">class="kw">datetime time1 = Time[furthestBajista];
         class="type">class="kw">double price1 = closeArray[furthestBajista];
         class="type">class="kw">double price2 = lowArray[furthestBajista];
         class=class="str">"cmt">//Assign the above variables to the structure
         newVela_Order_block_Book_bajista.price1 = price1;
         newVela_Order_block_Book_bajista.time1 = time1;
         newVela_Order_block_Book_bajista.price2 = price2;
         newVela_Order_block_Book_bajista.mitigated = class="kw">false;
         newVela_Order_block_Book_bajista.name = "Order Block Bajista Book " + TimeToString(newVela_Order_block_Book_bajista.time1);
         AddIndexToArray_bajistas(newVela_Order_block_Book_bajista);
        }
    }
class=class="str">"cmt">//--------------------    Bullish  -------------------- 
if(ArraySize(buy_volume) >= class="num">5 && ArraySize(sell_volume) >= class="num">5 && use_market_book == true)
  {
   class="type">bool case_book = class="kw">false;
   class="type">class="kw">double ratio = class="num">1.4;
   if(buy_volume[class="num">3] > buy_volume[class="num">4]*ratio && buy_volume[class="num">3] > buy_volume[class="num">2]*ratio &&
       buy_volume[class="num">3] > sell_volume[class="num">4]*ratio && buy_volume[class="num">3] > sell_volume[class="num">2]*ratio)
     {
       case_book = true;
     }
   class="type">class="kw">double body_tree = closeArray[class="num">3] - openArray[class="num">3];
   if(lowArray[class="num">2] > ((body_tree * class="num">0.5)+openArray[class="num">3]) && highArray[class="num">3] < closeArray[class="num">2] &&
       closeArray[class="num">3] > openArray[class="num">3] && closeArray[class="num">2] > openArray[class="num">2] && closeArray[class="num">1] > openArray[class="num">1])
     {

「远端看涨蜡烛的区块登记逻辑」

在订单块识别里,找「一根蜡烛之前」的远端看涨K线是关键一步。下面这段代码调用 FindFurthestAlcista,以当前序列第3根K线为基准向前搜20根,判断是否存在更早的看涨蜡烛。 返回值大于0就说明找到了远端看涨实体;若没有,函数会回退到「一根蜡烛」本身,索引也不会小于0。此时把该K线的时间和开、低价赋给结构体,并标记 mitigated 为未触发,再压入看涨数组待后续绘制。 直接在 MT5 里把这段粘进 EA 的 OnCalculate,把 openArray / lowArray 换成你自己的缓冲数组,就能在日志看到「Case Book Found」并验证远端区块是否按预期捕获。外汇与贵金属波动剧烈,这种结构仅描述价格痕迹,不预示后续方向。

MQL5 / C++
class="type">int furthestAlcista = FindFurthestAlcista(Time[class="num">3],class="num">20); class=class="str">"cmt">//We call the "FindFurthestAlcista" function to find out if there are bullish candlessticks before "one candle"
if(furthestAlcista > class="num">0) class=class="str">"cmt">// Whether or not there is a furthest Bullish candle, it will be greater than class="num">0 since if there is none, the previous candlestick returns to "one candle".
  {
  Print("Case Book Found");
  class="type">class="kw">datetime time1 = Time[furthestAlcista]; class=class="str">"cmt">//let&class="macro">#x27;s assign the index time of furthestAlcista to the variable time1
  class="type">class="kw">double price2 = openArray[furthestAlcista]; class=class="str">"cmt">//let&class="macro">#x27;s assign the open of furthestAlcista as price class="num">2 (remember that we draw it on a bearish candlestick most of the time)
  class="type">class="kw">double price1 = lowArray[furthestAlcista]; class=class="str">"cmt">//let&class="macro">#x27;s assign the low of furthestAlcista as price class="num">1
  class=class="str">"cmt">//Assign the above variables to the structure
  newVela_Order_block_Book.price1 = price1;
  newVela_Order_block_Book.time1 = time1;
  newVela_Order_block_Book.price2 = price2;
  newVela_Order_block_Book.mitigated = class="kw">false;
  newVela_Order_block_Book.name = "Bullish Order Block Book " + TimeToString(newVela_Order_block_Book.time1);
  AddIndexToArray_alcistas(newVela_Order_block_Book);
  }
 }

◍ 给订单块指标搭两个缓冲区

在 MT5 写订单块指标,第一步是把存储和画图的结构先立起来。全局里先放两个指标缓冲区、两个绘图对象,分别管看涨与看跌订单块,这样后续算出的价格水平才有地方存、有地方画。 具体做法是声明 buyOrderBlockBuffer 与 sellOrderBlockBuffer 两个动态数组,把它们绑到指标缓冲区上。前者装看涨订单块的价格,对应图表上价格可能获得支撑的区域;后者装看跌订单块的价格,对应价格可能遇到阻力的区域。外汇与贵金属波动大、杠杆高,这类区域只是概率性参考,不是绝对边界。 下面这段代码就是声明部分的硬核骨架:用 #property 告诉终端本指标有 2 个缓冲区和 2 个绘图,并给它们打上 Bullish / Bearish Order Block 的标签,再定义两个 double 型动态数组。复制进 MQ5 文件全局区就能直接编译通过。

MQL5 / C++
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_label1 "Bullish Order Block"
class="macro">#class="kw">property indicator_label2 "Bearish Order Block"
class=class="str">"cmt">//--- Define the buffers
class="type">class="kw">double buyOrderBlockBuffer[];  class=class="str">"cmt">// Buffer for bullish order blocks
class="type">class="kw">double sellOrderBlockBuffer[]; class=class="str">"cmt">// Buffer for bearish order blocks

在 OnInit 里把订单块数组挂上指标缓冲区

写 MT5 自定义指标时,订单块数据若想直接画在图表上,必须在 OnInit 里把承载数组绑到指标缓冲区。否则数组只是内存里的普通容器,终端不会按时间序列去渲染。 具体做法是调用 SetIndexBuffer,把 buyOrderBlockBuffer 映射到 0 号缓冲区、sellOrderBlockBuffer 映射到 1 号缓冲区,类型都指定为 INDICATOR_DATA。这样两个数组才具备「被终端读取并显示」的资格。 MT5 的时间轴是倒序的:下标 0 是最新一根 K 线。用 ArraySetAsSeries 把两个数组都设为 true,才能保证缓冲区顺序和图表时间轴对齐,不会出现老数据压在新数据头上的错位。 初始化阶段真实值还没算出来,若缓冲区残留随机值,图表可能闪出杂点。用 ArrayFill 以 EMPTY_VALUE 填充,终端在出值前就不绘制对应位置——外汇与贵金属波动剧烈,这种空值保护能避免误读假信号。

MQL5 / C++
class=class="str">"cmt">//--- Assign data buffers to the indicator
   SetIndexBuffer(class="num">0, buyOrderBlockBuffer, INDICATOR_DATA);
   SetIndexBuffer(class="num">1, sellOrderBlockBuffer, INDICATOR_DATA)
   ArraySetAsSeries(buyOrderBlockBuffer, true);
   ArraySetAsSeries(sellOrderBlockBuffer, true);
   ArrayFill(buyOrderBlockBuffer, class="num">0,class="num">0, EMPTY_VALUE); class=class="str">"cmt">// Initialize to EMPTY_VALUE
   ArrayFill(sellOrderBlockBuffer, class="num">0,class="num">0, EMPTY_VALUE); class=class="str">"cmt">// Initialize to EMPTY_VALUE

「用 iBarShift 把订单块价格钉进缓冲区」

订单块的价值不在算出来那一刻,而在它能按时间轴回贴到图表上。把看涨块和看跌块的 price2 写进指标缓冲区,靠的是 iBarShift 这个函数:它接收订单块的 time1,返回当前图表品种与周期下与之匹配的 K 线索引。 具体落地时,遍历 ob_alcistas 数组,对每一个看涨块取 iBarShift(_Symbol, _Period, ob_alcistas[i].time1) 得到索引,再把 price2 赋给 buyOrderBlockBuffer 对应位。看跌块同理,只是换成 ob_bajistas 与 sellOrderBlockBuffer。这样缓冲区里的每个非空位,都精确对应图表上那根形成订单块的 Bars。 外汇与贵金属市场杠杆高、跳空频繁,iBarShift 在跨周期或离线图表重绘时可能返回 -1,写缓冲区前最好加一层索引有效性判断,否则会触发数组越界报警。

MQL5 / C++
buyOrderBlockBuffer[iBarShift(_Symbol, _Period, ob_alcistas[i].time1)] = ob_alcistas[i].price2;
sellOrderBlockBuffer[iBarShift(_Symbol, _Period, ob_bajistas[i].time1)] = ob_bajistas[i].price2;

常见问题

等价格离开订单块区域后,在第三根K线收盘再判定有效性,可过滤掉多数毛刺假信号。
重点看挂单厚度与成交密度突变的位置,把订单块登记在流动性真实枯竭的价位而非肉眼支撑。
小布可在对应品种页直接跑订单块诊断,自动标出第三根K线确认的区域,省去手动画线。
只记远端收阳的块会忽略阴线端的真实吸收,需双向判断蜡烛实体与下一根回测关系。
在 OnInit 绑定数组到缓冲区,循环里用 iBarShift 取精确 bar 序号再写价,可避免时间轴错位。