使用MetaTrader 5作为MetaTrader 4的信号提供者·综合运用
🔗

使用MetaTrader 5作为MetaTrader 4的信号提供者·综合运用

(3/3)·从只读账户抓取到订单中转,把 MT5 信号喂给 MT4 的完整拼图

含代码示例实战向 第 3/3 篇
靠锦标赛首页每五分钟刷一次数据来跟单,往往错过最佳进场,还把服务器流量打爆。用只读模式直连选手账户抓实时活动,才是能跑通的路子。很多人在这上面反复试错,却没意识到 EA 在禁止交易权限下也能读仓位和订单。

◍ 平仓逻辑里只挑盈利单砍

这段代码在做的核心动作是:从订单池里筛出与信号反向、手数完全一致且当前利润为正的那一张,执行平仓。它不碰亏损单,也不做部分平仓,属于「锁定利润优先」的出场思路。 注意筛选条件层层嵌套:MagicNumber 要对上账户标识、品种要一致、订单类型必须是信号反向(SELL 信号对应 BUY 单,BUY 信号对应 SELL 单)、手数严格等于传入 volume、且 OrderProfit()>0 才允许 CloseOneOrder。外汇与贵金属杠杆高,这种只平盈利单的逻辑可能让亏损单一直飘着,回撤扩大概率不低,实盘前务必在 MT5 策略测试器里跑一遍。 如果市场里没有「同手数且盈利」的对应单,整个函数静默退出,不会兜底平任何其他单。这意味着信号端若漏发或手数四舍五入偏差,出场可能完全不触发。

MQL5 / C++
class="type">void DealOUT(class="type">class="kw">string symbol, class="type">int cmd, class="type">class="kw">double volume, class="type">class="kw">double price, class="type">class="kw">double stoploss, class="type">class="kw">double takeprofit, class="type">int account)
{
   class="type">int type = -class="num">1;
   class="type">int i=class="num">0;
   
   if(cmd==OP_SELL)
      type = OP_BUY;
   else if(cmd==OP_BUY)
      type = OP_SELL;  
   
   class="type">class="kw">string comment = "OUT."+Type2String(cmd);
   class=class="str">"cmt">//--- 寻找相同订单大小且利润大于0的订单
   for(i=class="num">0;i<OrdersTotal();i++)
   {
      if(OrderSelect(i,SELECT_BY_POS))
      {
         if(OrderMagicNumber()==account)
         {
            if(OrderSymbol()==symbol)
            {
               if(OrderType()==type)
               {
                  if(OrderLots()==volume)
                  {
                     if(OrderProfit()>class="num">0)
                     {
                        if(CloseOneOrder(OrderTicket(), symbol, type, volume))
                        {
                           Print("已经找到订单大小相同且利润大于0的订单,并且执行完毕。");
                           class="kw">return;
                        }
                     }
                  }
               }
            }
         }
      }
   }
}

按魔术码与手数撮合平仓的两种路径

这段逻辑干的事很直接:在订单池里按 account(魔术码)、symbol、type、volume 四个维度精确匹配,命中就调用 CloseOneOrder 平掉,并打印“已经找到相同大小订单并已执行。”后直接 return。 若没有完全一致的手数订单,代码转入第二段循环:先取 OrdersTotal() 存进 limit,再扫一遍同魔术码同品种同方向的订单,只要单笔手数小于等于待平量 volume_to_clear 且 OrderProfit()>0,就平掉该笔并把 volume_to_clear 减去已平手数;当 volume_to_clear 减到 0 时打印“所有所需订单已经关闭.”。 实盘里这种写法倾向用于网格或分批止盈的清理:优先平掉盈利小单凑手数,而非一把全平。外汇与贵金属杠杆高,这种循环平仓若遇点差扩大或成交拒绝,volume_to_clear 可能残留,需要在 CloseOneOrder 返回失败时任其继续而非中断。 把下面这段代码直接丢进 MT5 的 EA 里,把 account、symbol、type、volume 换成你的实参,就能在策略测试器里观察平仓顺序是否符合预期。

