教程 · 单套解锁 教程 · EA量化教学

EA01 MQL4开发环境与第一个EA

使用手册 + 操作教程 共 21 章全部免费开放,一步一步把软件用起来;核心知识点 6 章,¥69 一次解锁讲透原理。

作者 枢汇安全课堂 更新 2026-10-13 阅读 100 收录 EA量化教学 · 27 章
本课程 · 单套解锁
¥69¥104
单品永久解锁 · 会员可阅 · 一次购买终身使用
✓使用手册 + 操作教程 共 21 章全文免费 ✓核心知识点 6 章,¥69 一次解锁 ✓支持电脑 / 手机 · 永久免费更新
注册后解锁核心知识点
免费学习区 使用手册 + 操作教程 共 21 章全部免费开放 · 核心知识点 6 章为付费内容 ¥69 解锁核心知识点
PART ONE

使用手册

功能在哪 · 是什么 · 怎么调出来

一、MQL4 是什么

MQL4(MetaQuotes Language 4)是 MT4 内置的策略编程语言,类 C 语法,专门用于写四类程序:

程序类型作用入口事件
EA(Expert Advisor)自动交易程序,长期挂在图表上运行OnTick / OnTimer
自定义指标(Custom Indicator)画自己的指标线OnCalculate
脚本(Script)一次性执行的工具(跑完自动退出)OnStart
库(Library)可复用代码模块,本身不处理事件无

MT4 build 600 之后的"新版 MQL4"在语法上向 MQL5 大幅靠拢(结构体、类、严格编译),社区生态也是全球外汇 EA 最大的。本教程基于新版 MQL4,所有示例加 #property strict 严格模式。

白话注解:EA 就是挂在图表上的机器人:行情来一个报价(tick)它就醒一次,看看条件满足没有,满足就下单。你睡觉它盯盘——前提是 MT4 终端开着。

二、账户与市场信息函数

1、账户信息(Account\* 族)

函数含义
AccountBalance()账户余额(不含浮动盈亏)
AccountEquity()净值(余额 + 浮动盈亏)
AccountProfit()当前浮动盈亏
AccountMargin() / AccountFreeMargin()已用 / 可用保证金
AccountFreeMarginCheck(symbol,cmd,volume)预检:下单这么多手后剩余保证金,<0 会报 134
IsDemo()是否模拟账户

2、市场信息(MarketInfo)

参数含义
MODE_STOPLEVEL最小止损/挂单距离(点)——SL/TP 与挂单价离市价太近会报 130
MODE_MINLOT / MODE_MAXLOT / MODE_LOTSTEP最小 / 最大手数 / 手数步长(0.01 步长给 0.115 手会报 131)
MODE_SPREAD当前点差(点)
MODE_TICKVALUE / MODE_TICKSIZE一个 tick 的价值 / 大小(算每点盈亏金额用)

3、环境检查函数族(Is\*)

IsTradeAllowed()(交易允许)、IsConnected()(与服务器连接)、IsTesting()(回测中)、IsDemo()(模拟账户)、IsTradeContextBusy()(交易线程忙)。下单前组合检查是实盘 EA 的基本功。

白话注解:STOPLEVEL 是经纪商画的"安全距离":止损止盈挂太近会被拒。而 IsTradeContextBusy 是"交易线程正忙"——MQL4 交易操作排一条队,忙时下单直接失败,等一下再试即可。
PART TWO

操作教程

一步一步 · 跟着做完一件事

三、开发环境 MetaEditor

1、打开方式

  • MT4 菜单 Tools → MetaQuotes Language Editor,或直接按 F4;
  • 或在 Navigator(导航器)里右键 Expert Advisors → Create,用向导生成骨架文件。

MetaEditor 是一个类 VS 的 IDE:左侧 Navigator(文件树 + 函数列表),中间代码区,下方 Error/Output 提示窗口。

2、文件目录结构

MT4 数据文件夹下的 MQL4/ 目录(MT4 里点 File → Open Data Folder 直接打开):

MQL4/
├── Experts/ ← EA 源码(.mq4)与编译产物(.ex4)
├── Indicators/ ← 自定义指标
├── Scripts/ ← 脚本
├── Include/ ← 头文件(.mqh)
├── Libraries/ ← 库文件
├── Files/ ← 程序读写文件的沙箱目录
├── Presets/ ← 参数预设(.set)文件
└── Logs/ ← 日志
白话注解:文件放错位置是最常见的"新手谜题"——EA 放进 Indicators 目录它就不出现在 Expert Advisors 列表里。记住:谁的家谁回,编译器不会帮你搬家。注意 Files/ 是程序读写文件的"沙箱",FileOpen 只能在这一片活动,写别的路径要另想办法。

四、程序骨架与三个核心事件函数

//+------------------------------------------------------------------+
//| MyFirstEA.mq4 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2026, Your Name"
#property link      ""
#property version   "1.00"
#property strict

//+------------------------------------------------------------------+
//| 初始化:EA 加载到图表时执行一次 |
//+------------------------------------------------------------------+
int OnInit()
{
   Print("EA 已启动");
   return(INIT_SUCCEEDED);
}

//+------------------------------------------------------------------+
//| 去初始化:EA 从图表移除时执行一次 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
   Print("EA 已停止, 原因码=", reason);
}

