开发一个跨平台网格 EA·进阶篇
🔁

开发一个跨平台网格 EA·进阶篇

(2/3)· 换经纪商不必重写策略,一套代码让网格在双平台跑起来的关键拆解

案例拆解新手友好 第 2/3 篇

不少交易者刚在 MT5 写完网格 EA,就因为经纪商只给 MT4 账户而卡住,只能重头改代码。双平台语法差异藏在开仓函数和订单循环里,硬搬往往编译报错。本篇接着基础篇,把跨平台网格的落地细节一层层拆开。

「跨版本下单的统一封装」

EA 最核心的动作就是发单,但 MQL4 与 MQL5 的下单接口差异极大:MQL4 用 int 标记订单类型,MQL5 改用 ENUM_ORDER_TYPE 枚举,老版本里根本没有这套枚举。直接写死版本相关代码,后面迁移就是坑。 做法是在自己的工程里先定义一套私有枚举 TypeOfPos,把市价买、市价卖、四种挂单都收进去。这样上层逻辑只认 MY_BUY / MY_SELLSTOP 这类符号,底层再按编译版本翻译,EA 主体就不必关心跑在 MT4 还是 MT5。 pdxSendOrder() 把发单要用的信息一次性收进来:类型、价格、止损、止盈、手数、待改仓位的 ticket、注释和交易品种。sl/tp 不设为 0 即可,不传 symbol 就默认用 _Symbol 在当前图下发。 函数开头先规范化价格精度——取 SYMBOL_DIGITS 后用 NormalizeDouble 把 price/sl/tp 都压到经纪商小数位,否则 MT5 可能因价格尾差拒单。 #ifdef __MQL5__ 里才会出现 ENUM_TRADE_REQUEST_ACTIONS 和 MqlTradeRequest 结构,挂单与市价单靠 action 字段区分(TRADE_ACTION_PENDING 还是 TRADE_ACTION_DEAL)。MQL5 独有的成交模式参数也用条件编译隔离,MT4 编译时根本看不到这些代码。 幻数 EA_Magic 决定仓位归属:不填就接管同品种所有仓位,填了只认带该 magic 的仓。下单失败时不静默,把错误码丢给 msgErr() 弹本地化提示,下一节再拆这块。外汇与贵金属杠杆高,自动发单前务必在策略测试器用极小手数验证下单路径。

MQL5 / C++
enum TypeOfPos{
  MY_BUY,
  MY_SELL,
  MY_BUYSTOP,
  MY_BUYLIMIT,
  MY_SELLSTOP,
  MY_SELLLIMIT,
};
class=class="str">"cmt">// 发送订单的函数
class="type">bool pdxSendOrder(TypeOfPos mytype, class="type">class="kw">double price, class="type">class="kw">double sl, class="type">class="kw">double tp, class="type">class="kw">double volume, class="type">ulong position=class="num">0, class="type">class="kw">string comment="", class="type">class="kw">string sym=""){
  class=class="str">"cmt">// 检查传入的数值
  if( !StringLen(sym) ){
    sym=_Symbol;
  }
  class="type">int curDigits=(class="type">int) SymbolInfoInteger(sym, SYMBOL_DIGITS);
  if(sl>class="num">0){
    sl=NormalizeDouble(sl,curDigits);
  }
  if(tp>class="num">0){
    tp=NormalizeDouble(tp,curDigits);
  }
  if(price>class="num">0){
    price=NormalizeDouble(price,curDigits);
  }

  class="macro">#ifdef __MQL5__
    ENUM_TRADE_REQUEST_ACTIONS action=TRADE_ACTION_DEAL;
    ENUM_ORDER_TYPE type=ORDER_TYPE_BUY;
    class="kw">switch(mytype){
       case MY_BUY:
         action=TRADE_ACTION_DEAL;
         type=ORDER_TYPE_BUY;
         class="kw">break;
       case MY_BUYSTOP:
         action=TRADE_ACTION_PENDING;
         type=ORDER_TYPE_BUY_STOP;
         class="kw">break;
       case MY_BUYLIMIT:
         action=TRADE_ACTION_PENDING;
         type=ORDER_TYPE_BUY_LIMIT;
         class="kw">break;
       case MY_SELL:
         action=TRADE_ACTION_DEAL;
         type=ORDER_TYPE_SELL;
         class="kw">break;
       case MY_SELLSTOP:
         action=TRADE_ACTION_PENDING;
         type=ORDER_TYPE_SELL_STOP;
         class="kw">break;
       case MY_SELLLIMIT:
         action=TRADE_ACTION_PENDING;
         type=ORDER_TYPE_SELL_LIMIT;
         class="kw">break;
    }

