📘

用 UML 给 EA 搭骨架

◍ 用 UML 给 EA 搭骨架

写 EA 前先画 UML,不是学院派癖好,而是把交易逻辑从脑子里搬进 MT5 的最省错路径。爱因斯坦那句「科学家探索已有的,工程师创造没有的」放在 MQL5 开发里很贴切:指标和行情是既存的,EA 是你要从无到有造出来的东西。

  • 年 3 月那篇原帖在论坛拿到 5091 次浏览、15 条讨论,说明即便老教程,UML 建模对 EA 结构设计的参考价值仍被实盘开发者反复翻出来用。

类图先把 CExpert、CIndicators 这些标准库依赖关系理清楚,状态图管住「挂单—持仓—平仓」的跳转条件,比直接上手写 OnTick 更容易避开重码和逻辑死循环。外汇与贵金属杠杆高、滑点随机,EA 任何状态遗漏都可能放大成实亏,建模阶段多花一小时,回测阶段少烧几天。

为什么用 UML 给 MQL5 交易系统建模

前面有文章建议用动态系统思路给 EA 建模,但那只覆盖了系统的动态行为,没把面向对象的设计结构说清楚。专业开发者手里有更通用的表达工具,UML 就是其中一种能补全这块认知的图形语言。 MQL5 本身就属于面向对象语言家族,类、继承、封装这些概念和 UML 的语义是对得上的,用图形先画清楚再写代码,反而比直接堆逻辑更容易避坑。 建模工具我选了免费非商业的 Software Ideas Modeler,不花钱就能画用例图、类图、状态机,读者可以现在装一个,把下一版 EA 的类关系先拖出来看看。

「用 UML 给复杂 EA 搭骨架」

写 EA 最怕逻辑长胖后自己都看不懂。UML 的价值不在花哨,而是把模块、绑定和图表三类构造块画出来,让大而不乱的系统在图上一目了然。人类读图带字比纯文字快,这是 UML 设计者反复强调的感知事实。 UML 结构由实体(模型元素)、关系(绑定)和图表(模型表示)组成,公共机制里规范写语义、修饰标重点、通用划分分抽象与实例、扩展机制用约束/构造型/标记值兜底。 架构层最实用的是「4+1 视图」:逻辑、进程、开发、物理加场景。落到 EA 开发,UML 2.2 版提供 14 类图,足以从抽象一路切到具体交易模块。复杂策略先画视图再写代码,MT5 里改一个模块不会牵一发而动全身。外汇与贵金属杠杆高,模型清晰也只是降低开发错误率,不承诺实盘收益。

◍ 用 UML 给 EA 先画骨架再写代码

做 MT5 自动化交易,不少人一上来就写 EA,结果逻辑改一次乱一茬。先把系统用 UML 图钉死结构,再让编辑器生成类壳,能省掉大量返工。用例图负责从用户视角圈定边界:开发人员建系统、风控员管风险、管理员监控,末端用户(交易者/投资者)通过泛化关系归入“User”,建仓平仓又泛化自“Trading”基础用例,盈利用例只在卖价高于买价条件下以 extend 关系挂上去。 类图直接对应 OOP 静态结构。以 CTradeExpert 为例,属性 Magic_No 标“-”为 private、e_pnt 标“+”为 public,其余 protected;冒号后跟类型,如 int、CTrade。把它通过 dependency 虚线连到 CTrade / CAccountInfo 等标准库类,再泛化自 CObject,在 MetaEditor 侧栏点 Generate 选 C++ 导出,稍改就能转 MQL5。 活动图补足运行时行为。Test_TradeExpert.mq5 从初始节点建实例、初始化,决策节点分流;可中断区处理卸载,迭代区按多空两向循环跑“检查信号→建仓→检查仓位→平仓”。序列图则把时间轴竖起来:客户端发 Init 事件,1.1 虚线创建 CTradeExpert() 实例,1.2 控制权返回,2.1 调 OnInit() 再回传,消息编号 #.# 为同步、# 为异步。 外汇与贵金属杠杆高,模型画得再清楚也只是降低编程风险,不预示任何收益。开 MT5 建个空 EA,照类图补 TradeExpert.mqh,比从头硬写更容易控住嵌套层级。

