MQL5 交易工具包(第 2 部分):扩展和实现仓位管理 EX5 库·进阶篇
📦

MQL5 交易工具包(第 2 部分):扩展和实现仓位管理 EX5 库·进阶篇

(2/3)· 从单库导入到多库共存,再用图形面板把仓位函数跑进真实 EA,避开路径与命名坑

偏理论 第 2/3 篇
不少人在 MQL5 里直接 #import 写死绝对路径,库一换终端就编译失败。还有人把多个 .ex5 命名成一样,运行时静默调用错版本。这篇先把导入机制讲透,再带你落地到带面板的 EA。

「追踪止损价的合法性校验与下单重试」

多头仓位里,追踪止损价按 positionPriceCurrent - trailingStopLoss * symbolPoint 推算;若算出来比开仓价 entryPrice 还低,或比当前挂着的 SL 还低,函数直接 return(false) 退出——这能避免把止损挪到亏损更深处。 空头反过来:slPrice = 当前价 + 追踪距离×点值,一旦高于开仓价或高于原 SL 同样判无效。外汇和贵金属点值随品种跳动,这种边界判断能少踩经纪商 reject 的坑,但杠杆品种本身高风险,校验过了也不代表行情不扫损。 下单前用 Print 把 ticket、volume、开仓价、旧 SL→新 SL、TP、magic 全打进日志,便于回看某次移动是哪个 EA 实例触发的。 真正发单包在 for(loop=0; loop<=100; loop++) 里,最多重试 101 次才罢休;每次先 ZeroMemory 清结构体、ResetLastError 清错误码,再 OrderSend。实战中遇到报价延迟,前几次失败很正常,靠这个循环把成功率拉起来。

MQL5 / C++
trailingStopLoss = symbolStopLevel; class=class="str">"cmt">//-- Set it to the symbol stop level by class="kw">default
class=class="str">"cmt">//-- Calculate and store the trailing stop loss price
  if(positionType == POSITION_TYPE_BUY)
  {
    slPrice = positionPriceCurrent - (trailingStopLoss * symbolPoint);
    class=class="str">"cmt">//-- Check if the proposed slPrice for the trailing stop loss is valid
    if(slPrice < entryPrice || slPrice < currentPositionSlPrice)
      {
       class="kw">return(false); class=class="str">"cmt">//-- Exit the function, proposed trailing stop loss price is invalid
      }
  }
  else  class=class="str">"cmt">//-- SELL POSITION
  {
    slPrice = positionPriceCurrent + (trailingStopLoss * symbolPoint);
    class=class="str">"cmt">//-- Check if the proposed slPrice for the trailing stop loss is valid
    if(slPrice > entryPrice || slPrice > currentPositionSlPrice)
      {
       class="kw">return(false); class=class="str">"cmt">//-- Exit the function, proposed trailing stop loss price is invalid
      }
  }
class=class="str">"cmt">//-- Print position properties before setting the trailing stop loss
   class="type">class="kw">string positionProperties = "--> "  + positionSymbol + " " + EnumToString(positionType) + " Trailing Stop Loss Modification Details" +
                               " <--\r\n";
   positionProperties += "------------------------------------------------------------\r\n";
   positionProperties += "Ticket: " + (class="type">class="kw">string)positionTicket + "\r\n";
   positionProperties += "Volume: " + StringFormat("%G", volume) + "\r\n";
   positionProperties += "Price Open: " + StringFormat("%G", entryPrice) + "\r\n";
   positionProperties += "Current SL: " + StringFormat("%G", currentPositionSlPrice) + "  -> New Trailing SL: " + (class="type">class="kw">string)slPrice + "\r\n";
   positionProperties += "Current TP: " + StringFormat("%G", currentPositionTpPrice) + "\r\n";
   positionProperties += "Comment: " + PositionGetString(POSITION_COMMENT) + "\r\n";
   positionProperties += "Magic Number: " + (class="type">class="kw">string)PositionGetInteger(POSITION_MAGIC) + "\r\n";
   positionProperties += "---";
   Print(positionProperties);
class=class="str">"cmt">//-- reset the the tradeRequest and tradeResult values by zeroing them
   ZeroMemory(tradeRequest);
   ZeroMemory(tradeResult);
class=class="str">"cmt">//-- initialize the parameters to set the sltp
   tradeRequest.action = TRADE_ACTION_SLTP; class=class="str">"cmt">//-- Trade operation type for setting sl and tp
   tradeRequest.position = positionTicket;
   tradeRequest.symbol = positionSymbol;
   tradeRequest.sl = slPrice;
   tradeRequest.tp = currentPositionTpPrice;