//+------------------------------------------------------------------+
//| 核心函数:每收到一个新 tick 执行一次 |
//+------------------------------------------------------------------+
void OnTick()
{
   // 你的交易逻辑写在这里
}

1、OnInit 的返回码(官方 ENUM_INIT_RETCODE)

返回码含义
INIT_SUCCEEDED初始化成功,EA 可以继续(等同返回 0)
INIT_FAILED初始化失败,无继续意义(如必需的指标创建失败;等同返回非 0)
INIT_PARAMETERS_INCORRECT输入参数组合不正确——优化时该组参数将被跳过,结果行标红

官方注意:只有用 #property strict 编译的程序,终端才会分析 OnInit 的返回结果;非严格模式下返回值被忽略。OnInit 建议始终写 return(INIT_SUCCEEDED); 或明确的失败码。

2、官方冷知识

  • EA 挂上图表后,终端最多给 5 秒的启动延迟做数据同步,之后带着"现有数据"启动——断线时 EA 可能晚启动,不是卡死。
  • OnDeinit 的 reason 参数是去初始化原因码:换品种/换周期、改参数、重新编译、手动移除等都会先触发 Deinit 再重新 Init。
白话注解:OnInit 是"开机自检",检查表里有一项不过就 return(INIT_FAILED) 拒绝开机,比带着坏数据硬跑强。OnDeinit 的 reason 码能告诉你 EA 为什么被卸了——日志里打个 reason,排查"EA 怎么自己停了"一目了然。

五、数据类型与变量(input vs extern)

1、基本类型

类型说明正确示例
int整数int magic = 12345;
double浮点数(价格、手数、盈亏)double lots = 0.1;
string字符串string sym = "EURUSD";
bool布尔bool isLong = true;
datetime日期时间(秒级时间戳)datetime t = TimeCurrent();
color颜色color c = clrRed;

纠错:旧版教程示例 int lots = 0.1; 是错的——手数是小数,必须 double。把 0.1 塞进 int 结果是 0,下单时手数为 0 直接报 131 错误(invalid trade volume)。

2、input 与 extern 的区别

input int FastMAPeriod = 10; // 快线周期(面板可见,运行中不可修改)
input double Lots = 0.1; // 下单手数
extern int Slippage = 3; // 滑点容忍(点,运行中代码可修改)

// 全局变量(程序内部用)
double maFastCurrent;
double maFastPrevious;
关键字面板可调代码运行中可改值典型用途
input是否(常量语义)不该被程序自己改的参数
extern是是运行中需要按状态微调的参数

两种都会出现在 EA 属性面板的 Inputs 标签里。行尾的 // 注释 会成为面板里的参数名,写清楚点比写完自己都忘了强。

白话注解:input 是"顾客点的菜"——端上桌就不能改;extern 是"厨房自己还能调味"。拿不准就用 input,防止程序运行中把自己参数改乱。

六、运算符与控制结构

// if 判断
if(maFastCurrent > maSlowCurrent && maFastPrevious <= maSlowPrevious)
{
   // 金叉
}

// for 循环(订单遍历惯用倒序,平仓后索引不乱)
for(int i = OrdersTotal() - 1; i >= 0; i--)
{
   Print(i);
}

// while 循环
int count = 0;
while(count < 5)
{
   count++;
}

// switch
switch(OrderType())
{
case OP_BUY: Print("多单"); break;
   case OP_SELL: Print("空单"); break;
default: Print("挂单"); break;
}
  • 逻辑运算 &&(与)||(或)!(非);build 600 起采用 C 语言标准的短路求值——左条件已定结果时右条件不再计算。
  • 循环里 break 跳出整个循环,continue 跳过本轮。
白话注解:遍历订单用倒序(从大到小)是 MQL4 的祖传手艺:平掉一笔订单后,后面订单的索引会整体前移,倒序遍历才不会漏单或跳单。正序遍历边平仓边 i++,几乎必然出事。

七、行情数据访问

1、预定义变量(仅 EA/脚本环境)

变量含义
Ask / Bid当前卖价 / 买价(开多用 Ask,平多/开空用 Bid)
Point一个最小变动价位的大小
Digits当前品种的小数位数(5 位报价=5)
Bars当前图表 K 线数量
Time[] Open[] High[] Low[] Close[] Volume[]预定义时间序列数组,索引 0 = 当前(未走完的)K 线,越老越大

4 位 / 5 位报价:同样是 EURUSD,5 位报价经纪商的 Point=0.00001,4 位报价的 Point=0.0001。写 StopLossPoints * Point 这类距离计算时,先确认你的账户是几位报价,否则止损差 10 倍。通用做法是用 Digits 判断(Digits==5 或 Digits==3 时给距离乘 10)。

2、时间序列与指标访问函数(官方 Series 页全表收录)

函数作用
iMA(symbol,timeframe,period,ma_shift,ma_method,applied_price,shift)取均线值
iRSI(...) / iATR(...) / iMACD(...) 等取内置指标值
iOpen / iHigh / iLow / iClose / iTime / iVolume按品种+周期+索引取任意 K 线数据
iHighest / iLowest区间内最高/最低值的位置
iBarShift由时间反查 K 线索引
CopyRates / CopyClose / CopyOpen / CopyHigh / CopyLow / CopyTime / CopyTickVolume批量复制历史数据到数组(MQL4 build 600+ 支持)
RefreshRates()刷新预定义变量(Ask/Bid 等)与序列数组

