开发先进的 ICT 交易系统:在订单块指标中实现信号·综合运用
📡

开发先进的 ICT 交易系统:在订单块指标中实现信号·综合运用

(3/3)·从缓冲区到 TP/SL 可视化,把前兩篇的订单块逻辑真正跑成可下单的系统

偏理论 第 3/3 篇
很多人在指标里画出了订单块,却卡在“怎么让它发声”——信号不写进缓冲区,图表再漂亮也只是装饰。本篇把市场深度检测的订单块接上信号与交易骨架,让第三根烛形的量价失衡真正触发动作。

TP/SL 计算模式的参数化切口

给自定义指标加止盈止损,第一步不是写算法,而是把「算多远」的选择权交给使用者。这里用枚举 ENUM_TP_SL_STYLE 把两种风格钉死:ATR 按平均真实波幅动态拉伸距离,POINT 则用写死的点数做静态边界。 用 sinput group 把输入面板切成「-- Strategy --」「ATR」「POINT」几块,MT5 的参数框会按组折叠显示,不会一打开就堆出十几行让人眼晕。策略组里 tp_sl_style 默认落给 POINT,意味着不调参时指标走固定点逻辑。 ATR 组挂两个 double:Atr_Multiplier_1=1.5 管 TP,Atr_Multiplier_2=2.0 管 SL,乘数是相对当前 ATR 的倍率,欧元兑美元 M15 上 ATR 若为 80 点,SL 就自动落到约 160 点。POINT 组用 int 写死 TP_POINT=500、SL_POINT=275,波动再大也不偏移。 下面这段代码直接粘进 MQ5 文件头部就能编译,外汇与贵金属杠杆高、滑点随机,参数只是起点,真跑之前请在策略测试器用历史数据验证距离是否合理。

MQL5 / C++
enum ENUM_TP_SL_STYLE
  {
   ATR,
   POINT
  };
sinput group "-- Strategy --"
input ENUM_TP_SL_STYLE tp_sl_style = POINT; class=class="str">"cmt">// TP and SL style: ATR or fixed points
sinput group " ATR "
input class="type">class="kw">double Atr_Multiplier_1 = class="num">1.5; class=class="str">"cmt">// Multiplier for TP
input class="type">class="kw">double Atr_Multiplier_2 = class="num">2.0; class=class="str">"cmt">// Multiplier for SL
sinput group " POINT "
input class="type">int TP_POINT = class="num">500; class=class="str">"cmt">// Fixed points for TP
input class="type">int SL_POINT = class="num">275; class=class="str">"cmt">// Fixed points for SL
sinput group "--- Order Block Indicator settings ---"
sinput group "-- Order Block --"
input        class="type">int   Rango_universal_busqueda = class="num">500;
input        class="type">int   Witdth_order_block = class="num">1;
input        class="type">bool Back_order_block = true;
input        class="type">bool Fill_order_block = true;
input        class="type">class="kw">color Color_Order_Block_Bajista = clrRed;
input        class="type">class="kw">color Color_Order_Block_Alcista = clrGreen;
sinput group "-- Strategy --"
input        ENUM_TP_SL_STYLE tp_sl_style = POINT;
sinput group " ATR "
input        class="type">class="kw">double Atr_Multiplier_1 = class="num">1.5;
input        class="type">class="kw">double Atr_Multiplier_2 = class="num">2.0;
sinput group " POINT "
input        class="type">int TP_POINT = class="num">500;
input        class="type">int SL_POINT = class="num">275;

◍ 订单块缓和后的信号触发规则

信号不是订单块一缓和就发,而是靠两组静态变量卡住时间窗。time_a 和 time_b 记录缓和发生时刻,并叠加 5 根烛形的秒数边界;buscar_oba 与 buscar_obb 则像开关,决定当下还允不允许去找新的缓和块。 缓和发生时,time_a 被写成「当前时间 + 5 根烛形跨度」,同时 searcher 置 false,暂停继续搜块,先把这一笔的确认跑完。真正出信号看两条:30 周期 EMA 与缓和时间窗。买入要求 30 EMA 低于第 1 根烛形收盘价,且 time_a 仍大于当前时间;卖出反过来,30 EMA 高于第 1 根烛形收盘价,且 time_b 大于当前时间。这意味着信号只在缓和后 5 根烛形内有效,过期不候。 条件满足就填 TP、SL 缓冲区交后台执行;不满足则把 searcher 复位成 true,time_a、time_b 清 0,等最大时限过了重新搜块。外汇与贵金属波动剧烈,这套逻辑只是概率倾向,实盘前务必在 MT5 策略测试器用历史数据验证边界参数。

「从订单块缓和到触发下单的句柄与状态机」

这套策略在 OnInit 里先建一条周期 30、收盘价计算的 EMA 句柄,作为后续过滤趋势的背景线。若句柄返回 INVALID_HANDLE 直接 INIT_FAILED,避免在 MT5 上跑空指标。 用 static 布尔量 buscar_oba / buscar_obb 分别管多空 OB 的搜索开关,time_ 与 time_b 记录缓和发生的烛形时间。检测到软订单块被价格互动缓和后,立刻把对应开关置 false 并写时间,系统就不再重复扫同一块 OB。 触发交易的门槛是:buscar 为 false(说明已缓和)、time 大于 0(时间已落库)、new_vela 为真(仅限新烛形)。这三条同时成立,才去读 iClose(_Symbol,_Period,1) 取 1 号烛收盘价,并拿 EMA 数组里的历史值做比对。 若超过自定义最大等待时间仍不满足,代码重置 buscar 与 time,释放搜索让新 OB 能被捕获。外汇与贵金属杠杆高,上述缓和后触发只是概率倾向,实盘前请在策略测试器用历史数据校验最大等待参数的鲁棒性。

