MQL5 Cookbook - 以 MQL5 编写的多币种 EA,利用限价订单工作(基础篇)
📘

MQL5 Cookbook - 以 MQL5 编写的多币种 EA,利用限价订单工作(基础篇)

第 1/3 篇

◍ 用限价单做多币种自动交易的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 策略测试器按文内参数复跑。

MQL5 / C++
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 策略测试器逐品种核对。

MQL5 / C++
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 报出该品种会话时间非法,实盘前就能拦住错配。外汇与贵金属波动剧烈,参数错配可能瞬间放大回撤,先用策略测试器跑一轮再上真仓。

MQL5 / C++
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 价与初始手数,相当于给后续订单遍历建了一张轻量索引表。外汇与贵金属品种受时段流动性跳变影响明显,这类边界判断能降低在休市边缘误触挂单的概率,但无法消除滑点风险。

MQL5 / C++
   }
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,

常见问题

先定义品种数组和统一手数、挂单偏移、失效时间等参数,再在定时或Tick里遍历品种检查持仓后批量下lim单。
在下单前用品种交易时段函数做校验,只在允许交易窗口内挂单,并对已挂未成交单做超时撤单收口。
可以,小布能按你给的品种和参数跑诊断,标出非交易时段误挂单风险和偏移设置是否合理,省去手动回测。
主要加品种列表、每品种最大挂单数、挂单距现价偏移、单笔失效秒数,并把市价开仓改为lim单提交。
每次挂单前遍历该品种已有挂单与持仓,若达到上限或已有同方向lim单则跳过,只补差额。