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

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

第 1/3 篇

「把仓位管理拆成可复用 EX5 库」

在 MT5 里做系统化交易,很多人把开仓、加仓、平仓逻辑全写进 EA 主文件,结果改一个手数规则就要动整段代码。更干净的做法是把仓位管理单独抽成一个 EX5 库,主 EA 只管信号,库负责头寸规模与执行。 MQL5 的 #include 机制允许把类和方法编译进独立 EX5,再被多个 EA 调用。一套写好的仓位库,能在黄金 XAUUSD 和欧美 EURUSD 上复用同一套风控骨架,只是传参不同。 外汇与贵金属杠杆高、滑点跳空频繁,复用库前务必在策略测试器用 2023 年 1 月至 2024 年 12 月 Tick 数据回测,观察极端波动下的手数计算是否溢出。库本身不预测行情,只保证仓位按预设规则落地,实际盈亏仍取决于信号质量与市场风险。

从已编译的 EX5 库导入函数

上一节我们搭好了仓位管理 EX5 库的骨架,并写出了可导出函数的 .mq5 源码。这一节不重复造轮子,直接看怎么把已经编译好的 .ex5 二进制库拖进新 EA 里用。 在 MT5 里,导入已编译库靠的是 #import 指令指向 .ex5 文件路径,函数签名必须和编译库时声明的完全一致,否则链接阶段就报错。 实际做两个基础 EA 时,其中一个会带图形交易和信息面板,用来现场演示库函数的调用链路。你能立刻在 MT5 里验证的事:新建 EA,写几行 #import "\Files\PositionManager.ex5",把上一节的头文件函数原样抄进 import 块,编译若通过即说明二进制接口对齐。 外汇与贵金属杠杆高,实盘前务必在策略测试器用历史数据跑通面板逻辑,避免库调用异常导致下单失控。

◍ 在 MT5 里挂上 EX5 库的正确写法

在 MQL5 程序里用编译好的 .ex5 库,得在源码头部 #property 下方写 #import。编译器默认只搜两个地方:MQL5/Libraries 文件夹,以及当前源码所在文件夹。库若就丢在这两处,#import 后直接写带双引号的库名即可,不用填路径。 指定库时要显式写上 .ex5 扩展名,漏掉扩展名会被当成导入 .DLL,这是新手常踩的坑。导入段结构是:开头 #import "路径/库名.ex5",换行列导出函数原型,最后用单独一行 #import 收尾。 单个文件能挂多个库。多个库的写法区别在于收尾位置:第一个库原型写完后,用 #import "下一个库.ex5" 直接接续,既关前库又开新库;最后一个库列完原型,用不带库名的 #import 关门。 每个库有独立命名空间,里面函数名即便和内置函数撞车也不会冲突。但真碰上不同库有同名函数,编译器按规则排优先级,所以给库起不同名字更稳。下面这段是单库与双库导入的对照骨架,直接抄进 EA 头部就能改。

MQL5 / C++
class="macro">#class="kw">import "FilePath/LibraryName.ex5" class=class="str">"cmt">//-- Opening .EX5 Library class="kw">import directive
  class=class="str">"cmt">//-- Function definitions/descriptions prototypes
  class="type">int  FunctionPrototype1();
  class="type">void FunctionPrototype2(class="type">bool y);
  class="type">bool FunctionPrototype3(class="type">class="kw">double x);
class="macro">#class="kw">import class=class="str">"cmt">//--- Closing .EX5 Library class="kw">import directive
class="macro">#class="kw">import "FilePath/LibraryName.ex5"  class=class="str">"cmt">//-- Opening .EX5 Library class="kw">import directive
  class=class="str">"cmt">//-- Function definitions/descriptions prototypes for the first library here
  class="type">int  FunctionPrototype1();
  class="type">void FunctionPrototype2(class="type">bool y);
  
class="macro">#class="kw">import "FilePath/SecondLibraryName.ex5"
   class=class="str">"cmt">//-- Function definitions/descriptions prototypes for the second library here
  class="type">bool  FunctionPrototype();
  class="type">class="kw">string FunctionPrototype2(class="type">bool z);
class="macro">#class="kw">import class=class="str">"cmt">//--- Closing .EX5 Library class="kw">import directive

「EX5 库导入引发的隐蔽运行时报错」

EX5 库调试的坑主要在运行时:编译能过,但 MT5 一加载就崩。原因是编译器拿不到封装后的 .ex5 库源码,导入声明里写错路径、函数名、参数类型或返回值,它全检测不出来。 最常见的两类报错出现在「专家」和「日志」选项卡。其一为「未解析的导入函数调用:无法在 'Library_Name.ex5' 中找到 'Function_Name'」,属导入原型描述不符;其二为「无法打开文件 'Library_Name.ex5':加载 ExpertAdvisor_Name (GBPJPY,M15) 失败 [0]」,是终端找不到库文件。两者都不会在编译期拦截,新手极易漏看。 修法很直接:核对导入段里的库路径与函数原型,改完重新编译。外汇与贵金属 EA 调用第三方库时,这类运行时失败概率偏高,建议每次换品种周期(如 GBPJPY M15)加载前先确认库就位。

