MQL4模板架构:EA开发支点
🛠️

MQL4模板架构:EA开发支点

用MetaEditor模板把EA拆成可复用骨架

MQL5 编程 难度 · 进阶 2026-03-10 6 分钟阅读
#MQL4#MetaEditor#EA模板#架构设计

什么是MetaEditor模板

在MetaEditor中,模板是存储在终端目录下 experts/templates/ 文件夹中的 .mqt 文件。它们不是普通MQL4程序,而是代码生成器使用的原型。当用户通过Expert Advisor Wizard(EA向导)新建EA、脚本、指标或库时,向导会读取对应模板,将宏如 #header#、#copyright#、#link#、#extern_variables# 替换为用户在向导中填写的名称与参数,从而自动生成初始代码。

系统自带基本模板包括 Expert.mqt(EA)、Script.mqt(脚本)、Include.mqt(包含文件)、indicator.mqt(指标)、Library.mqt(库),以及按指标命名的如 Alligator.mqt 等。模板首行通常写有 type=LIBRARY_ADVISOR 或 type=SCRIPT_ADVISOR 等指令,告知编辑器生成何种类型文件。

✦ 快速体验
想让每个新脚本都支持外部参数窗口,只需在 Script.mqt 的 #extern_variables# 前加入 #property show_inputs,以后向导生成的脚本自动带参数界面。
<expert>
type=SCRIPT_ADVISOR
</expert>
#header#
#property copyright "#copyright#"
#property link      "#link#"

#property show_inputs

#extern_variables#
//+------------------------------------------------------------------+
//| script program start function                                    |
//+------------------------------------------------------------------+
int start()
  {
//----
   
//----
   return(0);
  }
//+------------------------------------------------------------------+

为什么要用模板构建EA原型

多数MQL4开发者习惯一次性写完整EA,但当策略需要调整(例如加止盈、换信号源、反转逻辑)时,常常发现代码结构混乱难以修改,最终重写。模板的价值在于:它强制你在动手前先设计EA的通用结构(SuperClass),把“做什么”和“怎么做”分离。模板只描述模块与调用顺序,具体算法由用户后续填充。

这种思路类似面向对象编程中的接口或纯虚函数。团队可共建一个标准模板,新人依骨架填空即可,降低个人风格导致的bug,也方便多人协作。安装MT4时系统模板会被覆盖,因此自定义模板需备份,建议另存为独立.mqt文件,在向导中手动指定。

EA逻辑的标准六块分解

文中提出的通用EA模板将自动交易拆为三个核心子问题,并进一步细化成六块:1) 接收交易信号(每 tick 或指定周期新柱);2) 计算已有订单的SL/TP、计算新订单参数、计算挂单参数;3) 修改市价单与挂单;4) 按信号或时间平市价单;5) 删除挂单;6) 市价建仓或下挂单。此顺序具有逻辑性:先清理与准备,再开新仓。

#define OP_BALANCE 6
extern int  TradeDay                   = 0; 
extern bool ReversDay                  = false;
extern bool ReversTradeSignal          = false;
extern bool TradeSignalEveryTick       = false;
extern int  TradeSignalBarPeriod       = 0;

关键基础设施函数实现

模板中预置了多个广义函数,用户只需在“形状函数”中填策略。isNewBar() 检测指定周期新柱;getCorrectTimeFrame() 校正错误周期;StartMessage() 在日志打印EA配置,防止参数遗忘。信号函数 getTradeSignal() 整合了交易日过滤、反转开关与频率控制,再调用用户的 yourFunction()。

bool isNewBar(int timeFrame)
   {
   bool res=false;
   static datetime _sTime[7];
   int i=6;
   switch (timeFrame)
      {
      case 1  : i=0; break;
      case 5  : i=2; break;
      case 15 : i=3; break;
      case 30 : i=4; break;
      case 60 : i=5; break;
      case 240: break;
      case 1440:break;
      default:  timeFrame = 1440;
      }
   if (_sTime[i]==0 || _sTime[i]!=iTime(Symbol(),timeFrame,0))
      {
      _sTime[i] = iTime(Symbol(),timeFrame,0);
      res=true;
      }
   return(res);
   }

“友好订单”识别是另一难点。PrepareTickets() 用循环配合 OrderSelect 与 MagicNumber、Symbol 过滤,并允许通过全局变量手动认领订单。isOurOrder() 在测试模式直接返回true,实盘则检查以EA名命名的全局变量,方便接管手动仓。

