创建一个在若干工具上交易的 EA 交易程序(基础篇)
📘

创建一个在若干工具上交易的 EA 交易程序(基础篇)

第 1/2 篇

用 EA 跨多个品种同时跑策略

在 MT5 里写一个 EA 并不难,难点在于让它同时盯住若干交易品种而不是只绑死当前图表。默认情况下,EA 只认 ChartSymbol(),也就是你加载它那张图的品种;要让它跨黄金、欧美、美日一起动手,就得在代码里显式去取其他品种的价格与交易通道。 Nikolay Kositsin 在 2013-11-15 发布的示例帖,累计被查看 3338 次、收到 59 条互动,说明多品种 EA 是实盘前绕不开的刚需。MT5 的 CA 架构(Chart + Market + Trade)比 MT4 灵活,但也要你手动管理每个品种的 tick 同步,否则会出现某品种信号迟到几根 K 线。 高风险提示:外汇与贵金属杠杆品种波动剧烈,多品种同时开仓会放大回撤,先在策略测试器里用 2020—2023 年 tick 数据跑一遍再上真金。

「为什么多货币 EA 直到 MT5 才真正可测」

在一张图表上驱动多个金融资产下单,这件事在 MQL4 里其实就能写,但最大的短板是没法用策略测试器做完整回测。MT5 客户端出现后,交易员才第一次拿到对这类跨品种自动系统做全量历史分析的能力。 多货币机器人现在比早几年普及得多,构建兴趣还在往上走。但真动手写,主要卡在代码体量随交易品种数近似算术级数膨胀,标准程序员往往不愿意接这种活。 本文要落地的,是一个结构缺陷尽量压到最小的多货币 EA 骨架。你开 MT5 新建 EA 时,可以直接照这个思路把品种数组和订单路由拆开,避免后期改一个逻辑要动几十行重复代码。

◍ 用 TEMA 搭一个十二货币 EA 的骨架

多货币 EA 的第一道坎不是信号逻辑,而是参数组织。原文把 12 个品种各自拆成独立输入块:代号、交易开关、TEMA 周期、应用价格、止损、获利、手数、滑点,全在全局声明。以 EURUSD 为例,周期给 15、止损 1000 点、获利 2000 点、手数 0.1、滑点 30,这种粒度意味着你每加一个品种就复制一整组变量,只是后缀数字变了。 OnTick() 里不做具体事,只把 12 个品种轮询一遍:TradeSignalCounter 算信号,TradePerformer 执行下单,两者靠静态 bool 数组 UpSignal[12]、DnSignal[12]、UpStop[12]、DnStop[12] 按引用传值。第一次跑策略测试器会非常慢,因为要加载全部 12 个品种的历史,这点要有心理准备。 信号函数本身只在每根 K 线切换时取一次 TEMA 句柄,执行函数再调四个封装好的下单子函数。逻辑朴素到用随机参数就指望赚钱是不现实的,外汇和贵金属本身高杠杆高风险,这套代码的价值在于证明多货币框架能这么轻量落地。 优化时别一次性跑全参数,传统算法吃不消十几个输入。务实做法是每次只开一个 TradeN 开关,其余十一组设 false,逐品种单独过参。