EX5 库改完后的正确重编译链路

改了 MQL5 库源码之后,重部署顺序错了就会让调用方还在跑旧逻辑。最关键的环节是先产出新的二进制,再动调用方的原型声明,最后统一重编项目。 第一步只做一件事:把更新过的 .mq5 库文件编译成新的 .ex5。此时不要急着去碰任何引用它的 EA 或指标,否则新接口没落地前编译会直接报找不到符号。 如果这次库更新动了导出函数的签名(参数类型、返回类型、函数名),必须回到每一个引用项目里,把 import 处的原型同步改掉。漏掉一个,那个项目编译出来仍绑定旧约定,运行时行为可能偏离预期。 最后把全部依赖该库的项目重新编译一遍。三步顺下来,新 EX5 里的改动才会真实反映到所有导入方;反过来先编项目后编库,MT5 大概率还在加载缓存里的旧 ex5。外汇与贵金属环境杠杆高,这类静默错位可能放大执行风险,建议每次发版后开 MT5 用示例 EA 跑一遍函数返回校验。

◍ 给仓位库补上追踪止损模块

追踪止损若实施得当,可能提高系统的利润率与整体成功率,所以任何像样的仓位管理库都不能缺这一环。这里要落地的函数叫 SetTrailingStopLoss(),用仓位编号做过滤,把「点数差」作为参数,去改服务器上对应未平仓位的中途止损。因为价格一直在动,这个函数得在每个交易日连续调用,才可能实时跟住行情。外汇与贵金属杠杆高、滑点频,追踪止损也只是降低回撤概率,不保证锁利。 函数定义为 bool 类型,带两个参:ulong positionTicket 是仓位唯一标识,int trailingStopLoss 是距当前价的点数。前面挂了 export,意思是别的 MQL5 文件只要引了这个库就能直接调。进去先卡两道关——交易是否被允许、trailingStopLoss 是否大于 0,任一不满足直接 return false 走人。 接着用 PositionSelectByTicket 按票号选仓,选失败就打日志并返回 false。通过后把交易品种、开仓价、成交量、现有 SL/TP、仓位类型全读出来;再取 SYMBOL_DIGITS、SYMBOL_TRADE_STOPS_LEVEL、SYMBOL_POINT、当前价、点差。若传进来的 trailingStopLoss 比品种止损水平还小,就把它抬到止损水平,否则发单会被拒。 买仓的追踪止损算在当前价之下,卖仓算在之上,算完还要验价是否有效。发单前先把仓位明细打印到 MT5 日志便于排查。tradeRequest 和 tradeResult 清零,填好改 SL 的参数,清错误缓存,然后循环发单直到成功或最多 101 次重试;成了打成功信息回 true,败了处理错误回 false。 下面这段是骨架与关键分支,顺序不能乱:先定义函数与早退判断,再选仓、取属性、校正止损水平。复制进 MT5 把注释处补完就能跑。

MQL5 / C++
class="type">bool SetTrailingStopLoss(class="type">ulong positionTicket, class="type">int trailingStopLoss) class="kw">export
  {
  class=class="str">"cmt">//-- place the function body here
  }
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);
   class="type">class="kw">double currentPositionSlPrice = PositionGetDouble(POSITION_SL);
   class="type">class="kw">double currentPositionTpPrice = PositionGetDouble(POSITION_TP);
   class="type">ENUM_POSITION_TYPE positionType = (class="type">ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
class=class="str">"cmt">//-- Get some information about the positions symbol
   class="type">int symbolDigits = (class="type">int)SymbolInfoInteger(positionSymbol, SYMBOL_DIGITS); class=class="str">"cmt">//-- Number of symbol decimal places
   class="type">int symbolStopLevel = (class="type">int)SymbolInfoInteger(positionSymbol, SYMBOL_TRADE_STOPS_LEVEL);
   class="type">class="kw">double symbolPoint = SymbolInfoDouble(positionSymbol, SYMBOL_POINT);
   class="type">class="kw">double positionPriceCurrent = PositionGetDouble(POSITION_PRICE_CURRENT);
   class="type">int spread = (class="type">int)SymbolInfoInteger(positionSymbol, SYMBOL_SPREAD);
class=class="str">"cmt">//-- Check if the trailing stop loss is less than the symbol trade stop levels
   if(trailingStopLoss < symbolStopLevel)
    {
      class=class="str">"cmt">//-- Trailing stop loss is less than the allowed level for the current symbol

常见问题

把开仓、平仓、仓位计算等函数单独编译成 EX5 库,主程序用 #import 调用,避免重复写代码,改逻辑只动库文件。
检查库里导出函数是否加了 export 修饰,且函数签名(参数类型、顺序)和主程序声明完全一致,库文件要放在 MQL5/Libraries 目录。
可以,小布能基于你的库调用日志和报错特征做 AIGC 诊断,指出常见导入失败或重编译遗漏点,省去你逐行排查。
改完必须重新编译库并重启调用它的 EA,终端不会热加载已加载的库,旧 EX5 仍驻留内存。
在库内新增追踪止损函数并 export,主程序补 #import 声明与定时调用,重编译库和 EA 后实盘前先用模拟盘验一遍。