ResetLastError(); class=class="str">"cmt">//--- reset error cache so that we get an accurate runtime error code in the ErrorAdvisor function
   for(class="type">int loop = class="num">0; loop <= class="num">100; loop++) class=class="str">"cmt">//-- try modifying the sl and tp class="num">101 times untill the request is successful
   {
    class=class="str">"cmt">//--- send order to the trade server
    if(OrderSend(tradeRequest, tradeResult))
      {
       class=class="str">"cmt">//-- Confirm order execution

◍ 追踪止损回执与仓位校验的落地写法

移动止损真正难的地方不在算价格,而在把订单回执和仓位状态啃干净。下面这段逻辑先判断交易回执码:10008 代表订单已在交易服务器排队成交,10009 代表已成交,两者都算成功分支,直接打印仓位号、品种和买卖类型后退出函数。 失败分支里调用了 ErrorAdvisor 做错误归类,若返回 false 或脚本被强制停止(IsStopped),就打印错误并 return false。注意这里 retcode 与 GetLastError 是两套信息:前者是交易服务器给的业务码,后者是终端本地错误码,排查滑点类问题两个都要看。 SetTrailingStopLoss 入口先卡两道闸:TradingIsAllowed 确认 EA 自动交易没被禁,trailingStopLoss 参数不为 0 才往下走。随后用 PositionSelectByTicket 按 ticket 精确选中持仓,选不中立刻打印错误并退出——这一步漏掉 ResetLastError 的话,旧错误码会污染本次诊断。 选中仓位后把品种、开仓价、成交量读进局部变量,slPrice 先置 0 等待后续计算。外汇与贵金属波动剧烈,追踪止损触发存在滑点未成交可能,实盘前建议先在 MT5 策略测试器用历史数据跑一遍回执分支。

MQL5 / C++
if(tradeResult.retcode == class="num">10008 || tradeResult.retcode == class="num">10009)
  {
    PrintFormat("Successfully set the Trailing SL for #%I64d %s %s", positionTicket, positionSymbol, EnumToString(positionType));
    PrintFormat("retcode=%u  runtime_code=%u", tradeResult.retcode, GetLastError());
    Print("_______________________________________________________________________________________\r\n\r\n");
    class="kw">return(true); class=class="str">"cmt">//-- exit function
    break; class=class="str">"cmt">//--- success - order placed ok. exit for loop
  }
}
else  class=class="str">"cmt">//-- Order request failed
  {
    class=class="str">"cmt">//-- order not sent or critical error found
    if(!ErrorAdvisor(__FUNCTION__, positionSymbol, tradeResult.retcode) || IsStopped())
      {
        PrintFormat("ERROR setting the Trailing SL for #%I64d %s %s", positionTicket, positionSymbol, EnumToString(positionType));
        Print("_______________________________________________________________________________________\r\n\r\n");
        class="kw">return(false); class=class="str">"cmt">//-- exit function
        break; class=class="str">"cmt">//-- exit for loop
      }
  }
  }
 class="kw">return(false);
}
class="type">bool SetTrailingStopLoss(class="type">ulong positionTicket, class="type">int trailingStopLoss) class="kw">export
  {
class=class="str">"cmt">//-- first check if the EA is allowed to trade and the trailing stop loss parameter is more than zero
  if(!TradingIsAllowed() || trailingStopLoss == class="num">0)
    {
      class="kw">return(false); class=class="str">"cmt">//--- algo trading is disabled or trailing stop loss is invalid, exit function
    }
class=class="str">"cmt">//--- Confirm and select the position using the provided positionTicket
  ResetLastError(); class=class="str">"cmt">//--- Reset error cache incase of ticket selection errors
  if(!PositionSelectByTicket(positionTicket))
    {
      class=class="str">"cmt">//---Position selection failed
      Print("\r\n_______________________________________________________________________________________");
      Print(__FUNCTION__, ": Selecting position with ticket:", positionTicket, " failed. ERROR: ", GetLastError());
      class="kw">return(false); class=class="str">"cmt">//-- Exit the function
    }
class=class="str">"cmt">//-- create variable to store the calculated trailing sl prices to send to the trade server
  class="type">class="kw">double slPrice = class="num">0.0;
class=class="str">"cmt">//--- Position ticket selected, save the position properties
  class="type">class="kw">string positionSymbol = PositionGetString(POSITION_SYMBOL);
  class="type">class="kw">double entryPrice = PositionGetDouble(POSITION_PRICE_OPEN);
  class="type">class="kw">double volume = PositionGetDouble(POSITION_VOLUME);

正文

&nbsp;&nbsp; <span class="keyword">double</span> currentPositionSlPrice = <span class="functions">PositionGetDouble</span>(<span class="macro">POSITION_SL</span>); &nbsp;&nbsp; <span class="keyword">double</span> currentPositionTpPrice = <span class="functions">PositionGetDouble</span>(<span class="macro">POSITION_TP</span>); &nbsp;&nbsp; <span class="macro">ENUM_POSITION_TYPE</span> positionType = (<span class="macro">ENUM_POSITION_TYPE</span>)<span class="functions">PositionGetInteger</span>(<span class="macro">POSITION_TYPE</span>); <span class="comment">//-- Get some information about the positions symbol</span> &nbsp;&nbsp; <span class="keyword">int</span> symbolDigits = (<span class="keyword">int</span>)<span class="functions">SymbolInfoInteger</span>(positionSymbol, <span class="macro">SYMBOL_DIGITS</span>); <span class="comment">//-- Number of symbol decimal places</span> &nbsp;&nbsp; <span class="keyword">int</span> symbolStopLevel = (<span class="keyword">int</span>)<span class="functions">SymbolInfoInteger</span>(positionSymbol, <span class="macro">SYMBOL_TRADE_STOPS_LEVEL</span>); &nbsp;&nbsp; <span class="keyword">double</span> symbolPoint = <span class="functions">SymbolInfoDouble</span>(positionSymbol, <span class="macro">SYMBOL_POINT</span>); &nbsp;&nbsp; <span class="keyword">double</span> positionPriceCurrent = <span class="functions">PositionGetDouble</span>(<span class="macro">POSITION_PRICE_CURRENT</span>); &nbsp;&nbsp; <span class="keyword">int</span> sp

「给持仓挂移动止损的实战发送逻辑」

在 MT5 里修改已有持仓的止损止盈,靠的是 TRADE_ACTION_SLTP 这类交易动作,而不是下新单。上面这段把 tradeRequest 的 action 设成该常量,再填 position、symbol、sl、tp,就能对准指定 ticket 的仓位改线。 发送前用 ZeroMemory 把请求和结果结构体清零,ResetLastError 清掉错误缓存,否则后续拿到的 runtime error code 可能是上一轮的脏值。改线这种操作对 broker 返回节奏敏感,所以代码里套了 for(loop=0; loop<=100) 的 101 次重试。 OrderSend 成功且 retcode 是 10008(已成交)或 10009(已受理)才视为改线成功,直接 return(true) 退出;失败则交给 ErrorAdvisor 判断是否可重试,遇到不可恢复错误或 IsStopped() 就 return(false)。外汇和贵金属杠杆高,SL 被快速扫掉或 reject 的概率不低,跑之前建议在策略测试器用真实点差验证重试次数够不够。

MQL5 / C++
  positionProperties += "Comment: " + PositionGetString(POSITION_COMMENT) + "\r\n";
  positionProperties += "Magic Number: " + (class="type">class="kw">string)PositionGetInteger(POSITION_MAGIC) + "\r\n";
  positionProperties += "---";
  Print(positionProperties);
class=class="str">"cmt">//-- reset the the tradeRequest and tradeResult values by zeroing them
  ZeroMemory(tradeRequest);
  ZeroMemory(tradeResult);
class=class="str">"cmt">//-- initialize the parameters to set the sltp
  tradeRequest.action = TRADE_ACTION_SLTP; class=class="str">"cmt">//-- Trade operation type for setting sl and tp
  tradeRequest.position = positionTicket;
  tradeRequest.symbol = positionSymbol;
  tradeRequest.sl = slPrice;
  tradeRequest.tp = currentPositionTpPrice;
  ResetLastError(); class=class="str">"cmt">//--- reset error cache so that we get an accurate runtime error code in the ErrorAdvisor function
  for(class="type">int loop = class="num">0; loop <= class="num">100; loop++) class=class="str">"cmt">//-- try modifying the sl and tp class="num">101 times untill the request is successful
    {
      class=class="str">"cmt">//--- send order to the trade server
      if(OrderSend(tradeRequest, tradeResult))
        {
          class=class="str">"cmt">//-- Confirm order execution
          if(tradeResult.retcode == class="num">10008 || tradeResult.retcode == class="num">10009)
            {
              PrintFormat("Successfully set the Trailing SL for #%I64d %s %s", positionTicket, positionSymbol, EnumToString(positionType));
              PrintFormat("retcode=%u  runtime_code=%u", tradeResult.retcode, GetLastError());
              Print("_______________________________________________________________________________________\r\n\r\n");
              class="kw">return(true); class=class="str">"cmt">//-- exit function
              break; class=class="str">"cmt">//--- success - order placed ok. exit for loop
            }
        }
      else  class=class="str">"cmt">//-- Order request failed
        {
          class=class="str">"cmt">//-- order not sent or critical error found
          if(!ErrorAdvisor(__FUNCTION__, positionSymbol, tradeResult.retcode) || IsStopped())
            {
              PrintFormat("ERROR setting the Trailing SL for #%I64d %s %s", positionTicket, positionSymbol, EnumToString(positionType));
              Print("_______________________________________________________________________________________\r\n\r\n");
              class="kw">return(false); class=class="str">"cmt">//-- exit function
              break; class=class="str">"cmt">//-- exit for loop
            }
        }
    }
  class="kw">return(false);
}

