构建蜡烛图趋势约束模型(第7部分):为EA开发优化我们的模型·进阶篇
🤖

构建蜡烛图趋势约束模型(第7部分):为EA开发优化我们的模型·进阶篇

(2/3)· 信号收到了却不敢自动下单?本篇拆解如何用MA交叉与RRR矩形补齐入场出场逻辑

含代码示例 第 2/3 篇
很多交易者把指标信号接进EA就以为万事大吉,结果实盘里该进不进、该退不退。第六版模型能推信号到聊天软件,却没回答‘信号够不够好、什么时候撤’这两个致命问题。带着这两个漏洞跑自动化,等于给盲飞的系统装了自动驾驶。

◍ 给信号缓冲区接上双均线交叉

在原有 RSI 买卖区逻辑之外,再开两个绘图缓冲区(Buffer 6 / Buffer 7)专门承载均线交叉信号:6 号画蓝色箭头标 Sell,7 号画橙色箭头标 Buy,两者条件互逆、处理对称。 输入参数定为快线 7 期、慢线 21 期均线(Entry_MA_fast / Entry_MA_slow),交易者可直接在 MT5 指标属性里改这两个数来过滤信号密度。外汇与贵金属波动剧烈,均线交叉仅是概率倾向,实盘须自担高风险。 核心判定写在 OnCalculate 里:当 MA8 下穿 MA9 且本根 K 线 Open 不大于前根 D1 收盘时,Buffer6 在 High 挂箭头并触发 Sell 弹窗;反向穿越且 Open 不低于收盘时,Buffer7 在 Low 挂箭头并触发 Buy。把这段代码贴进已有指标,编译后就能在图上直接比对 RSI 区与均线叉的共振。

MQL5 / C++
class="macro">#class="kw">property indicator_type6 DRAW_ARROW
class="macro">#class="kw">property indicator_width6 class="num">1
class="macro">#class="kw">property indicator_color6 0x0000FF
class="macro">#class="kw">property indicator_label6 "Sell"
class="macro">#class="kw">property indicator_type7 DRAW_ARROW
class="macro">#class="kw">property indicator_width7 class="num">1
class="macro">#class="kw">property indicator_color7 0xFFAA00
class="macro">#class="kw">property indicator_label7 "Buy"
class="kw">input class="type">int Entry_MA_fast = class="num">7 ;
class="kw">input class="type">int Entry_MA_slow = class="num">21 ;
SetIndexBuffer(class="num">5, Buffer6);
   PlotIndexSetDouble(class="num">5, PLOT_EMPTY_VALUE, EMPTY_VALUE);
   PlotIndexSetInteger(class="num">5, PLOT_DRAW_BEGIN, MathMax(Bars(Symbol(), PERIOD_CURRENT)-PLOT_MAXIMUM_BARS_BACK+class="num">1, OMIT_OLDEST_BARS+class="num">1));
   PlotIndexSetInteger(class="num">5, PLOT_ARROW, class="num">242);
   SetIndexBuffer(class="num">6, Buffer7);
   PlotIndexSetDouble(class="num">6, PLOT_EMPTY_VALUE, EMPTY_VALUE);
   PlotIndexSetInteger(class="num">6, PLOT_DRAW_BEGIN, MathMax(Bars(Symbol(), PERIOD_CURRENT)-PLOT_MAXIMUM_BARS_BACK+class="num">1, OMIT_OLDEST_BARS+class="num">1));
   PlotIndexSetInteger(class="num">6, PLOT_ARROW, class="num">241);