    class="type">MqlTradeRequest mrequest;

挂单与市价单的发送结构差异

这段 MT5 交易请求组装逻辑,把新交易框架(MqlTradeRequest)和旧 MQL4 兼容分支拆得很清楚。核心是先 ZeroMemory 清空结构体,再按 action 类型填 symbol、sl、tp、volume、price 等字段;当 action 不等于 TRADE_ACTION_SLTP 时,才允许写入挂单/市价单的价格与手数,并把 magic 固定为 EA_Magic。 成交策略由 useORDER_FILLING_RETURN 控制,FOK、RETURN、IOC 三种分别对应 ORDER_FILLING_FOK / RETURN / IOC,偏离值写死为 100 点。这个 deviation=100 在黄金跳空时段可能直接吃单失败,实盘前建议在策略测试器里改小到 10~30 验证滑点容忍度。 发送后只看 retcode:10009(已完成)或 10008(已受理)才返回 true,其余一律走 msgErr 报错。下面代码里 #else 之后是老终端的 OP_BUY / OP_BUYSTOP 等类型映射,纯兼容层,跑 MT5 时不会进这条分支。外汇与贵金属杠杆品种波动剧烈,回测通过不代表实盘安全。

MQL5 / C++
class="type">MqlTradeResult mresult;
ZeroMemory(mrequest);

mrequest.action = action;
mrequest.sl = sl;
mrequest.tp = tp;
mrequest.symbol = sym;
if(position>class="num">0){
  mrequest.position = position;
}
if(StringLen(comment)){
  mrequest.comment=comment;
}
if(action!=TRADE_ACTION_SLTP){
  if(price>class="num">0){
    mrequest.price = price;
  }
  if(volume>class="num">0){
    mrequest.volume = volume;
  }
  mrequest.type = type;
  mrequest.magic = EA_Magic;
  class="kw">switch(useORDER_FILLING_RETURN){
    case FOK:
      mrequest.type_filling = ORDER_FILLING_FOK;
      class="kw">break;
    case RETURN:
      mrequest.type_filling = ORDER_FILLING_RETURN;
      class="kw">break;
    case IOC:
      mrequest.type_filling = ORDER_FILLING_IOC;
      class="kw">break;
  }
  mrequest.deviation=class="num">100;
}
if(OrderSend(mrequest,mresult)){
  if(mresult.retcode==class="num">10009 || mresult.retcode==class="num">10008){
    class="kw">return true;
  }else{
    msgErr(GetLastError(), mresult.retcode);
  }
}
class="macro">#else
  class="type">int type=OP_BUY;
  class="kw">switch(mytype){
    case MY_BUY:
      type=OP_BUY;
      class="kw">break;
    case MY_BUYSTOP:
      type=OP_BUYSTOP;
      class="kw">break;
    case MY_BUYLIMIT:
      type=OP_BUYLIMIT;
      class="kw">break;
    case MY_SELL:
      type=OP_SELL;
      class="kw">break;
    case MY_SELLSTOP:
      type=OP_SELLSTOP;
      class="kw">break;
    case MY_SELLLIMIT:
      type=OP_SELLLIMIT;

◍ 下单失败别只盯错误码

这段逻辑把 MQL5 的成交流程和报错出口收在了一处。OrderSend 返回小于 0 时直接走 msgErr(GetLastError()),正常成交则返回 true,否则函数末尾兜底返回 false。 MQL5 用枚举 TypeOfFilling 约束成交方式:FOK 表示全部或撤单,RETURN 是部分成交余量撤销,IOC 允许不完全成交。实盘里若经纪商不支持你选的填充策略,OrderSend 会直接报错,外汇与贵金属杠杆交易高风险,参数错配可能瞬时吃掉预期滑点。 msgErr 函数把整数 err 和可选 retcode 翻译成提示串:err=1 映射 langs.err1,其余走 default 拼出 'err0: 具体码';retcode>0 时再追加如 10004 的服务器回执说明,最后弹 Alert。 把这段直接贴进 MT5 的 EA 源码,改 input useORDER_FILLING_RETURN 从 FOK 切到 RETURN,能在流动性薄的黄金时段直观看到成交行为的差异。

MQL5 / C++
      class="kw">break;
      }
      if(OrderSend(sym, type, volume, price, class="num">100, sl, tp, comment, EA_Magic, class="num">0)<class="num">0){
         msgErr(GetLastError());
      }else{
         class="kw">return true;
      }
  
