MQL4自定义指标编写入门实战
📊

MQL4自定义指标编写入门实战

从指标属性到iCustom信号指标的完整路径

技术分析基础 难度 · 入门 2026-05-21 7 分钟阅读
#MQL4#自定义指标#缓冲区#iCustom#MetaEditor

指标究竟是什么:从特征到参数

在MetaTrader 4中,指标是依附于价格图表或独立子窗口的技术分析工具。很多交易者只会加载现成指标,却不懂其背后结构。要编写自定义指标,必须先理解指标的“特征”与“参数”分类。特征指终端如何绘制与摆放指标,参数则是用户可调整的输入变量。例如移动平均线在图表窗口绘制、仅显示一个值、数值范围无限且依赖价格;威廉指标在子窗口绘制、数值严格限制在0到-100。这种差异决定了指标应置于何处。

当指标值范围与活动图表价格范围大致相同,例如各类移动平均线,就绘制在主图;若值超出价格范围(如负数或固定区间),终端会自动将其放入单独子窗口,否则线会被压得看不见。子窗口指标还可选择是否限制范围:限制后比例固定,超范围值不可见;不限制则终端自动缩放包含全部值。此外,线宽大于1时只能用实线,这是绘制上的硬限制。

✦ 理解缓冲区的意义
任何指标显示数据都依赖数据缓冲区,本质是由终端管理的轮转数组,每个元素对应一个柱。缓冲区可多于可见线,多余的可做中间计算。这是MQL4指标最核心的抽象。

数据缓冲区:指标的心脏

数据缓冲区(Data Buffer)是MQL4指标存储计算结果的数组。终端会在新柱到来时轮转数组,保证索引i始终对应特定柱。一个指标最多8个缓冲区,例如鳄鱼指标用3个缓冲区分别画三条平衡线,各自有颜色宽度样式。并非每个缓冲区都要绘制,很多用于中间运算,但每个缓冲区元素必须和图表柱对应,这是后续循环写值的根基。

