MQL对象化交易系统构建法
用信号量、观察器与头寸支持模块破解多EA同账户难题
为什么 MQL 需要对象方法
在 MQL 环境下编程,无论是初学者还是专业开发者,都会逐渐意识到语言本身的局限性。MQL4 本质上是一种面向过程的语言,并没有原生提供类与对象机制(MQL5 虽支持类但很多老代码与经纪环境仍基于 MQL4)。当我们只在一个账户运行一个 Expert Advisor 时,一切都很简单,不需要区分订单归属。但真实交易中,用户往往希望在同一账户上加载多个自动交易系统,这时就必须解决“哪些订单属于哪个系统”的问题。
MQL 官方给出的常规解法有两种:一是调用外部函数处理订单归属,二是使用 MAGIC 号码(幻数)来标记订单。即便使用外部函数,依然要在内部判断归属。理论上我们可以维护一个 OrderTicket 数组来记录某个系统的订单,但不少经纪商在订单交换时(例如一个订单关闭、另一个挂单触发)会更改 ticket 编号,导致数组失效。因此,使用 MAGIC 成为唯一稳健手段。但多个系统硬编码 MAGIC 容易造成混乱,且参数、止损、追踪逻辑难以隔离。
对象模型的三大核心模块
作者提出一种与自身对象模型一致的交易系统架构,分为三个程序块:信号系统(SS)、进入退出模块(EE)和头寸支持模块(PS)。该模型并非万能,但在 MQL 缺少原生对象语法时,用“伪对象”方式实现了关注点分离。
- A) 信号系统 SS:理解报价、计算指标、产生信号量(semaphore)
- B) EE 程序块:观察信号量,决策开仓/平仓/改单,并传递控制给 PS
- C) PS 程序块:对已开头寸进行追踪、挂单、锁定、盈亏控制
信号系统(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);
}
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 方案对比
- 传统 MAGIC:硬编码数字,全局搜索订单,参数散落,扩展易错
- 对象方法(伪):信号量+观察器+PS,参数数组化,逻辑分块,易加系统
- MQL5 类:原生封装,类型安全,最佳长期方案
实操步骤:搭建你的对象化 EA
第一步,定义信号宏与 CheckSignal 分发。第二步,编写各指标信号函数返回 OP_BUY/OP_SELL/-1。第三步,设计 SysPar 二维数组或 MQL5 结构,填写止盈止损等。第四步,在 start 中调用 Process 与 Trailing 系列函数。第五步,用 Tickets 数组按 MAGIC 过滤订单。第六步,接入追踪库或自写 Trailing 函数。如此即可在同一账户安全运行多个策略。
原文提供了 Signals.mqh、TradeSystem.mq4、Traling.mqh 供下载研究。虽基于 MQL4,但思想通用于 MQL5。开发者应关注官方对语言特性的演进,同时用本文模型解决当下多系统隔离需求。