基于交易模块创建多模块 EA 交易系统
一、为什么需要模块化 EA
MT4 时代一个 EA 只能绑死在当前图表品种与周期上,多品种系统要靠全局订单号逐个同步,极其笨重。MQL5 带来了面向对象、标准库基类与净持仓模型,但也带来新难题:同一品种只有一个总仓位(净持仓),EA 不再能靠“自己的订单”简单识别实例。把交易逻辑拆成独立「模型」单元,是应对这种复杂度的工程解法。
二、核心抽象:CModel 基类
每个交易思想封装成一个 CModel 派生类,持有自己的幻数、品种与周期,并暴露统一的 Processing/Save/Load 接口。引擎只认基类指针,从而可以无差别地调度任意多个模型。
class CModel
{
protected:
int m_magic; // 幻数,用于识别本模型产生的订单
string m_symbol; // 交易品种
ENUM_TIMEFRAMES m_period; // 时间周期
public:
CModel(void) {}
~CModel(void) {}
virtual bool Processing(void) { return false; } // 每 tick 处理逻辑
virtual bool Save(void) { return true; } // 持久化状态
virtual bool Load(void) { return true; }
int Type(void) { return m_type; }
int Compare(CModel* &m) { return m_magic - m.m_magic; }
};
三、注册与调度:list_models
用 CArrayObj 容器保存所有模型实例,主循环遍历并调用 Processing。这样一套 EA 就能同时驱动 MACD 模型、均线模型运行在不同品种/周期,互不干扰。
CArrayObj list_models; // 模型容器
// 注册两个不同模型
list_models.Add(new cmodel_macd(magicA, _Symbol, PERIOD_M15));
list_models.Add(new cmodel_moving(magicB, "EURUSD", PERIOD_H1));
void OnTick(void)
{
for(int i=0; i<list_models.Total(); i++)
{
CModel* m = list_models.At(i);
if(m != NULL) m.Processing();
}
}
四、净持仓与幻数:用幻数区分实例
五、状态持久化 Save / Load
模型若用到跨周期记忆(如突破位、序列号),应通过 Save/Load 或全局变量持久化,应对终端重启与历史同步中断。否则重启后会丢失状态,可能重复触发信号。
bool cmodel_macd::Save(void)
{
GlobalVariableSet(IntegerToString(m_magic)+"_lastcross", m_lastcross);
return true;
}
bool cmodel_macd::Load(void)
{
m_lastcross = (int)GlobalVariableGet(IntegerToString(m_magic)+"_lastcross");
return true;
}
六、实战陷阱
- 净持仓下不要用 PositionClose 直接平掉全部,必须按 m_magic + m_symbol 过滤。
- 多周期 OnTick 触发频率不同,慢周期模型可能被快周期模型“带着跑”,需用各自定时器。
- 多个模型同时要求加仓时,要在引擎层做总资金冲突仲裁,避免超额。
七、模块职责边界
多模块 EA 把系统拆成信号模块(出方向)、风控模块(定仓位与止损)、执行模块(下单对账)、甚至数据源模块(取行情/因子)。每个模块只管一件事,接口清晰,替换一个不影响其他。
边界模糊是 EA 难维护的根因。把「谁决定买、谁决定买多少、谁负责挂单」分清楚,debug 时你能立刻定位问题在哪一环。
八、模块间如何通信
模块间通信通常有两种:共享状态(一个全局上下文对象,各模块读写)或事件(某模块产出信号后广播,下游订阅)。共享状态简单但易耦合,事件更解耦但要多写调度。
九、配置化装配
进阶做法是用配置文件或参数决定「装哪些模块、怎么连」:换信号只需改配置,不必动代码。这把 EA 变成「可拼装平台」,策略迭代从改代码降级为改配置,速度快且不易错。
// 配置驱动装配示意
EA.LoadModule("signal", "MacdCross");
EA.LoadModule("risk", "RiskPercent");
EA.LoadModule("exec", "CTradeAdapter");
十、模块版本与回滚
把每个模块当成带版本的组件管理:改了信号逻辑就升一级版本号,出问题能一键回退到上一版。EA 不再是「一团改不清的代码」,而是「可审计的组件组合」,出了事能定位到哪个模块哪版引入的。
配合配置化装配,回滚就是换一行配置,不必动代码重编译。这对长期运维是巨大的减负,也是专业团队和业余脚本的分水岭。
十一、模块单体验证
模块解耦的最大好处是可单体测试:给信号模块喂一段历史数据,断言它在已知顶底返回正确方向;给风控模块喂一组盈亏,断言手数计算正确。单体过了再组合,bug 定位极快。
把单体测试写成脚本,每次改模块都重跑,等于给 EA 装了回归测试。策略越复杂,这套测试越值钱,能防止「改信号顺手弄坏风控」。
十二、小结
模块化 EA 的本质是“把策略当插件”:信号、资金、追踪各自独立,引擎统一调度。它牺牲了一点简单性,换来了多品种、多周期、多策略的可扩展性与可维护性。