MQL对象化交易系统构建法
🧩

MQL对象化交易系统构建法

用信号量、观察器与头寸支持模块破解多EA同账户难题

MQL5 编程 难度 · 进阶 2026-02-12 7 分钟阅读
#MQL4#对象方法#交易系统#MAGIC#多EA管理

为什么 MQL 需要对象方法

在 MQL 环境下编程,无论是初学者还是专业开发者,都会逐渐意识到语言本身的局限性。MQL4 本质上是一种面向过程的语言,并没有原生提供类与对象机制(MQL5 虽支持类但很多老代码与经纪环境仍基于 MQL4)。当我们只在一个账户运行一个 Expert Advisor 时,一切都很简单,不需要区分订单归属。但真实交易中,用户往往希望在同一账户上加载多个自动交易系统,这时就必须解决“哪些订单属于哪个系统”的问题。

MQL 官方给出的常规解法有两种:一是调用外部函数处理订单归属,二是使用 MAGIC 号码(幻数)来标记订单。即便使用外部函数,依然要在内部判断归属。理论上我们可以维护一个 OrderTicket 数组来记录某个系统的订单,但不少经纪商在订单交换时(例如一个订单关闭、另一个挂单触发)会更改 ticket 编号,导致数组失效。因此,使用 MAGIC 成为唯一稳健手段。但多个系统硬编码 MAGIC 容易造成混乱,且参数、止损、追踪逻辑难以隔离。

⚠ 常见坑
不要假设 OrderTicket 在订单生命周期内不变;不要多个 EA 使用相同 MAGIC,否则平仓、修改会互相干扰;不要将系统参数写成全局变量而缺乏结构,后期无法扩展。

对象模型的三大核心模块

作者提出一种与自身对象模型一致的交易系统架构,分为三个程序块:信号系统(SS)、进入退出模块(EE)和头寸支持模块(PS)。该模型并非万能,但在 MQL 缺少原生对象语法时,用“伪对象”方式实现了关注点分离。

信号系统(SS)的设计与实现

信号系统的“对象”通常是一组指标,例如移动平均线、包络线、MACD。它根据报价和指标值生成进入/退出信号,并发送给 EE 模块。在 MQL 中设置信号量很容易:首先用 #define 定义全局标识符,注意最好不要用连续数字(如1、2、3),而是间隔取值(如10、20、80、90),这样在一个 EA 内可让一个信号用于多个进程。最后编写信号处理函数。

下面代码展示了信号标识符定义与分发逻辑。CheckSignal 函数通过 SignalID 调用具体指标信号函数,实现了类似多态的行为。这种结构让新增信号源只需添加 case 与对应函数,不影响主流程。

#define BLACKSYS   10
#define BORCHAN    20
#define ELDER      80
#define ENVELOP    90

int CheckSignal(bool bEntry, int SignalID)
{
      switch (SignalID)
      {
                  case BLACKSYS:             return (BlackSys(bEntry)); break;
                  case BORCHAN:              return (BorChan(bEntry)); break;
                  case ELDER:                   return (Elder(bEntry)); break;
                  case ENVELOP:              return (Envelop(bEntry)); break;
                  default:                                     return (-1);
      }
}

以包络线指标为例,Envelope 函数读取当前与上一根 K 线的上下轨,结合中位数判断突破方向。bEntry 为 true 时生成开仓信号,false 时生成平仓信号。返回 OP_BUY / OP_SELL / -1。

int Envelope(bool bEntry)
{
      int MA=21;
      double Deviation=0.6;
      int Mode=MODE_SMA;
      int Price=PRICE_CLOSE;
      double envH0, envL0, m0;
      double envH1, envL1, m1;
      envH0=iEnvelopes(NULL, 0, MA, Mode, 0, Price, Deviation, MODE_UPPER, 0); 
      envL0=iEnvelopes(NULL, 0, MA, Mode, 0, Price, Deviation, MODE_LOWER, 0); 
      envH1=iEnvelopes(NULL, 0, MA, Mode, 0, Price, Deviation, MODE_UPPER, 1); 
      envL1=iEnvelopes(NULL, 0, MA, Mode, 0, Price, Deviation, MODE_LOWER, 1); 
      m0 = (Low[0]+High[0])/2;          m1 = (Low[1]+High[1])/2;
      if (bEntry)
      {          
                  if (envH0<m0 && envH1<m1) return (OP_SELL);
                  if (envL0>m0 && envL1>m1) return (OP_BUY);
      }
      else
      {
                  if (envH0<m0 && envH1<m1) return (OP_BUY);
                  if (envL0>m0 && envL1>m1) return (OP_SELL);
      }
   return (-1);
}

EE 模块:观察器与参数隔离

EE 程序块的对象任务最小:与信号量交互、观察信号、开平改仓,然后把控制权交给 PS。所有 EE 对象使用前缀 Process... 命名,例如 ProcessSimple 简单开仓、ProcessAvgLim 限价加仓均价、ProcessTurn 头寸反转。每个交易系统样本必须有独立特征:止盈、止损、资金管理、追踪参数等。

