MQL4模板架构:EA开发支点
用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 等指令,告知编辑器生成何种类型文件。
<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) 市价建仓或下挂单。此顺序具有逻辑性:先清理与准备,再开新仓。
- 块1:信号块,输出 OP_BUY / OP_SELL / OP_BALANCE 三态常量
- 块2:仓位计算块,含资金管理函数形状
- 块3:订单修改块,支持每tick或新柱触发
- 块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、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交叉)走通流程,体会“写骨架—填肉”的开发节奏。
- 备份自定义模板,避免MT4重装覆盖
- 先用演示策略跑通 start() 全流程
- 将通用接口放 .mqh,EA 只留策略形状
- 利用 StartMessage 输出参数防配置遗忘
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);
}