创建一个在若干工具上交易的 EA 交易程序(基础篇)
用 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,逐品种单独过参。
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 门槛不会被跳空缺口绕过。
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">//--- 设置数组为时间序列