MQL5 / C++
<span class="keyword">class="type">int</span> hanlde_ma;
<span class="keyword">class="type">class="kw">double</span> ma[];
hanlde_ma = <span class="indicators">iMA</span>(<span class="predefines">_Symbol</span>,<span class="predefines">_Period</span>,<span class="number">class="num">30</span>,<span class="number">class="num">0</span>,<span class="macro">MODE_EMA</span>,<span class="macro">PRICE_CLOSE</span>);
<span class="keyword">if</span>(hanlde_ma == <span class="macro">INVALID_HANDLE</span>)
{
<span class="functions">Print</span>(<span class="class="type">class="kw">string">"The EMA indicator is not available. Failure: "</span>, <span class="predefines">_LastError</span>);
<span class="keyword">class="kw">return</span> <span class="macro">INIT_FAILED</span>;
}
<span class="comment">class=class="str">"cmt">//Variables for buy</span>
<span class="keyword">class="kw">static</span> <span class="keyword">class="type">bool</span> buscar_oba = <span class="macro">true</span>;
<span class="keyword">class="kw">static</span> <span class="keyword">class="type">class="kw">datetime</span> time_ = <span class="number">class="num">0</span>;
<span class="comment">class=class="str">"cmt">//Variables for sell</span>
<span class="keyword">class="kw">static</span> <span class="keyword">class="type">bool</span> buscar_obb = <span class="macro">true</span>;
<span class="keyword">class="kw">static</span> <span class="keyword">class="type">class="kw">datetime</span> time_b = <span class="number">class="num">0</span>;
<span class="comment">class=class="str">"cmt">//Bullish case</span>
<span class="keyword">if</span>(buscar_oba == <span class="keyword">true</span>)
<span class="comment">class=class="str">"cmt">//Bearish case</span>
<span class="keyword">if</span>(buscar_obb == <span class="keyword">true</span>)
<span class="comment">class=class="str">"cmt">// Bearish case</span>
<span class="keyword">for</span>(<span class="keyword">class="type">int</span> i = <span class="number">class="num">0</span>; i &lt; <span class="functions">ArraySize</span>(ob_bajistas); i++) {
&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">if</span>(ob_bajistas[i].mitigated == <span class="macro">true</span> &amp;&amp;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; !Es_Eliminado_PriceTwo(ob_bajistas[i].name, pricetwo_eliminados_obb) &amp;&amp;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="functions">ObjectFind</span>(<span class="functions">ChartID</span>(), ob_bajistas[i].name) &gt;= <span class="number">class="num">0</span>) {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="functions">Alert</span>(<span class="class="type">class="kw">string">"The bearishorder block is being mitigated: "</span>, <span class="functions">TimeToString</span>(ob_bajistas[i].time1));
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;buscar_obb = <span class="macro">class="kw">false</span>;&nbsp;&nbsp;<span class="comment">class=class="str">"cmt">// Pause search</span>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;time_b = <span class="functions">iTime</span>(<span class="predefines">_Symbol</span>,<span class="predefines">_Period</span>,<span class="number">class="num">1</span>);&nbsp;&nbsp;<span class="comment">class=class="str">"cmt">//  Record the mitigation time</span>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Agregar_Index_Array_1(pricetwo_eliminados_obb, ob_bajistas[i].name);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">class="kw">break</span>;
&nbsp;&nbsp;&nbsp;&nbsp;}
}
<span class="comment">class=class="str">"cmt">// Bullish case</span>
<span class="keyword">for</span>(<span class="keyword">class="type">int</span> i = <span class="number">class="num">0</span>; i &lt; <span class="functions">ArraySize</span>(ob_alcistas); i++) {
&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">if</span>(ob_alcistas[i].mitigated == <span class="macro">true</span> &amp;&amp;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; !Es_Eliminado_PriceTwo(ob_alcistas[i].name, pricetwo_eliminados_oba) &amp;&amp;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="functions">ObjectFind</span>(<span class="functions">ChartID</span>(), ob_alcistas[i].name) &gt;= <span class="number">class="num">0</span>) {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="functions">Alert</span>(<span class="class="type">class="kw">string">"The bullish order block is mitigated: "</span>, <span class="functions">TimeToString</span>(ob_alcistas[i].time1));
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;time_ = <span class="functions">iTime</span>(<span class="predefines">_Symbol</span>,<span class="predefines">_Period</span>,<span class="number">class="num">0</span>);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Agregar_Index_Array_1(pricetwo_eliminados_oba, ob_alcistas[i].name);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;buscar_oba = <span class="macro">class="kw">false</span>;&nbsp;&nbsp;<span class="comment">class=class="str">"cmt">// Pause search</span>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">class="kw">break</span>;
&nbsp;&nbsp;&nbsp;&nbsp;}
}
<span class="comment">class=class="str">"cmt">// Buy</span>
<span class="keyword">if</span>(buscar_oba == <span class="keyword">class="kw">false</span> &amp;&amp; time_ &gt; <span class="number">class="num">0</span> &amp;&amp; new_vela) { <span class="comment">class=class="str">"cmt">/* Code for Buy */</span> }
<span class="comment">class=class="str">"cmt">// Sell</span>
<span class="keyword">if</span>(buscar_obb == <span class="keyword">class="kw">false</span> &amp;&amp; time_b &gt; <span class="number">class="num">0</span> &amp;&amp; new_vela) { <span class="comment">class=class="str">"cmt">/* Code for Sell */</span> }
<span class="comment">class=class="str">"cmt">// Buy</span>
<span class="keyword">class="type">class="kw">double</span> close_ = <span class="functions">NormalizeDouble</span>(<span class="functions">iClose</span>(<span class="predefines">_Symbol</span>,<span class="predefines">_Period</span>,<span class="number">class="num">1</span>),<span class="predefines">_Digits</span>);