MQL5 / C++
class="kw">input class="type">class="kw">string             Symb0 = "EURUSD";
class="kw">input class="type">bool              Trade0 = true;
class="kw">input class="type">int               Per0 = class="num">15;
class="kw">input ENUM_APPLIED_PRICE ApPrice0 = PRICE_CLOSE;
class="kw">input class="type">int               StLoss0 = class="num">1000;
class="kw">input class="type">int               TkProfit0 = class="num">2000;
class="kw">input class="type">class="kw">double            Lots0 = class="num">0.1;
class="kw">input class="type">int               Slippage0 = class="num">30;
class="type">void OnTick()
  {
class=class="str">"cmt">//--- 声明交易信号的变量数组 
   class="kw">static class="type">bool UpSignal[class="num">12], DnSignal[class="num">12], UpStop[class="num">12], DnStop[class="num">12];

class=class="str">"cmt">//--- 取得交易信号
   TradeSignalCounter( class="num">0, Symb0,  Trade0,  Per0,  ApPrice0,  UpSignal, DnSignal, UpStop, DnStop);
   TradeSignalCounter( class="num">1, Symb1,  Trade1,  Per1,  ApPrice1,  UpSignal, DnSignal, UpStop, DnStop);
   TradeSignalCounter( class="num">2, Symb2,  Trade2,  Per2,  ApPrice2,  UpSignal, DnSignal, UpStop, DnStop);
   TradeSignalCounter( class="num">3, Symb3,  Trade3,  Per3,  ApPrice3,  UpSignal, DnSignal, UpStop, DnStop);
   TradeSignalCounter( class="num">4, Symb4,  Trade4,  Per4,  ApPrice4,  UpSignal, DnSignal, UpStop, DnStop);
   TradeSignalCounter( class="num">5, Symb5,  Trade5,  Per5,  ApPrice5,  UpSignal, DnSignal, UpStop, DnStop);
   TradeSignalCounter( class="num">6, Symb6,  Trade6,  Per6,  ApPrice6,  UpSignal, DnSignal, UpStop, DnStop);
   TradeSignalCounter( class="num">7, Symb7,  Trade7,  Per7,  ApPrice7,  UpSignal, DnSignal, UpStop, DnStop);
   TradeSignalCounter( class="num">8, Symb8,  Trade8,  Per8,  ApPrice8,  UpSignal, DnSignal, UpStop, DnStop);
   TradeSignalCounter( class="num">9, Symb9,  Trade9,  Per9,  ApPrice9,  UpSignal, DnSignal, UpStop, DnStop);
   TradeSignalCounter(class="num">10, Symb10, Trade10, Per10, ApPrice10, UpSignal, DnSignal, UpStop, DnStop);
   TradeSignalCounter(class="num">11, Symb11, Trade11, Per11, ApPrice11, UpSignal, DnSignal, UpStop, DnStop);

class=class="str">"cmt">//--- 进行交易操作
   TradePerformer( class="num">0, Symb0,  Trade0,  StLoss0,  TkProfit0,  Lots0,  Slippage0,  UpSignal, DnSignal, UpStop, DnStop);

多品种调度与信号计数器的静态初始化

把 11 个交易对塞进同一个 EA 时,最直接的写法就是连续调用 TradePerformer,从编号 1 到 11 分别传入各自的符号名、开关、止损、止盈、手数和滑点,而上下信号与止损标记数组是共享的。这样主循环里一行一个对,肉眼就能核对哪个编号对应哪品种,改 Symb7 的参数不会影响 Symb8。 TradeSignalCounter 用静态数组 Handle[]、Recount[]、MinBars[] 来跨 tick 保留每个编号的指标句柄与状态,避免每次重算。首次进入时按 Number+1 扩容,并给 MinBars[Number] 赋值为 3*period——也就是说 period 取 20 时,该品种至少要累积 60 根 K 线才够 TEMA 三层平滑出数,少于这个数信号数组保持 false。 初始化块里把 DnSignal、UpSignal、DnStop、UpStop 对应编号位清零,相当于该品种在这一刻既无开仓信号也无止损触发。外汇与贵金属杠杆高,这类多品种并行 EA 在实盘前务必在 MT5 策略测试器用完整品种历史跑一遍,确认每个编号的 MinBars 门槛不会被跳空缺口绕过。

MQL5 / C++
TradePerformer( class="num">1, Symb1,  Trade1,  StLoss1,  TkProfit1,  Lots1,  Slippage1,  UpSignal, DnSignal, UpStop, DnStop);
TradePerformer( class="num">2, Symb2,  Trade2,  StLoss2,  TkProfit2,  Lots2,  Slippage2,  UpSignal, DnSignal, UpStop, DnStop);
TradePerformer( class="num">3, Symb3,  Trade3,  StLoss3,  TkProfit3,  Lots3,  Slippage3,  UpSignal, DnSignal, UpStop, DnStop);
TradePerformer( class="num">4, Symb4,  Trade4,  StLoss4,  TkProfit4,  Lots4,  Slippage4,  UpSignal, DnSignal, UpStop, DnStop);
TradePerformer( class="num">5, Symb5,  Trade5,  StLoss5,  TkProfit5,  Lots5,  Slippage5,  UpSignal, DnSignal, UpStop, DnStop);
TradePerformer( class="num">6, Symb6,  Trade6,  StLoss6,  TkProfit6,  Lots6,  Slippage6,  UpSignal, DnSignal, UpStop, DnStop);
TradePerformer( class="num">7, Symb7,  Trade7,  StLoss7,  TkProfit7,  Lots7,  Slippage7,  UpSignal, DnSignal, UpStop, DnStop);
TradePerformer( class="num">8, Symb8,  Trade8,  StLoss8,  TkProfit8,  Lots8,  Slippage8,  UpSignal, DnSignal, UpStop, DnStop);
TradePerformer( class="num">9, Symb9,  Trade9,  StLoss9,  TkProfit9,  Lots9,  Slippage9,  UpSignal, DnSignal, UpStop, DnStop);
TradePerformer(class="num">10, Symb10, Trade10, StLoss10, TkProfit10, Lots10, Slippage10, UpSignal, DnSignal, UpStop, DnStop);
TradePerformer(class="num">11, Symb11, Trade11, StLoss11, TkProfit11, Lots11, Slippage11, UpSignal, DnSignal, UpStop, DnStop);
class=class="str">"cmt">//---
 }