   class="macro">#endif
   class="kw">return false;
}
class="macro">#ifdef __MQL5__
   enum TypeOfFilling class=class="str">"cmt">//执行模式
   {
      FOK,class=class="str">"cmt">//ORDER_FILLING_FOK
      RETURN,class=class="str">"cmt">// ORDER_FILLING_RETURN
      IOC,class=class="str">"cmt">//ORDER_FILLING_IOC
   };
   input TypeOfFilling  useORDER_FILLING_RETURN=FOK; class=class="str">"cmt">//执行模式
class="macro">#endif

class="type">void msgErr(class="type">int err, class="type">int retcode=class="num">0){
   class="type">class="kw">string curErr="";
   class="kw">switch(err){
      case class="num">1:
         curErr=langs.err1;
         class="kw">break;
class=class="str">"cmt">//      case N:
class=class="str">"cmt">//         curErr=langs.errN;
class=class="str">"cmt">//         class="kw">break;
      class="kw">default:
         curErr=langs.err0+": "+(class="type">class="kw">string) err;
   }
   if(retcode>class="num">0){
      curErr+=" ";
      class="kw">switch(retcode){
         case class="num">10004:
            curErr+=langs.retcode10004;
            class="kw">break;
class=class="str">"cmt">//      case N:
class=class="str">"cmt">//         curErr+=langs.retcodeN;
class=class="str">"cmt">//         class="kw">break;
      }
   }

   Alert(curErr);
}

「给EA塞进双语切换的骨架」

做跨市场喊单或跟单EA时,俄语用户和英语用户同在服务器上跑是常态。把提示语写死成一种语言,后期维护就是灾难。先在输入参数层放一个语言枚举,让用户在下单前就选好界面语种,比运行时动态判断更省资源。 核心做法是建一个 struct 把所有EA内用到的字符串收拢,再用一个 init_lang() 按枚举分支填值。下面这段就是最小可编译骨架,注意 LANG 默认给了 MY_RUS,意味着不改动参数时俄文优先。 调用时机别乱放,只在 OnInit() 里调一次 init_lang() 就够了——EA每次加载只触发一次,字符串填充完驻留内存,避免每笔 tick 重复赋值拖慢执行。 实盘外汇与贵金属EA多语言化后,仍需警惕不同语种字符长度导致面板排版错位,这属于高杠杆环境下的细节风险,可能引发行情紧急时读错提示。

MQL5 / C++
enum TypeOfLang{
   MY_ENG, class=class="str">"cmt">// 英语
   MY_RUS, class=class="str">"cmt">// 俄语
}; 
input TypeOfLang  LANG=MY_RUS; class=class="str">"cmt">// 语言
class="kw">struct translate{
   class="type">class="kw">string err1;
   class="type">class="kw">string err2;
class=class="str">"cmt">//   ... 其它字符串
};
translate langs;
class="type">void init_lang(){
   class="kw">switch(LANG){
      case MY_ENG:
         langs.err1="No error, but unknown result. (class="num">1)";
         langs.err2="General error(class="num">2)";
         langs.err3="Incorrect parameters(class="num">3)";
class=class="str">"cmt">//         ... other strings
         class="kw">break;
      case MY_RUS:
         langs.err0="Во время выполнения запроса произошла ошибка";
         langs.err1="Нет ошибки, но результат неизвестен (class="num">1)";
         langs.err2="Общая ошибка (class="num">2)";
         langs.err3="Неправильные параметры (class="num">3)";
class=class="str">"cmt">//         ... other strings
         class="kw">break;
   }
}

网格EA的核心输入参数怎么设