用时间窗卡死入场时机

这段逻辑给顺势单加了硬性的时间约束:只在信号出现后的 5 根当前周期K线内允许开仓,过期就复位重等。外汇与贵金属波动快,这种限时机制能过滤掉滞后追单,但高频重算也带来滑点风险,实盘前务必在 MT5 策略测试器跑一遍。 datetime max_time_espera = time_ + (PeriodSeconds() * 5); 先按当前周期秒数乘 5,算出买侧信号有效期终点。若收盘价站上均线前一根、且当前K线时间未超窗,才进买入分支。 卖侧同理:取前一根收盘价归一化后,低于 ma[1] 且未超时则允许卖。两侧各用 time_ / time_b 记录起点,超时后把标记归零并重新打开搜索开关 buscar_oba / buscar_obb。 直接把下面代码贴进 EA 的 OnTick 尾部,改 PeriodSeconds()*5 里的 5 就能调时间窗长短——比如日内品种用 3,日线波段用 10,参数本身不改变胜率方向,只影响触发密度。

MQL5 / C++
class="type">class="kw">datetime max_time_espera = time_ + (PeriodSeconds() * class="num">5);
if(close_ > ma[class="num">1] && iTime(_Symbol,_Period,class="num">0) <= max_time_espera) {
   class=class="str">"cmt">// Code for Buy...
}
class=class="str">"cmt">// Sell
close_ = NormalizeDouble(iClose(_Symbol,_Period,class="num">1),_Digits);
max_time_espera = time_b + (PeriodSeconds() * class="num">5);
if(close_ < ma[class="num">1] && iTime(_Symbol,_Period,class="num">0) <= max_time_espera) {
   class=class="str">"cmt">// Code for Sell...
}
class=class="str">"cmt">// Reset for Buy
if(iTime(_Symbol,_Period,class="num">0) > max_time_espera) {
   time_ = class="num">0;
   buscar_oba = true;
}
class=class="str">"cmt">// Reset for Sell
if(iTime(_Symbol,_Period,class="num">0) > max_time_espera) {
   time_b = class="num">0;
   buscar_obb = true;
}

◍ 用 ATR 与点值双轨算止损止盈

EA 里止盈止损别写死,GetTP_SL 这个函数同时接了两种算法:一种是 ATR 动态宽度,一种是固定点数,由外部参数 tp_sl_style 切换。 先说 ATR 路径。函数进来先建 atr 数组并 ArraySetAsSeries 设成时间序列,再用 CopyBuffer(atr_i,0,0,1,atr) 把最新一根 K 的 ATR 值捞进 atr[0];买仓 sl1=开仓价-atr[0]*Atr_Multiplier_1、tp1=开仓价+atr[0]*Atr_Multiplier_1,卖仓方向相反,sl2/tp2 用 Atr_Multiplier_2 再拉一倍距离。 点数路径更直接:SL_POINT 和 TP_POINT 乘上 _Point 再按多空加减,sl2/tp2 是点数乘 2,相当于给仓位留了双层缓冲。外汇和贵金属杠杆高,ATR 在重大数据后会瞬间膨胀,用动态乘数可能把止损甩得太远,实盘前建议在 MT5 策略测试器里把两种 style 各跑一遍看权益曲线断层。 下面这段是函数主干,注意它把 tp1/tp2/sl1/sl2 用引用传出来,调用方直接读这几个变量就行。

MQL5 / C++
class="type">void GetTP_SL(class="type">class="kw">double price_open_position, class="type">ENUM_POSITION_TYPE type, class="type">class="kw">double &tp1, class="type">class="kw">double &tp2, class="type">class="kw">double &sl1, class="type">class="kw">double &sl2)
class="type">class="kw">double atr[];
ArraySetAsSeries(atr, true);
CopyBuffer(atr_i, class="num">0, class="num">0, class="num">1, atr);
if (type == POSITION_TYPE_BUY) {
    sl1 = price_open_position - (atr[class="num">0] * Atr_Multiplier_1);
    sl2 = price_open_position - (atr[class="num">0] * Atr_Multiplier_2);
    tp1 = price_open_position + (atr[class="num">0] * Atr_Multiplier_1);
    tp2 = price_open_position + (atr[class="num">0] * Atr_Multiplier_2);
}
if (type == POSITION_TYPE_SELL) {
    sl1 = price_open_position + (atr[class="num">0] * Atr_Multiplier_1);
    sl2 = price_open_position + (atr[class="num">0] * Atr_Multiplier_2);
    tp1 = price_open_position - (atr[class="num">0] * Atr_Multiplier_1);
    tp2 = price_open_position - (atr[class="num">0] * Atr_Multiplier_2);
}
if (type == POSITION_TYPE_BUY) {
    sl1 = price_open_position - (SL_POINT * _Point);
    sl2 = price_open_position - (SL_POINT * _Point * class="num">2);
    tp1 = price_open_position + (TP_POINT * _Point);
    tp2 = price_open_position + (TP_POINT * _Point * class="num">2);
}
if (type == POSITION_TYPE_SELL) {
    sl1 = price_open_position + (SL_POINT * _Point);
    sl2 = price_open_position + (SL_POINT * _Point * class="num">2);
    tp1 = price_open_position - (TP_POINT * _Point);
    tp2 = price_open_position - (TP_POINT * _Point * class="num">2);
}

