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