两个官方要点:

  • 时间序列索引 0 是未走完的当根 K 线;shift=1 才是上一根已收线的值。指标比较用"当根 vs 上一根"(shift 0 和 1)时,盘中当根值会一直变——要么接受,要么统一用已收盘的 shift 1 和 2。
  • 跨品种/跨周期取数可能触发 4066(ERR_HISTORY_WILL_UPDATED,历史数据更新中),官方说明此时需稍后重新请求。
double fast0 = iMA(Symbol(), 0, 10, 0, MODE_EMA, PRICE_CLOSE, 0); // 当根
double fast1 = iMA(Symbol(), 0, 10, 0, MODE_EMA, PRICE_CLOSE, 1); // 上一根
double prevDayClose = iClose(Symbol(), PERIOD_D1, 1); // 昨日收盘
白话注解:iMA 第二个参数是 timeframe,写 0 表示跟图表周期一致——回测时改周期不用改代码,这是最常用的写法。RefreshRates 是长循环里的续命药:循环里折腾半天后 Ask/Bid 还是旧的,刷一下再下单,能少踩一次 138(requote)。

八、第一个完整 EA:双均线自动交易

下面是一个结构完整、可编译、可回测的 EA 示例。它只演示 MQL4 怎么写,不代表这个策略能赚钱。

//+------------------------------------------------------------------+
//| DualMA_EA.mq4 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2026, Your Name"
#property link      ""
#property version   "1.00"
#property strict

//=== 可调参数 ===
input int FastMAPeriod = 10; // 快线周期
input int SlowMAPeriod = 30; // 慢线周期
input double Lots = 0.1; // 下单手数
input int Slippage = 3; // 滑点容忍(点)
input int StopLossPoints = 50; // 止损(点)
input int TakeProfitPoints = 100; // 止盈(点)

//=== 全局变量 ===
int ticket; // 当前持仓订单号
double point;

//+------------------------------------------------------------------+
//| 初始化 |
//+------------------------------------------------------------------+
int OnInit()
{
   point = Point;
   Print("双均线 EA 启动, 快线=", FastMAPeriod, " 慢线=", SlowMAPeriod);
   return(INIT_SUCCEEDED);
}

//+------------------------------------------------------------------+
//| 主循环 |
//+------------------------------------------------------------------+
void OnTick()
{
   // 1. 取当前根与上一根的快慢均线值
   double fastMA0 = iMA(Symbol(), 0, FastMAPeriod, 0, MODE_EMA, PRICE_CLOSE, 0);
   double fastMA1 = iMA(Symbol(), 0, FastMAPeriod, 0, MODE_EMA, PRICE_CLOSE, 1);
   double slowMA0 = iMA(Symbol(), 0, SlowMAPeriod, 0, MODE_EMA, PRICE_CLOSE, 0);
   double slowMA1 = iMA(Symbol(), 0, SlowMAPeriod, 0, MODE_EMA, PRICE_CLOSE, 1);

   // 2. 遍历订单,检查本 EA(Magic=12345)在本品种上是否已有持仓
   bool hasPosition = false;
int posType = -1; // 0=多 1=空

   for(int i = OrdersTotal() - 1; i >= 0; i--)
   {
      if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES))
      {
         if(OrderSymbol() == Symbol() && OrderMagicNumber() == 12345)
         {
            hasPosition = true;
posType = OrderType();
ticket = OrderTicket();
            break;
         }
      }
   }

   // 3. 判断金叉死叉
bool crossUp = (fastMA0 > slowMA0) && (fastMA1 <= slowMA1);
   bool crossDown = (fastMA0 < slowMA0) && (fastMA1 >= slowMA1);

   // 4. 金叉:平空 + 开多
   if(crossUp)
   {
      if(hasPosition && posType == OP_SELL)
         OrderClose(ticket, OrderLots(), Bid, Slippage, Red);

      if(!hasPosition || posType == OP_SELL)
      {
         double sl = Ask - StopLossPoints * point;
         double tp = Ask + TakeProfitPoints * point;
         ticket = OrderSend(Symbol(), OP_BUY, Lots, Ask, Slippage,
                            sl, tp, "DualMA", 12345, 0, Blue);
         Print("开多单 ticket=", ticket);
      }
   }

   // 5. 死叉:平多 + 开空
   if(crossDown)
   {
      if(hasPosition && posType == OP_BUY)
         OrderClose(ticket, OrderLots(), Bid, Slippage, Red);

      if(!hasPosition || posType == OP_BUY)
      {
         double sl = Bid + StopLossPoints * point;
         double tp = Bid - TakeProfitPoints * point;
         ticket = OrderSend(Symbol(), OP_SELL, Lots, Bid, Slippage,
                            sl, tp, "DualMA", 12345, 0, Orange);
         Print("开空单 ticket=", ticket);
      }
   }
}

教学示例与实盘的差距,自己心里要有数:这份代码没有检查 OrderSend 返回值是否 -1、没有 NormalizeDouble、没有 STOPLEVEL 预检、没有重试逻辑——这些都是第 2 课《订单管理进阶》的内容。别拿它直接上实盘。

九、代码逐段解释与 Magic Number