class="type">bool TradeSignalCounter(class="type">int Number,
                      class="type">class="kw">string Symbol_,
                      class="type">bool Trade,
                      class="type">int period,
                      ENUM_APPLIED_PRICE ApPrice,
                      class="type">bool &UpSignal[],
                      class="type">bool &DnSignal[],
                      class="type">bool &UpStop[],
                      class="type">bool &DnStop[])
  {
class=class="str">"cmt">//--- 检查交易是否被禁止
   if(!Trade)class="kw">return(true);
class=class="str">"cmt">//--- 声明变量用于保存最后变量数组的大小
   class="kw">static class="type">int Size_=class="num">0;
class=class="str">"cmt">//--- 声明数组用于以静态变量形式保存指标句柄
   class="kw">static class="type">int Handle[];
   class="kw">static class="type">int Recount[],MinBars[];
   class="type">class="kw">double TEMA[class="num">4],dtema1,dtema2;
class=class="str">"cmt">//--- 初始化
   if(Number+class="num">1>Size_) class=class="str">"cmt">// 只有第一次开始时进入初始化区块
     {
      Size_=Number+class="num">1; class=class="str">"cmt">// 禁止此号进入该区块
      class=class="str">"cmt">//--- 改变变量数组大小
      ArrayResize(Handle,Size_);
      ArrayResize(Recount,Size_);
      ArrayResize(MinBars,Size_);
      class=class="str">"cmt">//--- 决定足够用于计算的最小柱数 
      MinBars[Number]=class="num">3*period;
      class=class="str">"cmt">//--- 把数组元素清零
      DnSignal[Number] = false;
      UpSignal[Number] = false;
      DnStop  [Number] = false;
      UpStop  [Number] = false;
      class=class="str">"cmt">//--- 设置数组为时间序列

常见问题

用支持多品种调度的 EA 把策略写成统一信号逻辑,在初始化时把品种名塞进数组,循环扫一遍即可批量下单,不用盯每个窗口。
早期平台对多标的同步 tick 和真实点差还原能力弱,现在的新架构能按真实成交环境回放多品种,结果才接近实盘概率。
小布可以接入你的品种页,自动统计各标的历史信号触发次数和空仓比,你直接看排序就不用自己跑日志了。
把计数器声明为静态并按品种索引一一对应,在 OnInit 里用循环清零,避免不同货币共用同一变量导致信号误计。
重点防多品种同向下单后的整体敞口叠加,建议设总仓位上限与相关性过滤,外汇贵金属波动大,可能单日回撤超预期。