MQL5 / C++
   class=class="str">"cmt">//--- 寻找相同订单大小且不计利润的订单
   for(i=class="num">0;i<OrdersTotal();i++)
   {
      if(OrderSelect(i,SELECT_BY_POS))
      {
         if(OrderMagicNumber()==account)
         {
            if(OrderSymbol()==symbol)
            {
               if(OrderType()==type)
               {
                  if(OrderLots()==volume)
                  {
                     if(CloseOneOrder(OrderTicket(), symbol, type, volume))
                     {
                        Print("已经找到相同大小订单并已执行。");
                        class="kw">return;
                     }
                  }
               }
            }
         }
      }
   }
   class="type">class="kw">double volume_to_clear = volume;
   class=class="str">"cmt">//--- 寻找订单大小小于所需并且利润大于0的订单
   class="type">int limit = OrdersTotal();
   for(i=class="num">0;i<limit;i++)
   {
      if(OrderSelect(i,SELECT_BY_POS))
      {
         if(OrderMagicNumber()==account)
         {
            if(OrderSymbol()==symbol)
            {
               if(OrderType()==type)
               {
                  if(OrderLots()<=volume_to_clear)
                  {
                     if(OrderProfit()>class="num">0)
                     {
                        if(CloseOneOrder(OrderTicket(), symbol, type, OrderLots()))
                        {
                           Print("找到订单大小小于所需且利润大于0的订单并且已经执行.");
                           volume_to_clear-=OrderLots();
                           if(volume_to_clear==class="num">0)
                           {
                              Print("所有所需订单已经关闭.");

「按手数分层平仓的循环写法」

这段逻辑干的事很直接:先扫一遍当前账户所有订单,把 magic 号、交易品种、订单类型都匹配上、且手数小于等于待平量 volume_to_clear 的仓单逐个平掉。每平一单,volume_to_clear 就减去该单手数,归零就直接 return 退出。 注意循环里有个反直觉的写法:i 初始化成 -1,因为 for 循环末尾 i++ 会把它拉回 0,下一轮才能从持仓列表头部重新扫。每次平掉订单后 OrdersTotal() 会变,所以 limit 必须重新取,否则会漏单或越界。 手数大于待平量的订单放到第二段循环处理,同样用 OrderSelect(SELECT_BY_POS) 按位置遍历,靠 OrderMagicNumber()==account 和 OrderSymbol()==symbol 做过滤。外汇与贵金属杠杆高,这类批量平仓脚本在滑点扩大时可能平不完全,上 MT5 策略测试器用真实点差回测一遍才放心。

MQL5 / C++
  class=class="str">"cmt">//--- 寻找所有手数小于所需的订单
  limit = OrdersTotal();
  for(i=class="num">0;i<limit;i++)
  {
    if(OrderSelect(i,SELECT_BY_POS))
    {
      if(OrderMagicNumber()==account)
      {
        if(OrderSymbol()==symbol)
        {
          if(OrderType()==type)
          {
            if(OrderLots()<=volume_to_clear)
            {
              if(CloseOneOrder(OrderTicket(), symbol, type, OrderLots()))
              {
                Print("订单大小较小的订单已经找到并执行。");
                volume_to_clear-=OrderLots();
                if(volume_to_clear==class="num">0)
                {
                  Print("所有所需订单都已关闭.");
                  class="kw">return;
                }
                limit = OrdersTotal();
                i = -class="num">1; class=class="str">"cmt">// 因为它会在循环结束前增加,会结果为0.
              }
            }
          }
        }
      }
    }
  }
  class=class="str">"cmt">//--- 寻找订单大小较大的订单
  for(i=class="num">0;i<OrdersTotal();i++)
  {
    if(OrderSelect(i,SELECT_BY_POS))
    {
      if(OrderMagicNumber()==account)
      {
        if(OrderSymbol()==symbol)

◍ 按手数清仓时的超额回补逻辑

在批量平仓函数里,若指定要清掉的手数 volume_to_clear 比当前订单手数小,会优先平掉手数更大的单。当 OrderLots() 大于等于 volume_to_clear 时,调用 CloseOneOrder 平掉该 ticket,并把 volume_to_clear 减去已平手数。 如果减完之后 volume_to_clear 变成负数,说明这一单平多了。代码里用 DealIN 按原单的止损止盈反向开一张单,把多平的那部分手数补回来,避免账户净头寸被误改。 循环结束后若 volume_to_clear 仍不为 0,说明池子里没有足够大的单凑满目标手数,Print 会打出剩余未平量。外汇与贵金属杠杆高,这类自动回补若价格跳空可能成交在预期外点位,实盘前建议在 MT5 策略测试器用历史数据跑一遍确认。

MQL5 / C++
{
   if(OrderType()==type)
   {
      if(OrderLots()>=volume_to_clear)
      {
         if(CloseOneOrder(OrderTicket(), symbol, type, OrderLots()))
         {
            Print("订单大小较大的订单已经找到并执行.");
            volume_to_clear-=OrderLots();
            if(volume_to_clear<class="num">0)class=class="str">"cmt">//关闭太多订单了
            {
               class=class="str">"cmt">//开新订单抵消关闭多出的部分
               DealIN(symbol,type,volume_to_clear,price,OrderStopLoss(),OrderTakeProfit(),account);
            }
            else if(volume_to_clear==class="num">0)
            {
               Print("所有所需订单都已关闭.");
               class="kw">return;
            }
         }
      }
   }
}
if(volume_to_clear!=class="num">0)
{
   Print("一定大小的订单剩余未关闭: ",volume_to_clear);
}

别急着下结论

这套把 MT5 信号转发到 MT4 的私有桥接方案,作者只验证了可行性:用个人电脑跑 dealclient_mql4 与 dealserver_mql5,跟随自选策略与锦标赛选手,已连续运行一周未报错。ZIP 包体积很小,dealserver_mql5.zip 25.12 KB、dealclient_mql4.zip 8.82 KB,普通用户下载即可在本地搭环境。 但评论区暴露了实盘坑:2014 年有用户反映 MT4 端只拿到 .mql4 源码、缺编译好的 .ex4,导致图表加载失败;错误 129「平均价格已更改」也说明订单同步对 broker 端成交回报敏感。外汇与贵金属杠杆高,桥接延迟或丢包都可能放大滑点损失。 真要上真实账户,先在自己 VPS 上用策略测试器跑满两周,确认 ex4 编译通过、server 心跳正常再考虑。代码别人能改、能提 PR,但亏的钱不会有人替你担。

让小布替你跑这套
跨平台信号中转的监控与异常告警,小布盯盘的 AIGC 已内置,打开对应品种页即可看到延迟与断连提示,你只需专注策略匹配。

常见问题

可以读取仓位、订单与已成交交易的信息,包括挂单与进场类型,这为跨平台信号传输提供了基础。
可以,小布盯盘内置了跨源延迟和断连检测,打开品种页就能看到中转健康度,不必自己写日志轮询。
订单含挂单与预进场信息但缺进场类型,交易带 ENUM_DEAL_ENTRY 能区分开平仓性质,实际落地多倾向以交易为主、订单补挂单。
合理设计的中转服务在实时条件下可维持效率,关键在于用轻量协议广播而非逐端同步阻塞。