创建多币种多系统 EA 交易(基础篇)
📘

创建多币种多系统 EA 交易(基础篇)

第 1/2 篇

「在 MT5 里搭多币种多系统 EA 的骨架」

想在 MT5 上同时跑多个货币对、多套策略逻辑,核心不是堆指标,而是把交易对象与策略系统解耦成可枚举的配置单元。这样同一套 EA 框架能横向扩展到 EURUSD、XAUUSD 等品种,而不必为每个品种重写一遍。 外汇与贵金属属高杠杆品种,多币种同跑会放大净值波动,实盘前务必在策略测试器用真实点差回测。MT5 的策略测试器支持同时加载多个交易品种的历史数据,这是多系统验证的基础环境。 一个最小可验证的起点:用 Symbol() 与 Period() 枚举当前图表品种和周期,把下单逻辑包进按系统编号分支的函数里。下面这段代码演示了如何按外部参数切换系统编号并获取当前品种名,复制到 MT5 的 EA 模板里编译即可看到效果。

MQL5 / C++
input class="type">int SystemID = class="num">1; class=class="str">"cmt">// 系统编号,外部参数切换多系统
class="type">void OnTick()
  {
   class="type">class="kw">string sym = Symbol();   class=class="str">"cmt">// 获取当前图表交易品种,如 "EURUSD"
   class="type">int tf = Period();       class=class="str">"cmt">// 获取当前图表周期,如 PERIOD_M15
   if(SystemID == class="num">1)
      TradeSystemA(sym, tf); class=class="str">"cmt">// 调用系统1逻辑
   else
      TradeSystemB(sym, tf); class=class="str">"cmt">// 调用系统2逻辑
  }

先把多策略塞进一个 EA 会怎样

做 EA 的第一道关是优化:把各品种、各策略的输入参数跑一遍,挑出回测里表现最稳的那组值,技术上才算ready。 但很多人忽略一个事实——当你把好几套系统、好几个品种全塞进同一个 EA,回测报告里的整体曲线可能掩盖同步崩盘。某几个品种同周期亏损叠加时,浮亏会在同一根 K 线放大,预付款追缴经常毫无预兆。 外汇和贵金属本身杠杆高、跳空频繁,这种同步回撤不是小概率事件,而是多品种 EA 实盘里必须提前算清的坑。 本文要落地的思路,就是用多币种多系统 EA 的结构,把‘各策略同跑时总风险到底多大’这件事变成可计算的数值,而不是靠感觉。

◍ 多币种 EA 的 for 循环骨架

MT5 里写多策略多品种 EA,底层通常就是一个 for 循环:每一次迭代盯一个交易品种,循环体内挂任意数量的策略。理论上品种和策略数可以无限加,但实盘跑不跑得动,取决于你机器的算力余量。 要注意 MT5 的仓位机制——每个交易品种同一时刻只允许一个净仓位,它是之前所有买卖的累加结果。所以把一个品种上的多策略放一起回测,和把这些策略拆开分别回测再相加,结果倾向不一致,这点做组合测试时容易踩坑。 为了让 EA 不被单个品种的新报价事件绑死,多币种模式建议用 OnTimer() 驱动计算,而不是依赖 OnTick()。初始化时 EventSetTimer(1) 设成每 1 秒触发一次,去初始化用 EventKillTimer() 停掉;若想更细可用 EventSetMillisecondTimer() 精确到毫秒,但调用频率过高会拖垮终端,别滥用。 账户、仓位、品种和交易操作分别交给 CAccountInfo、CPositionInfo、CSymbolInfo、CTrade 四个类。因为主结构是 for 循环,外部参数得先定义数组大小,例如用 #define Strategy_A 2 表示策略 A 覆盖 2 个品种,再据此声明外部输入和指标句柄。 策略 A 在 EURUSD 上用 H1 周期布林带(周期 20、偏差 2.0、偏移 0),并配了 IsTrade_A0 这个布尔开关放在循环开头,方便单独禁掉某个品种的交易。外汇与贵金属杠杆高、滑点跳空频繁,这类多品种 EA 在实盘前务必用策略测试器逐品种验证资源占用。

MQL5 / C++
class=class="str">"cmt">// Include standard libraries
class=class="str">"cmt">// Create external parameters
class=class="str">"cmt">// Create arrays, variables, indicator handles, etc.
class=class="str">"cmt">//--- Initialization of the Expert Advisor
class="type">int OnInit()
  {
  class=class="str">"cmt">//--- Set event generation frequency
  EventSetTimer(class="num">1); class=class="str">"cmt">// class="num">1 second
  class=class="str">"cmt">// ...
  class="kw">return(class="num">0);
  }
class="type">void OnTimer()
  {
  class=class="str">"cmt">// ...
  }
class=class="str">"cmt">//--- Deinitialization of the Expert Advisor
class="type">void OnDeinit(class="kw">const class="type">int reason)
  {
  class=class="str">"cmt">//--- Stop event generation
  EventKillTimer();
  class=class="str">"cmt">// ...
  }
