构建蜡烛图趋势约束模型(第8部分):EA开发(II)·综合运用
📘

构建蜡烛图趋势约束模型(第8部分):EA开发(II)·综合运用

第 3/3 篇

EA 骨架里的手工作业:参数与 RSI 句柄

这段 MT5 专家顾问的头部声明,把仓位、止损、止盈和移动止损直接做成外部输入:Lots=0.1、StopLoss=100 点、TakeProfit=200 点、TrailingStop=50 点。调参时不用动代码,在属性框改数字即可,但外汇和贵金属杠杆高,0.1 手在迷你账户也可能触发强平,先按自己保证金算一遍。 全局里除了 RSI 相关变量,还挂了一个 CTrade trade 实例,后续发单、平仓都靠它。OnInit 里用 iRSI(_Symbol, PERIOD_CURRENT, RSI_Period, PRICE_CLOSE) 拿指标句柄,失败就打印并返回 INIT_FAILED,这是 MT5 里最容易忽略的坑——句柄无效时后面全废。 OnDeinit 只做一件事:IndicatorRelease(rsi_handle) 释放资源,避免反复加载 EA 时句柄泄漏。OnTick 开头先抓日线开盘价和收盘价判断趋势方向,再用 CopyBuffer 取当前 RSI 值,取不到就直接 return。下面这段是原文截到一半的循环,用来在趋势反转时平掉旧仓。

