MQL4通用EA模板拆解与实战
从变量设计到风控函数的完整教学
为什么需要一个通用 EA 模板
许多交易者在迈出算法交易第一步时,往往不知道从何写起。是直接写买卖逻辑,还是先处理手数、止损、滑点?通用 EA 模板的价值在于:它把几乎所有自动交易系统都需要的公共部分——参数声明、初始化、仓位计算、订单管理、风控修改——预先结构化,你只需替换信号函数即可。这样不仅能减少重复劳动,还能保证在策略测试器中优化参数时行为一致。本文以 MetaQuotes 官方译介的通用模板为例,逐层拆解这个骨架。
需要强调的是,模板本身不包含圣杯策略,它提供的是工程规范。真实交易中,超过七成的 Bug 来自手数越界、止损小于平台最小止损位、追踪止损修改失败等基础问题,模板正是为规避这些而设计。
外部变量:让策略可被优化
模板使用 extern 修饰符暴露参数,使它们在 EA 属性框和策略测试器优化中可见。关键设计是:用 0/1 整数而非 true/false 布尔,因为测试器对数值型扫描更友好。例如 Condition1=1 表示启用条件一,设 0 即关闭,优化时可遍历启用组合。
MaxLot 限制最大手数,防止过度下注;TakeProfit、StopLoss、TrailingStop 为 0 时代表该功能禁用;MinProfit 与 ProfitPoints 配合实现“回撤平仓”:价格触及 ProfitPoints 后回落至 MinProfit 就离场;LotSpliter 是仓位分割器,如 0.1 表示只用可用保证金的十分之一;CloseByOtherSideCondition 控制反向信号出现时是否平掉原单。
extern double MaxLot; extern double TakeProfit; extern double TrailingStop; extern double StopLoss; extern double MinProfit; extern double ProfitPoints; extern int Slippage=0; extern int Condition1=1; extern double LotSpliter=1.0; extern int CloseByOtherSideCondition;
内部变量与初始化函数
内部变量如 Lot、PP(最高浮盈点记录)、slu/sld(上下轨差)等在 init() 中预赋初值。init() 仅启动一次,适合读取止损模板值 tp/sl,避免每次 tick 重算。注释用 Comment() 显示版本,\n 换行。
double Lot;
double PP=0;
double slu,sld,a,b;
double tp,sl;
int init()
{
tp=TakeProfit;
sl=StopLoss;
return(0);
}
Comment("cloud trade \n v2.0.11");
preinit() 在每轮无单时调用,核心是根据余额、杠杆、ask 算 Lot,并受 MaxLot 与平台 MODE_MAXLOT 约束;同时把止损止盈拉升至不小于 MODE_STOPLEVEL,否则 OrderSend 会报错。
int preinit()
{
Lot=NormalizeDouble(MathFloor(LotSpliter*AccountBalance()*AccountLeverage()/Ask/MathPow(10,Digits+1)*10)/10,1);
if(MaxLot>0 && Lot>MaxLot){Lot=MaxLot;}
if(Lot>MarketInfo(Symbol(),MODE_MAXLOT)){Lot=MarketInfo(Symbol(),MODE_MAXLOT);}
PP=0;
StopLoss=sl;
TakeProfit=tp;
if(TakeProfit!=0 && TakeProfit<(MarketInfo(Symbol(),MODE_STOPLEVEL)))
{ TakeProfit=MarketInfo(Symbol(),MODE_STOPLEVEL); }
if(StopLoss!=0 && StopLoss<(MarketInfo(Symbol(),MODE_STOPLEVEL)))
{ StopLoss=MarketInfo(Symbol(),MODE_STOPLEVEL); }
return(0);
}
主循环:无单与有单的分支
EA 核心逻辑极简:若 OrdersTotal()==0,调用 preinit 后由 U() 返回信号,1 买 2 卖;若有单,则选中管理。这种“只管一个单”的假设简化了代码,但实盘多单需改写。
if(OrdersTotal()==0)
{
preinit();
if(U()==1)
{ OrderBuy(); return(0); }
if(U()==2)
{ OrderSell(); return(0); }
return(0);
}
有单时,先 SelectOnlyOrder 选位,再分配价格变量防止跳变。随后处理追踪止损、最小获利平仓、反向平单。注意模板用 PP 记录最高盈利点,实现 ProfitPoints 回撤逻辑。
if(OrderType()==OP_BUY)
{
if((slu)>PP) { PP=slu; }
if(((slu)>0.001) && (OrderStopLoss()<(b-TrailingStop)) && (OrderOpenPrice()<(b-TrailingStop)) && (OrderProfit()>MathAbs(OrderSwap())))
{ if(TrailingStop!=0) { OrderModify(OrderTicket(), 0, b-TrailingStop, 0, 0, 0); } }
}
信号函数 U() 与条件封装
U() 汇总 U1()(随机指标交叉)与 U2()(分形趋势)信号,由 Condition1/2 开关。返回 0 无信号,1 买,2 卖。这种块化设计让策略拼接像搭积木。
int U()
{
if((U1()==2 && Condition1==1) || (U2()==2 && Condition2==1)){return(2);}
if((U1()==1 && Condition1==1) || (U2()==1 && Condition2==1)){return(1);}
return(0);
}
U1 用 iStochastic 判断超买超卖与信号线主线交叉;U2 用 iFractals 数最近两个分形方向。你可以依样画葫芦加入均线、MACD 块,只需遵守返回 1/2/0 约定。
下单与平仓函数细节
OrderBuy/OrderSell 按止损止盈是否为零分四种 OrderSend 组合,并用 NormalizeDouble 规范精度。CloseOnlyOrder 封装 OrderClose,SelectOnlyOrder 封装 OrderSelect,降低主逻辑噪声。
bool OrderBuy()
{
bool res=false;
if(StopLoss!=0 && TakeProfit!=0)
{ res=OrderSend(Symbol(), 0, NormalizeDouble(Lot,1), Ask, Slippage, NormalizeDouble(Ask-StopLoss,4), NormalizeDouble(Ask+TakeProfit,4), 0, 0, 0, 0); return(res); }
if(StopLoss==0 && TakeProfit!=0)
{ res=OrderSend(Symbol(), 0, NormalizeDouble(Lot,1), Ask, Slippage, 0, NormalizeDouble(Ask+TakeProfit,4), 0, 0, 0, 0); return(res); }
if(StopLoss==0 && TakeProfit==0)
{ res=OrderSend(Symbol(), 0, NormalizeDouble(Lot,1), Ask, Slippage, 0, 0, 0, 0, 0, 0); return(res); }
if(StopLoss!=0 && TakeProfit==0)
{ res=OrderSend(Symbol(), 0, NormalizeDouble(Lot,1), Ask, Slippage, NormalizeDouble(Ask-StopLoss,4), 0, 0, 0, 0, 0); return(res); }
return(res);
}
模板的局限与进阶改造
该模板假设同时间仅一单、不区分魔术码、不处理多货币。实盘可加入 magic number 过滤、订单遍历池、部分平仓。但作为教学骨架,它完整示范了“参数—初始化—信号—执行—风控”的闭环。掌握后,你写任何 EA 都能以它为底座,效率倍增。
原文附 template.mq4 可于 https://www.mql5.com/zh/articles/1495 下载 ZIP 参考。注意材料版权归 MetaQuotes,禁止整段转载。