MQL4自定义指标编写入门实战
从指标属性到iCustom信号指标的完整路径
指标究竟是什么:从特征到参数
在MetaTrader 4中,指标是依附于价格图表或独立子窗口的技术分析工具。很多交易者只会加载现成指标,却不懂其背后结构。要编写自定义指标,必须先理解指标的“特征”与“参数”分类。特征指终端如何绘制与摆放指标,参数则是用户可调整的输入变量。例如移动平均线在图表窗口绘制、仅显示一个值、数值范围无限且依赖价格;威廉指标在子窗口绘制、数值严格限制在0到-100。这种差异决定了指标应置于何处。
当指标值范围与活动图表价格范围大致相同,例如各类移动平均线,就绘制在主图;若值超出价格范围(如负数或固定区间),终端会自动将其放入单独子窗口,否则线会被压得看不见。子窗口指标还可选择是否限制范围:限制后比例固定,超范围值不可见;不限制则终端自动缩放包含全部值。此外,线宽大于1时只能用实线,这是绘制上的硬限制。
- 绘制位置:图表窗口或单独子窗口
- 数值范围:无限或与价格相近,或严格限制
- 显示方式:线条、直方图、符号、箭头
- 颜色、样式、宽度各自独立设置
- 一个或多个数据缓冲区,最多8个
数据缓冲区:指标的心脏
数据缓冲区(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 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集成。遇到难点重读或去论坛提问。
- 用英语命名,避免拼音
- 缩进体现代码块层级
- 对函数参数与用途写注释
- 循环条件复用标准优化模板
- 箭头缓冲区每区仅一种符号