1、主循环五步拆解

  1. iMA() 取当根(shift 0)与上一根(shift 1)的快慢均线值,共 4 个数;
  2. 倒序遍历订单,用"品种 + Magic Number"双重过滤找自己的持仓;
  3. 金叉 = 快线当前在慢线上方且上一根不在上方(反之死叉);
  4. 金叉时:有空单先 OrderClose,再 OrderSend 开多;
  5. 死叉对称处理。

2、关键函数速查

函数参数作用
iMA()品种,时间框架,均线周期,偏移,均线类型,价格类型,索引取均线值
OrdersTotal()无当前市场持仓+挂单总数
OrderSelect()索引或票号,选择方式,交易池选中订单后才能读其属性
OrderSend()品种,方向,手数,价格,滑点,止损,止盈,注释,Magic,过期时间,箭头颜色下单,成功返回 ticket,失败返回 -1
OrderClose()订单号,手数,平仓价,滑点,箭头颜色平仓

3、Magic Number 是什么

每个 EA 下单时打一个"标签"(这里是 12345),作用:

  • 多个 EA 同时跑不会互相干扰——各自只认自己的 Magic;
  • EA 只管理自己开的单,不会误平你手动下的仓(手动单 Magic=0)。
白话注解:Magic Number 就是"这是我的孩子"的认亲信物。不加 Magic 过滤的 EA 是灾难:它会把你账户里所有单子都当成自己的来平。同一策略跑多个品种时也建议每个图表实例用不同 Magic,方便对账。

十、编译与加载

1、编译

  1. MetaEditor 打开 DualMA_EA.mq4;
  2. 按 F7(或工具栏 Compile);
  3. 下方 Errors 窗口显示 0 errors, 0 warnings 最理想(warning 也别无视);
  4. 编译生成 .ex4,MT4 只加载 .ex4。

2、加载到图表

  1. 切回 MT4,Navigator → Expert Advisors → 找到 DualMA_EA(找不到先刷新右键菜单);
  2. 拖到品种图表上,弹出属性窗口:Common 标签勾选 Allow live trading(允许实时交易);Inputs 标签调参数;
  3. 确认 MT4 工具栏的 AutoTrading(自动交易)总开关是亮的绿色;
  4. 图表右上角出现笑脸 = EA 正常运行;出现哭脸或 = 没跑起来。
白话注解:能交易需要"三重门"同时打开:终端总开关 AutoTrading、EA 属性里的 Allow live trading、(有的经纪商还要求账户本身允许程序化)。哭脸只说明"当前不允许交易",不一定是 EA 写错了。

十一、EA 跑不起来的排查清单

现象原因
图表右上角哭脸工具栏 AutoTrading 总开关没开,或 EA 属性没勾 Allow live trading
笑脸但不下单信号条件从未满足;或下单被拒(看 Experts 日志的错误码,见第十八章)
Navigator 里找不到 EA文件没编译成功(无 .ex4),或放错了目录
改了代码没生效没重新编译,或 MT4 里挂的还是旧 .ex4(重新编译后 EA 会自动重载)
Terminal → Experts 报错运行时错误:手数非法、止损太近(130)、余额不足(134)等
Terminal → Journal 报错账户/连接层面问题:断线、登录失效等
白话注解:排查顺序永远是:笑脸有没有 → Experts 日志 → Journal 日志。Experts 是"你的 EA 说了什么",Journal 是"终端和服务器说了什么",两层日志分开看。

十二、日志与调试

1、三种输出

函数输出位置用途
Print(...)Terminal → Experts 标签标准日志,支持逗号拼接多参数
Comment(...)图表左上角实时状态面板(持仓、盈亏等),OnDeinit 里 Comment("") 清掉
Alert(...)弹窗 + 声音需要人工注意的事件,别滥用

2、调试手段

  • GetLastError():取最近一次错误码(配合 ResetLastError() 先清零再调用,见第十八章);
  • MetaEditor 支持断点调试(Debug 模式下在图表上启动 EA,可逐行单步、查看变量);
  • DebugBreak():代码内硬断点(函数列表官方收录);
  • 日志双语对照:自己的 Print 都带上下文(品种、ticket、价格),否则三个月后你自己都看不懂日志。
int t = OrderSend(Symbol(), OP_BUY, Lots, Ask, Slippage, sl, tp, "DualMA", 12345, 0, Blue);
if(t < 0)
   Print("开多失败 ticket=", t, " err=", GetLastError(), " Ask=", DoubleToString(Ask, Digits));
白话注解:所有交易函数的返回值都必须检查——OrderSend 失败返回 -1,不检查你就以为"没信号",其实是"下单被拒"。err 码加上 Ask 现价一起打出来,一眼定位是滑点问题还是止损距离问题。

十三、事件函数全集与事件队列规则

1、官方事件函数表(docs.mql4.com Program Running 页)

函数签名适用
OnInitint OnInit()(或 void)EA、指标、脚本
OnDeinitvoid OnDeinit(const int reason)EA、指标、脚本
OnTickvoid OnTick()仅 EA
OnTimervoid OnTimer()EA、指标(脚本不可用)
OnTesterdouble OnTester()仅 EA(测试器自定义评估值)
OnChartEventvoid OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)EA、指标
OnStartvoid OnStart()仅脚本
OnCalculate_rates_total 等 9 参仅指标

