开发先进的 ICT 交易系统:在指标中实现订单区块·综合运用
连续阴线叠加成交量扩张的订单块判定
这段逻辑在抓一种偏空的订单块形态:先确认第1、第2根K线都是阴线(收盘价低于开盘价),再把标志位 VelaCorrecta_casevolumen 置真。若只看这两根,容易把普通回调误判成机构痕迹,所以代码里还接了一个成交量递增的复核分支。 具体说,case_vol_2 要求第1根量大于前一根、第2根量大于第1根,且第3、第4根也收阴。只要「标准形态+高位约束」或「量能递增形态」任一满足,就调用 FindFurthestBajista(Time[one_vela],20) 在左侧20根范围内找最远的阴线作为订单块基准,赋值给结构体的价格与时间字段。 确认订单块后并不能直接用,还得排除已被 mitigation 的情况。对看多订单块调用 mitigados_alcsitas(...),返回0代表未被触及;对看空订单块用 mitigado_bajista(...) 同样返回0才把 mitigated 标为 false,并写入名称如 "Order Block Bajista "+时间字符串后入数组。外汇与贵金属杠杆高,这类形态失败概率不低,实盘前请在 MT5 用历史数据验证回测命中率。
class=class="str">"cmt">//we look here for class="num">2 consecutive bearish candles if(closeArray[one_vela] < openArray[one_vela] && closeArray[two_vela] < openArray[two_vela]) { VelaCorrecta_casevolumen = true; class=class="str">"cmt">//we set the variable "VelaCorrecta_casevolumen" to true } class=class="str">"cmt">//we look for an increasing volume in addition to the 3rd candle and 4th candle being bearish class="type">bool case_vol_2 = class="kw">false; if(Volumen[one_vela] > volumen_vela_atras_one && Volumen[two_vela] > Volumen[one_vela] && openArray[tree_vela] > closeArray[tree_vela] && openArray[four_vela] > closeArray[four_vela]) case_vol_2 = true; if((VolumenCorrecto == true && VelaCorrecta_casevolumen == true && highArray[two_vela] < ((body1 * class="num">0.5)+closeArray[one_vela]) && ((body2 *class="num">0.5)+closeArray[two_vela]) < lowArray[one_vela]) || case_vol_2 == true) class=class="str">"cmt">// verificamos si se cumple { class=class="str">"cmt">// the peak volume case or increasing volume case class="type">int furthestBajista = FindFurthestBajista(Time[one_vela],class="num">20); class=class="str">"cmt">//we look for the bearish candle closest to the 1st candle if(furthestBajista > class="num">0) { class=class="str">"cmt">//if this is true, which as I said before it will always be, we assign the candle values class=class="str">"cmt">//to the structure variables to draw the rectangles class="type">class="kw">datetime time1 = Time[furthestBajista]; class="type">class="kw">double price1 = closeArray[furthestBajista]; class="type">class="kw">double price2 = lowArray[furthestBajista]; newVela_Order_Block_bajista_2.price1 = price1; newVela_Order_Block_bajista_2.time1 = time1; newVela_Order_Block_bajista_2.price2 = price2 ; case_OrderBlockBajista_volumen = true; } else case_OrderBlockBajista_volumen = class="kw">false; } class=class="str">"cmt">//+------------------------------------------------------------------+ if(case_OrderBlockAlcista_normal == true && mitigados_alcsitas(newVela_Order_block_alcista.price2,openArray,closeArray,highArray,lowArray,Time,newVela_Order_block_alcista.time1,Time[class="num">0]) == class="num">0) class=class="str">"cmt">//we verify that the order block has not been mitigated { newVela_Order_block_alcista.mitigated = class="kw">false; class=class="str">"cmt">//we activate the order block status as unmitigated = class="kw">false newVela_Order_block_alcista.name = "Order Block Alcista normal" + TimeToString(newVela_Order_block_alcista.time1) ; class=class="str">"cmt">//we assign the name "Normal Bullish Order Block" + the time of one_Vela AddIndexToArray_alcistas(newVela_Order_block_alcista); class=class="str">"cmt">//we add to the array to then check if they are being mitigated and draw them } class=class="str">"cmt">//the same would be for the volume case if(case_orderblock_vol == true && mitigados_alcsitas(newVela_Order_block_volumen.price2,openArray,closeArray,highArray,lowArray,Time,newVela_Order_block_volumen.time1,Time[class="num">0]) == class="num">0) { newVela_Order_block_volumen.mitigated = class="kw">false; newVela_Order_block_volumen.name = "Order Block Alcista vol" + TimeToString(newVela_Order_block_volumen.time1) ; AddIndexToArray_alcistas(newVela_Order_block_volumen); } } class=class="str">"cmt">//--- Bearish version if(case_OrderBlockBajista_normal == true && mitigado_bajista(newVela_Order_Block_bajista.price2,openArray, closeArray, highArray, lowArray, Time, Time[class="num">0],newVela_Order_Block_bajista.time1) == class="num">0 ) class=class="str">"cmt">//we check if the bearish order block was not mitigated and the normal case is true { newVela_Order_Block_bajista.mitigated = class="kw">false; class=class="str">"cmt">//we initialize the state of the order block as unmitigated = class="kw">false newVela_Order_Block_bajista.name = ("Order Block Bajista ")+ TimeToString(newVela_Order_Block_bajista.time1) ; class=class="str">"cmt">//we assign the name as "Bearish Block Order" + the time of the 1st candle
◍ 未成交订单区块的矩形绘制与实时延展
订单区块被识别后,真正的价值在于把「未被价格触碰」的区块持续画在图上。上面这段逻辑先做了一层过滤:只对 mitigated 标记为 false 的牛市区块,调用 esOb_mitigado_array_alcista 拿回触碰时间,若返回 0 说明价格还没扫到这个区块。 绘制时先用 ObjectFind 查重,ChartID() 下找不到同名对象才 RectangleCreate,锚点 0 用区块首根 K 线时间 time1 与 price1,锚点 1 拉到当前 Time[0] 与 price2,线宽、填充、层级由外部变量控制。若对象已存在,只走 ObjectMove 把锚点 1 推到最新时间,避免每帧重建导致闪烁。 熊市区块 ob_bajistas 走的是同一套判断,只是配色与函数名换成 Bajista 系列。实盘里你开 MT5 把这段接进 OnCalculate 尾段,就能看到未 mitigation 的 OB 随 K 线右移自动延长;外汇与贵金属波动大,区块被瞬间刺穿的概率偏高,需把 mitigado 函数的容差参数按品种 ATR 调一调。
for(class="type">int i = class="num">0; i < ArraySize(ob_alcistas); i++) { class="type">class="kw">datetime mitigadoTime = esOb_mitigado_array_alcista(ob_alcistas[i],ob_alcistas[i].time1); if(ob_alcistas[i].mitigated == class="kw">false) { if(mitigadoTime == class="num">0) { if(ObjectFind(ChartID(),ob_alcistas[i].name) < class="num">0) { RectangleCreate(ChartID(), ob_alcistas[i].name, class="num">0, ob_alcistas[i].time1, ob_alcistas[i].price1, Time[class="num">0], ob_alcistas[i].price2,Color_Order_Block_Alcista, Witdth_order_block, Fill_order_block, Back_order_block,STYLE_SOLID); } else ObjectMove(ChartID(),ob_alcistas[i].name,class="num">1,Time[class="num">0],ob_alcistas[i].price2); } } } for(class="type">int i = class="num">0; i < ArraySize(ob_bajistas); i++) { class="type">class="kw">datetime mitigadoTime = esOb_mitigado_array_bajista(ob_bajistas[i],ob_bajistas[i].time1); if(ob_bajistas[i].mitigated == class="kw">false) { if(mitigadoTime == class="num">0) { if(ObjectFind(ChartID(),ob_bajistas[i].name) < class="num">0) { RectangleCreate(ChartID(), ob_bajistas[i].name,class="num">0, ob_bajistas[i].time1, ob_bajistas[i].price1, Time[class="num">0], ob_bajistas[i].price2,Color_Order_Block_Bajista,Witdth_order_block,Fill_order_block,Back_order_block,STYLE_SOLID); } else
「被 mitigation 的 OB 如何只报警一次」
订单块(OB)一旦被价格触碰 mitigation,脚本若不做去重就会每根 K 线弹一次 Alert,实战里纯属噪音。解决思路是给已处理的 OB 名字写进独立字符串数组,下次循环先查重再决定是否提示。 核心查重函数 Es_Eliminado_PriceTwo 用 for 遍历引用数组,命中名字就返回 true 并 break;Agregar_Index_Array_1 则用 ArrayResize 把数组加 1 并把新名字塞进末尾,失败会 Print 报错。两个函数配合,让多头数组 pricetwo_eliminados_oba 与空头数组 pricetwo_eliminados_obb 各自记住已报警的 OB。 遍历逻辑很直接:ob_alcistas[i].mitigated == true 且名字不在已删数组里,才 Alert 并写入数组然后 break;空头 ob_bajistas 同理。这样每个 OB 从被 mitigation 到图表删除前,终端只响一声。 外汇与贵金属市场高杠杆、滑点无常,这类 OB 预警仅作结构参考,价格回归概率不等于确定性。开 MT5 把下面两段直接挂进 EA,调 ob_xxx 数组来源即可验证报警次数是否收敛。
class="type">bool Es_Eliminado_PriceTwo(class="type">class="kw">string pName_ob , class="type">class="kw">string &pArray_price_two_eliminados[]) { class="type">bool a = class="kw">false; class=class="str">"cmt">// we create the variable "a" and initialize it to class="kw">false for(class="type">int i = class="num">0 ; i < ArraySize(pArray_price_two_eliminados) ; i++) class=class="str">"cmt">// we traverse all the indices of the array passed as a reference { if(pName_ob == pArray_price_two_eliminados[i]) class=class="str">"cmt">// we will compare all the positions in the array with the variable "pName_ob" { class=class="str">"cmt">// if the comparison is identical the variable "a" becomes true a = true; break; class=class="str">"cmt">// we exit the loop } } class="kw">return a; class=class="str">"cmt">//we class="kw">return the value of "a" } class="type">void Agregar_Index_Array_1(class="type">class="kw">string &array[], class="type">class="kw">string pValor_Aagregar) { class="type">int num_array = ArraySize(array); if (ArrayResize(array, num_array + class="num">1) == num_array + class="num">1) { array[num_array] = pValor_Aagregar; } else { Print("Error resizing array"); } } for(class="type">int i = class="num">0; i < ArraySize(ob_alcistas); i++) { if(ob_alcistas[i].mitigated == true && Es_Eliminado_PriceTwo(ob_alcistas[i].name, pricetwo_eliminados_oba) == class="kw">false) { Alert("El order block alcista esta siendo mitigado: ", TimeToString(ob_alcistas[i].time1)); Agregar_Index_Array_1(pricetwo_eliminados_oba, ob_alcistas[i].name); break; } } for(class="type">int i = class="num">0; i < ArraySize(ob_bajistas); i++) { if(ob_bajistas[i].mitigated == true && Es_Eliminado_PriceTwo(ob_bajistas[i].name, pricetwo_eliminados_obb) == class="kw">false) { Alert("El order block bajista esta siendo mitigado: ", TimeToString(ob_bajistas[i].time1)); Agregar_Index_Array_1(pricetwo_eliminados_obb, ob_bajistas[i].name); break; } }
订单块对象的清理与指标句柄释放
在 EA 或指标退出时,若不主动清掉画在图上的订单块对象,MT5 图表会残留一堆无名矩形,下次加载还可能因重名报错。下面的循环就是按名称逐个删除多头与空头订单块对象:先遍历 ob_alcistas 数组,再遍历 ob_bajistas 数组,每次用 ObjectDelete(ChartID(), 名称) 精准移除。 OnDeinit 里除了删对象,还要用 ArrayFree 把四个动态数组(ob_bajistas、ob_alcistas、pricetwo_eliminados_oba、pricetwo_eliminados_obb)的内存释放掉;若 atr_i 句柄有效,必须调用 IndicatorRelease 归还 ATR 指标句柄,否则会泄漏指标资源。 OnInit 的初始化逻辑里有两道硬校验:搜索区间 Rango_universal_busqueda 小于 40 会直接 Print('Search range too small') 并返回 INIT_PARAMETERS_INCORRECT;atr_i 创建失败(iATR 返回 INVALID_HANDLE)则返回 INIT_FAILED。只有都过了才把四个数组 Resize 为 0 并回 INIT_SUCCEEDED。外汇与贵金属波动剧烈,ATR(14) 周期参数可按品种特性微调,但搜索区间设得太小会漏掉有效订单块。
for(class="type">int i = class="num">0 ; i < ArraySize(ob_alcistas) ; i++) class=class="str">"cmt">// we iterate through the array of bullish order blocks { ObjectDelete(ChartID(),ob_alcistas[i].name); class=class="str">"cmt">// we class="kw">delete the object using the name of the order block } for(class="type">int n = class="num">0 ; n < ArraySize(ob_bajistas) ; n++) class=class="str">"cmt">// we iterate through the array of bearish order blocks { ObjectDelete(ChartID(),ob_bajistas[n].name); class=class="str">"cmt">// we class="kw">delete the object using the name of the order block } } OnInit() y OnDeinit() OnDeinit() if(atr_i != INVALID_HANDLE) IndicatorRelease(atr_i); OnDeinit() class="type">void OnDeinit(const class="type">int reason) { class=class="str">"cmt">//--- Eliminar_Objetos(); ArrayFree(ob_bajistas); ArrayFree(ob_alcistas); ArrayFree(pricetwo_eliminados_oba); ArrayFree(pricetwo_eliminados_obb); if(atr_i != INVALID_HANDLE) IndicatorRelease(atr_i ); } class=class="str">"cmt">//--- class="type">int OnInit() { class=class="str">"cmt">//--- indicator buffers mapping tiempo_ultima_vela = class="num">0; atr_i = iATR(_Symbol,PERIOD_CURRENT,class="num">14); if(Rango_universal_busqueda < class="num">40) { Print("Search range too small"); class="kw">return (INIT_PARAMETERS_INCORRECT); } if( atr_i== INVALID_HANDLE) { Print("Error copying data for indicators"); class="kw">return(INIT_FAILED); } ArrayResize(ob_bajistas,class="num">0); ArrayResize(ob_alcistas,class="num">0); ArrayResize(pricetwo_eliminados_oba,class="num">0); ArrayResize(pricetwo_eliminados_obb,class="num">0); class=class="str">"cmt">//--- class="kw">return(INIT_SUCCEEDED); }
◍ 记住这一条就够了
这套订单块指标的雏形已经跑通:用‘聪明钱’思路标记内圈交易者活动区,配合矩形绘制和提醒配置,MT5 里加载 Order_Block_Indicator_New.mq5(85.47 KB)就能直接看信号。有读者在 EURUSD H4 回测 2025.1.1–2.1 时发现指标选了测试窗口外的 2/3 区块,作者回应循环从 Universal_search_range 倒推到蜡烛 6,four_candle = one_candle - 3 最低也只到 3,并未引用未来 K 线。 外汇和贵金属波动剧烈、杠杆高危,任何指标都只是概率参考;下一阶段计划把缓冲区和 TP/SL 线接上,甚至用订单簿做更准的区块检测。先在你的图表上验证这一版,比追新功能更实在。