从序列图到 EA 骨架的代码落地

Init 返回非零时,EA 会先走 Deinit 再交还控制权,标准模板里其实没写这块失败处理,用 UML 的 opt 块(警备条件 OnInit()!=0)补上逻辑才完整。NewTick 进来后由 OnTick 接管,里面是一个无限循环,直到手动禁用 EA 才停。 循环体里用 4 个自返消息跑 CheckSignal、OpenPosition、CheckPosition、ClosePosition,所谓自返就是 EA 对象给自己发消息。外层 loop(2) 表示两次遍历,分别对应多空两个方向——从步骤 7 到 10 就是把长仓和短仓各跑一遍。 下面这段是照着图转出来的类骨架与调用结构。CTradeExpert 先聚合了交易、账户、成交、品种四类对象,Magic_No 私有,e_pnt 公有暴露点数价值;OnTick 里用 for(dir=0;dir<2;dir++) 把四个函数按方向各调一次。

MQL5 / C++
class=class="str">"cmt">//includes
class="macro">#include <Trade\Trade.mqh>
class="macro">#include <Trade\AccountInfo.mqh>
class="macro">#include <Trade\DealInfo.mqh>
class="macro">#include <Trade\SymbolInfo.mqh>
class=class="str">"cmt">//+------------------------------------------------------------------+
class CTradeExpert
  {
class="kw">private:
   class="type">int            Magic_No;    class=class="str">"cmt">// Expert Advisor magic
class="kw">protected:
   CTrade         e_trade;     class=class="str">"cmt">// An object for executing trade orders
   CAccountInfo   e_account;   class=class="str">"cmt">// An object for receiving account properties
   CDealInfo      e_deal;      class=class="str">"cmt">// An object for receiving deal properties
   CSymbolInfo    e_symbol;    class=class="str">"cmt">// An object for receiving symbol properties
class="kw">public:
   class="type">class="kw">double         e_pnt;       class=class="str">"cmt">// Value in points
class="kw">public:
                  CTradeExpert()
   {
   }
  };
class=class="str">"cmt">//+------------------------------------------------------------------+
for(class="type">long dir=class="num">0;dir<class="num">2;dir++)
   {
      myTE.CheckSignal(dir);
      myTE.OpenPosition(dir);
      myTE.CheckPosition(dir);
      myTE.ClosePosition(dir);
   }
逐行拆解:前 4 个 include 拉入 MT5 标准交易库;CTradeExpert 类里 Magic_No 用于订单识别,e_trade 发单、e_account 读账户、e_deal 读成交、e_symbol 读品种属性;e_pnt 留作点值计算接口;构造函数为空,析构以后再加。for 循环 dir 从 0 到 1,把四个成员函数按方向串行调用,对应图里的 loop(2)。 复杂 EA 直接写容易漏分支,先画图再转代码能把多空逻辑钉死。外汇与贵金属杠杆高,回测通过不代表实盘概率占优,拿 MT5 新建 EA 把这段骨架贴进去,补全四个函数体就能跑通基础事件流。

MQL5 / C++
class=class="str">"cmt">//includes
class="macro">#include <Trade\Trade.mqh>
class="macro">#include <Trade\AccountInfo.mqh>
class="macro">#include <Trade\DealInfo.mqh>
class="macro">#include <Trade\SymbolInfo.mqh>
class=class="str">"cmt">//+------------------------------------------------------------------+
class CTradeExpert
  {
class="kw">private:
   class="type">int            Magic_No;    class=class="str">"cmt">// Expert Advisor magic
class="kw">protected:
   CTrade         e_trade;     class=class="str">"cmt">// An object for executing trade orders
   CAccountInfo   e_account;   class=class="str">"cmt">// An object for receiving account properties
   CDealInfo      e_deal;      class=class="str">"cmt">// An object for receiving deal properties
   CSymbolInfo    e_symbol;    class=class="str">"cmt">// An object for receiving symbol properties
class="kw">public:
   class="type">class="kw">double         e_pnt;       class=class="str">"cmt">// Value in points
class="kw">public:
                  CTradeExpert()
   {
   }
  };