警告:OnTrade() 是 MQL5 的交易事件,MQL4 的事件函数表里没有它。MQL4 里要感知成交变化,只能自己在 OnTick/OnTimer 里遍历订单比对。网上把 OnTrade 写在"MQL4 教程"里的,都是 MQL5 内容串了台。

2、官方事件队列规则

  • NewTick 事件:队列里已有未处理的 NewTick 或正在处理时,新 tick 不入队——处理太慢的 EA 会自动丢 tick,不会堆积;
  • Timer、ChartEvent 同理各排各的队;
  • 每个程序(及图表)有独立事件队列,按到达顺序处理;
  • 官方明确不建议用死循环处理事件(脚本例外)。

3、各程序类型的函数禁用表(官方)

程序类型禁止调用
指标OrderSend、SendFTP、Sleep、ExpertRemove、MessageBox(交易与阻塞类全禁)
EA、脚本SetIndexBuffer、IndicatorSetDouble/Integer 等"指标专用"函数
库不处理任何事件
白话注解:指标住在主界面线程里,一个指标死循环会让整个终端卡死——所以官方干脆把 Sleep、下单全给禁了。这也解释了"为什么我的指标里不能下单":不是 bug,是设计。

十四、订单体系与 OrderSend 全参数

1、六种订单类型(OP_ 常量)

常量值含义
OP_BUY0市价买
OP_SELL1市价卖
OP_BUYLIMIT2买入限价(下方挂单)
OP_SELLLIMIT3卖出限价(上方挂单)
OP_BUYSTOP4买入止损(上方挂单)
OP_SELLSTOP5卖出止损(下方挂单)

2、OrderSend 完整签名

int ticket = OrderSend(
symbol, // 品种
cmd, // OP_ 六选一
volume, // 手数
price, // 委托价(市价单用 Ask/Bid,挂单用挂单价)
slippage, // 滑点容忍(点,市价单有效)
stoploss, // 止损价(绝对价格,不是距离!)
takeprofit, // 止盈价(绝对价格)
comment, // 订单注释
magic, // Magic Number
expiration, // 挂单过期时间(0=不过期,仅挂单有效)
arrow_color // 图表箭头颜色
);
// 成功返回 ticket(正数),失败返回 -1
  • SL/TP 是绝对价格(如 1.0850),不是"亏 50 点"这种距离——新手第一大坑;
  • SL/TP 距离必须满足经纪商 STOPLEVEL 限制,否则报 130,见第十七、十八章;
  • expiration 只对挂单有意义;市价单写 0;部分经纪商禁止挂单过期(报 147)。
白话注解:止损参数填的是"价格线画在哪",不是"离进场多远"。要"亏 50 点止损",得自己算 Ask - 50*Point 再传进去——算错方向或忘记 NormalizeDouble,就是 130 错误伺候。

十五、订单遍历与 OrderSelect

// 遍历当前持仓+挂单
for(int i = OrdersTotal() - 1; i >= 0; i--)
{
   if(!OrderSelect(i, SELECT_BY_POS, MODE_TRADES)) continue;
   if(OrderSymbol() != Symbol() || OrderMagicNumber() != 12345) continue;

   Print("ticket=", OrderTicket(), " type=", OrderType(),
         " lots=", OrderLots(), " open=", OrderOpenPrice(),
         " sl=", OrderStopLoss(), " tp=", OrderTakeProfit(),
         " profit=", OrderProfit());
}

// 按 ticket 选中(平仓/改单前必做)
if(OrderSelect(ticket, SELECT_BY_TICKET))
{
   // 注意:历史单也会被选中,用 OrderCloseTime() 判断是否已平
   if(OrderCloseTime() == 0)
   {
      // 还在持仓/挂单中,可以操作
   }
}
要点说明
SELECT_BY_POS / SELECT_BY_TICKET按位置索引 / 按订单号选中
MODE_TRADES当前持仓+挂单池(OrdersTotal() 计数范围)
MODE_HISTORY历史池,配合 OrdersHistoryTotal() 遍历已平仓单
OrderCloseTime()==0未平仓;非 0 是平仓时间(SELECT_BY_TICKET 可能选中历史单,必查)
白话注解:OrderSelect 是 MQL4 的"选中才能读"机制——先选中,OrderSymbol/OrderLots 这些属性函数才有意义。按 ticket 选中时连已平仓的历史单都能选中,所以判断"这单还活着吗"要看 OrderCloseTime() 是不是 0。

十六、OnTimer 定时器

int OnInit()
{
EventSetTimer(5); // 每 5 秒触发一次 OnTimer
   return(INIT_SUCCEEDED);
}

void OnDeinit(const int reason)
{
EventKillTimer(); // 卸载时必须销毁定时器
}

void OnTimer()
{
   if(!IsTradeAllowed()) return;
   // 无 tick 时段的自救逻辑:检查持仓、移动止损、状态巡检
}
函数说明
EventSetTimer(秒)启动秒级定时器(再次调用会重设周期)
EventSetMillisecondTimer(毫秒)亚秒级高精度定时器
EventKillTimer()销毁定时器(OnDeinit 里必须调用)
  • OnTimer 只对 EA 和指标有效,脚本用不了;
  • 官方规则:Timer 事件在队列中或处理中时,新 Timer 事件不入队;
  • 市场没报价(冷清时段)OnTick 不触发,但 OnTimer 照常触发——用它做"无 tick 自救"(断线检测、状态上报、悬挂单巡检)。