class=class="str">"cmt">//Indicator Buffer class="num">6
        if(MA8[i] < MA9[i]
        && MA8[i+class="num">1] > MA9[i+class="num">1] class=class="str">"cmt">//Moving Average crosses below Moving Average
        && Open2[i] <= Close[class="num">1+barshift_D1[i]] class=class="str">"cmt">//Candlestick Open <= Candlestick Close
        )
            {
             Buffer6[i] = High[i]; class=class="str">"cmt">//Set indicator value at Candlestick High
             if(i == class="num">1 && Time[class="num">1] != time_alert) myAlert("indicator", "Sell"); class=class="str">"cmt">//Alert on next bar open
             time_alert = Time[class="num">1];
            }
        else
            {
             Buffer6[i] = EMPTY_VALUE;
            }
        class=class="str">"cmt">//Indicator Buffer class="num">7
        if(MA8[i] > MA9[i]
        && MA8[i+class="num">1] < MA9[i+class="num">1] class=class="str">"cmt">//Moving Average crosses above Moving Average
        && Open2[i] >= Close[class="num">1+barshift_D1[i]] class=class="str">"cmt">//Candlestick Open >= Candlestick Close
        )
            {
             Buffer7[i] = Low[i]; class=class="str">"cmt">//Set indicator value at Candlestick Low
             if(i == class="num">1 && Time[class="num">1] != time_alert) myAlert("indicator", "Buy"); class=class="str">"cmt">//Alert on next bar open
             time_alert = Time[class="num">1];
            }
        else
            {

空值缓冲区的初始化写法

在 MQL5 自定义指标里,未触发信号的坐标必须显式赋 EMPTY_VALUE,否则历史柱上会残留上一轮计算的脏数据,造成图表线条断裂或误连。 上面这段只截了收尾两行,核心动作就是把 Buffer7 的第 i 根柱置为空值,通常写在循环体的 else 分支或条件不满足的兜底位置。 实盘验证时把指标加载到 EURUSD 的 M5,观察 Buffer7 对应彩线是否在无信号区彻底隐去;外汇与贵金属杠杆高,信号缺失不等于方向反转,仅代表当前过滤条件未通过。

MQL5 / C++
      Buffer7[i] = EMPTY_VALUE;
      }

「用矩形框出盈利与风险区」

在双均线交叉加 RSI 过滤的信号逻辑上,可以顺手把「盈利空间」和「回撤风险」画成矩形。思路不复杂:快线下穿慢线且 Buffer7 条件成立时,在当前 K 线低点下方放一个绿色矩形代表盈利区,宽度至少 5 根 K 线,高度向下延伸 X 点;其上再叠一个红色矩形,高度为绿色的 1/3,代表风险区。反向信号同理,只是矩形翻到高点上方。 参数必须全开放。下面这段代码把矩形宽度、买卖两侧盈利矩形高度(默认都是 50 点)、颜色都做成 input,你按品种波动率和自己能承受的回撤直接改数就行。外汇和贵金属杠杆高,点值随合约变,50 点只是样例,实盘前务必在 MT5 用历史数据核对矩形是否覆盖了你真正想标记的价格带。 核心画图靠三个函数:ObjectFind 先查重名对象避免重复创建;ObjectCreate 指定 OBJ_RECTANGLE 类型并给四个坐标(时间轴取 time[i] 到 time[i+RectWidth],价格轴取高低边);ObjectSetInteger 设颜色、线型、线宽,返回 true 才代表属性写成功。把这段塞进 OnCalculate 的 Buffer6/Buffer7 分支里,每出一个信号就自动落框。 [CODE] 标记内的片段已经把 RSI 超买超卖判定和矩形创建耦合在一起:以 Sell 为例,close[i] 做上边,减去 RectHeightPointsSell*myPoint 做下边,ObjectCreate 用 time[i] 和 time[i+RectWidth] 拉出 5 根 K 线宽的绿框,再用 ObjectSetInteger 染绿、实线、2 像素。复制去编译,开 EURUSD 的 M5 就能看到信号旁自动长出框。