「把双止盈双止损画进图表右侧」

做价格行为分析时,止盈止损如果只存在代码变量里,肉眼很难和 K 线对照。写个函数用趋势线和文本对象把 TP/SL 直接投影到图表上,能省掉反复切换订单面板确认的麻烦。 函数入口吃四个价格:tp1、tp2 为止盈两档,sl1、sl2 为止损两档。时间维度上先抓当前烛形时间,再向后推 15 个周期得到 extension_time,线画到那里;文本标签的 text_time 再稍微错开一点,避免压在线上看不清。 画线逻辑很直白:tp1、tp2 用 STYLE_DOT 绿色虚线加「TP1」「TP2」标签,sl1、sl2 用红色虚线加「SL1」「SL2」。外汇和贵金属波动快、滑点大,这类水平线只是参照,实际成交价可能偏离,属高风险品种。 下面这段是创建趋势线的底层封装,参数顺序固定,改颜色或线型只需动 clr 和 style 两个入参。

MQL5 / C++
class="type">bool TrendCreate(class="type">long               chart_ID,      class=class="str">"cmt">// Chart ID
                    class="type">class="kw">string            name,   class=class="str">"cmt">// Line name
                    class="type">int               sub_window,       class=class="str">"cmt">// Subwindow index
                    class="type">class="kw">datetime                time1,         class=class="str">"cmt">// Time of the first point
                    class="type">class="kw">double                  price1,         class=class="str">"cmt">// Price of the first point
                    class="type">class="kw">datetime                time2,         class=class="str">"cmt">// Time of the second point
                    class="type">class="kw">double                  price2,         class=class="str">"cmt">// Price of the second point
                    class="type">class="kw">color             clr,           class=class="str">"cmt">// Line class="type">class="kw">color
                    ENUM_LINE_STYLE  style, class=class="str">"cmt">// Line style
                    class="type">int               width,           class=class="str">"cmt">// Line width
                    class="type">bool              back,           class=class="str">"cmt">// in the background
                    class="type">bool              selection   class=class="str">"cmt">// Selectable form moving
                    )
  {
  ResetLastError();
  if(!ObjectCreate(chart_ID,name,OBJ_TREND,sub_window,time1,price1,time2,price2))
    {
      Print(__FUNCTION__,
            ": ¡Failed to create trend line! Error code = ",GetLastError());
      class="kw">return(class="kw">false);
    }
  ObjectSetInteger(chart_ID,name,OBJPROP_COLOR,clr);
  ObjectSetInteger(chart_ID,name,OBJPROP_STYLE,style);
  ObjectSetInteger(chart_ID,name,OBJPROP_WIDTH,width);
  ObjectSetInteger(chart_ID,name,OBJPROP_BACK,back);
  ObjectSetInteger(chart_ID,name,OBJPROP_SELECTABLE,selection);
  ObjectSetInteger(chart_ID,name,OBJPROP_SELECTED,selection);
  ChartRedraw(chart_ID);
  class="kw">return(true);
  }

用代码在K线锚点贴自定义文字

在 MT5 图表上动态标注关键价位或信号,比手动拖文本框靠谱得多。下面这个函数把创建文本对象的过程封装好了,传入图表 ID、锚点时间和价格就能把字钉在指定位置。 TextCreate 的第一个参数是 chart_ID,通常主图用 0;sub_window 填 0 表示主窗口,指标子窗格则从 1 开始计数。anchor 用 ENUM_ANCHOR_POINT 控制文字相对锚点的对齐方式,比如左上、右下,避免遮挡蜡烛。 函数里先调 ResetLastError() 清掉旧错误码,再 ObjectCreate 建 OBJ_TEXT 对象。若返回 false,说明创建失败,此时用 GetLastError() 打错误码,常见失败原因是同名对象已存在(错误码 4200)。 创建成功后一连串 ObjectSetString / ObjectSetInteger / ObjectSetDouble 把文字内容、字体、字号、旋转角、对齐点和颜色写进去。angle 以度为单位,0 为水平;想让标注倾斜着跟趋势线平行,就给它传趋势角度值。 实盘里你可以把这个函数挂在 EA 的 OnTick,当黄金 1 小时图出现吞没形态时,在形态低点价位数组位置贴一句“PB 倾向反弹”,高风险品种务必人工复核再跟单。