白话注解:OnTick 是"有行情才醒",OnTimer 是"闹钟到点就醒"。周末停盘、清淡时段,OnTick 一小时不来一次,但你的 EA 还得巡检持仓状态——这时候 OnTimer 就是唯一的心跳。

十七、OnChartEvent 图表事件

void OnChartEvent(const int id, const long &lparam,
                  const double &dparam, const string &sparam)
{
   if(id == CHARTEVENT_CLICK)
      Print("鼠标点击: x=", lparam, " y=", dparam);

   if(id == CHARTEVENT_KEYDOWN)
      Print("按键虚拟码: ", lparam);

   if(id == CHARTEVENT_OBJECT_CLICK)
Print("点击了对象: ", sparam); // 按钮做交易面板的入口
}
  • 事件类型常量:CHARTEVENT_CLICK(点击)、CHARTEVENT_KEYDOWN(按键)、CHARTEVENT_OBJECT_CLICK / OBJECT_CREATE / OBJECT_DELETE / OBJECT_DRAG(对象类)、CHARTEVENT_CHART_CHANGE(图表切换)等;
  • EventChartCustom() 可生成自定义事件,实现程序内部/程序间的消息传递;
  • 典型用途:图形化交易面板(按钮下单、拖动改止损)、热键控制 EA 开关。
白话注解:想让 EA 有个"一键清仓"按钮,路径就是:ObjectCreate 画个按钮对象 → OnChartEvent 监听 CHARTEVENT_OBJECT_CLICK → 判断 sparam 是不是你的按钮名 → 执行平仓。MQL4 的图形面板全是这么攒出来的。

十八、数组与 CopyRates 数据复制

1、MqlRates 结构与 CopyRates

MqlRates rates[];
ArraySetAsSeries(rates, true); // 索引0=最新K线
int copied = CopyRates(Symbol(), 0, 0, 100, rates);
if(copied < 0)
{
Print("复制失败 err=", GetLastError()); // 常见 4066:历史更新中
   return;
}
for(int i = 0; i < copied; i++)
   Print(rates[i].time, " O=", rates[i].open,
         " H=", rates[i].high, " L=", rates[i].low, " C=", rates[i].close);
  • 三种重载:按 起始位置+数量、按 起始时间+数量、按 时间区间;
  • 失败返回 -1:请求区间超出服务器数据范围、超过图表最大 K 线数(TERMINAL_MAXBARS)、数据尚未下载都会返回 -1;
  • 返回值 > 0 时是实际复制的元素个数,循环边界用它,别自己假设 100 根就一定拿满 100 根;
  • 同族的 CopyOpen/High/Low/Close/Time/TickVolume 按字段复制到 double/datetime/long 数组。

2、ArraySetAsSeries 的真相(官方注)

该函数只改索引方向,不搬数据——数组元素物理存储顺序不变,series=true 只是把"读下标的方式"反过来。官方建议:频繁在 OnTick 里复制的场景用静态数组省去重新分配内存的开销。

白话注解:MQL4 的老 Time[]/Close[] 数组天生就是"0 号最新",而 CopyRates 塞进来的数组默认"0 号最旧"。ArraySetAsSeries(arr, true) 就是把它们统一成老习惯——只想换读法,不想动数据。

十九、字符串与时间函数

1、新旧函数名并存(build 600+ 特点)

旧名(兼容保留)新名(推荐)
DoubleToStr(x, d)DoubleToString(x, d)
StrToDouble(s)StringToDouble(s)
StrToInteger(s)StringToInteger(s)
TimeToStr(t, flags)TimeToString(t, flags)

两套都在官方函数列表里,同一程序里别混用,风格统一。

2、常用时间函数

函数含义
TimeCurrent()服务器时间(最后已知报价时间)
TimeLocal()本机时间
Time[0] / iTime(...)K 线开盘时间
Day() / DayOfWeek() / DayOfYear()按服务器时间的日 / 星期(0=周日)/ 年积日
TimeHour(t) / TimeMinute(t)从时间戳里拆时 / 分
// 过滤开仓时间窗口示例(仅交易欧美时段)
int h = TimeHour(TimeCurrent());
if(h >= 7 && h < 20)
{
   // 该时段才允许开新仓
}
白话注解:做时间过滤一定用服务器时间 TimeCurrent——它和你手表的时间不是一回事(差时区),而 K 线就是按服务器时间切的。DayOfWeek 0 是周日,周五下午收盘前减仓这种逻辑全靠它。

二十、终端全局变量与文件读写

1、终端全局变量(GlobalVariable 族)

GlobalVariableSet("MyEA_State", 1); // 写
double s = GlobalVariableGet("MyEA_State"); // 读
GlobalVariableDel("MyEA_State"); // 删
  • 这是终端级的共享变量:同一 MT4 里的多个 EA/脚本/指标都能读写,跨图表传状态就靠它;
  • 和程序内的全局变量是两码事:程序内的只在内存里,EA 卸载就没了;终端全局变量持久保存(带过期时间的变体也有);
  • 典型用途:两个 EA 互斥(同一时间只允许一个开仓)、EA 与配套指标传状态。

2、文件读写(File 族)