class=class="str">"cmt">//--- Include standard libraries
class="macro">#include <Trade\AccountInfo.mqh>
class="macro">#include <Trade\PositionInfo.mqh>
class="macro">#include <Trade\SymbolInfo.mqh>
class="macro">#include <Trade\Trade.mqh>
class=class="str">"cmt">//--- Number of traded symbols for each strategy
class="macro">#define Strategy_A class="num">2
class="macro">#define Strategy_B class="num">2
class=class="str">"cmt">//------------------- External parameters of strategy A
input class="type">class="kw">string             Data_for_Strategy_A="Strategy A -----------------------";
class=class="str">"cmt">//--- Symbol class="num">0
input class="type">class="kw">string             Symbol_A0       = "EURUSD";    class=class="str">"cmt">// Symbol
input class="type">bool               IsTrade_A0      = true;        class=class="str">"cmt">// Permission for trading
class=class="str">"cmt">//--- Bollinger Bands(BB) parameters
input ENUM_TIMEFRAMES    Period_A0       = PERIOD_H1;   class=class="str">"cmt">// ВВ period
input class="type">uint               BBPeriod_A0     = class="num">20;          class=class="str">"cmt">// Period for calculation of the moving average of BB
input class="type">int                BBShift_A0      = class="num">0;           class=class="str">"cmt">// Horizontal shift of ВВ
input class="type">class="kw">double             BBDeviation_A0 = class="num">2.0;          class=class="str">"cmt">// Number of standard deviations of BB
class=class="str">"cmt">//...
class=class="str">"cmt">//--- General parameters of strategy A
input class="type">class="kw">double             DealOfFreeMargin_A = class="num">1.0;      class=class="str">"cmt">// Percent of free margin for a deal
input class="type">uint               MagicNumber_A       = class="num">555;     class=class="str">"cmt">// Magic number

「策略A的多品种布林参数怎么塞进数组」

想在 MT5 里跑一套覆盖多品种、多周期的布林带策略,最忌讳把每个品种的变量写死成独立标量。上面这段声明把策略 A 的外部参数和运行状态全拆成了以 Strategy_A 为长度的数组,等于给后续循环遍历留了口子。 Slippage_A 设成 100 点,是给成交容错留的余量;外汇与贵金属杠杆高、滑点跳变频繁,实盘里这个值偏紧可能漏单,偏松可能吃劣价,建议先在策略测试器里按品种波动率回测校准。 外部参数数组里 Symbol_A 存交易品种名,IsTrade_A 控制该子策略是否参与,Period_A 用 ENUM_TIMEFRAMES 指定周期,BBPeriod_A / BBShift_A / BBDeviation_A 三件套直接对应 iBands 的 Period、Shift、Deviation。全局变量数组则缓存了每品种的最小最大手数、点值、合约规模和已成交单数,避免每次 OnTick 都去调账户接口。 指标句柄 BB_handle_high_A 和 BB_handle_low_A 分开存,说明高低轨可能挂在不同品种或不同 TF 上;BB_upper_band_high 这类缓冲数组供 CopyBuffer 取数。最后 CTrade Trade_A 与 CAccountInfo 等标准库对象,是把下单和账户查询从主逻辑里解耦出来的常规做法。

MQL5 / C++
input class="type">uint             Slippage_A          = class="num">100;   class=class="str">"cmt">// Permissible slippage for a deal
class=class="str">"cmt">//...
class=class="str">"cmt">//------------- Set variables of strategy A -----
class=class="str">"cmt">//--- Arrays for external parameters
class="type">class="kw">string              Symbol_A[Strategy_A];
class="type">bool                IsTrade_A[Strategy_A];
ENUM_TIMEFRAMES Period_A[Strategy_A];
class="type">int                 BBPeriod_A[Strategy_A];
class="type">int                 BBShift_A[Strategy_A];
class="type">class="kw">double              BBDeviation_A[Strategy_A];
class=class="str">"cmt">//--- Arrays for global variables
class="type">class="kw">double              MinLot_A[Strategy_A],MaxLot_A[Strategy_A];
class="type">class="kw">double              Point_A[Strategy_A],ContractSize_A[Strategy_A];
class="type">uint                DealNumber_A[Strategy_A];
class="type">class="kw">datetime            Locked_bar_time_A[Strategy_A],time_arr_A[];
class=class="str">"cmt">//--- Indicator handles
class="type">int                 BB_handle_high_A[Strategy_A];
class="type">int                 BB_handle_low_A[Strategy_A];
class=class="str">"cmt">//--- Arrays for indicator values
class="type">class="kw">double              BB_upper_band_high[],BB_lower_band_high[];
class="type">class="kw">double              BB_upper_band_low[],BB_lower_band_low[];
class=class="str">"cmt">//--- Class
CTrade              Trade_A;
class=class="str">"cmt">//...
class=class="str">"cmt">//--- Set global variables for all strategies
class="type">long                Leverage;
class=class="str">"cmt">//--- Classes
CAccountInfo        AccountInfo;
CPositionInfo      PositionInfo;
CSymbolInfo        SymbolInfo;

常见问题

大概率会,单线程顺序执行会让tick处理超时,建议用数组拆分策略参数再循环调用。
把周期、偏差等参数按品种索引塞进二维数组,循环里用SymbolArray[i]取用即可。
可以,小布能读取你粘贴的EA骨架代码,标出数组越界或品种漏配的风险点。
反而更省,共享指标句柄和全局变量,但需注意句柄用完后释放避免泄漏。
别在OnTick里重复创建指标,先把品种和策略参数外置成数组再统一调度。