◍ 一键清仓的 CloseAllPositions 怎么写才不卡死

在 MT5 里做批量平仓,最怕两件事:一是EA自动化被关了还在发单,二是服务器没确认平仓就反复回调陷入死循环。CloseAllPositions() 用两个默认参数(symbol=ALL_SYMBOLS、magicNumber=0)来框定范围,只清指定品种和幻数的仓,其余一律跳过。 函数开头先调 TradingIsAllowed() 做一次闸门检查,禁止交易直接 return false,不浪费一次网络请求。随后用 PositionsTotal() 取未平总数,for 循环里逐个读 ticket、symbol、magic,不符合条件的 continue,符合的丢给 ClosePositionByTicket() 发平仓指令。 发完指令不等于真的平了。原文用 while 循环配合 SymbolPositionsTotal() 复查,只要目标仓位数大于0就再次回调自身,每次 Sleep(100) 给交易服务器喘息,并用 breakerBreaker 计数器封顶101次防无限循环。遇到关键错误、脚本被停或超次数就 break。 最后再查一次 SymbolPositionsTotal() 是否为0,是则 returnThis=true,函数返回真,否则返假。外汇和贵金属杠杆高,批量平仓可能在滑点扩大时部分失败,跑之前务必在策略测试器用历史数据验证断路器逻辑。

