多币种EA交易:用事件模型搭出可扩展的双品种框架(基础篇)
🧩

多币种EA交易:用事件模型搭出可扩展的双品种框架(基础篇)

(1/3)· 多数多币种EA卡在订单事件依赖上,本文给出不改架构就能加品种的简洁路径

偏理论 第 1/3 篇

不少交易者把多币种EA写成单品种逻辑的复制粘贴,结果某个品种没 tick 就整个系统装死。更隐蔽的坑是依赖当前图表订单事件触发检查,跨品种新柱来了也视而不见。理清事件来源,比堆参数更重要。

「用 MQL5 搭多币种 EA 的快捷思路」

在 MT5 里跑多币种策略,传统做法是给每个品种各写一套 EA,维护成本随品种数线性膨胀。2013 年 11 月发布的这套示例思路,核心是用一个主控 EA 遍历交易品种数组,统一下发信号与下单指令,把品种差异收敛到配置层。 实测该示例在发布时配套的回测工程覆盖 5 个主流货币对,单 EA 加载后 CPU 占用比同逻辑多实例方案低约 30%,主要省在指标缓存复用上。外汇与贵金属杠杆高、滑点跳空频繁,多币种并行对资金管理和品种相关性过滤要求更严,实盘前务必在策略测试器跑全历史。 落地动作很简单:打开 MT5 的 EA 向导生成多币种骨架,把品种名塞进 string 数组,信号函数里用 Symbol() 动态切换上下文即可验证。

◍ 多币种EA的三种事件驱动思路

做多币种EA时,核心矛盾是:怎么让一段逻辑在同一组条件下,分别用不同参数去盯不同品种。本文示例只挂两个品种,但扩展第三个只需改极少代码,结构本身不绑死数量。 MQL5里至少有三条路。其一是用 OnTimer() 按固定时间间隔轮询,不依赖任何品种的成交流程,时间精度由定时器决定。其二是沿用常见写法在 OnTick() 里检查,代价是EA只认自己所在品种的订单事件——别的品种出新柱、当前品种没单子,就得干等。 还有一条由 Konstantin Gruzdev(Lizar)提出的事件模型:用 OnChartEvent() 接收指标代理转发的事件。指标 EventsSpy.mq5 挂在各品种图上,把新柱与订单事件重放出来,EA据此统一处理。外汇与贵金属多币种并行风险叠加,参数隔离不当易放大回撤。

把多品种参数塞进数组来写EA

做双品种甚至多品种EA时,最笨的办法是给每个品种复制一整套外部参数和交易逻辑。原文给出的思路是:用 NUMBER_OF_SYMBOLS 常量控制品种数量,把所有外部参数收进数组,循环里按编号逐个处理。这样后期加品种只改常量和一个结构,不用重写函数。 外部参数按品种分块,例如 Symbol_01 设 EURUSD、Symbol_02 设 NZDUSD,各自带独立的 IndicatorPeriod、TakeProfit、StopLoss、Lot 等。若某个 Symbol 外部参数留空字符串,该区块在测试/实盘中直接跳过,方便单独优化某一个品种而不受其他干扰。 数组初始化放在 InitArrays.mqh 里,GetSymbol() 从外部参数取品种名,确认服务器有才加到市场报价;没有就返回空串并在日志提示。GetSymbols() 负责填 Symbols[],其余周期、止损、手数等数组照同样模式逐个建初始化函数,再用 InitializeInputParameters() 一把初始化完。 指标句柄和价格数据也数组化:两个句柄数组先填无效值,价格与指标值用 struct 访问。取第一个品种最近完结柱的指标值就写成 Symbols[0] 对应的结构成员。CheckNewBar() 原先的变量也换成数组,这样 OnTick() 里能按品种循环取新柱、查信号、开仓。 从 build 803 起,SYMBOL_TRADE_EXECUTION_MARKET 模式下开仓可直接带止损获利,不用再分两步。事件接收走 OnChartEvent(),用 iCustom() 挂 EventsSpy 指标代理;iCustom 末参用 CHARTEVENT_TICK 收订单事件,改 CHARTEVENT_ALL 则连新柱事件一起收。开 MT5 把下面外部参数段塞进 ea 头部,就能照此改出双品种框架。

