最近在折腾MQL5的EA编写,翻了好几篇文章,发现所有程序都有个固定的套路。整理一下自己的理解,方便以后查阅。一个规范的EA,代码组织得好,后期改起来才不头疼。
我习惯的代码顺序是:

预处理器在编译前干活,处理所有以#打头的指令。记住,这些不是语句,后面不要加分号。
这些信息会显示在MT5的属性窗口里,常用的有:
#property copyright "SEM醉猫"// 版权信息
#property link ""// 网址
#property version "1.00"// 版本号
#property description "EA结构示例"// 描述文字
#property icon "\\Images\\myicon.ico"// 图标路径(可选)
#property stacksize 4096 // 堆栈大小把外部.mqh头文件的内容拿进来用。尖括号和双引号的区别在于查找路径不同:
#include<Trade/Trade.mqh>// 从MQL5/Include/目录找
#include"MyCustomFunctions.mqh"// 先从当前目录找,找不到再去Include目录编译时会把标识符替换成对应的内容。有两种用法:
#define PI 3.14159265 // 简单常量
#define MAX_ORDERS 10 // 最大订单数
#define CIRC(dia) PI * (dia) // 带参数的宏
用input关键字声明,在回测或实盘时可以在“输入参数”选项卡里调整:
input double TradeVolume = 0.1; // 交易手数
input int StopLoss = 500; // 止损点数(单位:点)
input int TakeProfit = 1000; // 止盈点数
input int MAPeriod = 14; // 均线周期
input bool UseTrailingStop = true; // 是否启用追踪止损写在所有函数外面,从EA加载到卸载一直存在。我习惯加gl前缀,一眼认出是全局的:
bool glBuyPlaced = false; // 买入订单是否已下
bool glSellPlaced = false; // 卖出订单是否已下
datetime glLastBarTime; // 上一根K线的时间
MqlTick glCurrentTick; // 当前报价数据用const关键字,声明时必须初始化:
constdouble POINT_FACTOR = 100000.0; // 点值转换因子
constint MAX_RETRIES = 5; // 最大重试次数这些函数由MT5平台自动调用,响应特定事件。
EA加载到图表时自动调用。成功返回INIT_SUCCEEDED,失败返回INIT_FAILED(EA会停止):
intOnInit()
{
Print("EA已初始化,品种:", _Symbol, ",周期:", _Period);
EventSetTimer(60); // 设置60秒触发一次的定时器
return(INIT_SUCCEEDED);
}EA被移除或图表周期变更前调用,用来做清理工作:
voidOnDeinit(constint reason)
{
EventKillTimer(); // 关掉定时器
IndicatorRelease(maHandle); // 释放指标句柄
ObjectsDeleteAll(0); // 删除图表上的所有图形对象
Print("EA已卸载,原因码:", reason);
}这是最常用的函数,每个新的tick都会调它。放主要的交易逻辑:
voidOnTick()
{
// 获取价格 → 算指标 → 判断信号 → 下单/改单/平仓
}需要先在OnInit()里用EventSetTimer()设置好,之后每隔指定秒数自动调用:
voidOnTimer()
{
Print("定时器触发,当前时间:", TimeCurrent());
// 放不需要每个tick都执行的代码
}下单成交、订单修改、平仓等操作发生时调用:
voidOnTrade()
{
Print("交易事件发生了");
// 更新订单状态、记录日志等
}
把复杂逻辑拆成小块,放在文件末尾。比如:
doubleCalculateLotSize(double riskPercent, int stopLossPoints)
{
// 根据风险和止损点数算手数
return(lotSize);
}
boolCheckBuySignal()
{
// 判断买入条件
return(true_or_false);
}把上面的拼起来,就是一个完整的EA框架:
//+------------------------------------------------------------------+
//| 预处理指令 |
//+------------------------------------------------------------------+
#property copyright "SEM醉猫"
#property link ""
#property version "1.00"
#property description "EA结构示例"
#include<Trade/Trade.mqh>
#define MAX_ORDERS 5
//+------------------------------------------------------------------+
//| 输入变量 |
//+------------------------------------------------------------------+
input double TradeVolume = 0.1;
input int StopLoss = 500;
input int TakeProfit = 1000;
//+------------------------------------------------------------------+
//| 全局变量 |
//+------------------------------------------------------------------+
bool glBuyPlaced = false;
CTrade Trade; // 交易类实例
//+------------------------------------------------------------------+
//| 初始化函数 |
//+------------------------------------------------------------------+
intOnInit()
{
Print("EA初始化完成");
return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| 反初始化函数 |
//+------------------------------------------------------------------+
voidOnDeinit(constint reason)
{
Print("EA已卸载,原因:", reason);
}
//+------------------------------------------------------------------+
//| Tick事件函数 |
//+------------------------------------------------------------------+
voidOnTick()
{
if(/* 买入条件成立 && */ !glBuyPlaced) {
glBuyPlaced = Trade.Buy(_Symbol, TradeVolume);
}
}
//+------------------------------------------------------------------+
//| 自定义函数(放最后) |
//+------------------------------------------------------------------+
// 你的辅助函数写这里理清这个结构之后,写EA就不慌了。记住几点:

#property、#include、#define,编译前就处理好OnInit初始化 → OnTick跑逻辑 → OnDeinit做清理按这个套路组织代码,维护起来省心不少。