🧱
基于交易模块创建多模块 EA 交易系统
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;
}
实战陷阱
- 净持仓下不要用 PositionClose 直接平掉全部,必须按 m_magic + m_symbol 过滤。
- 多周期 OnTick 触发频率不同,慢周期模型可能被快周期模型“带着跑”,需用各自定时器。
- 多个模型同时要求加仓时,要在引擎层做总资金冲突仲裁,避免超额。
小结
模块化 EA 的本质是“把策略当插件”:信号、资金、追踪各自独立,引擎统一调度。它牺牲了一点简单性,换来了多品种、多周期、多策略的可扩展性与可维护性。
常见问题
普通 EA 绑定单一图表品种/周期;多模块 EA 在引擎内注册多个 CModel 实例,可在不同品种/周期独立运算,统一用幻数归属订单。
遍历 PositionSelect / HistorySelect 时用 m_magic 过滤,并判断 m_symbol 匹配;不要直接平掉该品种的全部仓位。
若用到跨周期记忆(突破位、序列号),用 Save/Load 或 GlobalVariable 持久化,否则终端重启会丢状态导致重复开仓。