MQL5 / C++
class="type">bool TextCreate(class="type">long               chart_ID,               class=class="str">"cmt">// Chart ID
                    class="type">class="kw">string             name,                   class=class="str">"cmt">// Object name
                    class="type">int                sub_window,             class=class="str">"cmt">// Subwindow index
                    class="type">class="kw">datetime           time,                   class=class="str">"cmt">// Anchor time
                    class="type">class="kw">double             price,                  class=class="str">"cmt">// Anchor price
                    class="type">class="kw">string             text,                   class=class="str">"cmt">// the text
                    class="type">class="kw">string             font,                   class=class="str">"cmt">// Font
                    class="type">int                font_size,              class=class="str">"cmt">// Font size
                    class="type">class="kw">color              clr,                    class=class="str">"cmt">// class="type">class="kw">color
                    class="type">class="kw">double             angle,                  class=class="str">"cmt">// Text angle
                    ENUM_ANCHOR_POINT  anchor,                 class=class="str">"cmt">// Anchor point
                    class="type">bool               back=class="kw">false,             class=class="str">"cmt">// font
                    class="type">bool               selection=class="kw">false)        class=class="str">"cmt">// Selectable for moving
  {
class=class="str">"cmt">//--- reset error value
   ResetLastError();
class=class="str">"cmt">//--- create "Text" object
   if(!ObjectCreate(chart_ID,name,OBJ_TEXT,sub_window,time,price))
     {
      Print(__FUNCTION__,
            ": ¡Failed to create object \"Text\"! Error code = ",GetLastError());
      class="kw">return(class="kw">false);
     }
   ObjectSetString(chart_ID,name,OBJPROP_TEXT,text);
   ObjectSetString(chart_ID,name,OBJPROP_FONT,font);
   ObjectSetInteger(chart_ID,name,OBJPROP_FONTSIZE,font_size);
   ObjectSetDouble(chart_ID,name,OBJPROP_ANGLE,angle);
   ObjectSetInteger(chart_ID,name,OBJPROP_ANCHOR,anchor);
   ObjectSetInteger(chart_ID,name,OBJPROP_COLOR,clr);

◍ 用脚本把 TP/SL 画线甩到图上

在 MT5 里手动拉止盈止损线既慢又容易点错,这段逻辑直接把 4 条水平线(TP1、TP2、SL1、SL2)和对应文字一次性画到当前图表。绿线代表两个止盈位,红线代表两个止损位,都用点线样式,方便和 K 线区分。 画线时以当前 K 线开盘时间为起点,向右延伸 15 根当前周期的时间长度(extension_time),文字标签再往后推 2 根周期,避免压在线上看不清。比如你在 H1 图表跑这段,线会向右铺 15 小时,标签落在第 17 小时位置。 TrendCreate 里把 back 参数设 true、selectable 和 selected 设 false,意味着这些线置于底层且不可被鼠标选中,不会干扰你正常拖拽分析。外汇和贵金属波动剧烈,这类辅助线只是视觉参考,实际成交价可能因滑点偏离标注位,属高风险场景。 想验证就新建一个 EA 或脚本,把下面代码贴进 DrawTP_SL 函数,编译后传两组 TP 和 SL 价格,图表上立刻出图。

MQL5 / C++
class="type">void DrawTP_SL(class="type">class="kw">double tp1, class="type">class="kw">double tp2, class="type">class="kw">double sl1, class="type">class="kw">double sl2)
  {
   class="type">class="kw">string   curr_time = TimeToString(iTime(_Symbol,_Period,class="num">0));
   class="type">class="kw">datetime extension_time = iTime(_Symbol,_Period,class="num">0) + (PeriodSeconds(PERIOD_CURRENT) * class="num">15);
   class="type">class="kw">datetime text_time = extension_time + (PeriodSeconds(PERIOD_CURRENT) * class="num">2);
   TrendCreate(ChartID(), curr_time + " TP1", class="num">0, iTime(_Symbol,_Period,class="num">0), tp1, extension_time, tp1, clrGreen, STYLE_DOT, class="num">1, true, class="kw">false);
   TextCreate(ChartID(), curr_time + " TP1 - Text", class="num">0, text_time, tp1, "TP1", "Arial", class="num">8, clrGreen, class="num">0.0, ANCHOR_CENTER);
   TrendCreate(ChartID(), curr_time + " TP2", class="num">0, iTime(_Symbol,_Period,class="num">0), tp2, extension_time, tp2, clrGreen, STYLE_DOT, class="num">1, true, class="kw">false);
   TextCreate(ChartID(), curr_time + " TP2 - Text", class="num">0, text_time, tp2, "TP2", "Arial", class="num">8, clrGreen, class="num">0.0, ANCHOR_CENTER);
   TrendCreate(ChartID(), curr_time + " SL1", class="num">0, iTime(_Symbol,_Period,class="num">0), sl1, extension_time, sl1, clrRed, STYLE_DOT, class="num">1, true, class="kw">false);
   TextCreate(ChartID(), curr_time + " SL1 - Text", class="num">0, text_time, sl1, "SL1", "Arial", class="num">8, clrRed, class="num">0.0, ANCHOR_CENTER);
   TrendCreate(ChartID(), curr_time + " SL2", class="num">0, iTime(_Symbol,_Period,class="num">0), sl2, extension_time, sl2, clrRed, STYLE_DOT, class="num">1, true, class="kw">false);
   TextCreate(ChartID(), curr_time + " SL2 - Text", class="num">0, text_time, sl2, "SL2", "Arial", class="num">8, clrRed, class="num">0.0, ANCHOR_CENTER);
  }

「把止盈止损画进图表的那几行」

上面这段调用把策略算出的 TP1、TP2、SL1、SL2 一次性渲染到当前图表。TrendCreate 负责画水平延伸线,TextCreate 在相同价位打上标签,绿线代表止盈、红线代表止损,肉眼扫一眼就能核对挂单位置。 TrendCreate 的第一参数用 ChartID() 锁定图表,第二参数用 curr_time+" TP1" 拼出唯一对象名,避免重复执行时旧线残留。起点时间取 iTime(_Symbol,_Period,0) 即当前 K 线开盘,终点延伸到 extension_time,射线样式 STYLE_DOT、宽度 1、背后填充关掉。 TextCreate 的锚点用 ANCHOR_CENTER,字体 Arial 8 号,偏移 0.0,所以文字正好压在虚线中间不偏不倚。若你改过 tp1/tp2/sl1/sl2 的计算逻辑,直接重跑这段就能看见新边界,不用手动拖线。 外汇与贵金属杠杆高,这类画线仅作参考,实际成交价可能因滑点偏离标注位,概率上不保证触线即反转。

MQL5 / C++
  TrendCreate(ChartID(),curr_time+" TP1",class="num">0,iTime(_Symbol,_Period,class="num">0),tp1,extension_time,tp1,clrGreen,STYLE_DOT,class="num">1,true,class="kw">false);
  TextCreate(ChartID(),curr_time+" TP1 - Text",class="num">0,text_time,tp1,"TP1","Arial",class="num">8,clrGreen,class="num">0.0,ANCHOR_CENTER);
  TrendCreate(ChartID(),curr_time+" TP2",class="num">0,iTime(_Symbol,_Period,class="num">0),tp2,extension_time,tp2,clrGreen,STYLE_DOT,class="num">1,true,class="kw">false);
  TextCreate(ChartID(),curr_time+" TP2 - Text",class="num">0,text_time,tp2,"TP2","Arial",class="num">8,clrGreen,class="num">0.0,ANCHOR_CENTER);
  TrendCreate(ChartID(),curr_time+" SL1",class="num">0,iTime(_Symbol,_Period,class="num">0),sl1,extension_time,sl1,clrRed,STYLE_DOT,class="num">1,true,class="kw">false);
  TextCreate(ChartID(),curr_time+" SL1 - Text",class="num">0,text_time,sl1,"SL1","Arial",class="num">8,clrRed,class="num">0.0,ANCHOR_CENTER);
  TrendCreate(ChartID(),curr_time+" SL2",class="num">0,iTime(_Symbol,_Period,class="num">0),sl2,extension_time,sl2,clrRed,STYLE_DOT,class="num">1,true,class="kw">false);
  TextCreate(ChartID(),curr_time+" SL2 - Text",class="num">0,text_time,sl2,"SL2","Arial",class="num">8,clrRed,class="num">0.0,ANCHOR_CENTER);
}