在 MQL 中最适合存储这些特征的是二维数组。SysPar[nSignal][11] 以信号 ID 为行、参数类型为列。宏定义使代码可读。这样多个系统参数完全隔离,互不覆盖。

double SysPar[nSignal][11];
#define _TP        0
#define _NullTP    1
#define _NullTP2   2
#define _TS        3
#define _NullSL    4
#define _SL        5
#define _dSL       6
#define _dStep     7
#define _dLot      8
#define _nLot      9
string SysParName[nSignal];

设置参数后,在 start() 中调用观察器函数,即与信号系统交互。如下代码注册了 4 个信号量与 3 个观察器,观察器 1 用 ProcessSimple 处理 MACD,观察器 2、3 控制两个信号量且逻辑更复杂。

void start()
{
      ProcessSimple(MACD, MACD, Black, Plum); TrailingSimple(MACD, Black, Plum);
      ProcessAvgLim(ENVELOPE, ENVELOPE, Green, Red);  TrailingAvgLim(ENVELOPE, Green, Red);
}
✦ 实践提示
用二维数组管理参数比全局变量更清晰;信号 ID 间隔定义可避免以后新增信号时改大量常量;观察器前缀有助于快速定位逻辑。

PS 模块:头寸支持与追踪

PS 程序块被认为最有趣且重要不亚于信号量。它实现不同追踪变体、挂单打开、头寸支持与锁定、盈亏控制。PS 对 EE 信号充分反应,在亏损头寸出现时退出市场以降低损失。所有支持对象以前缀 Trailing... 开始,控制传递也通过 start() 完成。

Yury Dzyuban 的追踪函数库提供了丰富变体,可轻松接入。在 MQL4 中,由于无类,我们用函数模拟;在 MQL5 中可用类封装。核心原则是:PS 只处理属于自己 MAGIC 的订单,通过 CreatTicketArray 筛选。

用 C++ 类思维升级到 MQL5

作者呼吁 MQL 开发者扩展语言选择,并给出 C++ 风格类示例。MQL5 已支持 class,我们可以将系统抽象为 MTS 类,包含 SignalID、Tickets 数组、SystemParam 结构、处理与追踪方法。这样多系统实例化各自对象,彻底避免 MAGIC 冲突。

struct SystemParam
{
    double TP;
    double NullTP;
    double NullTP2;
    double TS;
    double NullSL;
    double SL;
    double dSL;
    double dStep;
    double dLot;
}

class MTS 
{
    public:
    string m_NameTS;
    int m_SignalID;
    long int Tickets[1000];
    SystemParam SysPar;
    color ClrBuy;
    color ClrSell;
    void MyMTS ();
    int CheckSignal();
    int m_nProcessMode;
    int m_nTrailingMode;
    void Process();
    void Trailing();
    bool CreatTicketArray(int dir);
};

在 MQL5 的 init() 中初始化 MyTS 对象,设置 SignalID、参数与模式;start() 中调用 Process 与 Trailing。全局 CheckSignal 仍按 ID 分发,但对象内聚使代码可维护。注意 MQL4 无类,需用结构体+函数模拟,且所有订单函数加 SignalID 作 MAGIC。

对象方法与传统 MAGIC 方案对比

⚠ 迁移注意
从 MQL4 转 MQL5 时,iEnvelopes 等函数名与参数可能变化;旧 EA 的 MAGIC 管理需重构为类实例字段,避免直接复制全局变量。

实操步骤:搭建你的对象化 EA

第一步,定义信号宏与 CheckSignal 分发。第二步,编写各指标信号函数返回 OP_BUY/OP_SELL/-1。第三步,设计 SysPar 二维数组或 MQL5 结构,填写止盈止损等。第四步,在 start 中调用 Process 与 Trailing 系列函数。第五步,用 Tickets 数组按 MAGIC 过滤订单。第六步,接入追踪库或自写 Trailing 函数。如此即可在同一账户安全运行多个策略。

原文提供了 Signals.mqh、TradeSystem.mq4、Traling.mqh 供下载研究。虽基于 MQL4,但思想通用于 MQL5。开发者应关注官方对语言特性的演进,同时用本文模型解决当下多系统隔离需求。

常见问题

给每个 EA 分配独立的 MAGIC 号码(幻数),并在订单操作函数(如 OrderSend、OrderSelect)中严格按 MagicNumber 过滤。使用本文的对象模型将参数与订单数组隔离更能降低冲突。
用 #define 定义信号 ID、用二维数组存储系统参数、用函数前缀(如 Process/Trailing)模拟对象行为,并结合结构体(MQL5 可用 class)封装状态与门票数组,实现关注点分离。
部分经纪商在订单交换(平仓并开新单)时会改变 ticket 编号,导致数组失效。应使用 MAGIC 标识归属,并在每次操作前用 OrderSelect 按 MagicNumber 重新筛选。
将信号 ID 与参数放入 class 的字段,信号函数作为方法或全局分发;用 CTrade 类代替 OrderSend;用 ulong 数组存 ticket。注意 iEnvelopes 等函数参数可能需调整。