int fh = FileOpen("mydata.csv", FILE_WRITE|FILE_CSV, ',');
if(fh != INVALID_HANDLE)
{
   FileWrite(fh, "time", "ticket", "profit");
   FileClose(fh);
}
  • 读写范围限定在 MQL4/Files/ 沙箱(官方函数表含 FileOpen/FileClose/FileWrite/FileRead\*/FileIsExist/FileDelete 等完整一族);
  • 模式常量:FILE_WRITE / FILE_READ / FILE_APPEND / FILE_CSV / FILE_TXT / FILE_BIN;
  • 典型用途:导出交易记录做分析、EA 重启后恢复状态。
白话注解:GlobalVariable 是"EA 之间的便签条",File 是"EA 的日记本"。EA 崩溃重启后想恢复"我今天已经开了几单"这类状态,要么写终端全局变量,要么写文件——只靠程序内存,重启全清零。

二十一、本课小结

  • MQL4 四类程序:EA/指标/脚本/库,入口事件各不同;EA 主循环是 OnTick
  • OnInit 返回 INIT_SUCCEEDED / INIT_FAILED / INIT_PARAMETERS_INCORRECT,#property strict 下才被分析
  • 手数用 double;input 不可运行中修改、extern 可以
  • Ask/Bid/Point/Digits 是预定义变量;时间序列索引 0 = 未走完的当根 K 线;4/5 位报价 Point 差 10 倍
  • OrderSend 的 SL/TP 是绝对价格;订单遍历用倒序;一切交易函数返回值必须检查
  • Magic Number 是 EA 的认亲信物;SELECT_BY_TICKET 选中历史单要看 OrderCloseTime()==0
  • 事件函数全集里没有 OnTrade(MQL5 专属);OnTimer 做"无 tick 自救",OnDeinit 必须 EventKillTimer
  • 错误码 128/142/143 = 结果未知先查成交;130 = 止损违规;可重试的是 135/136/138
  • 回测三档模型:粗筛 Open prices only,精测 Every tick;回测 ≠ 实盘,业绩不代表未来收益

下一课预告:EA02 订单管理进阶——追踪止损、部分平仓、多持仓管理、错误码处理(已在库中)。

本站固定免责声明:本教程仅讲解软件编程与操作方法,不构成投资建议或策略推荐。回测业绩不代表未来收益,EA自动交易风险大,请谨慎决策。
PART THREE

核心知识点

为什么会这样 · 规则是什么

二十二、回测:策略测试器

1、打开与设置

  • 菜单 View → Strategy Tester,或按 Ctrl+R;
  • 底部出现测试器面板,按下表设置:
选项填什么
Expert选 DualMA_EA
Symbol / Period回测品种(如 EURUSD)与周期(如 H1)
ModelEvery tick(最精确最慢)/ Control points / Open prices only(最快最粗)
Date / Use date回测时间段
Visual mode勾上可看图表逐根回放

2、三档模型怎么选

  • Open prices only:只在 K 线开盘价成交——只适合"信号只在 K 线走完产生、且用市价单"的粗筛,速度飞快;
  • Control points:最近 K 线细、远处用控制点插值——中等精度;
  • Every tick:基于每一笔 tick 模拟——最接近实盘,也最慢。含盘中止损止盈(SL/TP 在 K 线内触发)的 EA 必须用它,否则回测结果失真。

面板里还可勾选优化(Optimization)并启用遗传算法对 input 参数做批量寻优。

白话注解:先用 Open prices only 快速筛掉明显不行的参数,再用 Every tick 精测幸存者——顺序别反,精测全参数组合能跑到天荒地老。模型档位越低回测越"好看",因为它看不见 K 线内的波动。

二十三、回测报告解读

跑完后看两个标签:Graph(资金曲线)和 Report(指标)。重点字段:

指标含义怎么看
Total Net Profit总净利润必须为正,但只看它必被坑
Gross Profit / Gross Loss总盈利 / 总亏损两者之比即 Profit Factor
Profit Factor盈利因子(总盈÷总亏)<1 直接淘汰;略大于 1 扣除点差滑点后可能归零
Maximal Drawdown最大回撤决定你实盘拿不拿得住
Total Trades总交易笔数几十笔的样本没有统计意义

回测 ≠ 实盘:回测好的策略实盘可能亏钱——过拟合、滑点、点差放大、流动性不足、报价延迟都吃利润。回测是淘汰工具,不是赚钱保证书。

白话注解:不要只看净利润。赚 10 万但中间回撤 8 万的策略,你大概率在谷底手动关掉它——对你来说它就是亏 8 万。资金曲线"陡不陡"比终点"高不高"更值得看。

二十四、错误处理与常见错误码

ResetLastError(); // 先清零,防止读到旧错误
int t = OrderSend(...);
if(t < 0)
{
   int err = GetLastError();
   Print("下单失败 err=", err);
}

官方错误码常量表中交易类常用项(完整表见 docs.mql4.com 的错误码页):