TP/SL 缓冲区也要扩到六个

之前我们只用 2 个缓冲区存价格区间,现在要把绘图和缓冲区数量一起从 2 拉到 6,否则 TP、SL 画不出来也没法回看。 除了原有的买/卖订单块缓冲,新建了 tp1、tp2、sl1、sl2 四个 double 数组,并在 SetIndexBuffer 里分别挂到索引 2~5。注意索引 0、1 已被订单块占用,别写串了。 每个数组都要 ArraySetAsSeries(..., true) 设成时间序列,再用 ArrayFill 填 EMPTY_VALUE 初始化。这样 MT5 图表上 TP/SL 线才会按时间轴正确显示,空值处不连线。外汇与贵金属波动剧烈,缓冲区错位可能导致止损线误绘,实盘前务必在策略测试器里核对。

MQL5 / C++
class="macro">#class="kw">property indicator_label3 "Take Profit class="num">1"
class="macro">#class="kw">property indicator_label4 "Take Profit class="num">2"
class="macro">#class="kw">property indicator_label5 "Stop Loss class="num">1"
class="macro">#class="kw">property indicator_label6 "Stop Loss class="num">2"
class="macro">#class="kw">property  indicator_buffers class="num">6
class="macro">#class="kw">property  indicator_plots class="num">6
class="type">class="kw">double tp1_buffer[];
class="type">class="kw">double tp2_buffer[];
class="type">class="kw">double sl1_buffer[];
class="type">class="kw">double sl2_buffer[];
SetIndexBuffer(class="num">2, tp1_buffer, INDICATOR_DATA);
SetIndexBuffer(class="num">3, tp2_buffer, INDICATOR_DATA);
SetIndexBuffer(class="num">4, sl1_buffer, INDICATOR_DATA);
SetIndexBuffer(class="num">5, sl2_buffer, 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
ArraySetAsSeries(tp1_buffer, true);
ArraySetAsSeries(tp2_buffer, true);
ArrayFill(tp1_buffer, class="num">0, class="num">0, EMPTY_VALUE); class=class="str">"cmt">// Initialize to EMPTY_VALUE
ArrayFill(tp2_buffer, class="num">0, class="num">0, EMPTY_VALUE); class=class="str">"cmt">// Initialize to EMPTY_VALUE
ArraySetAsSeries(sl1_buffer, true);
ArraySetAsSeries(sl2_buffer, true);
ArrayFill(sl1_buffer, class="num">0, class="num">0, EMPTY_VALUE); class=class="str">"cmt">// Initialize to EMPTY_VALUE
ArrayFill(sl2_buffer, class="num">0, class="num">0, EMPTY_VALUE); class=class="str">"cmt">// Initialize to EMPTY_VALUE

◍ 内存回收与图表清理的落地细节

指标跑久了,订单块数组和TP/SL对象会持续堆积。日线换新柱时若不清场,ob_alcistas、ob_bajistas 这些数组在回溯测试里可能拖慢整体性能,也可能让图表越画越乱。 OnCalculate 里用全局变量 tiempo_ultima_vela_1 记录上一根 D1 柱时间,一旦 iTime(_Symbol,PERIOD_D1,0) 变化就触发 Eliminar_Objetos 并 ArrayFree 四个数组,旧数据不再占用内存。 OnDeinit 里除了清数组,还要释放 ATR 与 EMA 的句柄:IndicatorRelease 对无效句柄先判 INVALID_HANDLE 再调用,避免移除指标时留下孤儿资源。MarketBookRelease 的返回值得打印出来,能直接看到订单簿是否真的断开。 开仓挂单的 TP/SL 计算上,买入用规范化后的卖价走 GetTP_SL,卖出用买价;DrawTP_SL 把 tp1/tp2/sl1/sl2 画到图上,同时用 iBarShift 找当前柱索引写进对应缓冲区。画完一轮记得重置 time_a、buscar_oba(看涨)和 time_b、search_obb(看跌)这些静态变量,下一根才重新搜块。外汇与贵金属波动剧烈,这类自动画线仅作辅助参考,实际止损可能被瞬滑点击穿。

MQL5 / C++
class="type">class="kw">datetime    tiempo_ultima_vela_1;
if(tiempo_ultima_vela_1 != iTime(_Symbol,PERIOD_D1,  class="num">0))
   {
     Eliminar_Objetos();
     ArrayFree(ob_bajistas);
     ArrayFree(ob_alcistas);
     ArrayFree(pricetwo_eliminados_oba);
     ArrayFree(pricetwo_eliminados_obb);
     tiempo_ultima_vela_1 = iTime(_Symbol,PERIOD_D1,  class="num">0);
   }
class="type">void OnDeinit(class="kw">const class="type">int reason)
  {
   Eliminar_Objetos();
   ArrayFree(ob_bajistas);
   ArrayFree(ob_alcistas);
   ArrayFree(pricetwo_eliminados_oba);
   ArrayFree(pricetwo_eliminados_obb);
   if(atr_i  != INVALID_HANDLE)
      IndicatorRelease(atr_i);
   if(hanlde_ma != INVALID_HANDLE) class=class="str">"cmt">//EMA
      IndicatorRelease(hanlde_ma);
   ResetLastError();
    if(MarketBookRelease(_Symbol)) class=class="str">"cmt">//Verify if closure was successful
     Print("Order book successfully closed for: " , _Symbol); class=class="str">"cmt">//Print success message if so
   else
     Print("Order book closed with errors for: " , _Symbol , "  Last error: " , GetLastError()); class=class="str">"cmt">//Print error message with code if not
  }
class="type">void Eliminar_Objetos()
  {
   for(class="type">int i = class="num">0 ; i < ArraySize(ob_alcistas) ; i++) class=class="str">"cmt">// iterate through the array of bullish order blocks
     {
       ObjectDelete(ChartID(),ob_alcistas[i].name); class=class="str">"cmt">// class="kw">delete the object class="kw">using the order block&class="macro">#x27;s name
     }
   for(class="type">int n = class="num">0 ; n < ArraySize(ob_bajistas) ; n++) class=class="str">"cmt">// iterate through the array of bearish order blocks
     {
       ObjectDelete(ChartID(),ob_bajistas[n].name);  class=class="str">"cmt">// class="kw">delete the object class="kw">using the order block&class="macro">#x27;s name
     }
class=class="str">"cmt">//Delete all TP and SL lines
   ObjectsDeleteAll(class="num">0," TP",-class="num">1,-class="num">1);
   ObjectsDeleteAll(class="num">0," SL",-class="num">1,-class="num">1);
  }
   class="type">class="kw">string short_name = "Order Block Indicator";
   IndicatorSetString(INDICATOR_SHORTNAME,short_name);
class=class="str">"cmt">// Set data precision for digits
class=class="str">"cmt">// Assign labels for each plot
   PlotIndexSetString(class="num">0, PLOT_LABEL, "Bullish Order Block");

「多空双向挂单的标签与缓冲写入」

画线指标的图例命名得在初始化后单独指定,否则 MT5 默认只显示『Buffer 1』这类无意义标签。下面这组调用把 5 条绘制线分别标成 Bearish Order Block、Take Profit 1/2、Stop Loss 1/2,索引号从 1 起算,和 OnCalculate 里 buffer 下标差一位,复制时容易踩坑。 买侧先取当前 normalized ask 价,再调 GetTP_SL 按 POSITION_TYPE_BUY 算出两档止盈、两档止损,DrawTP_SL 负责画水平线。随后用 iBarShift 配 iTime(_Symbol,_Period,0) 把数值写进当前 K 线对应的 buffer 位——注意是 0 号 K 线(最右一根),不是历史柱。 卖侧逻辑镜像:取 bid 价、传 POSITION_TYPE_SELL、同样写 tp1/tp2/sl1/sl2 四个 buffer。写完把 time_ 与 time_b 归零,buscar_oba、buscar_obb 置 true,相当于告诉下一帧『可以继续找 OB 区块了』。外汇与贵金属杠杆高,这类自动标线仅作结构参考,实盘信号失效概率不低,需自行回测。 直接把这段粘进 EA 的订单触发分支,能在 MT5 上立刻看到多空两套 TP/SL 线随 tick 刷新;若标签乱码,优先查 PlotIndexSetString 的索引是否和 #property indicator_buffers 总数对得上。

MQL5 / C++
  PlotIndexSetString(class="num">1, PLOT_LABEL, "Bearish Order Block");
  PlotIndexSetString(class="num">2, PLOT_LABEL, "Take Profit class="num">1");
  PlotIndexSetString(class="num">3, PLOT_LABEL, "Take Profit class="num">2");
  PlotIndexSetString(class="num">4, PLOT_LABEL, "Stop Loss class="num">1");
  PlotIndexSetString(class="num">5, PLOT_LABEL, "Stop Loss class="num">2");
class=class="str">"cmt">//Buy
class="type">class="kw">double ask= NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK),_Digits);
class="type">class="kw">double tp1;
class="type">class="kw">double tp2;
class="type">class="kw">double sl1;
class="type">class="kw">double sl2;
GetTP_SL(ask,POSITION_TYPE_BUY,tp1,tp2,sl1,sl2);
DrawTP_SL(tp1,tp2,sl1,sl2);
tp1_buffer[iBarShift(_Symbol,PERIOD_CURRENT,iTime(_Symbol,_Period,class="num">0))] = tp1;
tp2_buffer[iBarShift(_Symbol,PERIOD_CURRENT,iTime(_Symbol,_Period,class="num">0))] = tp2;
sl1_buffer[iBarShift(_Symbol,PERIOD_CURRENT,iTime(_Symbol,_Period,class="num">0))] = sl1;
sl2_buffer[iBarShift(_Symbol,PERIOD_CURRENT,iTime(_Symbol,_Period,class="num">0))] = sl2;
time_ = class="num">0;
buscar_oba = true;
class=class="str">"cmt">//Sell
class="type">class="kw">double bid = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID),_Digits);
class="type">class="kw">double tp1;
class="type">class="kw">double tp2;
class="type">class="kw">double sl1;
class="type">class="kw">double sl2;
GetTP_SL(bid,POSITION_TYPE_SELL,tp1,tp2,sl1,sl2);
DrawTP_SL(tp1,tp2,sl1,sl2);
tp1_buffer[iBarShift(_Symbol,PERIOD_CURRENT,iTime(_Symbol,_Period,class="num">0))] = tp1;
tp2_buffer[iBarShift(_Symbol,PERIOD_CURRENT,iTime(_Symbol,_Period,class="num">0))] = tp2;
sl1_buffer[iBarShift(_Symbol,PERIOD_CURRENT,iTime(_Symbol,_Period,class="num">0))] = sl1;
sl2_buffer[iBarShift(_Symbol,PERIOD_CURRENT,iTime(_Symbol,_Period,class="num">0))] = sl2;
time_b = class="num">0;
buscar_obb = true;