缓冲区的编号在指令(#property)中从1开始,在函数(SetIndexBuffer等)中从0开始,这个差异极易引发bug。例如#property indicator_color1设置1号缓冲区颜色,而SetIndexBuffer(0,...)绑定0号数组。初学者务必区分上下文。缓冲区类型可为线条、直方图、箭头等,箭头缓冲区每缓冲区只能显示一种符号。

用向导创建你的第一个指标

启动MetaEditor,文件->新建,选自定义指标。填写名称、作者、链接,可添加参数(extern变量)。例如添加barsToProcess=100,控制计算柱数,避免无谓消耗资源。接着设置绘制位置(单独窗口或主图)、是否限范围、添加数据缓冲区数量与类型。完成后MetaEditor生成框架代码,文件存于experts/indicators。

生成代码包含#property指令与三个函数:init()启动时一次,deinit()卸载时一次,start()每次报价触发。#property copyright/link写入元信息;indicator_separate_window控制子窗口;indicator_buffers声明缓冲区数;indicator_color1设颜色。extern int barsToProcess=100;即用户参数。

#property copyright "Antonuk Oleg"
#property link      "banderass@i.ua"
#property indicator_separate_window
#property indicator_buffers 1
#property indicator_color1 DarkOrchid
extern int       barsToProcess=100;
double ExtMapBuffer1[];
int init()
{
   SetIndexStyle(0,DRAW_LINE);
   SetIndexBuffer(0,ExtMapBuffer1);
   return(0);
}
int deinit()
{
   return(0);
}
int start()
{
   int counted_bars=IndicatorCounted();
   return(0);
}

编写随机指标并理解柱对应关系

在init()中加入MathSrand(TimeLocal())初始化随机发生器。start()中用for(int i=0;i<Bars;i++)循环,向ExtMapBuffer1[i]赋MathRand()%1001。此时每个缓冲区元素i对应第i根柱(0为最后一根)。若改为ExtMapBuffer1[i]=i,可见柱编号从0递增,直观验证映射关系。

未优化版本每次报价重算所有柱,指标图疯狂跳动且浪费CPU。优化关键是用IndicatorCounted()获取已计算且未变柱数,limit=Bars-counted_bars,循环仅算新柱。若counted_bars>0则减1,防最后一柱漏算。再加入if(limit>barsToProcess) limit=barsToProcess;即可限制计算量。

int start()
{
   int counted_bars=IndicatorCounted(),
       limit;
   if(counted_bars>0) counted_bars--;
   limit=Bars-counted_bars;
   if(limit>barsToProcess) limit=barsToProcess;
   for(int i=0;i<limit;i++)
   {
      ExtMapBuffer1[i]=MathRand()%1001;
   }
   return(0);
}
⚠ 常见坑:缓冲区编号起点
#property中缓冲区编号从1起,SetIndex系列函数从0起。写错会导致颜色或绑定错位,且编译不报错,难以排查。

水平线与其他绘制属性

用#property indicator_level1 800.0与indicator_level2 200.0添加水平线,最多8条。levelcolor、levelwidth(1-5)、levelstyle(STYLE_SOLID等)统一作用于所有水平线,无法单独设样式。若需差异化,须用图形对象而非水平线。水平线常用于界定超买超卖区。

#property indicator_level1 800.0
#property indicator_level2 200.0
#property indicator_levelcolor LimeGreen
#property indicator_levelwidth 2
#property indicator_levelstyle STYLE_SOLID

iCustom:调用任意自定义指标

iCustom(symbol,timeframe,name,...,mode,shift)获取编译好的ex4自定义指标值。symbol用NULL表当前品种,timeframe用0或PERIOD_*常量,name不含.ex4,参数按声明顺序传,mode为缓冲区编号(从0起),shift为柱偏移。例如iCustom(NULL,0,"RandomIndicator",barsToProcess,0,i)取随机指标第i柱值。

double iCustom( string symbol, int timeframe, string name, ..., int mode, int shift);
// example
double signalLast=iCustom("EURUSD",PERIOD_D1,"MACD",12,26,9,1,0);

构建信号指标:箭头与空值

新建指标,加两个箭头缓冲区(DRAW_ARROW),用SetIndexArrow设符号代码(如236买、238卖),SetIndexEmptyValue设0.0为空。start()中循环取随机指标值,>800在High[i]+5*Point写买箭头,<200在Low[i]-5*Point写卖箭头,否则写0隐藏。这样把难盯的信号变成直观箭头。

int init()
{
   SetIndexStyle(0,DRAW_ARROW);
   SetIndexArrow(0,236);
   SetIndexBuffer(0,ExtMapBuffer1);
   SetIndexEmptyValue(0,0.0);
   SetIndexStyle(1,DRAW_ARROW);
   SetIndexArrow(1,238);
   SetIndexBuffer(1,ExtMapBuffer2);
   SetIndexEmptyValue(1,0.0);
   return(0);
}
int start()
{
   int counted_bars=IndicatorCounted(), limit;
   if(counted_bars>0) counted_bars--;
   limit=Bars-counted_bars;
   if(limit>barsToProcess) limit=barsToProcess;
   for(int i=0;i<limit;i++)
   {
      double randomValue=iCustom(NULL,0,"RandomIndicator",barsToProcess,0,i);
      if(randomValue>800.0) ExtMapBuffer1[i]=High[i]+5*Point;
      else ExtMapBuffer1[i]=0.0;
      if(randomValue<200.0) ExtMapBuffer2[i]=Low[i]-5*Point;
      else ExtMapBuffer2[i]=0.0;
   }
   return(0);
}

编程风格与后续建议

代码风格影响维护效率:一行不堆多语句;变量名用英文、大写分隔;少缩写;统一缩进;同类型变量分组;长块加注释。本文指标虽无交易效用,但打通了属性、缓冲区、优化、iCustom与信号化全套逻辑。下一步可学研究对象绘制与复杂EA集成。遇到难点重读或去论坛提问。

常见问题

#property指令(如indicator_color1)中缓冲区编号从1开始;SetIndexBuffer、SetIndexStyle等函数中从0开始。编写时须按上下文区分,否则颜色绑定错位且编译无报错。
在向导添加extern int barsToProcess=100;参数,在start()优化循环前写 if(limit>barsToProcess) limit=barsToProcess; 其中limit=Bars-IndicatorCounted(); 即可只算最近N柱。
使用 iCustom(NULL,0,"RandomIndicator",barsToProcess,0,i) 获取当前品种当前周期下该指标第i柱(shift=i)的0号缓冲区值,名称不带.ex4扩展名。
不能。#property indicator_levelstyle/levelcolor/levelwidth统一作用于所有水平线。若需单独样式,应放弃水平线改用OBJ_HLINE等图形对象。
因start()未用IndicatorCounted()优化,每次报价重算全部Bars。应设limit=Bars-IndicatorCounted(),循环仅处理未计算柱,并将counted_bars>0时减1防漏算。