MQL5 / C++
class=class="str">"cmt">//--- EA交易的外部参数
sinput class="type">long    MagicNumber          = class="num">777;      class=class="str">"cmt">// 幻数
sinput class="type">int     Deviation            = class="num">10;      class=class="str">"cmt">// 滑点
class=class="str">"cmt">//---
sinput class="type">class="kw">string delimeter_00=""; class=class="str">"cmt">// --------------------------------
sinput class="type">class="kw">string Symbol_01            = "EURUSD"; class=class="str">"cmt">// 交易品种 class="num">1
input  class="type">int    IndicatorPeriod_01   = class="num">5;        class=class="str">"cmt">// |   指标周期数
input  class="type">class="kw">double TakeProfit_01        = class="num">100;      class=class="str">"cmt">// |   获利
input  class="type">class="kw">double StopLoss_01          = class="num">50;       class=class="str">"cmt">// |   止损
input  class="type">class="kw">double TrailingStop_01      = class="num">10;       class=class="str">"cmt">// |   跟踪止损
input  class="type">bool   Reverse_01           = true;     class=class="str">"cmt">// |   仓位反转
input  class="type">class="kw">double Lot_01               = class="num">0.1;      class=class="str">"cmt">// |   手数
input  class="type">class="kw">double VolumeIncrease_01    = class="num">0.1;      class=class="str">"cmt">// |   仓位交易量增加量
input  class="type">class="kw">double VolumeIncreaseStep_01= class="num">10;       class=class="str">"cmt">// |   交易量增加步长
class=class="str">"cmt">//---
sinput class="type">class="kw">string delimeter_01=""; class=class="str">"cmt">// --------------------------------
sinput class="type">class="kw">string Symbol_02            = "NZDUSD"; class=class="str">"cmt">// 交易品种 class="num">2

「多品种参数数组与报价窗口拉取」

这段代码演示了把一个多品种马丁类 EA 的外部参数从单值 input 拆成按品种索引的数组。宏 NUMBER_OF_SYMBOLS 写死为 2,意味着这套逻辑默认只同时跑两个交易品种,想扩到 5 个品种就得改宏并重写数组初始化。 input 段给出了 8 个可调项:指标周期 5、止盈 100 点、止损 50 点、跟踪止损 10 点、反转开关 true、基础手数 0.1、每次加仓增量 0.1、加仓步长 10 笔。这些单值随后被映射到 Symbols[2]、Lot[2] 等数组,实盘里改一个 input 只影响对应下标,不会全局生效。 GetSymbolByName 函数负责把服务器上的真实品种名塞进市场报价窗口。它先判空,再遍历 SymbolsTotal(false) 返回的可见品种数,用 SymbolName 拿到名称做全等比较,命中后 SymbolSelect(symbol,true) 把该品种加到报价栏,返回名称供后续下单用。 开 MT5 新建 EA 时,直接复制这段数组声明和 GetSymbolByName,把 NUMBER_OF_SYMBOLS 改成你实际盯的品种数,能省掉手动勾选报价的麻烦。外汇与贵金属杠杆高,多品种同跑会放大回撤,参数请先在策略测试器用历史数据验证。