码含义
128交易超时——结果未知,必须先查订单状态再决定是否重发
129无效价格(价格离市价过远/过期)
130无效止损止盈(违反 STOPLEVEL 或没 NormalizeDouble)
131无效手数(低于 MINLOT / 不符 LOTSTEP)
132市场关闭
133交易被禁用
134余额/保证金不足
135价格已变动(可刷新后重试)
136离价(off quotes)
137经纪商忙
138重新报价(requote)——刷新价格后可重试
139订单被锁定
141请求过于频繁
146交易上下文忙(IsTradeContextBusy() 检测)——稍候重试
147经纪商拒绝使用 expiration(挂单过期时间被禁)
148持仓/挂单数量超上限
142 / 143请求送达但连接中断类——结果未知,先查成交再重试
4066请求的历史数据正在更新(ERR_HISTORY_WILL_UPDATED),稍后重新请求
白话注解:128、142、143 这三个码是"石沉大海"型——请求发出去了,成没成交不知道。这时直接重发可能开出双倍仓位,正确动作是先遍历订单查这笔单到底成没成。可安全重试的是 135/136/138 这类"明确被拒"的码。

二十五、常见问题全集 FAQ

1、Q1:input 和 extern 到底用哪个?

默认用 input(防程序运行中自己改参数);确实需要运行中改(如自适应滑点)用 extern。两者都出现在属性面板且都能参与优化。

2、Q2:OrderSend 返回 -1 怎么办?

先 GetLastError() 拿错误码再对症下药:130 查 STOPLEVEL 和 NormalizeDouble,131 查手数规范,134 查保证金,138 刷新价格重试,128/142/143 结果未知先查成交。逐条对照第十八章。

3、Q3:止损总是报 130?

三大原因:SL/TP 当成了"点数距离"而不是绝对价格;距离小于经纪商 STOPLEVEL;价格没 NormalizeDouble 到 Digits 位。三样全查。

4、Q4:为什么 OnTick 有时不触发?

市场没有新报价(冷清时段/休市)自然不来 tick;另外官方规则:上一 tick 还没处理完时,新 tick 不入队。对时效敏感的逻辑配 OnTimer(第十九章)。

5、Q5:MQL4 能用 OnTrade 吗?

不能。OnTrade 是 MQL5 的事件。MQL4 感知成交变化要自己在 OnTick/OnTimer 里遍历订单,比对 ticket 和持仓变化。

6、Q6:回测选哪个 Model?

粗筛用 Open prices only,精测用 Every tick。EA 用了 K 线内触发的 SL/TP 或盘中逻辑,必须 Every tick,否则回测结果不可信。样本(Total Trades)太少的结果直接无视。

7、Q7:EA 重编译后图表上的会自动更新吗?

会——重新编译触发 Deinit→重新加载。但改了 input 默认值不会覆盖图表上已保存的参数设置;参数改变本身也会触发一次 Deinit/Init 循环。

8、Q8:能拿别人的 EA 直接跑实盘吗?

先模拟盘跑够长时间,看懂它的 Magic、手数、止损逻辑再用。来路不明的 EA 是资金盘与盗号的高发载体(要求远程"托管账户"、承诺稳赚收益的更要远离)。只讲避坑,不荐任何 EA。

风险声明:本手册仅用于软件操作教学,不构成任何投资建议、开户建议或交易推荐。境外券商/平台涉及跨境资金与外汇管理问题,境内居民参与不受国内法律保护。保证金、期货、期权交易风险极高,可能损失超过本金的资金。请根据自身情况谨慎决策。

EA01-MQL4开发环境与第一个EA25 章 · 39 节仅供学习参考

二十六、学习路线

  1. 先学基础语法
  2. 写第一个简单指标
  3. 写第一个自动交易EA
  4. 回测优化
  5. 模拟盘验证
  6. 小资金实盘

二十七、快速上手

1、第一步:装环境

安装对应语言开发环境。

2、第二步:跑示例

先运行官方示例代码。

3、第三步:改参数

修改参数看效果。

4、第四步:写策略

从简单的均线策略开始写。

5、第五步:回测

用历史数据回测看效果。

付费专享 · 核心知识点 6 章

继续阅读:核心知识点,搞懂原理

EA01 MQL4开发环境与第一个EA — 共 27 章,已免费阅读 21 章,剩余 6 章核心知识点讲透「为什么这样、点了会怎样」。

二十二、回测:策略测试器
二十三、回测报告解读
二十四、错误处理与常见错误码
二十五、常见问题全集 FAQ
二十六、学习路线
二十七、快速上手
✍️ 注册后扫码购买 🔐 已有账号,去登录 单套 ¥69 永久 · 会员可阅
SERVICE & PRICING
服务收费说明 · 明码标价,无隐藏收费
全部课程统一 ¥39-69,买一套永久使用。以下为购买与使用的完整流程。
01先免费试读

每套教程的「使用手册」部分均可免费阅读,先确认内容是否适合自己再决定,不必盲买。

02扫码付款

选定课程后点「立即解锁」,页面展示收款二维码与客服微信,扫码转账即可,无需注册海外支付。

03获取卡密

付款完成后客服第一时间发送卡密号与卡密密码,站内消息与微信同步通知。

04兑换解锁

在用户中心「兑换卡密」输入卡密号与密码,系统即时解锁全部章节,无需二次付款。

一次付费,终身可用 · 解锁后课程永久留在账号里,不限次数、不限时间阅读 持续免费更新 · 平台改版、规则变化时课程同步修订
RECOMMEND
相关教程推荐 · 同栏目更多上手机会

💬 用户评价 (0)

🔒 登录并成为会员后可发表评价
还没有评价 · 成为第一个评价的人吧 🎯