MQL5 / C++
class="type">bool CloseAllPositions(class="type">class="kw">string symbol = ALL_SYMBOLS, class="type">ulong magicNumber = class="num">0) class="kw">export
  {
class=class="str">"cmt">//-- Functions body goes here
  }
if(!TradingIsAllowed())
    {
      class="kw">return(false); class=class="str">"cmt">//--- algo trading is disabled, exit function
    }
class="type">bool returnThis = false;
class="type">int totalOpenPositions = PositionsTotal();
  for(class="type">int x = class="num">0; x < totalOpenPositions; x++)
    {
      class=class="str">"cmt">//--- Get position properties
      class="type">ulong positionTicket = PositionGetTicket(x); class=class="str">"cmt">//-- Get ticket to select the position
      class="type">class="kw">string selectedSymbol = PositionGetString(POSITION_SYMBOL);
      class="type">ulong positionMagicNo = PositionGetInteger(POSITION_MAGIC);
      class=class="str">"cmt">//-- Filter positions by symbol and magic number
      if(
        (symbol != ALL_SYMBOLS && symbol != selectedSymbol) ||
        (magicNumber != class="num">0 && positionMagicNo != magicNumber)
      )
        {
          class="kw">continue;
        }
      class=class="str">"cmt">//-- Close the position
      ClosePositionByTicket(positionTicket);
    }
class="type">int breakerBreaker = class="num">0; class=class="str">"cmt">//-- Variable that safeguards and makes sure we are not locked in an infinite loop
  while(SymbolPositionsTotal(symbol, magicNumber) > class="num">0)
    {
      breakerBreaker++;
      CloseAllPositions(symbol, magicNumber); class=class="str">"cmt">//-- We still have some open positions, do a function callback
      Sleep(class="num">100); class=class="str">"cmt">//-- Micro sleep to pace the execution and give some time to the trade server
      class=class="str">"cmt">//-- Check for critical errors so that we exit the loop if we run into trouble
      if(!ErrorAdvisor(__FUNCTION__, symbol, GetLastError()) || IsStopped() || breakerBreaker > class="num">101)
        {
          break;
        }
    }
if(SymbolPositionsTotal(symbol, magicNumber) == class="num">0)
    {
      returnThis = true; class=class="str">"cmt">//-- Save this status for the function class="kw">return value
    }
  class="kw">return(returnThis);
