开发先进的 ICT 交易系统:在订单块指标中实现信号·综合运用
(3/3)·从缓冲区到 TP/SL 可视化,把前兩篇的订单块逻辑真正跑成可下单的系统
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 文件头部就能编译,外汇与贵金属杠杆高、滑点随机,参数只是起点,真跑之前请在策略测试器用历史数据验证距离是否合理。
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 能被捕获。外汇与贵金属杠杆高,上述缓和后触发只是概率倾向,实盘前请在策略测试器用历史数据校验最大等待参数的鲁棒性。
<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 < <span class="functions">ArraySize</span>(ob_bajistas); i++) { <span class="keyword">if</span>(ob_bajistas[i].mitigated == <span class="macro">true</span> && !Es_Eliminado_PriceTwo(ob_bajistas[i].name, pricetwo_eliminados_obb) && <span class="functions">ObjectFind</span>(<span class="functions">ChartID</span>(), ob_bajistas[i].name) >= <span class="number">class="num">0</span>) { <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)); buscar_obb = <span class="macro">class="kw">false</span>; <span class="comment">class=class="str">"cmt">// Pause search</span> time_b = <span class="functions">iTime</span>(<span class="predefines">_Symbol</span>,<span class="predefines">_Period</span>,<span class="number">class="num">1</span>); <span class="comment">class=class="str">"cmt">// Record the mitigation time</span> Agregar_Index_Array_1(pricetwo_eliminados_obb, ob_bajistas[i].name); <span class="keyword">class="kw">break</span>; } } <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 < <span class="functions">ArraySize</span>(ob_alcistas); i++) { <span class="keyword">if</span>(ob_alcistas[i].mitigated == <span class="macro">true</span> && !Es_Eliminado_PriceTwo(ob_alcistas[i].name, pricetwo_eliminados_oba) && <span class="functions">ObjectFind</span>(<span class="functions">ChartID</span>(), ob_alcistas[i].name) >= <span class="number">class="num">0</span>) { <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)); time_ = <span class="functions">iTime</span>(<span class="predefines">_Symbol</span>,<span class="predefines">_Period</span>,<span class="number">class="num">0</span>); Agregar_Index_Array_1(pricetwo_eliminados_oba, ob_alcistas[i].name); buscar_oba = <span class="macro">class="kw">false</span>; <span class="comment">class=class="str">"cmt">// Pause search</span> <span class="keyword">class="kw">break</span>; } } <span class="comment">class=class="str">"cmt">// Buy</span> <span class="keyword">if</span>(buscar_oba == <span class="keyword">class="kw">false</span> && time_ > <span class="number">class="num">0</span> && 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> && time_b > <span class="number">class="num">0</span> && 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,参数本身不改变胜率方向,只影响触发密度。
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 用引用传出来,调用方直接读这几个变量就行。
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 两个入参。
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 倾向反弹”,高风险品种务必人工复核再跟单。
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 价格,图表上立刻出图。
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 的计算逻辑,直接重跑这段就能看见新边界,不用手动拖线。 外汇与贵金属杠杆高,这类画线仅作参考,实际成交价可能因滑点偏离标注位,概率上不保证触线即反转。
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 线才会按时间轴正确显示,空值处不连线。外汇与贵金属波动剧烈,缓冲区错位可能导致止损线误绘,实盘前务必在策略测试器里核对。
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(看跌)这些静态变量,下一根才重新搜块。外汇与贵金属波动剧烈,这类自动画线仅作辅助参考,实际止损可能被瞬滑点击穿。
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 总数对得上。
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 数据,看缓冲线触发频率和权益曲线衰减再上实盘。
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;