double Tickets[][9];
string CommentsTicket[1000][2];
void PrepareTickets(double & arrayTickets[][9], string & comm[][2],int MN)
   {
   int count=0;
   ArrayResize(arrayTickets,20);
   int total=OrdersTotal();
   for (int i=0;i<total;i++)
      {
      if (OrderSelect(i,SELECT_BY_POS))
         {
         if (!isOurOrder(OrderTicket()))
            {
            if (OrderSymbol()!= Symbol()) continue;
            if (OrderMagicNumber()!=ExpertMagicNumber) continue;
            }
         arrayTickets[count][0] = OrderTicket();
         arrayTickets[count][1] = OrderType();
         arrayTickets[count][2] = OrderLots();
         arrayTickets[count][3] = OrderOpenPrice();
         arrayTickets[count][4] = OrderStopLoss();
         arrayTickets[count][5] = OrderTakeProfit();
         arrayTickets[count][6] = OrderMagicNumber();
         arrayTickets[count][7] = OrderExpiration();
         arrayTickets[count][8] = OrderOpenTime();
         comm[count][0] = OrderSymbol();
         comm[count][1] = OrderComment();
         count++;
         }
      }
   ArrayResize(arrayTickets,count);
   ArrayResize(comm,count);
   }

形状函数与策略填充

模板提供空实现的“形状函数”如 getNewSL()、getNewTP()、yourFunction(),返回负值时表示未计算。用户编写具体策略时仅需替换这些函数体。例如基于 Stochastic 的策略,yourFunction 中调用 iStochastic 判断穿越高低位即可,仅需几行;CalculateNewMarketValues 中按信号计算SL/TP也仅五行的量。

int yourFunction(int workPeriod)
   {
   bool res=OP_BALANCE;
   double prevValue = iStochastic(Symbol(),workPeriod,Kperiod,Dperiod,slowPeriod,MODE_SMA,0,MODE_SIGNAL,2);
   double currValue = iStochastic(Symbol(),workPeriod,Kperiod,Dperiod,slowPeriod,MODE_SMA,0,MODE_SIGNAL,1);
   if (currValue>DownLevel && prevValue<DownLevel) res=OP_BUY;
   if (currValue<UpLevel && prevValue>UpLevel) res=OP_SELL;
   return (res);
   }
⚠ 常见坑
ReversTradeSignal 开启时,系统会互换买卖并加点差偏移。若SL或TP为0表示不设置,反转代码中必须保留零值逻辑,否则会下错单。另外模板安装后若覆盖原 Expert.mqt,旧向导行为会变,务必备份。

反转与日期过滤的实战红利

模板外部参数 ReversTradeSignal、TradeDay、ReversDay 让同一EA无需改代码即可反转策略或限定交易星期。文中测试 EURUSD H1 的 Stochastic EA:原始430笔交易,设 ReversTradeSignal=true 后买卖笔数互换;设 TradeDay=5 仅周五交易剩81笔;再设 ReversDay=true 则排除周五得349笔。这证明模板架构在策略验证上的灵活度。

对比传统重写方式,模板法在第一次设计耗时较多,但后续每个新策略只需填形状函数,且统一的结构让调试范围清晰:信号错误查块1,平仓异常查块4。也可将服务函数抽离到 .mqh 包含文件,保持EA主体简洁。

从模板到工程的落地建议

将 Template_EA.mqt 存入 templates 文件夹,新建EA时手动选用即可获得全套框架。团队可扩展模板:加入错误处理宏、交易时段小时级限制、特殊报告函数等。对于中文交易者,建议先消化六块分解图,再用一个小策略(如双MA交叉)走通流程,体会“写骨架—填肉”的开发节奏。

int start()
  {
   ArrayResize(Tickets,0);
   PrepareTickets(Tickets,CommentsTicket,ExpertMagicNumber);
   int trSignal=getTradeSignal(TradeDay,ReversDay,TradeSignalEveryTick,TradeSignalBarPeriod);
   CalculateSL_and_TP(ReversTradeSignal,Tickets,newSL_and_TP);
   CalculateNewMarketValues(trSignal, marketType, marketLots,marketSL,marketTP,marketComment);
   ModifyMarkets(ReversTradeSignal,ModifyMarketOrderEveryTick,ModifyMarketBarPeriod,newSL_and_TP);
   PrepareTicketsToClose(trSignal,ReversTradeSignal,ticketsToClose,lotsToClose,Tickets);
   CloseMarketOrders(ticketsToClose,lotsToClose);
   OpenMarketOrder(ReversTradeSignal,marketType,marketLots,marketSL,marketTP,marketComment);
   return(0);
  }

常见问题

位于MT4终端安装目录下的 experts/templates/ 文件夹,例如 C:\Program Files\MetaTrader 4\experts\templates\。自定义模板建议另存独立文件名,避免被系统重装覆盖。
用MetaEditor打开 Script.mqt,在 #extern_variables# 宏之前加入一行 #property show_inputs 并保存。之后通过向导新建脚本会自动包含参数输入界面。
这是模板首行的标识指令,告诉MetaEditor该模板用于生成脚本类型文件。类似还有 type=EXPERT_ADVISOR、type=LIBRARY_ADVISOR,向导依此选择代码框架与宏替换规则。
在模板外部参数中设置 extern bool ReversTradeSignal=true,模板的 getTradeSignal 与下单函数会自动互换 OP_BUY/OP_SELL 并加点差偏移,无需重写策略逻辑。
模板用 isOurOrder() 检查是否存在以EA名称命名的全局变量且值等于订单号,实盘时若匹配则视为“友好订单”纳入 Tickets 数组;测试模式直接认领全部订单。