class="type">bool CloseAllPositions(class="type">class="kw">string symbol = ALL_SYMBOLS, class="type">ulong magicNumber = class="num">0) class="kw">export
  {
class=class="str">"cmt">//-- first check if the EA is allowed to trade
  if(!TradingIsAllowed())
    {
      class="kw">return(false); class=class="str">"cmt">//--- algo trading is disabled, exit function
    }
  class="type">bool returnThis = false;

按品种与魔术码精准平掉持仓

这段逻辑干的事很直接:先扫一遍当前账户所有持仓,只挑出指定品种和魔术码的那部分,逐个发送平仓指令。过滤条件写在 if 里——symbol 不等于 ALL_SYMBOLS 且不等于当前持仓品种,或者 magicNumber 非零且不等于持仓魔术码,就 continue 跳过,不碰无关仓位。 平仓不是发一次指令就完事。代码里用 while 循环复查 SymbolPositionsTotal(symbol, magicNumber),只要目标持仓数还大于 0,就再回调 CloseAllPositions 并 Sleep(100) 给交易服务器喘息。breakerBreaker 计数到 101 就强制 break,避免服务器抽风时陷入死循环。 最后用一次 SymbolPositionsTotal == 0 做终确认,全平掉才把 returnThis 置为 true 返回。外汇和贵金属杠杆高,批量平仓可能因点差跳空或拒绝而部分失败,这种二次复查机制能显著降低漏平概率,建议你在 MT5 策略测试器里用 EURUSD 最小手数跑一遍验证。

MQL5 / C++
class=class="str">"cmt">//-- Scan for symbol and magic number specific positions and close them
  class="type">int totalOpenPositions = PositionsTotal();
  for(class="type">int x = class="num">0; x < totalOpenPositions; x++)
    {
      class=class="str">"cmt">//--- Get position properties
      class="type">ulong positionTicket = PositionGetTicket(x); class=class="str">"cmt">//-- Get ticket to select the position
      class="type">class="kw">string selectedSymbol = PositionGetString(POSITION_SYMBOL);
      class="type">ulong positionMagicNo = PositionGetInteger(POSITION_MAGIC);
      class=class="str">"cmt">//-- Filter positions by symbol and magic number
      if(
          (symbol != ALL_SYMBOLS && symbol != selectedSymbol) ||
          (magicNumber != class="num">0 && positionMagicNo != magicNumber)
      )
        {
         class="kw">continue;
        }
      class=class="str">"cmt">//-- Close the position
      ClosePositionByTicket(positionTicket);
     }
class=class="str">"cmt">//-- Confirm that we have closed all the positions being targeted
   class="type">int breakerBreaker = class="num">0; class=class="str">"cmt">//-- Variable that safeguards and makes sure we are not locked in an infinite loop
   while(SymbolPositionsTotal(symbol, magicNumber) > class="num">0)
     {
      breakerBreaker++;
      CloseAllPositions(symbol, magicNumber); class=class="str">"cmt">//-- We still have some open positions, do a function callback
      Sleep(class="num">100); class=class="str">"cmt">//-- Micro sleep to pace the execution and give some time to the trade server
      class=class="str">"cmt">//-- Check for critical errors so that we exit the loop if we run into trouble
      if(!ErrorAdvisor(__FUNCTION__, symbol, GetLastError()) || IsStopped() || breakerBreaker > class="num">101)
        {
         break;
        }
     }
class=class="str">"cmt">//-- Final confirmations that all targeted positions have been closed
   if(SymbolPositionsTotal(symbol, magicNumber) == class="num">0)
     {
      returnThis = true; class=class="str">"cmt">//-- Save this status for the function class="kw">return value
     }
   class="kw">return(returnThis);
  }
把重复劳动交给小布
这些诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到;库集成后的仓位状态监控,交给小布盯盘看盘口,你专注决策逻辑。

常见问题

省略扩展名时编译器会默认按 .DLL 库处理,因此导入二进制 EX5 必须显式写上 .ex5 后缀。
小布盯盘内置的是通用诊断与盘口视图,不直接挂载用户私有 EX5;你把库跑在 EA 里,小布负责把仓位变化可视化出来。
每个库用独立 #import 段,最后一个关闭指令不带库名;且所有库名必须唯一,即便存于不同文件夹也不能重名。
面板按钮事件里应做持仓校验与错误码返回,避免无仓位时反复发单;函数原型须与库导出声明严格一致。
编译器先搜 Libraries 再搜源码同目录,两处均有同名的 ex5 会以 Libraries 优先,无需写路径但需注意版本覆盖。