从这节课开始,我们正式进入实战EA的开发环节。
构建一个EA,就像是建造一栋房子,绝不是随意堆砌砖块。所有专业、稳定、高效的EA,都遵循着一个经过时间考验的、标准的建筑“蓝图”。这个蓝图,就是我们今天要掌握的EA标准结构。
一个标准的EA主要由7大模块构成,并遵循着清晰的“生命周期”:初始化 → 实时处理 → 退出清理。
EA的建筑蓝图:7大核心模块
① 预处理指令区:房子的“施工许可证”与“材料清单”。这部分位于代码的最顶部,是给编译器看的“施工前说明”,以#
号开头。
#property
: 声明EA的基本信息(版权、版本号、网址等),就像是房子的名牌和房产证。#include
: 引入外部的函数库文件,好比是把预制好的“厨房模块”、“浴室模块”整个搬进来组装。#define
: 定义一些全局常量,比如固定的滑点、EA的名称等。
② 外部输入参数区:房子的“智能控制面板”。这是我们用input
(或extern
)关键字定义的用户设置项。它让使用者无需修改代码,就能在EA加载时,通过参数面板灵活地调整策略,是连接代码与用户的桥梁。
③ 全局变量区:房子的“中央信息中心”。这里存放着需要在多个函数之间共享的数据,它们的生命周期和EA一样长。比如EA的总交易次数、当前持仓状态等。这些是EA内部流转的信息,用户无法在外部面板上看到。
④ 初始化函数 OnInit()
:房子的“开工仪式”
int OnInit()
这是EA的第一个“生命周期”函数。当你把EA加载到图表上时,它会被执行一次,且仅执行一次。 它的核心任务是“准备工作”和“环境检查”:
- 初始化全局变量。
- 检查当前账户、服务器、图表环境是否符合EA运行要求。
- 打印EA启动日志。
- 创建需要的图表对象(如按钮、面板)。
学长建议:OnInit()
是你的第一道,也是最重要的一道“安全门”。如果环境检查失败(比如用户设置了不合理的参数),你应该在这里return(INIT_FAILED)
,阻止EA带着错误的设置继续运行,从而避免不必要的亏损。
⑤ 主逻辑函数 OnTick()
:房子的“日常运作核心”
void OnTick()
这是EA的“心脏”和“大脑”。只要图表窗口在,市场每发生一次价格变动(Tick),这个函数就会被不知疲倦地调用一次。 EA 99%的工作时间都在这里度过,负责所有核心的交易逻辑:
- 分析行情,寻找交易信号。
- 执行开仓、平仓操作。
- 管理已有的持仓(如追踪止损)。
学长避坑指南:在活跃的市场,OnTick()
的执行频率会非常高。如果你在里面放入了复杂的计算,会导致EA处理不过来。一个非常重要的专业技巧是,在OnTick()
的开头加入“新K线判断”逻辑,让EA从“每个tick都思考一次”的“高频交易员”,变成“每根K线只在开盘时思考一次”的“冷静决策者”。
⑥ 清理函数OnDeinit()
:房子的“离场清扫”
void OnDeinit(const int reason)
当EA被卸载、图表被关闭、或者MT4软件关闭时,这个函数会被执行一次。 它的任务是“清理和收尾”,确保EA“优雅地离开”:
- 删除在图表上创建的所有图形对象(按钮、标签、线条等)。
- 释放占用的系统资源。
- 打印EA的退出日志和最终的业绩总结。
一个专业的EA,一定会把自己留在图表上的“痕迹”清理得干干净净。
⑦ 自定义函数区:房子的“专业功能房”。这里是你自己创建的、用来处理特定任务的函数。比如OpenBuyTrade()
开仓函数、CalculateRiskLotSize()
手数计算函数、ManageTrailingStop()
追踪止损函数等。
把复杂的逻辑拆分到不同的自定义函数中,然后让OnTick()
作为“总指挥”去调用它们。这是让代码结构保持清晰、易于维护的唯一方法。
EA的生命周期流程图
EA加载到图表 ➜
OnInit()
(初始化,执行1次)
➜
市场来一个新报价(Tick) ➜
OnTick()
(主逻辑,循环执行)
➜
EA从图表移除 ➜
OnDeinit()
(清理,执行1次)
一个更规范的EA结构模板
下面的模板结构更清晰,并包含了详细的中文注释。这是你未来所有EA项目的一个绝佳起点,请仔细理解每一部分的用途。
// === 1. 预处理指令区 ===
#property strict // 开启严格模式 (强烈建议所有新代码都加上)
#property copyright "Your Name"
#property link "Your Website"
#property version "1.00" // EA版本号,方便管理
// === 2. 外部输入参数区 (input比extern更安全) ===
input group "交易核心参数"
input double LotSize = 0.1; // 交易手数
input int Slippage = 3; // 最大允许滑点
input int TakeProfit = 500; // 止盈点数 (5位平台)
input int StopLoss = 300; // 止损点数 (5位平台)
input int MagicNumber = 123456; // EA魔术号 (身份证)
input group "功能开关"
input bool UseTrailingStop = true; // 功能开关:是否启用追踪止损?
// === 3. 全局变量区 (程序内部状态) ===
int g_ticket = -1; // 全局变量: 保存当前持仓的订单号
bool g_hasOpenPosition = false; // 全局变量: 标记是否已有持仓
//+------------------------------------------------------------------+
//| 4. 初始化函数 (EA加载时执行一次) |
//+------------------------------------------------------------------+
int OnInit()
{
Print("EA初始化完成: ", MQLInfoString(MQL_PROGRAM_NAME));
g_hasOpenPosition = false; // 初始化持仓状态
return(INIT_SUCCEEDED); // 返回初始化成功
}
//+------------------------------------------------------------------+
//| 5. 清理函数 (EA被卸载时执行一次) |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
Print("EA卸载,原因代码: ", reason);
// 如果有创建图表对象,在这里清理
// ObjectsDeleteAll(0, "prefix_");
}
//+------------------------------------------------------------------+
//| 6. 核心执行函数 (每个新的Tick执行) |
//+------------------------------------------------------------------+
void OnTick()
{
// --- 核心逻辑流程 ---
// 1. 新K线判断 (避免重复执行)
if(!IsNewBar())
return; // 如果不是新K线,则直接退出本次OnTick,等待下一根
// 2. 检查持仓状态
if(!g_hasOpenPosition)
{
// 2.1 如果没有持仓,则检查入场条件
if(CheckEntrySignal())
{
OpenNewOrder(); // 如果信号成立,开仓
}
}
else
{
// 2.2 如果有持仓,则执行持仓管理
ManageOpenPositions();
}
}
//+------------------------------------------------------------------+
//| 7. 自定义函数区 (所有辅助功能) |
//+------------------------------------------------------------------+
// 函数:检测是否形成新K线
bool IsNewBar()
{
static datetime lastBarTime = 0; // 静态变量,有记忆功能
if(Time[0] != lastBarTime)
{
lastBarTime = Time[0];
return true;
}
return false;
}
// 函数:检查入场信号
bool CheckEntrySignal()
{
// --- 在这里替换为你自己的交易信号逻辑 ---
// 示例:RSI低于30,产生买入信号
if(iRSI(NULL, 0, 14, PRICE_CLOSE, 0) < 30)
{
return true;
}
return false;
}
// 函数:执行开仓
void OpenNewOrder()
{
double price = Ask;
double sl = price - StopLoss * _Point;
double tp = price + TakeProfit * _Point;
int ticket = OrderSend(Symbol(), OP_BUY, LotSize, price, Slippage, sl, tp, "EA Order", MagicNumber, 0, clrBlue);
if(ticket > 0)
{
g_ticket = ticket;
g_hasOpenPosition = true;
Print("开仓成功,订单号: ", ticket);
}
else
{
Print("开仓失败,错误代码: ", GetLastError());
}
}
// 函数:管理已有持仓 (例如追踪止损)
void ManageOpenPositions()
{
if(UseTrailingStop)
{
// ApplyTrailingStop(); // 在这里调用追踪止损函数
}
}
//+------------------------------------------------------------------+