MQL5 / C++
class="kw">input class="type">class="kw">double Lots = class="num">0.1;                    class=class="str">"cmt">// Lot size
class="kw">input class="type">class="kw">double StopLoss = class="num">100;                 class=class="str">"cmt">// Stop Loss in points
class="kw">input class="type">class="kw">double TakeProfit = class="num">200;               class=class="str">"cmt">// Take Profit in points
class="kw">input class="type">class="kw">double TrailingStop = class="num">50;              class=class="str">"cmt">// Trailing Stop in points
class=class="str">"cmt">// Global variables
class="type">class="kw">double rsi_value;
class="type">int rsi_handle;
CTrade trade;  class=class="str">"cmt">// Declare an instance of the CTrade class
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert initialization function                                     |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnInit()
  {
   class=class="str">"cmt">// Create an RSI indicator handle
   rsi_handle = iRSI(_Symbol, PERIOD_CURRENT, RSI_Period, PRICE_CLOSE);
   if (rsi_handle == INVALID_HANDLE)
     {
       Print("Failed to create RSI indicator handle");
       class="kw">return(INIT_FAILED);
     }
   class="kw">return(INIT_SUCCEEDED);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert deinitialization function                                   |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnDeinit(class="kw">const class="type">int reason)
  {
   class=class="str">"cmt">// Release the RSI indicator handle
   IndicatorRelease(rsi_handle);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert tick function                                               |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnTick()
  {
   class=class="str">"cmt">// Determine current daily trend(bullish or bearish)
   class="type">class="kw">double daily_open = iOpen(_Symbol, PERIOD_D1, class="num">0);
   class="type">class="kw">double daily_close = iClose(_Symbol, PERIOD_D1, class="num">0);
   class="type">bool is_bullish = daily_close > daily_open;
   class="type">bool is_bearish = daily_close < daily_open;
   class=class="str">"cmt">// Get the RSI value for the current bar
   class="type">class="kw">double rsi_values[];
   if (CopyBuffer(rsi_handle, class="num">0, class="num">0, class="num">1, rsi_values) <= class="num">0)
     {
       Print("Failed to get RSI value");
       class="kw">return;
     }
   rsi_value = rsi_values[class="num">0];
   class=class="str">"cmt">// Close open positions if the trend changes
   for (class="type">int i = PositionsTotal() - class="num">1; i >= class="num">0; i--)
     {
       if (PositionSelect(PositionGetSymbol(i)))  class=class="str">"cmt">// Corrected usage
         {

◍ 反向持仓与趋势冲突时的平仓逻辑

这段逻辑解决一个实战常见坑:当持仓方向与实时判定的趋势相反时,EA 必须能拿到具体 ticket 并立刻平掉,而不是笼统遍历。下面这段代码先取持仓类型和 ticket,再比对 is_bearish / is_bullish 信号,冲突即用 trade.PositionClose 平仓。 int position_type = PositionGetInteger(POSITION_TYPE); ulong ticket = PositionGetInteger(POSITION_TICKET); // Get the position ticket

if ((position_type == POSITION_TYPE_BUY && is_bearish)

(position_type == POSITION_TYPE_SELL && is_bullish)) { trade.PositionClose(ticket); // Use the ulong variable directly } } } 开仓部分则卡死「无持仓」前提:多头条件为 is_bullish 且 rsi_value < RSI_Oversold,空单为 is_bearish 且 rsi_value > RSI_Overbought,两者都要求 PositionsTotal()==0 才下手,避免马丁加仓。SL/TP 直接用 StopLoss、TakeProfit 乘 _Point 算,外汇与贵金属波动大,这套参数在高杠杆下回撤可能很猛。 trailing stop 段从 PositionsTotal()-1 倒序扫,PositionSelect 配合 PositionGetSymbol(i) 选对持仓;买仓用 SYMBOL_BID 测距,盈利超过 TrailingStop*_Point 才移动止损。倒序是为防止平仓后索引错位,直接抄正序循环会漏单。

MQL5 / C++
class="type">int position_type = PositionGetInteger(POSITION_TYPE);
class="type">ulong ticket = PositionGetInteger(POSITION_TICKET); class=class="str">"cmt">// Get the position ticket
if ((position_type == POSITION_TYPE_BUY && is_bearish) ||
    (position_type == POSITION_TYPE_SELL && is_bullish))
    {
    trade.PositionClose(ticket); class=class="str">"cmt">// Use the class="type">ulong variable directly
    }
}
}
class=class="str">"cmt">// Check for buy condition(bullish trend + RSI oversold)
if (is_bullish && rsi_value < RSI_Oversold)
  {
  class=class="str">"cmt">// No open positions? Place a buy order
  if (PositionsTotal() == class="num">0)
    {
    class="type">class="kw">double price = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
    class="type">class="kw">double sl = price - StopLoss * _Point;
    class="type">class="kw">double tp = price + TakeProfit * _Point;
    class=class="str">"cmt">// Open a buy order
trade.Buy(Lots, _Symbol, price, sl, tp, "TrendConstraintExpert Buy");
    }
  }
class=class="str">"cmt">// Check for sell condition(bearish trend + RSI overbought)
if (is_bearish && rsi_value > RSI_Overbought)
  {
  class=class="str">"cmt">// No open positions? Place a sell order
  if (PositionsTotal() == class="num">0)
    {
    class="type">class="kw">double price = SymbolInfoDouble(_Symbol, SYMBOL_BID);
    class="type">class="kw">double sl = price + StopLoss * _Point;
    class="type">class="kw">double tp = price - TakeProfit * _Point;
    class=class="str">"cmt">// Open a sell order
trade.Sell(Lots, _Symbol, price, sl, tp, "TrendConstraintExpert Sell");
    }
  }
class=class="str">"cmt">// Apply trailing stop
for (class="type">int i = PositionsTotal() - class="num">1; i >= class="num">0; i--)
  {
  if (PositionSelect(PositionGetSymbol(i))) class=class="str">"cmt">// Corrected usage
    {
    class="type">class="kw">double price = PositionGetDouble(POSITION_PRICE_OPEN);
    class="type">class="kw">double stopLoss = PositionGetDouble(POSITION_SL);
    class="type">class="kw">double current_price;
    if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY)
      {
      current_price = SymbolInfoDouble(_Symbol, SYMBOL_BID);
      if (current_price - price > TrailingStop * _Point)
        {
        if (stopLoss < current_price - TrailingStop * _Point)

「空单尾随止损的修改逻辑」

上面这段是处理卖出仓位的尾随止损分支。先取当前卖价 SymbolInfoDouble(_Symbol, SYMBOL_ASK),再用持仓开价与现价的差值判断是否越过 TrailingStop 个点的阈值。 判定条件 price - current_price > TrailingStop * _Point 成立时,说明浮盈空间已够移动止损。此时若原止损位在 current_price + TrailingStop*_Point 之外,或止损从未设置(stopLoss==0),就通过 trade.PositionModify 把止损提到市价上方 TrailingStop 点处,止盈维持原 POSITION_TP 不变。 在 MT5 里跑这段,TrailingStop 若设为 50,意味着空单盈利超过 50 点才触发第一次移位;外汇与贵金属杠杆高,点位跳动快,这类尾随可能在剧烈反转中连续改单,实际滑点成本需自己回测确认。

MQL5 / C++
         {
            trade.PositionModify(PositionGetInteger(POSITION_TICKET), current_price - TrailingStop * _Point, PositionGetDouble(POSITION_TP));
         }
      }
   }
   else if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL)
   {
      current_price = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
      if (price - current_price > TrailingStop * _Point)
      {
         if (stopLoss > current_price + TrailingStop * _Point || stopLoss == class="num">0)
         {
            trade.PositionModify(PositionGetInteger(POSITION_TICKET), current_price + TrailingStop * _Point, PositionGetDouble(POSITION_TP));
         }
      }
   }
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//HAPPY DEVELOPING!

在 MT5 回测里压一压趋势约束 EA

把「趋势约束 EA」丢进 MetaTrader 5 策略测试器,第一步是用历史数据搭回测环境,重点看它在不同波动段里是否真的只做趋势跟随、且严格卡 RSI 条件。先在测试器里选定 M1 周期,再把 EA 的输入参数和成交环境(点差、滑点、杠杆)按你实盘打算用的填进去,不然跑出来的曲线没有参考价值。 我自己习惯拿 Boom 500 指数先试水——这个品种跳点凶、趋势段清晰,很适合检验趋势类逻辑会不会乱开单。在 2023 年 1 月到 12 月的样本里跑一遍,主要盯三件事:盈利曲线斜率、最大回撤、以及 EA 违背 RSI 过滤的成交次数。 外汇和贵金属品种的高杠杆与滑点会放大回测与实盘的偏差,回测「可能」通过不代表实盘「倾向」盈利。任何参数微调都先在测试器里复跑,再考虑上真金白银。

◍ 下一步要补的缺口

这套不依赖外部指标的 Trend Constraint Expert 已经能在 MT5 测试器里跑出真实订单流,源码文件 5.76 KB,直接拖进编辑器就能编译验证。但它目前没写 Magic 数字,我在真实模拟账户上跑的时候,EA 会把账户里已有的其他订单也一并接管,这是实盘前必须修的坑。 日线趋势过滤那一层还太粗,入场条件只做了最基础的约束,和当前日线方向对齐的盈利概率并不高,后续得把入场逻辑拆细。外汇和贵金属杠杆高、滑点跳空频繁,没隔离订单的 EA 直接上模拟盘都有搅乱仓位的风险。 下一篇会补上 Magic 数字并把入场技术换一套写法,你現在就可以把源码里的 OrderSend 部分翻一遍,找找它没带标识位的地方。等 Magic 补齐,再拿同一条 EURUSD 日线样本回测,应该能看到订单互不干扰的差别。

常见问题

检查 OnInit 中是否调用 iRSI 并取得句柄,若返回 INVALID_HANDLE 需立即 return INIT_FAILED,避免后续数组越界。
在 Tick 事件里用 PositionGetInteger 读方向,若与趋势约束模型信号相反,调用 OrderClose 或 CTrade 平仓,不犹豫留仓。
可以,把 EA 代码或信号截图丢给小布,它会按价格行为规则指出反向持仓处理漏洞并给修改建议。
确认 ModifyOrder 的止损价优于市价且滑点允许,并在无挂单冲突时调用,回测用逐笔成交模式验证。
用对应品种最近 2 年实盘 tick 数据、点差按券商真实值设,开启任意方向成交,看权益曲线回撤段。