class=class="str">"cmt">//+------------------------------------------------------------------+
for(class="type">long dir=class="num">0;dir<class="num">2;dir++)
   {
      myTE.CheckSignal(dir);
      myTE.OpenPosition(dir);
      myTE.CheckPosition(dir);
      myTE.ClosePosition(dir);
   }

「用 UML 拆开标准库里的 Expert 类族」

想真正搞懂 MT5 标准库的交易骨架,光读 Expert.mqh 不够,得把它反向画成类图。CExpert 往上追有两层基类:直接继承 CExpertBase,再往上追到 CObject。手动逆向虽然慢,但比直接丢给 IBM Rational Rose 更能记住依赖链。 CExpert 本身不是孤岛。它内部挂了 CExpertTrade、CExpertSignal、CExpertMoney、CExpertTrailing、CIndicators、CPositionInfo、COrderInfo 等实例,还引用了 ENUM_TRADE_EVENTS、ENUM_TIMEFRAMES 和 MqlDateTime 结构。把这些块用「use」构造型连起来,图 13 那版就已经能看清主依赖了。 图太挤怎么办?用包封装。CTrade 单独封一个包,通过橙色接口向外暴露关系,点接口就能跳回原类图(图 15、16)。CExpertBase 更夸张:它用到 CiOpen、CiHigh、CiLow、CiSpread 等八个序列类,前四个派生于 CPriceSeries,后四个派生于 CSeries,而 CPriceSeries 又是 CSeries 的子类。把这些塞进包里再调样式,图 18 才勉强能读。 到自己写 EA 时,建一个 CmyExpert 继承 CExpert,再派生 CmyExpertSignal / CmyExpertMoney / CmyExpertTrailing 覆盖虚函数(图 25)。这一步才是你把策略塞进标准库模板的落点。 序列图管运行流。全局创建 CmyExpert 实例 myTrader 后,OnInit、Deinit 各自拆成独立序列图,用 ref 边框跳转(图 26、27、28)。EA 主体一大,就靠这种块级拆分保住可读性。打开 MT5 看 Expert.mqh 第 1 行起的继承声明,对照上面包图一条条核对,比盲抄代码强。

◍ 把 UML 草图直接丢进 MT5 工程

用 UML 给 EA 开发画可视草图,核心价值就一条:不读代码也能记住类结构和调用关系,后续维护、重构都快一截。作者随文给了四个落地文件,路径分别是 %MetaTrader%\MQL5\Include 下的 TradeExpert.mqh(EA 父类)、%MetaTrader%\MQL5\Experts 下的 Test_TradeExpert.mq5(可直接编译跑的样例)、%Documents%\UML projects 里的 Expert.simp(UML 图源)、以及 %Program Files%\SoftwareIdeasModeler 里的建模工具分发包。 这套方法不是银弹。UML 本身有冗余和语义不精确的毛病,复杂 EA 画多了图比代码还难读,新手容易在画图阶段过度设计。外汇与贵金属杠杆高、滑点随机,任何可视化设计都不替代实盘风险,回测通过也不代表实盘概率占优。 想验证就现在开 MT5:把 TradeExpert.mqh 塞进 Include,Test_TradeExpert.mq5 丢进 Experts 编译,对照 Expert.simp 看类图怎么映射成头文件里的继承。能跑通一次,比看十篇 UML 教程都实在。

常见问题

先用UML类图把交易系统里的信号、风控、下单模块画成类与关系,再照图写代码,能少走很多弯路。
用UML类图把CExpert、CExpertSignal等基类和派生类拉出来,标清继承和组合箭头,结构一眼就清楚。
可以,把你的交易规则告诉小布,它能按UML思路帮你拆出模块关系和序列流程,省去手画图纸的时间。
先写控制主流程的Expert类骨架和OnTick调度,再补信号类和订单管理类,避免早期陷进细节。
新手常把信号、平仓、风控混在一个函数里,后期改一处崩一片;先画UML骨架能强制模块分离。