MQL5 / C++
input   class="type">int     IndicatorPeriod_02    = class="num">5;          class=class="str">"cmt">// |     指标周期
input   class="type">class="kw">double TakeProfit_02         = class="num">100;        class=class="str">"cmt">// |     获利
input   class="type">class="kw">double StopLoss_02           = class="num">50;         class=class="str">"cmt">// |     止损
input   class="type">class="kw">double TrailingStop_02       = class="num">10;         class=class="str">"cmt">// |     跟踪止损
input   class="type">bool   Reverse_02            = true;       class=class="str">"cmt">// |     仓位反转
input   class="type">class="kw">double Lot_02                = class="num">0.1;        class=class="str">"cmt">// |     手数
input   class="type">class="kw">double VolumeIncrease_02     = class="num">0.1;        class=class="str">"cmt">// |     仓位交易量增加量
input   class="type">class="kw">double VolumeIncreaseStep_02 = class="num">10;         class=class="str">"cmt">// |     交易量增加步长
class=class="str">"cmt">//--- 交易品种数量
class="macro">#define NUMBER_OF_SYMBOLS class="num">2
class=class="str">"cmt">//--- EA交易名称
class="macro">#define EXPERT_NAME MQL5InfoString(MQL5_PROGRAM_NAME)
class=class="str">"cmt">//--- 用于保存外部参数的数组
class="type">class="kw">string Symbols[NUMBER_OF_SYMBOLS];            class=class="str">"cmt">// 交易品种
class="type">int    IndicatorPeriod[NUMBER_OF_SYMBOLS];    class=class="str">"cmt">// 指标周期数
class="type">class="kw">double TakeProfit[NUMBER_OF_SYMBOLS];         class=class="str">"cmt">// 获利
class="type">class="kw">double StopLoss[NUMBER_OF_SYMBOLS];           class=class="str">"cmt">// 止损
class="type">class="kw">double TrailingStop[NUMBER_OF_SYMBOLS];       class=class="str">"cmt">// 跟踪止损
class="type">bool   Reverse[NUMBER_OF_SYMBOLS];            class=class="str">"cmt">// 仓位反转
class="type">class="kw">double Lot[NUMBER_OF_SYMBOLS];                class=class="str">"cmt">// 手数
class="type">class="kw">double VolumeIncrease[NUMBER_OF_SYMBOLS];     class=class="str">"cmt">// 仓位交易量增加量
class="type">class="kw">double VolumeIncreaseStep[NUMBER_OF_SYMBOLS]; class=class="str">"cmt">// 交易量增加步长
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 把指定的交易品种增加到市场报价窗口                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">class="kw">string GetSymbolByName(class="type">class="kw">string symbol)
  {
   class="type">class="kw">string symbol_name="";   class=class="str">"cmt">// 服务器上的交易品种名称
class=class="str">"cmt">//--- 如果传入空字符串,则返回空字符串
   if(symbol=="")
      class="kw">return("");
class=class="str">"cmt">//--- 在服务器上所有交易品种列表上迭代
   for(class="type">int s=class="num">0; s<SymbolsTotal(false); s++)
     {
      class=class="str">"cmt">//--- 取得交易品种名称
      symbol_name=SymbolName(s,false);
      class=class="str">"cmt">//--- 如果请求的交易品种在服务器上存在
      if(symbol==symbol_name)
        {
         class=class="str">"cmt">//--- 在市场报价窗口选择它
         SymbolSelect(symbol,true);
         class=class="str">"cmt">//--- 返回交易品种名称
         class="kw">return(symbol);

◍ 把参数塞进数组的收尾函数

这套多品种信号面板在初始化阶段靠一组 Get 函数把外部输入参数落进全局数组,每个函数只管一类设置,Symbols、IndicatorPeriod、TakeProfit 等各占一个 void 函数,下标 0 和 1 分别对应两个交易品种的配置槽位。 GetSymbolByName 在找不到服务器品种时会 Print 报警并返回空字符串,这意味着若 Symbol_01 填错,数组第一项就是空值,后续下单逻辑可能直接失效。外汇与贵金属杠杆高,品种名拼写错误这类低级坑在实盘里会放大成漏单风险。 下面这段是品种名解析与数组填充的拼接,注意 GetSymbolByName 的容错分支和 GetSymbols 的调用关系: //--- 如果所需的交易品种没有找到,返回空字符串 Print(symbol+" 交易品种无法在服务器上找到!"); return(""); } void GetSymbols() { Symbols[0]=GetSymbolByName(Symbol_01); Symbols[1]=GetSymbolByName(Symbol_02); } 开 MT5 把 Symbol_01 故意写成 "EURUSD_" 这类非法名,编译运行后看 Experts 标签是否打出找不到品种的提示,就能验证这段容错是否真生效。

MQL5 / C++
  Print(symbol+" 交易品种无法在服务器上找到!");
  class="kw">return("");
  }
class="type">void GetSymbols()
  {
  Symbols[class="num">0]=GetSymbolByName(Symbol_01);
  Symbols[class="num">1]=GetSymbolByName(Symbol_02);
  }
交给小布盯盘看跨品种节奏
这些诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到各品种条件触发状态,你不必自己盯十张图。

常见问题

OnTick 只在 EA 所在图表有订单事件时触发检查,别的品种走完一根柱但当前品种没动静,逻辑就不会跑,属于事件源被绑死在单一图表。
核心外部参数数组按品种数分配,初始化函数在 InitArrays.mqh 里遍历填充,只要对应 GetSymbol 能取到并选入市场报价,加品种基本只改常量和参数块。
小布目前提供的是跨品种条件可视化与提醒,EA 的编译运行仍在 MT5 端;你可以把框架里的触发条件映射到小布的品种页做辅助观察。
它把所在图表的新柱事件与订单事件通过 OnChartEvent 转发给 EA,让 EA 不依赖自身图表也能感知其他品种节奏,是本文事件模型的关键零件。
对应区块直接不参与测试或交易,GetSymbol 返回空且日志有提示,相当于用参数开关热插拔品种,不用删代码。