把这条线请下神坛

订单块指标做到这一步,本质只是把市场深度量转成了几条可观测的缓冲线,给手动或自动交易一个参考锚点,不是决策本身。上面那段代码把止盈、止损写进独立缓冲区,用的就是当前品种、当前周期、0号K线时间映射出的索引: tp1_buffer、tp2_buffer 存两档止盈价,sl1_buffer、sl2_buffer 存两档止损价,全部挂在最新一根bar上。逻辑直白,但实战里若价格跳空,iBarShift 映射的索引可能错位,回测和实盘会出现细微偏差。 外汇和贵金属杠杆高、滑点随机,这类缓冲区信号只能提高动作一致性,不保证胜率。下一步若接风险管理类做EA,建议先在策略测试器跑至少三个月 tick 数据,看缓冲线触发频率和权益曲线衰减再上实盘。

MQL5 / C++
tp1_buffer[iBarShift(_Symbol,PERIOD_CURRENT,iTime(_Symbol,_Period,class="num">0))] = tp1;
tp2_buffer[iBarShift(_Symbol,PERIOD_CURRENT,iTime(_Symbol,_Period,class="num">0))] = tp2;
sl1_buffer[iBarShift(_Symbol,PERIOD_CURRENT,iTime(_Symbol,_Period,class="num">0))] = sl1;
sl2_buffer[iBarShift(_Symbol,PERIOD_CURRENT,iTime(_Symbol,_Period,class="num">0))] = sl2;
把信号监控交给小布盯盘
这些基于市场深度的订单块信号与 TP/SL 可视化逻辑,小布盯盘的 AIGC 已内置到对应品种页,你只管看策略是否吻合自己的风控阈值,重复计算交给小布。

常见问题

宽区间容易把噪声当成块,聚焦第三根烛形配合前后两根的量比与主体位置,能更干净地捕捉供需失衡的那一下。
通常在缓冲区状态翻转或块失效条件触发前加标志位锁,文中主代码清理段会处理这类去重与重置。
小布盯盘内置了同源思路的 AIGC 诊断,打开品种页即可看到订单块与信号提示,不必自己编译指标也能先验证逻辑。
缓冲区坐标要和蜡烛时间轴对齐,否则 TP/SL 线会漂;另外点值换算错会让止损视觉窄实际宽,外汇贵金属波动大更要注意。
订单块基于概率而非确定性,外汇贵金属属高风险品种,任何信号都可能在流动性断裂时失效,应先在模拟环境跑样本外数据。