2026/10/23 20:51:57

MQL4编程入门:从变量、数组到自定义指标的核心避坑指南

MQL4编程入门:从变量、数组到自定义指标的核心避坑指南 简介《MT4的MQL4编程入门》是一份面向外汇交易初学者和编程零基础用户的MQL4语言入门教程基于韬客外汇论坛经典入门系列整理而成。教程系统讲解从自定义变量到函数的完整语法包括int、double、string、bool四种基本类型extern附加变量用于设置可调参数自定义函数支持有返回值类型与void无返回值类型数组则用于批量处理同类数据同时结合MT4平台演示如何编写交易策略、自定义指标和EA自动化程序覆盖市场数据取值等后续进阶内容。资源为DOC文档共1个文件压缩包约197KB内容精炼、结构清晰适合随时翻阅。目前已有389人浏览学习。通过该教程零基础读者可快速掌握MQL4的核心编程逻辑理解变量作用域、函数调用与数组引用规则并逐步构建个人交易策略的自动执行脚本提升交易效率与自动化水平。1. MT4 MQL4 编程入门先看懂语言再谈策略落地很多人拿到 MT4 之后的第一反应是找现成的 EA 和指标源码复制进去编译然后面对一堆红色报错只能干瞪眼。这套《MT4 的 MQL4 编程入门》教程不教你预测行情也不承诺一键生成策略它做的事情很朴素把 MQL4 这门语言的骨架拆开——变量与类型、extern 参数、函数与数组、市场数据取值、指标结构与循环判断每一节都带着能直接编译的小例子。它适合刚入门的交易者也适合会一点 C 但没碰过 MQL4 的程序员。看完你能读懂别人指标源码的八成逻辑能改参数、能画第一条指标线也知道出了问题该去哪里查而不是把 MT4 当黑匣子瞎猜。2. 变量、extern 与函数先立起 MQL4 的语法骨架2.1 四种基础类型与“先定义后使用”规则MQL4 不是脚本语言它和 C 很像所有要用的数据都必须在使用前声明类型和名称。教程里那句“自定义变量在使用前必须创建”翻译过来就是编译器要提前知道这个变量占多大空间、用什么方式解释它。拿int mark;举例int 是类型定义词mark 是变量名这条语句告诉编译器“给我一个用来存整数的盒子名字叫 mark”。四种基本类型我列了一张表方便对照类型含义典型用途int整型包括负整数循环计数、指标周期参数double双精度小数价格、指标计算值string字符串指标名、邮件内容、注释bool逻辑型 true/false开关参数、报警状态标记声明时可以直接赋初值比如bool 是否报警 true;、string 说明 测试;。这里有一个 MQL4 的特别之处变量名可以用中文这在老牌编程语言里不多见。不过中文变量名参与字符串拼接时要注意编码教程示例本身没毛病但如果源码从网页直接复制粘贴偶尔会出现中文名乱码导致编译失败。我一般遇到这种现象先把源码另存为 UTF-8 编码再重新编译大多数情况下就过去了。类型定义词只在创建变量时用一次。创建之后给变量赋值直接写变量名mark 5;。新手最常见的编译错误就是赋值时顺手又写了一遍int mark 5;这在 MQL4 里属于重复定义直接报错。记住这个原则int、double 这类关键字只在变量第一次出现的时候写后面再出现就是错的。2.2 extern 附加变量把参数交到用户手里的入口extern 是 MQL4 里最值得先记住的关键字。一个变量声明前面加上 extern它就会出现在 MT4 加载指标或 EA 时的“输入参数”面板里用户不用打开源码就能改数值。MACD(12,26,9) 这种括号参数本质就是三个 extern 变量的默认值。extern int 周期 9; extern double 止损点数 30.0; extern bool 是否开启报警 true;第一个 extern 声明了整型变量 周期默认 9用户在输入参数面板里把它改成 20指标内部所有用 周期 的地方就自动换成 20。第二个是双精度型适合放点数、金额这类带小数参数。第三个是逻辑型适合做开关。这里要提一个默认值的坑extern 变量的默认值直接影响用户第一次加载时的行为很多人策略翻车不是逻辑写错而是默认参数本身不合理。我一般会把默认值设置成能在当前周期画出正常图形的值而不是随手写一个整数。extern 还有两个使用边界它只能修饰全局变量不能放在 start() 函数或自定义函数内部否则编译不通过命名不要用 MQL4 保留字比如 int、double、start 这些都不能作为变量名。另外extern 变量在程序运行中改动后不会立即生效需要重新加载指标或 EA 才会按新参数计算调试时要记得这一步。2.3 自定义函数参数传递、返回值和局部变量的生命周期函数的意义是把一段反复用到的运算打包。教程里那个 ft(x,y)3x2y 的例子非常典型写成 MQL4 就是int ft(int x, int y) { int temp; temp 3 * x 2 * y; return(temp); } void 显示提示(string 文本) { Comment(文本); }逻辑说明函数头的 int 是函数类型表示这个函数返回整数函数名后面的括号里是参数表x 和 y 在使用前也必须声明类型所以写成 int x, int y大括号里是函数体temp 是函数内部定义的局部变量return(temp) 把 temp 的值带回调用处。调用时写 ft(4,5)程序按顺序把 4 传给 x、5 传给 y函数计算返回 23调用点拿到这个值继续往下执行。void 类型是 MQL4 里第二常见的函数类型它没有 return 值函数体执行完就结束。像 显示提示 这种只负责在图表左上角输出文本的函数用 void 最合适。判断什么时候用 int、double 还是 void核心看调用方是否需要这个函数算出一个结果来参与后续计算。局部变量的生命周期值得多说一句ft 函数里的 x、y、temp 都在函数被调用时才创建函数执行结束立刻注销。这就带来一个实际问题——如果你需要一个变量记录“上一次报警发生在什么时候”放在函数内部每次调用都会重置。解决方法是把这种状态变量放到函数外面定义成全局变量或者在声明时加 static 关键字让它在多次调用之间保留值。这算是 MQL4 新手从函数语法过渡到实际指标编程的一道小坎。2.4 数组与下标一次只能操作一个元素数组在 MQL4 里是一组同类型变量的集合。int aa[4] {4,3,2,1};声明了一个长度 4 的整型数组方括号里的数字是下标。数组下标从 0 开始也就是说 aa[0] 的值是 4aa[1] 是 3aa[2] 是 2aa[3] 是 1。这个“第 0 个元素”的规则和日常计数习惯冲突新手第一次遍历数组时经常越界下标写成 4 去访问 aa[4] 就直接报 array out of range。int aa[4] {4,3,2,1}; // 长度为 4aa[0]4 ... aa[3]1 double buf[]; // 不定长数组后面绑定指标缓冲 int cc[8]; // 定长数组8 个元素未赋初值数组最容易被忽略的一点定义之后一次只能通过下标访问一个元素不能像某些语言那样整体赋值或整体比较。要处理整列数据就写循环逐个操作。MQL4 里的动态数组不定长数组有一个常见陷阱int bb[];这种声明在普通场景下直接写 bb[0]1 会在运行时越界报错因为编译器没有给这个数组分配实际容量。我常用的做法是先用ArrayResize(bb, 数量)明确容量再按下标写入。指标缓冲数组是例外它由 SetIndexBuffer 在初始化函数里绑定容量由 MT4 内部管理这个后面会讲到。3. 市场数据与指标取值搞懂 Open[] 和 iMA 的参数习惯3.1 预定义数组记住 0 是最新一根 K 线MT4 把每根 K 线的开、高、低、收、成交量、时间分别做成了可以直接使用的数组Open[]、High[]、Low[]、Close[]、Volume[]、Time[]。这些数组的下标代表 K 线位置方向跟多数人的直觉相反0 是当前最新一根1 是它左边那一根2 再往左i 表示第 i1 根。我之前带人写第一个指标时十个人里有八个把方向写反最后画出来的线整体平移了一根 K 线。double 最新收盘 Close[0]; double 前一根最高 High[1]; double 倒数第三根开盘 Open[2]; int 柱总数 Bars;Close[0] 是最新收盘价High[1] 是前一根最高价Bars 是当前图表的总柱数。这些预定义数组只属于当前图表——也就是说它们在哪个货币对、哪个周期加载取的就是那个图表的数据。想要读别的周期就得用带 i 前缀的函数见 3.2。除了这些数组还有几个预定义变量要顺手记住Ask 和 Bid 是当前实时卖价和买价double 型Digits 是当前品种的小数位数无日元币对一般是 4有日元的是 2Point 是点值和 Digits 对应写止损止盈计算时离不开它们。有一个使用上的陷阱当软件刚连接行情、历史数据还没有完全加载时Close[0] 这类值可能是 0 或者无效数据。如果在指标初始化阶段就读取这些数组做判断偶尔会得到错误结论。我的习惯是在 start() 里先检查 Bars等柱数足够后再开始计算避免把无效值写进指标线。3.2 跨周期取数iOpen、iClose 系列与周期常量预定义数组只能看当前图表数据但实际策略经常需要“大周期定方向、小周期找进场”。这时候要用 iOpen(symbol, timeframe, shift) 这一系列函数。它们的参数结构完全一致第一个参数是品种字符串第二个是 K 线周期常量第三个是位置序号。周期常量对应关系如下常量周期PERIOD_M11 分钟PERIOD_M55 分钟PERIOD_M1515 分钟PERIOD_M3030 分钟PERIOD_H11 小时PERIOD_H44 小时PERIOD_D1日线PERIOD_W1周线PERIOD_MN1月线double 指定品种小时最新开盘 iOpen(USDJPY, PERIOD_H1, 0); double 当前周期最新收盘 iClose(NULL, 0, 0); double 当前图表第3根收盘 iClose(NULL, 0, 2);第一行表示某货币对 1 小时图最新一根开盘价。第二行是“不指定品种也不指定周期”NULL 表示跟随当前图表品种0 表示当前图表周期常用于写通用指标。第三行的 2 是位置序号注意这里 2 表示从右往左第 3 根。位置序号和预定义数组的下标是同一个坐标系这样设计的好处是一个指标里混用 Close[0] 和 iClose(NULL,0,0)结果完全一致不会出现偏移。用 NULL 有个副作用要心里有数它跟随当前图表图表切到别的品种或周期取值跟着变。写通用指标是优点写固定品种策略时反而要小心最好显式指定品种字符串。iHigh、iLow、iVolume、iTime 系列与 iOpen 完全同构参数含义一致区别只在 iTime 返回的是 datetime 类型其他返回 double。3.3 iMA 移动平均线七个参数错一个就不是你想要的线均线是 MQL4 里调用频率最高的指标函数iMA 一共有七个参数也是新手最容易记混的一组。完整形式是double maValue iMA(symbol, timeframe, period, ma_shift, ma_method, applied_price, shift);参数依次是品种、周期、均线周期、均线偏移、平均模式、价格种类、位置。举例double ma20 iMA(NULL, 0, 20, 0, MODE_SMA, PRICE_CLOSE, 0); double ema20_3根前 iMA(NULL, 0, 20, 0, MODE_EMA, PRICE_CLOSE, 2);第一行取当前图表 20 周期简单移动平均线在最新位置的值。第二行取 20 周期指数移动平均线在第 3 根 K 线位置的值。第五个参数平均模式有四个常量MODE_SMA 简单移动平均、MODE_EMA 指数移动平均、MODE_SMMA 平滑移动平均、MODE_LWMA 线性加权。第六个参数价格种类最常用 PRICE_CLOSE 收盘价此外还有 PRICE_OPEN、PRICE_HIGH、PRICE_LOW、PRICE_MEDIAN 等。第七个 shift 是 K 线位置0 是当前坐标系和 3.1 一致。注意第五、第六个参数位置经常被写反。MODE_EMA 和 PRICE_CLOSE 一个是模式、一个是价格写反了编译不报错但计算出来的线完全不是预期中的均线回测时才发现数值对不上。这种错误在代码里非常隐蔽。ma_shift 参数大多数时候填 0它表示均线整体左右平移的柱数。需要提醒的是如果你在循环里从 i0 一直取值到 1000每次 iMA 调用都会重新计算一次均线性能开销比较大。对于已经算好的数组可以使用 iMAOnArray 来自定义数据源的移动平均值它参数更短适合做二次计算。3.4 iCustom 读取自定义指标参数的坑在看不见的地方当指标是别人写的、没有内置取数函数时用 iCustom 可以拿到自定义指标的指标线值。标准形式double val iCustom(NULL, 0, mymacd, 12, 26, 9, 2, 0);这段的意思是在当前图表当前周期加载自定义指标 mymacd把 12、26、9 三个参数传给它取它的第 3 条指标线编号从 0 开始所以 2 是第 3 条在最新位置的值。为什么说坑在看不见的地方——iCustom 的参数个数必须和自定义指标源码里的 extern 声明完全一致多一个少一个通常在编译阶段不报错运行日志里才有提示参数顺序也必须和源码声明顺序一致否则取到的是另一个参数组合算出的结果。我一般拿到陌生指标第一步先打开它的源码数清楚有几个 extern 变量按顺序把值填进 iCustom。如果指标有多根指标线编号规则是第一条为 0、第二条为 1最后一个 shift 参数仍然是 K 线位置。另外 iCustom 每次调用都会让 MT4 去重新计算那个自定义指标在 for 循环里频繁调用性能很差合理的做法是把 iCustom 的结果先存进数组再使用而不是每根 K 线都调好几遍。4. 自定义指标避坑四段结构、画线流程与四类报错4.1 先理解指标的四段结构与 #property 指令MT4 自定义指标文件由四部分组成文件头部的 #property 预处理指令、全局变量定义区、init() 初始化函数、start() 主函数另外还有一个可选的 deinit() 在指标卸载时执行。#property 指令不写分号它负责告诉编译器“这个指标要在哪个窗口显示、有几条线、什么颜色”。新手第一道坎就在这里常见组合如下#property indicator_chart_window // 显示在主图如均线类 #property indicator_separate_window // 显示在副图如 MACD、RSI 类 #property indicator_buffers 2 // 声明 2 条指标缓冲线 #property indicator_color1 Red // 第 1 条线红色 #property indicator_width1 1 // 第 1 条线粗细为 1 #property indicator_level1 0.0 // 在 0 值处画一条水平虚线第一个和第二个是一组互斥指令一个指标只能选其一。indicator_buffers 的数量必须不小于你实际绑定的指标线数量少写会导致高编号的线丢失。indicator_color1、indicator_color2 按编号对应第 1、2 条线indicator_level1 常用于 MACD 这类在 0 轴上下摆动的指标固定画一条零值参考线。这些指令全部放在文件最顶部位置靠后会影响编译阶段的缓冲区分配所以务必保持在第一条语句之前。4.2 init() 里绑定缓冲区start() 里逐根画线MACD 白线示例一个指标至少要有三件事一个存储数值的数组、一次缓冲区绑定、一段给数组赋值的循环。教程里那个只画 MACD 白线的示例很干净我把它完整贴出来逐段拆#property indicator_separate_window #property indicator_color1 White #property indicator_level1 0 extern int FMA 12; extern int SMA 26; double buf[]; int init() { SetIndexBuffer(0, buf); SetIndexStyle(0, DRAW_LINE); return(0); } int start() { int limit Bars - IndicatorCounted(); for(int i 0; i limit; i) { buf[i] iMA(NULL, 0, FMA, 0, MODE_EMA, PRICE_CLOSE, i) - iMA(NULL, 0, SMA, 0, MODE_EMA, PRICE_CLOSE, i); } return(0); }逻辑说明文件头部声明这是一条副图白线并在 0 位置画水平参考线。FMA 和 SMA 两个 extern 变量是快慢均线周期默认 12 和 26。double buf[] 是数据缓冲区它本身没有长度真正的容量在 SetIndexBuffer 完成时由 MT4 内部分配。init() 在指标加载时只运行一次SetIndexBuffer(0, buf) 把 buf 绑定为第 1 条指标线SetIndexStyle(0, DRAW_LINE) 把它画成连续曲线。start() 在有新数据变动时被触发关键在 limit Bars - IndicatorCounted()Bars 是当前图表总柱数IndicatorCounted() 返回已经计算过的柱数两者相减就是“还没算过的柱数”。循环从 i0 跑到 limit逐根取两条 iMA 在相同位置的差值写入 buf[i]已经算过的部分不再重复计算。这就是 MQL4 的增量计算机制指标加载在几千根 K 线的图表上也不会卡。循环部分如果习惯用 while 也是一回事int i 0; while(i limit) { buf[i] iMA(NULL, 0, FMA, 0, MODE_EMA, PRICE_CLOSE, i) - iMA(NULL, 0, SMA, 0, MODE_EMA, PRICE_CLOSE, i); i; }两者执行逻辑完全一样先判断条件成立就执行循环体再回到判断。for 只是把初始化变量、判断条件、递增语句压缩到一行。4.3 坑一报警条件永远不触发或每次都触发写价格穿越报警时条件判断看起来简单实际最容易翻车。看这段典型的上下穿 1.0000 报警逻辑int mark 0; if(High[1] 1.0000 High[0] 1.0000 mark ! 1) { Alert(Symbol(), 价格向上触及 1.0000); mark 1; } if(Low[1] 1.0000 Low[0] 1.0000 mark ! 2) { Alert(Symbol(), 价格向下触及 1.0000); mark 2; }现象报警条件写好后要么一根 K 线触发十几次要么一次都不触发。原因第一没有用 mark 标记去重上穿条件在同一根 K 线持续成立start() 每来一个报价都会 Alert 一次第二判断上穿用的是当前 K 线和前一根 K 线的最高价如果当前 K 线开盘价已经在 1.0000 上方High[0] 从一开始就大于等于 1.0000条件被误判成“刚刚上穿”。解决用 mark 记录上次报警类型0 表示从未报警1 表示已上穿报警2 表示已下穿报警。上穿后 mark 置 1只有 mark 不等于 1 时才允许再次触发同时把穿越判断写成 High[1] 1.0000 High[0] 1.0000用前一根 K 线的价格状态和当前 K 线做比较保证这是真正发生过的一次穿越而不是状态一直满足。这是我个人的调试经验先把 Alert 换成 Print 输出 mark 和两个 High 值看日志确认条件每一步都符合预期再加回 Alert。4.4 坑二数组越界导致指标线中断现象指标编译通过加载后前半段正常后半段空白日志窗口反复出现 array out of range。原因start() 里循环上限写成了 Bars而不是 Bars - IndicatorCounted()。当 MT4 给指标缓冲数组分配的容量小于你要访问的最大下标时就会越界。动态数组没分配容量直接写入也是同样的报错。解决循环上限一律用 Bars - IndicatorCounted()并且在计算前加一个空值保护int limit Bars - IndicatorCounted(); if(limit 0) return(0); for(int i 0; i limit; i) { // 计算逻辑 }如果你的数据不是指标缓冲区而是普通动态数组记得先用 ArrayResize 分配容量ArrayResize(tmp, Bars);。注意 ArrayResize 的第二个参数是元素个数不是字节数这里写成 Bars 就是和当前图表柱数一致。4.5 坑三iCustom 参数对不上取到错误数值却不报错现象用 iCustom 引用别人写好的指标图形加载正常但取到的指标值跟原指标显示的值对不上。原因参数顺序和源码 extern 声明顺序不一致或者指标线编号选错。自定义指标的 extern 参数顺序是硬规则iCustom 按位置传参跟参数名无关。解决打开自定义指标源码把 extern 声明顺序抄下来依次填进 iCustom。如果原指标有 3 个 extern 参数形如 extern int A12; extern int B26; extern int C9;那调用就是 iCustom(NULL,0,指标名,12,26,9,线编号,位置)。线编号从 0 开始第一根线是 0第二根是 1。这个错误最迷惑人的地方在于参数个数一致时编译器不报任何警告只有把取到的值和图上叠加的原指标对比才能发现。提示调用 iCustom 前先打开自定义指标源码数清 extern 个数并按顺序填参数这是减少排查时间最有效的一步。5. 把行情提醒和验证变成习惯SendMail 与数据窗口抽查5.1 用 SendMail 把行情推到手机一个很实用的落地小技巧利用 MT4 的邮件发送函数每间隔固定分钟数就把最新价格发到你的邮箱配合邮箱服务商的短信提醒功能手机就变成了行情接收机。完整指标如下#property indicator_chart_window extern int 间隔分钟 10; datetime NextTime 0; int start() { if(TimeLocal() NextTime) { SendMail(Symbol() DoubleToStr(Close[0], Digits), ); NextTime TimeLocal() 间隔分钟 * 60; } return(0); }逻辑说明这个指标加载到主图后每次报价到来都会触发 start()。TimeLocal() 返回本机当前时间NextTime 是预定的下次发送时间初始为 0所以第一次触发立即发送。发送完成后把 NextTime 更新为当前时间加上间隔分钟乘以 60 秒。datetime 类型在 MQL4 里本质是自 1970 年起的秒数所以分钟要乘 60。SendMail 的第一个参数是邮件主题第二个参数是正文这里我把品种和最新价格都拼进主题里正文留空手机短信一般直接展示主题内容一眼就能看到行情。把 间隔分钟 改成 30就是半小时一封。这里有一个亲身踩过的坑MT4 的邮件功能需要在工具选项里预先配置好发件邮箱、收件邮箱和 SMTP 服务器否则 SendMail 什么也不做。我第一次测试时日志里全是错误一度以为是代码问题后来发现是 SMTP 连接没有建立重启 MT4 再测一次才收到测试邮件。所以写完这个指标第一件事是先在工具选项里发一封测试邮件确认通道通了再挂到图表上。5.2 用数据窗口抽查指标线数值写完指标不要急着上实盘验证数值比写代码更要紧。MT4 图表顶部工具栏有数据窗口开关打开后鼠标移到任意一根 K 线上就能看到该 K 线位置指标线的具体数值。我每次都在最早的几根 K 线、中间某一根、最新一根分别读数和手工计算或另一周期的同指标做个交叉对比数值连续没有跳变才敢说这条线画对了。从那以后我每次把指标挂到图表上都强制走一遍这个流程先看编译警告再核对 #property 窗口属性最后用数据窗口抽查三根 K 线的值。这套流程挡住过不少低级错误也让我后来写 EA 时少走很多弯路。希望帮到你。本文还有配套的精品资源点击获取