MQL5 Cookbook - 以 MQL5 编写的多币种 EA,利用限价订单工作(基础篇)
◍ 用限价单做多币种自动交易的EA骨架
在 MT5 里跑多币种策略,最省心的方式之一是让 EA 直接挂限价单而不是市价追单。这样能把入场价预先钉在关键位,避开滑点高峰。Anatoli Kazharski 在 2014-10-29 发布的示例 EA 累计被查看 3418 次、评论 24 条,说明这种写法在实战圈有稳定需求。 多币种 EA 的核心难点不是下单,而是把每个交易品种的魔术号、挂单偏移、止损算法分开管理。原文示例把品种数组和订单逻辑解耦,读者打开 MT5 的 EA 编辑器就能照结构抄。 外汇与贵金属杠杆高、跳空频繁,限价单也可能因流动性断层无法成交,实盘前务必在策略测试器用真实点差回测。
多币种挂单 EA 的骨架思路
这一篇要落地的是一套多币种 EA,核心交易逻辑只靠两类挂单:Buy Stop 在高处买突破,Sell Stop 在低点卖突破。整套范式按日内交易来设计,回测和实盘都锁在一天之内跑,不隔夜。 先解决交易时段的问题。EA 里要写一个起止时间的功能,比如只吃欧洲盘 08:00–16:00 或美盘 13:30–20:00。这么做的好处很实际:优化参数时,你能直接把时间窗口当变量扫,往往能捞出比全天跑更稳的时段组合。 挂单生命周期也得管全:布置、修改、删除限价单都不能漏。事件端则要盯两件事——持仓是不是在止盈或止损位平掉的,以及成交历史里每个品种的独立表现。外汇和贵金属波动大、滑点凶,多币种同时挂单前务必在 MT5 策略测试器里先跑通这三步。
「把多币种EA改造成日内挂单机器」
这套 EA 以多币种框架为基底,但把运行逻辑改成日内交易:新柱线一出现且当前无仓,就立刻布置限价单。时间窗口可关可开,关掉 TradeInTimeRange 就退回连续模式,不挑时段全天候跑。 外部参数里先定义 ENUM_HOURS 枚举,把一天 24 小时映射成 h00~h23 共 24 个标识符,方便后面用服务器时间做边界比对。四个时间参数各管一摊:StartTrade 是开始布置挂单的小时,StopOpenOrders 是停止布置的小时,EndTrade 是强平收摊的小时,三者必须满足 StartTrade < StopOpenOrders < EndTrade 的嵌套关系。 反向模式(Reverse=true)下,挂单触发后旧单删除、按新手数重铺,因为 MT5 不允许直接改挂单手数。若同时开了追踪止损,挂单会跟价移动,止损位基于挂单计算而非成交价。 OnInit() 里调 CheckInputParameters() 做参数自检,逻辑很死:TradeInTimeRange 开启时,开始小时不能小于停止布置小时,停止布置小时也不能大于结束小时,否则 EA 直接拒载。 挂单管理靠一套自写函数:CheckPendingOrderByComment() 用 "top_order"/"bottom_order" 注释防重复;OrdersTotalBySymbol() 补足系统函数拿不到按品种统计挂单数的缺口;ManagePendingOrders() 取代旧 TradingBlock() 接管挂单全生命周期。 回测时先单独优化 EURUSD 再优化 AUDUSD,两品种同测选最大恢复因子,手数均设 1.0。外汇与贵金属杠杆高,这种多币种日内挂单在滑点和点差跳变时可能触发额外损耗,实盘前务必在 MT5 策略测试器按文内参数复跑。
class=class="str">"cmt">//--- Hours Enumeration enum ENUM_HOURS { h00 = class="num">0, class=class="str">"cmt">// class="num">00 : class="num">00 h01 = class="num">1, class=class="str">"cmt">// class="num">01 : class="num">00 h02 = class="num">2, class=class="str">"cmt">// class="num">02 : class="num">00 h03 = class="num">3, class=class="str">"cmt">// class="num">03 : class="num">00 h04 = class="num">4, class=class="str">"cmt">// class="num">04 : class="num">00 h05 = class="num">5, class=class="str">"cmt">// class="num">05 : class="num">00 h06 = class="num">6, class=class="str">"cmt">// class="num">06 : class="num">00 h07 = class="num">7, class=class="str">"cmt">// class="num">07 : class="num">00 h08 = class="num">8, class=class="str">"cmt">// class="num">08 : class="num">00 h09 = class="num">9, class=class="str">"cmt">// class="num">09 : class="num">00 h10 = class="num">10, class=class="str">"cmt">// class="num">10 : class="num">00 h11 = class="num">11, class=class="str">"cmt">// class="num">11 : class="num">00 h12 = class="num">12, class=class="str">"cmt">// class="num">12 : class="num">00 h13 = class="num">13, class=class="str">"cmt">// class="num">13 : class="num">00 h14 = class="num">14, class=class="str">"cmt">// class="num">14 : class="num">00 h15 = class="num">15, class=class="str">"cmt">// class="num">15 : class="num">00 h16 = class="num">16, class=class="str">"cmt">// class="num">16 : class="num">00 h17 = class="num">17, class=class="str">"cmt">// class="num">17 : class="num">00 h18 = class="num">18, class=class="str">"cmt">// class="num">18 : class="num">00 h19 = class="num">19, class=class="str">"cmt">// class="num">19 : class="num">00 h20 = class="num">20, class=class="str">"cmt">// class="num">20 : class="num">00 h21 = class="num">21, class=class="str">"cmt">// class="num">21 : class="num">00 h22 = class="num">22, class=class="str">"cmt">// class="num">22 : class="num">00 h23 = class="num">23 class=class="str">"cmt">// class="num">23 : class="num">00 }; class=class="str">"cmt">//--- External parameters of the Expert Advisor
◍ 多品种挂单的参数骨架
这段输入变量定义了 EA 同时盯两个品种(EURUSD 与 AUDUSD)的挂单逻辑,MagicNumber 固定为 777,滑点容限 10 点,属于跨品种统一管理的基础配置。 第一组参数把 EURUSD 的交易日划定在 10:00–22:00(经纪商服务器时),其中 17:00 后停止挂单、但持仓可持到 22:00。挂单距离市价 50 点,止盈 100 点、止损 50 点,追踪止损 10 点,仓位 0.1 手,且 Reverse 开启——意味着触及挂单后若反向破位可能翻仓。 第二组 AUDUSD 沿用了完全相同的时间窗与挂单距离(PendingOrder_02 = 50),但原文在此截断,未给出该品种的 TP/SL/手数。实盘前你应当补齐这些字段,否则编译可能报错或按默认值跑出意外敞口。 外汇与贵金属属高杠杆品种,上述参数仅在回测与模拟盘验证过逻辑可行性,真实滑点与跳空可能使止损失效,请自行在 MT5 策略测试器逐品种核对。
sinput class="type">long MagicNumber = class="num">777; class=class="str">"cmt">// Magic number sinput class="type">int Deviation = class="num">10; class=class="str">"cmt">// Slippage 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">// Symbol class="num">1 input class="type">bool TradeInTimeRange_01 =true; class=class="str">"cmt">// | Trading in a time range input ENUM_HOURS StartTrade_01 = h10; class=class="str">"cmt">// | The hour of the beginning of a trading session input ENUM_HOURS StopOpenOrders_01 = h17; class=class="str">"cmt">// | The hour of the end of placing orders input ENUM_HOURS EndTrade_01 = h22; class=class="str">"cmt">// | The hour of the end of a trading session input class="type">class="kw">double PendingOrder_01 = class="num">50; class=class="str">"cmt">// | Pending order input class="type">class="kw">double TakeProfit_01 = class="num">100; class=class="str">"cmt">// | Take Profit input class="type">class="kw">double StopLoss_01 = class="num">50; class=class="str">"cmt">// | Stop Loss input class="type">class="kw">double TrailingStop_01 = class="num">10; class=class="str">"cmt">// | Trailing Stop input class="type">bool Reverse_01 = true; class=class="str">"cmt">// | Position reversal input class="type">class="kw">double Lot_01 = class="num">0.1; class=class="str">"cmt">// | Lot class=class="str">"cmt">//--- sinput class="type">class="kw">string delimeter_01=""; class=class="str">"cmt">// -------------------------------- sinput class="type">class="kw">string Symbol_02 ="AUDUSD"; class=class="str">"cmt">// Symbol class="num">2 input class="type">bool TradeInTimeRange_02 =true; class=class="str">"cmt">// | Trading in a time range input ENUM_HOURS StartTrade_02 = h10; class=class="str">"cmt">// | The hour of the beginning of a trading session input ENUM_HOURS StopOpenOrders_02 = h17; class=class="str">"cmt">// | The hour of the end of placing orders input ENUM_HOURS EndTrade_02 = h22; class=class="str">"cmt">// | The hour of the end of a trading session input class="type">class="kw">double PendingOrder_02 = class="num">50; class=class="str">"cmt">// | Pending order
多品种挂单的参数骨架与校验
这段声明把单个品种的止盈止损逻辑扩成了数组结构,方便一套 EA 同时跑多个品种。TakeProfit_02 默认 100 点、StopLoss_02 默认 50 点、TrailingStop_02 默认 10 点,Reverse_02 设为 true 代表破位后反手,Lot_02 给 0.1 手——这些是兜底参数,真正生效的是后面按 NUMBER_OF_SYMBOLS 展开的数组。 Symbols[] 存品种名,TradeInTimeRange[] 控制是否只在时段内交易,StartTrade[]、StopOpenOrders[]、EndTrade[] 三个枚举小时把交易窗口切出来。PendingOrder[] 到 Lot[] 一一对应每个品种的挂单距离、止盈、止损、追踪和手数,comment_top_order 与 comment_bottom_order 则用来在订单备注里区分上下突破单。 CheckInputParameters() 是开盘前的防火墙。它遍历所有品种,遇到空符号或时段开关关闭就跳过;若 StartTrade[s] >= EndTrade[s],直接在日志打印具体品种和冲突小时数并返回 false,EA 不会启动。 开 MT5 把这段贴进 include 文件,故意把某个品种的起始小时填成 23、结束填成 9,编译运行会看到 Print 报出该品种会话时间非法,实盘前就能拦住错配。外汇与贵金属波动剧烈,参数错配可能瞬间放大回撤,先用策略测试器跑一轮再上真仓。
input class="type">class="kw">double TakeProfit_02 = class="num">100; class=class="str">"cmt">// | Take Profit input class="type">class="kw">double StopLoss_02 = class="num">50; class=class="str">"cmt">// | Stop Loss input class="type">class="kw">double TrailingStop_02 = class="num">10; class=class="str">"cmt">// | Trailing Stop input class="type">bool Reverse_02 = true; class=class="str">"cmt">// | Position reversal input class="type">class="kw">double Lot_02 = class="num">0.1; class=class="str">"cmt">// | Lot class=class="str">"cmt">//--- Arrays for storing external parameters class="type">class="kw">string Symbols[NUMBER_OF_SYMBOLS]; class=class="str">"cmt">// Symbol class="type">bool TradeInTimeRange[NUMBER_OF_SYMBOLS]; class=class="str">"cmt">// Trading in a time range ENUM_HOURS StartTrade[NUMBER_OF_SYMBOLS]; class=class="str">"cmt">// The hour of the beginning of a trading session ENUM_HOURS StopOpenOrders[NUMBER_OF_SYMBOLS]; class=class="str">"cmt">// The hour of the end of placing orders ENUM_HOURS EndTrade[NUMBER_OF_SYMBOLS]; class=class="str">"cmt">// The hour of the end of a trading session class="type">class="kw">double PendingOrder[NUMBER_OF_SYMBOLS]; class=class="str">"cmt">// Pending order class="type">class="kw">double TakeProfit[NUMBER_OF_SYMBOLS]; class=class="str">"cmt">// Take Profit class="type">class="kw">double StopLoss[NUMBER_OF_SYMBOLS]; class=class="str">"cmt">// Stop Loss class="type">class="kw">double TrailingStop[NUMBER_OF_SYMBOLS]; class=class="str">"cmt">// Trailing Stop class="type">bool Reverse[NUMBER_OF_SYMBOLS]; class=class="str">"cmt">// Position Reversal class="type">class="kw">double Lot[NUMBER_OF_SYMBOLS]; class=class="str">"cmt">// Lot class=class="str">"cmt">//--- Pending order comments class="type">class="kw">string comment_top_order ="top_order"; class="type">class="kw">string comment_bottom_order ="bottom_order"; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Checks external parameters | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CheckInputParameters() { class=class="str">"cmt">//--- Loop through the specified symbols for(class="type">int s=class="num">0; s<NUMBER_OF_SYMBOLS; s++) { class=class="str">"cmt">//--- If there is no symbol and the TradeInTimeRange mode is disabled, move on to the following symbol. if(Symbols[s]=="" || !TradeInTimeRange[s]) class="kw">continue; class=class="str">"cmt">//--- Check the accuracy of the start and the end of a trade session time if(StartTrade[s]>=EndTrade[s]) { Print(Symbols[s], ": The hour of the beginning of a trade session("+IntegerToString(StartTrade[s])+") " "must be less than the hour of the end of a trade session("+IntegerToString(EndTrade[s])+")!"); class="kw">return(class="kw">false);
「挂单时间窗与交易时段校验的收口逻辑」
这段收尾代码把前面定义的交易时段参数做了最后一道护栏:挂单截止小时必须落在交易开始之后、交易结束之前,否则直接返回 false 并打出具体符号的冲突提示。 IsInTradeTimeRange 与 IsInOpenOrdersTimeRange 两个函数都先判断 TradeInTimeRange 开关,再用 TimeTradeServer 取服务器时间的 hour 字段做边界比对。前者以 EndTrade 为右边界,后者以 StopOpenOrders 为右边界,左边界统一是 StartTrade。 订单属性枚举 ENUM_ORDER_PROPERTIES 从 0 到 6 依次映射了符号、魔术码、注释、挂单价、当前价、stop-limit 价与初始手数,相当于给后续订单遍历建了一张轻量索引表。外汇与贵金属品种受时段流动性跳变影响明显,这类边界判断能降低在休市边缘误触挂单的概率,但无法消除滑点风险。
} class=class="str">"cmt">//--- Parameters are correct class="kw">return(true); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Checks if we are within the time range for trade | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool IsInTradeTimeRange(class="type">int symbol_number) { class=class="str">"cmt">//--- If TradeInTimeRange mode is enabled if(TradeInTimeRange[symbol_number]) { class=class="str">"cmt">//--- Structure of the date and time class="type">MqlDateTime last_date; class=class="str">"cmt">//--- Get the last value of the date and time data set TimeTradeServer(last_date); class=class="str">"cmt">//--- Outside of the allowed time range if(last_date.hour<StartTrade[symbol_number] || last_date.hour>=EndTrade[symbol_number]) class="kw">return(class="kw">false); } class=class="str">"cmt">//--- Within the allowed time range class="kw">return(true); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Checks if we are within the time range for placing orders | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool IsInOpenOrdersTimeRange(class="type">int symbol_number) { class=class="str">"cmt">//--- If the TradeInTimeRange mode if enabled if(TradeInTimeRange[symbol_number]) { class=class="str">"cmt">//--- Structure of the date and time class="type">MqlDateTime last_date; class=class="str">"cmt">//--- Get the last value of the date and time data set TimeTradeServer(last_date); class=class="str">"cmt">//--- Outside the allowed time range if(last_date.hour<StartTrade[symbol_number] || last_date.hour>=StopOpenOrders[symbol_number]) class="kw">return(class="kw">false); } class=class="str">"cmt">//--- Within the allowed time range class="kw">return(true); } class=class="str">"cmt">//--- Enumeration of the properties of a pending order enum ENUM_ORDER_PROPERTIES { O_SYMBOL = class="num">0, O_MAGIC = class="num">1, O_COMMENT = class="num">2, O_PRICE_OPEN = class="num">3, O_PRICE_CURRENT = class="num">4, O_PRICE_STOPLIMIT = class="num">5, O_VOLUME_INITIAL = class="num">6,