MQL5 / C++
<span class="comment">class=class="str">"cmt">//--- new inputs for rectangles</span>
<span class="keyword">class="kw">input</span> <span class="keyword">class="type">int</span> RectWidth = <span class="number">class="num">5</span>;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="comment">class=class="str">"cmt">// Width of the rectangle in bars</span>
<span class="keyword">class="kw">input</span> <span class="keyword">class="type">int</span> RectHeightPointsBuy = <span class="number">class="num">50</span>;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">class=class="str">"cmt">// Height of the profit rectangle in points for Buy</span>
<span class="keyword">class="kw">input</span> <span class="keyword">class="type">int</span> RectHeightPointsSell = <span class="number">class="num">50</span>;&nbsp;&nbsp;&nbsp;&nbsp; <span class="comment">class=class="str">"cmt">// Height of the profit rectangle in points for Sell</span>
<span class="keyword">class="kw">input</span> <span class="keyword">class="type">color</span> ProfitRectColor = <span class="macro">clrGreen</span>;&nbsp;&nbsp;<span class="comment">class=class="str">"cmt">// Color of the profit rectangle</span>
<span class="keyword">class="kw">input</span> <span class="keyword">class="type">color</span> RiskRectColor = <span class="macro">clrRed</span>;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">class=class="str">"cmt">// Color of the risk rectangle</span>
<span class="keyword">if</span>(RSI[i] &gt; Overbought) {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">if</span>(close[i] &gt; MA[i])
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Buffer6[i] = close[i] - pips * myPoint;
&nbsp;&nbsp;&nbsp;&nbsp;}
&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">if</span>(RSI[i] &lt; Oversold) {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">if</span>(close[i] &lt; MA[i])
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Buffer7[i] = close[i] + pips * myPoint;
&nbsp;&nbsp;&nbsp;&nbsp;}
&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">if</span>(Buffer6[i] &gt; <span class="number">class="num">0</span>) {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Buffer1[i] = close[i] - pips * myPoint;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Buffer3[i] = close[i] - pips * myPoint;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">if</span> (Buffer6[i - <span class="number">class="num">1</span>] &lt; <span class="number">class="num">0</span>) {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;myAlert(<span class="class="type">class="kw">string">"indicator"</span>, <span class="class="type">class="kw">string">"Sell Signal Detected!"</span>);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">if</span> (Audible_Alerts)
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="functions">Alert</span>(<span class="functions">Symbol</span>(), <span class="class="type">class="kw">string">" "</span>, <span class="functions">Period</span>(), <span class="class="type">class="kw">string">": Sell Signal Detected!"</span>);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">class=class="str">"cmt">// Create profit rectangle for Sell</span>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">class="type">class="kw">double</span> highProfitRect = close[i];
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">class="type">class="kw">double</span> lowProfitRect = close[i] - RectHeightPointsSell * myPoint;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">class="type">class="kw">string</span> profitRectName = <span class="class="type">class="kw">string">"SellProfitRect"</span> + <span class="functions">IntegerToString</span>(i);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">if</span> (<span class="functions">ObjectFind</span>(<span class="number">class="num">0</span>, profitRectName) != <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">ObjectCreate</span>(<span class="number">class="num">0</span>, profitRectName, <span class="macro">OBJ_RECTANGLE</span>, <span class="number">class="num">0</span>, time[i], highProfitRect, time[i + RectWidth], lowProfitRect);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="functions">ObjectSetInteger</span>(<span class="number">class="num">0</span>, profitRectName, <span class="macro">OBJPROP_COLOR</span>, ProfitRectColor);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="functions">ObjectSetInteger</span>(<span class="number">class="num">0</span>, profitRectName, <span class="macro">OBJPROP_STYLE</span>, <span class="macro">STYLE_SOLID</span>);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="functions">ObjectSetInteger</span>(<span class="number">class="num">0</span>, profitRectName, <span class="macro">OBJPROP_WIDTH</span>, <span class="number">class="num">2</span>);

◍ 买卖信号触发时的矩形标注逻辑

当指标 Buffer7[i] 大于 0 时,脚本判定为买入信号,并在 K 线 close[i] 基础上向上偏移 pips*myPoint 同时写入 Buffer2 与 Buffer4 两个缓冲位。若前一根 K 线 Buffer7[i-1] 小于 0,则视为信号翻转,调用 myAlert 并依据 Audible_Alerts 开关决定是否弹窗 Alert,提示「Buy Signal Detected!」。 信号确认后,代码会在图表上画两类矩形:盈利区从 close[i] 到 close[i]+RectHeightPointsBuy*myPoint,风险区则从 close[i]-(RectHeightPointsBuy/3)*myPoint 到 close[i],用 ObjectCreate 以 OBJ_RECTANGLE 类型绘制,线宽固定为 2、实线、按 ProfitRectColor / RiskRectColor 上色。矩形横向跨度由 time[i] 到 time[i+RectWidth] 决定,因此 RectWidth 直接控制标注窗口的 K 线数量。 对应地,卖出分支里风险矩形只取 RectHeightPointsSell/3 的高度,且 lowRiskRect = close[i] + (RectHeightPointsSell/3)*myPoint,即风险区被压在收盘价上方三分之一处。外汇与贵金属波动随机性高,这类矩形仅作视觉风险边界参考,实际止损仍应以账户承受度为准。 开 MT5 把 RectWidth 从默认改到 10 再看 EURUSD 小时图,能直观比对信号标注覆盖了几天行情;若矩形不显示,先查 ObjectFind 返回非 0 的分支是否被跳过。

MQL5 / C++
   class=class="str">"cmt">// Create risk rectangle for Sell
   class="type">class="kw">double highRiskRect = close[i];
   class="type">class="kw">double lowRiskRect = close[i] + (RectHeightPointsSell / class="num">3) * myPoint;
   class="type">class="kw">string riskRectName = "SellRiskRect" + IntegerToString(i);
   if (ObjectFind(class="num">0, riskRectName) != class="num">0) {
      ObjectCreate(class="num">0, riskRectName, OBJ_RECTANGLE, class="num">0, time[i], highRiskRect, time[i + RectWidth], lowRiskRect);
      ObjectSetInteger(class="num">0, riskRectName, OBJPROP_COLOR, RiskRectColor);
      ObjectSetInteger(class="num">0, riskRectName, OBJPROP_STYLE, STYLE_SOLID);
      ObjectSetInteger(class="num">0, riskRectName, OBJPROP_WIDTH, class="num">2);
   }
   }
   }
   if(Buffer7[i] > class="num">0) {
      Buffer2[i] = close[i] + pips * myPoint;
      Buffer4[i] = close[i] + pips * myPoint;
      if (Buffer7[i - class="num">1] < class="num">0) {
         myAlert("indicator", "Buy Signal Detected!");
         if (Audible_Alerts)
            Alert(Symbol(), " ", Period(), ": Buy Signal Detected!");
         class=class="str">"cmt">// Create profit rectangle for Buy
         class="type">class="kw">double highProfitRect = close[i] + RectHeightPointsBuy * myPoint;
         class="type">class="kw">double lowProfitRect = close[i];
         class="type">class="kw">string profitRectName = "BuyProfitRect" + IntegerToString(i);
         if (ObjectFind(class="num">0, profitRectName) != class="num">0) {
            ObjectCreate(class="num">0, profitRectName, OBJ_RECTANGLE, class="num">0, time[i], highProfitRect, time[i + RectWidth], lowProfitRect);
            ObjectSetInteger(class="num">0, profitRectName, OBJPROP_COLOR, ProfitRectColor);
            ObjectSetInteger(class="num">0, profitRectName, OBJPROP_STYLE, STYLE_SOLID);
            ObjectSetInteger(class="num">0, profitRectName, OBJPROP_WIDTH, class="num">2);
         }
         class=class="str">"cmt">// Create risk rectangle for Buy
         class="type">class="kw">double highRiskRect = close[i] - (RectHeightPointsBuy / class="num">3) * myPoint;
         class="type">class="kw">double lowRiskRect = close[i];

用矩形把买点风险区钉在图上

在循环里给每个候选买点画一块风险矩形,核心是先查重再创建。用 ObjectFind 在当前图表(chart_id=0)按名字搜 "BuyRiskRect"+序号,返回非 0 表示对象还不存在,这时才进创建分支,避免每帧重画导致闪烁或对象堆积。 创建时用 OBJ_RECTANGLE 类型,时间轴取 time[i] 到 time[i+RectWidth],价格轴上下边界是 highRiskRect 与 lowRiskRect,这两个变量由前序逻辑算好,代表该买点上方止损缓冲与下方支撑失效区。 属性三连设的是实线、指定颜色、线宽 2。线宽参数若设 1 在部分高分屏几乎看不见,2 是兼顾可见与克制的经验值;外汇与贵金属波动跳空频繁,这类矩形只标概率风险区,不预示方向。 ObjectFind 与 ObjectCreate、ObjectSetInteger 的原型决定了第一参数永远传图表 ID,0 即当前图。开 MT5 把这段塞进 OnCalculate 的循环末尾,调一下 RectWidth 从 3 改成 5,能直观看到风险区时间跨度拉长。

MQL5 / C++
class="type">class="kw">string riskRectName = "BuyRiskRect" + IntegerToString(i);
if (ObjectFind(class="num">0, riskRectName) != class="num">0) {
   ObjectCreate(class="num">0, riskRectName, OBJ_RECTANGLE, class="num">0, time[i], highRiskRect, time[i + RectWidth], lowRiskRect);
   ObjectSetInteger(class="num">0, riskRectName, OBJPROP_COLOR, RiskRectColor);
   ObjectSetInteger(class="num">0, riskRectName, OBJPROP_STYLE, STYLE_SOLID);
   ObjectSetInteger(class="num">0, riskRectName, OBJPROP_WIDTH, class="num">2);
}

class="type">int ObjectFind(
  class="type">long  chart_id,   class=class="str">"cmt">// Chart ID(class="num">0 means the current chart)
  class="type">class="kw">string name       class=class="str">"cmt">// Name of the object to search for
);
class="type">bool ObjectCreate(
  class="type">long   chart_id,  class=class="str">"cmt">// Chart ID(class="num">0 means the current chart)
  class="type">class="kw">string name,      class=class="str">"cmt">// Name of the object to create
  ENUM_OBJECT type, class=class="str">"cmt">// Type of the object(e.g., OBJ_RECTANGLE, OBJ_TREND, etc.)
  class="type">int    sub_window, class=class="str">"cmt">// Number of the subwindow(class="num">0 means the main chart window)
  class="type">class="kw">datetime time1,    class=class="str">"cmt">// First coordinate time
  class="type">class="kw">double price1,     class=class="str">"cmt">// First coordinate price
  ...                class=class="str">"cmt">// Additional coordinates depending on the object type
);
class="type">bool ObjectSetInteger(
  class="type">long  chart_id,    class=class="str">"cmt">// Chart ID(class="num">0 means the current chart)
  class="type">class="kw">string name,       class=class="str">"cmt">// Name of the object
  class="type">int   prop_id,     class=class="str">"cmt">// ID of the class="kw">property to set(e.g., OBJPROP_COLOR, OBJPROP_STYLE, etc.)
  class="type">long  value        class=class="str">"cmt">// Value of the class="kw">property
);
把信号诊断交给小布盯盘
这些关于缓冲区组织和RRR框的判读,小布盯盘的AIGC已内置,打开对应品种页即可看到实时约束带,你只管确认要不要跟单。

常见问题

可将条件硬编码进EA算法,使EA脱离指标独立运行;或读取指标缓冲区布尔值响应,但发布时需两文件同路径,社区验证常报错。
MA交叉提供明确的入场触发,降低纯蜡烛形态误报,让手动可跟的逻辑更容易转成自动化条件。
用入场价与止损价差作潜在损失,与目标价差作潜在收益,RRR=潜在收益/潜在损失,绿红矩形分别标盈利与亏损区间。
目前小布盯盘内置的是信号与约束带可视化,EA编译发布涉及MQL5路径限制,需自行部署,小布负责把盘口诊断跑起来。
出场倾向结合RRR目标位与反向信号,红色矩形边界常作止损参考,具体在测试和验证阶段按历史表现微调。