首页 / 交易学院 / 小布知识库 / 基于交易模块创建多模块 EA 交易系统
基于交易模块创建多模块 EA 交易系统
🧱

基于交易模块创建多模块 EA 交易系统

EA 开发实战 难度 · 高阶 2011 13 分钟阅读
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();
   }
}

四、净持仓与幻数:用幻数区分实例

⚠ 净持仓下的归属问题
净持仓模式下同一品种只有一个总仓位,必须用 m_magic 过滤出“本模型”的订单,否则会把别的模型或手动单算进来,导致重复开仓或错误平仓。Prev/Next 节点管理也要注意当前指针位置。

五、状态持久化 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;
}

六、实战陷阱

七、模块职责边界

多模块 EA 把系统拆成信号模块(出方向)、风控模块(定仓位与止损)、执行模块(下单对账)、甚至数据源模块(取行情/因子)。每个模块只管一件事,接口清晰,替换一个不影响其他。

边界模糊是 EA 难维护的根因。把「谁决定买、谁决定买多少、谁负责挂单」分清楚,debug 时你能立刻定位问题在哪一环。

八、模块间如何通信

模块间通信通常有两种:共享状态(一个全局上下文对象,各模块读写)或事件(某模块产出信号后广播,下游订阅)。共享状态简单但易耦合,事件更解耦但要多写调度。

✦ 通信方式定耦合度
共享上下文简单但易耦合;事件广播解耦但要多写调度。小 EA 用共享状态够,大系统用事件更稳。

九、配置化装配

进阶做法是用配置文件或参数决定「装哪些模块、怎么连」:换信号只需改配置,不必动代码。这把 EA 变成「可拼装平台」,策略迭代从改代码降级为改配置,速度快且不易错。

// 配置驱动装配示意
EA.LoadModule("signal", "MacdCross");
EA.LoadModule("risk",   "RiskPercent");
EA.LoadModule("exec",   "CTradeAdapter");

十、模块版本与回滚

把每个模块当成带版本的组件管理:改了信号逻辑就升一级版本号,出问题能一键回退到上一版。EA 不再是「一团改不清的代码」,而是「可审计的组件组合」,出了事能定位到哪个模块哪版引入的。

配合配置化装配,回滚就是换一行配置,不必动代码重编译。这对长期运维是巨大的减负,也是专业团队和业余脚本的分水岭。

十一、模块单体验证

模块解耦的最大好处是可单体测试:给信号模块喂一段历史数据,断言它在已知顶底返回正确方向;给风控模块喂一组盈亏,断言手数计算正确。单体过了再组合,bug 定位极快。

把单体测试写成脚本,每次改模块都重跑,等于给 EA 装了回归测试。策略越复杂,这套测试越值钱,能防止「改信号顺手弄坏风控」。

十二、小结

模块化 EA 的本质是“把策略当插件”:信号、资金、追踪各自独立,引擎统一调度。它牺牲了一点简单性,换来了多品种、多周期、多策略的可扩展性与可维护性。

常见问题

普通 EA 绑定单一图表品种/周期;多模块 EA 在引擎内注册多个 CModel 实例,可在不同品种/周期独立运算,统一用幻数归属订单。
遍历 PositionSelect / HistorySelect 时用 m_magic 过滤,并判断 m_symbol 匹配;不要直接平掉该品种的全部仓位。
若用到跨周期记忆(突破位、序列号),用 Save/Load 或 GlobalVariable 持久化,否则终端重启会丢状态导致重复开仓。