给网格型EA接参数时,除了识别码和语言项,真正决定仓位结构的是四件套:单笔手数、单向挂单上限、网格步长、以及触发平仓的利润额。 以一段典型声明为例,Lot=0.01 代表每单固定微型手;maxLimits=7 表示同一方向最多铺 7 张限价单,反向也对称铺 7 张,合计最多 14 张挂单在跑。 Step=10 是网格间距,首单距现价 10 点,次单再距首单 10 点,依此类推呈等差排布。takeProfit=1 意为浮盈凑满 1 美元就清场——所有未平头寸与挂单全撤,网格重置。 这套逻辑只认盈利出场,文本里明确不处理亏损情形,因而外汇与贵金属市场里遇单边拉伸时回撤可能失控,属典型高风险结构,参数上线前建议在 MT5 策略测试器用历史数据跑一遍验证。

MQL5 / C++
input class="type">class="kw">double      Lot=class="num">0.01;     class=class="str">"cmt">//手数大小
input class="type">uint        maxLimits=class="num">7;  class=class="str">"cmt">//在一个方向上的限价订单数量
input class="type">int         Step=class="num">10;      class=class="str">"cmt">//网格步长点数
input class="type">class="kw">double      takeProfit=class="num">1; class=class="str">"cmt">//当达到指定利润时关闭交易, $

◍ EA 初始化里先把小数位和交易权限卡死

OnInit() 在 EA 首次加载时只跑一次,框架里已经先调了 init_lang() 做语言绑定,剩下要干的就两件硬活:按经纪商小数位修正网格步长,以及确认终端是否放行交易。 外汇和贵金属品种常见 5 位或 3 位小数(对应标准账户的 4 位、2 位加一个冗余位)。若 _Digits 为 5 或 3,就把用户填的 Step 乘 10,否则网格间距会缩水一个数量级。 修正后的 ST 不直接用点数,而是再乘 SymbolInfoDouble(_Symbol, SYMBOL_POINT),转成真实价格距离。这样后面挂网格时用的是价差而非点值,跨品种不用手改。 如果 MQLInfoInteger(MQL_TRADE_ALLOWED) 返回 false,说明终端禁了自动交易,立刻 Alert 用选定语言提示并 ExpertRemove() 卸载 EA,避免静默空跑。外汇贵金属杠杆高,终端权限没开就硬跑策略只会无意义消耗。 下面这段是可直接拷进 MT5 的 OnInit 终版,注意 ST 是 double 型全局变量,必须在调用前声明:

MQL5 / C++
  ST=Step;
  if(_Digits==class="num">5 || _Digits==class="num">3){
      ST*=class="num">10;
  }
  ST*=SymbolInfoDouble(_Symbol, SYMBOL_POINT);
  if(!MQLInfoInteger(MQL_TRADE_ALLOWED)){
      Alert(langs.noBuy+" ("+(class="type">class="kw">string) EA_Magic+")");
      ExpertRemove();
  }  
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| EA 交易初始化函数                                                                     |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnInit()
  {
  init_lang();
  
  if(!MQLInfoInteger(MQL_TRADE_ALLOWED)){
      Alert(langs.noBuy+" ("+(class="type">class="kw">string) EA_Magic+")");
      ExpertRemove();
  }
  ST=Step;
  if(_Digits==class="num">5 || _Digits==class="num">3){
      ST*=class="num">10;
  }
  ST*=SymbolInfoDouble(_Symbol, SYMBOL_POINT);
  
  class="kw">return(INIT_SUCCEEDED);
  }
把跨平台编译检查交给小布
这些诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到 MT4/MT5 双环境的编译冲突提示,你专注策略逻辑而非语法搬运。

常见问题

在 MQL5 分支写 CTrade::BuyLimit,MQL4 分支写 OrderSend 限价单,编译时只保留对应平台代码,其余丢弃。
可以,小布的品种页会标注当前经纪商终端类型并提示条件编译遗漏点,减少手动排查时间。
通常遍历所有持仓与挂单,全部平仓撤单后按当前价重新以固定步距双向挂限价单,倾向在盘整中更稳。
若用条件编译隔离 MQL5 专有本地化宏,MT4 会跳过该块,不影响执行,但需注意字符串资源路径差异。