引言
每个使用指标的交易者迟早都会提出这样的要求:"还是这个指标,但要能自己交易。"程序员的回答听起来很简单:用 iCustom() 或 IndicatorCreate() 创建指标,用 CopyBuffer() 读取它的数值,信号出现时开仓。实际上,第一个问题根本与交易无关,而是:信号在哪个缓冲区里?
箭头指标可能用一个缓冲区存放买入箭头、另一个存放卖出箭头,也可能用一个缓冲区同时存放两者,再在旁边配一个颜色索引。"趋势"指标可能只画一条会变色的线,而颜色保存在一个从不绘制的单独缓冲区中。在 Market 上购买的已编译指标不附带源代码,因此无法查看其中的 SetIndexBuffer() 调用。数据窗口(Data Window)能帮上一点忙,但它每次只显示一根 K 线上被绘制的数值,颜色缓冲区和计算缓冲区则完全隐藏。
本文介绍如何从外部为任意指标回答这个问题:缓冲区是如何存储的,如何从另一个程序读取全部缓冲区,如何仅凭数值区分箭头缓冲区、线和颜色索引,如何检查信号是否重绘,以及如何把答案标回图表,让用户看到哪个编号对应哪个箭头。成果是三个附带源代码的小程序:一个检查图表上每个指标的脚本(在代码库 Code Base 中免费提供),一个包含所有类型缓冲区的演示指标,以及一个按四条通用规则交易任意指标的智能交易系统(EA);后两者的完整代码附在文末。
1. 指标缓冲区究竟是什么
指标把输出保存在用 SetIndexBuffer() 绑定的数组中。缓冲区共有三种:
- INDICATOR_DATA:被绘制(线、柱状图、箭头)并显示在数据窗口中的数值;
- INDICATOR_COLOR_INDEX:用于 DRAW_COLOR_* 样式,表示每根 K 线所用颜色的编号;
- INDICATOR_CALCULATIONS:既不绘制也不显示的中间数组。
这三种缓冲区按指标声明它们的顺序统一编号,并且都可以用 CopyBuffer() 读取。这是让许多程序员感到意外的第一点:在图表上从来看不到的缓冲区依然可以读取,而对于彩色指标,它往往恰恰是最有用的那一个。
绘图(plot)和缓冲区不是一回事。一个 DRAW_COLOR_LINE 绘图使用两个缓冲区(数值和颜色索引),DRAW_COLOR_CANDLES 使用五个(四个价格和颜色索引),DRAW_COLOR_ARROW 使用两个(箭头的价格及其颜色)。标准安装包中的 Heiken Ashi 指标就是一个很好的例子:缓冲区 0 到 3 是 K 线的价格,缓冲区 4 是颜色索引,阳线为 0,阴线为 1。
第二点是"没有值"如何存储。线缓冲区通常在每根 K 线上都有数值。箭头缓冲区只在有箭头的 K 线上存放价格,其余位置是"空"值。哪个值代表空由作者决定:通常的选择是 EMPTY_VALUE(DBL_MAX),通过 PlotIndexSetDouble(PLOT_EMPTY_VALUE) 设置,但许多指标使用 0.0。如果读取方把 0.0 当作有效的箭头价格,就会在每根 K 线上都看到箭头。
为了有一个具体的观察对象,DemoBuffers.mq5 指标(源代码见文末)声明了所有类型的缓冲区:以线绘制的简单移动平均线、收盘价向上穿越该均线处的箭头、以彩色线绘制的指数移动平均线,以及作为计算缓冲区的该均线斜率。
SetIndexBuffer(0,ExtSma,INDICATOR_DATA); // line SetIndexBuffer(1,ExtCross,INDICATOR_DATA); // arrows, EMPTY_VALUE where there is none SetIndexBuffer(2,ExtEma,INDICATOR_DATA); // colour line: the value SetIndexBuffer(3,ExtEmaColor,INDICATOR_COLOR_INDEX); // colour line: 0 rising, 1 falling SetIndexBuffer(4,ExtSlope,INDICATOR_CALCULATIONS); // not drawn, not in the Data Window PlotIndexSetInteger(1,PLOT_ARROW,233); PlotIndexSetDouble(1,PLOT_EMPTY_VALUE,EMPTY_VALUE);
数据窗口为它显示三个数值:均线、箭头和指数均线。缓冲区 3 和 4 在那里看不到,然而线的颜色——这个指标最自然的"趋势"信号——恰恰保存在缓冲区 3 中。图1 显示了程序读取全部五个缓冲区时看到的内容。

图1. EURUSD H1 上的 DemoBuffers:由 Buffer Inspector Kb 脚本读出的两条线、一个箭头缓冲区、一个颜色索引和一个计算缓冲区。
2. 读取指标的全部缓冲区
对于已经加载到图表上的指标,无需知道其输入参数就能获得句柄:
for(int w=0; w<(int)ChartGetInteger(0,CHART_WINDOWS_TOTAL); w++)
for(int i=0; i<ChartIndicatorsTotal(0,w); i++)
{
string name=ChartIndicatorName(0,w,i);
int h=ChartIndicatorGet(0,w,name); // the same instance the user sees
...
IndicatorRelease(h); // releases our handle, not the indicator
} 这一点比看上去更重要:用户已经在图表上选好了指标的输入参数,而程序读取的正是这个实例。对于不在图表上的指标,可以使用带 IND_CUSTOM 的 IndicatorCreate(),它接受相对于 MQL5\Indicators 的名称和一个 MqlParam 数组。如果输入参数来自文本字段,例如 "14,0,1.5,true",就必须为每一项指定正确的类型:
int CreateByName(const string name,const string params)
{
MqlParam p[];
ArrayResize(p,1);
p[0].type=TYPE_STRING; // the first element is always the name
p[0].string_value=name;
string parts[];
const int n=(StringLen(params)>0) ? StringSplit(params,',',parts) : 0;
ArrayResize(p,n+1);
for(int i=0; i<n; i++)
{
string s=parts[i];
StringTrimLeft(s);
StringTrimRight(s);
if(s=="true" || s=="false")
{ p[i+1].type=TYPE_BOOL; p[i+1].integer_value=(s=="true"); }
else if(StringFind(s,".")>=0 && MathIsValidNumber(StringToDouble(s)))
{ p[i+1].type=TYPE_DOUBLE; p[i+1].double_value=StringToDouble(s); }
else if(s=="0" || StringToInteger(s)!=0)
{ p[i+1].type=TYPE_INT; p[i+1].integer_value=StringToInteger(s); }
else
{ p[i+1].type=TYPE_STRING; p[i+1].string_value=s; }
}
return(IndicatorCreate(_Symbol,_Period,IND_CUSTOM,ArraySize(p),p));
} 使用这个函数时,记住三条规则能省下大量时间。数值必须遵循指标输入参数窗口中的顺序,而且不能跳过任何参数:要设置第三个参数,前两个也必须给出。枚举按其数值传递,因此 MODE_EMA 是 1,PRICE_CLOSE 也是 1。未给出的参数保持默认值,因此空字符串会完全按原样创建指标。
一共有多少个缓冲区?句柄不会告诉您,但 CopyBuffer() 会:对于不存在的缓冲区编号,它返回 -1。依次读取缓冲区 0、1、2……直到第一次失败,就能得到缓冲区总数,数据、颜色和计算缓冲区都包括在内。
两个实用细节:
- 句柄创建后,指标需要时间完成计算;在计算完成之前,BarsCalculated(handle) 返回 -1,因此脚本用一个带 Sleep() 的短循环等待,而 EA 只需在下一个报价时重试;
- 从 K 线 1,即最后一根已收盘的 K 线开始复制。许多箭头指标在正在形成的 K 线上的值会一直变化,直到该 K 线收盘;基于它的分析会显示出之后消失的箭头。
3. 区分箭头、线和颜色索引
把缓冲区在 N 根已收盘 K 线上的数值读入数组后,三个简单的统计量就足够了:
- 有效值:既不是 EMPTY_VALUE 也不是 NaN 的数值;
- 箭头:有效且不为 0.0 的数值;
- 小整数:绝对值较小的整数值。
由此即可得出分类:
| 缓冲区存放的内容 | 在 500 根 K 线上的表现 |
|---|---|
| 无内容 | 没有有效值,或者全是零 |
| 箭头 | 有一些数值,但只出现在不超过 30% 的 K 线上 |
| 颜色索引 | 只有窄范围内的小整数(例如 0 和 1) |
| 线 | 其他所有情况:(几乎)每根 K 线上都有数值 |
int valid=0, arrows=0, ints=0;
double lo=DBL_MAX, hi=-DBL_MAX;
for(int i=0; i<n; i++)
{
if(v[i]==EMPTY_VALUE || !MathIsValidNumber(v[i])) continue;
valid++; lo=MathMin(lo,v[i]); hi=MathMax(hi,v[i]);
if(v[i]!=0.0) arrows++;
if(MathAbs(v[i]-MathRound(v[i]))<1e-9 && MathAbs(v[i])<64) ints++;
}
if(valid==0 || (arrows==0 && hi==0.0 && lo==0.0)) kind="empty";
else if(arrows>0 && arrows<=n*0.3) kind="arrows";
else if(ints==valid && hi-lo<=16) kind="colour index";
else kind="line"; 为什么是 30%?箭头指标标记的是转折点,而转折点本来就少:Fractals 只标记百分之几的 K 线,典型的"信号"指标不到 10%。在部分 K 线上为空的线(例如历史开头处的移动平均线)在大多数 K 线上仍然有数值。这个阈值是有意放宽的;接近阈值的情况会报告给用户,而不是悄悄地猜测。
为什么用"窄范围内的小整数"来识别颜色?颜色索引的取值为 0、1、2 等,每个值对应绘图的一种颜色。线也可能是整数(被作者取整的振荡指标、阶梯函数),但它的变化范围要比一个绘图可能拥有的寥寥几种颜色宽得多。
图2 显示了 Heiken Ashi 的结果:四条价格线,以及缓冲区 4 中取值为 0 和 1 的颜色索引——这正是 EA 对 K 线颜色变化作出反应所需要的。

图2. Heiken Ashi:四条价格线,以及缓冲区 4 中数据窗口不显示的颜色索引。
有几种情况值得单独说明,因为匆匆一看往往会在这些地方得出错误的答案:
- 用 0.0 而不是 EMPTY_VALUE 存储的箭头。上面的分类已经把 0.0 视为"没有箭头",但只与 EMPTY_VALUE 比较的程序会在每根 K 线上触发。务必同时检查两者。
- 每根 K 线上都有点,例如 Parabolic SAR。它们在图表上看起来像箭头,但缓冲区在每根 K 线上都有数值,因此它是一条线;信号是点跳到价格另一侧的那一刻,而不是点的存在。
- 彩色箭头(DRAW_COLOR_ARROW)。箭头缓冲区给出 K 线和价格,旁边的颜色索引说明它是买入箭头还是卖出箭头。一个信号需要同时用到这两个缓冲区。
- 计算缓冲区。它们通常被归类为线,数值往往与价格相差很远。它们值得一看:有些指标把平滑或过滤之前的"原始"信号保存在那里。
4. 向用户显示哪个箭头对应哪个缓冲区
一张写着"缓冲区 1:箭头,37 个,最近一个在 2026.10.07 08:00"的表格是正确的,但交易者仍然需要把日期与图表对照,才能看出缓冲区 1 是向上箭头还是向下箭头。把缓冲区编号直接写在箭头旁边要快得多:
for(int i=n-1, done=0; i>=0 && done<20; i--)
{
if(v[i]==EMPTY_VALUE || v[i]==0.0) continue;
int shift=n-i; // v[] runs from shift n (oldest) to shift 1
datetime t=iTime(_Symbol,_Period,shift);
string obj=StringFormat("BufInspector_%d_%d",buffer,shift);
ObjectCreate(0,obj,OBJ_TEXT,0,t,v[i]); // at the arrow's own price
ObjectSetString(0,obj,OBJPROP_TEXT,"B"+(string)buffer);
done++;
} 标签放在缓冲区中保存的数值处,对箭头指标而言就是绘制箭头的价格,因此 "B0" 和 "B1" 正好出现在箭头上。每个缓冲区使用一种颜色,两个箭头缓冲区就很容易区分。

图3. Examples\Fractals:B0 标在上分形上方,B1 标在下分形下方。
5. 指标会重绘吗?
存放箭头的缓冲区只是答案的一半。另一半是:箭头一旦出现在已收盘的 K 线上,是否会一直留在那里。有些指标在新 K 线到来时会移动或删除箭头——Fractals 按设计会对最后两根 K 线这样做,ZigZag 类指标涉及的范围则长得多。EA 如果按一个后来消失的箭头交易,交易的就是一个事后看来从未存在过的信号,它在策略测试器中的结果也不会与图表相符。
这项检查很容易自动化:按开盘时间记住最后几根已收盘 K 线的数值,等待几根新 K 线,再次读取同样的 K 线并进行比较。
datetime ExtTimes[]; // open times of the remembered bars
double ExtValues[]; // buffer values on those bars
//--- remember the last 100 closed bars of buffer b
int Remember(const int handle,const int b)
{
if(CopyTime(_Symbol,_Period,1,100,ExtTimes)!=100) return(0);
return(CopyBuffer(handle,b,1,100,ExtValues));
}
//--- later: how many of the remembered bars changed their value?
int Changed(const int handle,const int b)
{
int changed=0;
for(int i=0; i<ArraySize(ExtTimes); i++)
{
double v[];
if(CopyBuffer(handle,b,ExtTimes[i],1,v)==1 && v[0]!=ExtValues[i])
changed++;
}
return(changed);
} 这里关键是按时间而不是按索引读取:新 K 线到来后,同一根 K 线的索引会变大。结果表明指标在往回多少根 K 线的范围内仍会改变主意,这个数字决定了应使用哪根信号 K 线。对于 Fractals,答案总是两根 K 线,这就是为什么要在 K 线 3 而不是 K 线 1 上读取分形。一个在十根 K 线之前仍会修改箭头的指标,不能在箭头首次出现的那根 K 线上交易,无论它在策略测试器中的图形看起来如何。
6. 没有可读取的缓冲区时
有些指标用 ObjectCreate() 而不是缓冲区来绘制箭头。箭头在图表上,数据窗口却是空的,CopyBuffer() 只返回线,或者什么也不返回。任何程序都无法通过指标句柄读取这类信号。值得直截了当地告诉用户这一点,否则寻找"正确缓冲区编号"的过程永远不会结束。脚本可以间接地发现这种情况:如果用户能看到箭头,指标却没有箭头缓冲区,那么它几乎肯定是在绘制对象。Buffer Inspector Kb 会在报告中指出这一点。
7. 从缓冲区到交易规则
一旦知道了缓冲区,四条规则就能覆盖交易者带来的几乎所有指标:
| 规则 | 典型指标 | 信号 K 线上的条件 |
|---|---|---|
| 出现箭头 | Fractals、箭头指标 | 数值不为空且不为 0 |
| 缓冲区穿越某一水平 | RSI 30/70、CCI +-100、围绕 0 波动的振荡指标 | 前值 < 水平 <= 当前值 |
| 一个缓冲区穿越另一个 | MACD 主线/信号线、Stochastic %K/%D、Alligator 的各条线 | 前一根 a < b 且当前 a >= b |
| 颜色索引变为某个值 | Heiken Ashi、彩色均线、"趋势"柱 | 前值 != k 且当前值 == k |
BufferSignalEA.mq5(源代码见文末)实现的正是这些规则,指标名称、指标的输入参数、规则和缓冲区编号都作为输入参数,因此同一个 EA 可以指向任意指标。它的核心是一个函数,读取信号 K 线及其前一根 K 线上的两个数值:
bool RuleFires(const ENUM_SIGNAL_RULE rule,const int buffer,const double level,const int buffer2)
{
if(rule==SIGNAL_OFF)
return(false);
double a[],b[];
//--- a[0] = the bar before the signal bar, a[1] = the signal bar
if(CopyBuffer(ExtHandle,buffer,InpSignalBar,2,a)!=2)
return(false);
switch(rule)
{
case SIGNAL_ARROW:
return(!IsEmpty(a[1]) && a[1]!=0.0);
case SIGNAL_CROSS_UP:
return(!IsEmpty(a[0]) && !IsEmpty(a[1]) && a[0]<level && a[1]>=level);
case SIGNAL_CROSS_DOWN:
return(!IsEmpty(a[0]) && !IsEmpty(a[1]) && a[0]>level && a[1]<=level);
case SIGNAL_BECOMES:
return(!IsEmpty(a[1]) && MathAbs(a[1]-level)<0.5 && (IsEmpty(a[0]) || MathAbs(a[0]-level)>=0.5));
case SIGNAL_LINE_UP:
case SIGNAL_LINE_DOWN:
{
if(CopyBuffer(ExtHandle,buffer2,InpSignalBar,2,b)!=2)
return(false);
if(IsEmpty(a[0]) || IsEmpty(a[1]) || IsEmpty(b[0]) || IsEmpty(b[1]))
return(false);
if(rule==SIGNAL_LINE_UP)
return(a[0]<b[0] && a[1]>=b[1]);
return(a[0]>b[0] && a[1]<=b[1]);
}
}
return(false);
} 颜色索引以 0.5 的容差进行比较,因为它是以 double 类型存储的。决策在每根新 K 线上只做一次,并且在 OnTick() 中先于其他所有操作:
const datetime bar=iTime(_Symbol,_Period,0); if(bar==ExtLastBar) return; if(BarsCalculated(ExtHandle)<InpSignalBar+2) return; // the indicator is not ready yet: try on the next tick ExtLastBar=bar; const bool buy =RuleFires(InpBuyRule, InpBuyBuffer, InpBuyLevel, InpBuyBuffer2); const bool sell=RuleFires(InpSellRule,InpSellBuffer,InpSellLevel,InpSellBuffer2); if(buy==sell || HasPosition()) return; // no signal, both at once, or already in the market
如果在正在形成的 K 线的每个报价上评估规则,它会多次触发,而且会对随后消失的信号触发;在已收盘的 K 线上每根 K 线只决策一次,能让策略测试器与实时图表保持一致。
为了检验每条规则确实会触发,我们在策略测试器中用五个指标运行了这个 EA,所有指标使用相同的简单出场方式(0.1 手,止损 300 点,止盈 600 点,同一时间只持有一个仓位)。下面的数字是对规则的功能测试,而不是交易成绩:不加任何过滤、没有仓位管理地按指标交易,并不是一个策略。
| 指标与规则 | 缓冲区 | 交易数 | 盈利交易 |
|---|---|---|---|
| Fractals,出现箭头(信号 K 线 3) | 买入 1,卖出 0 | 184 | 53 |
| MACD,主线穿越信号线 | 0 和 1 | 160 | 46 |
| RSI(14),向上穿越 30 / 向下穿越 70 | 0 | 113 | 36 |
| Heiken Ashi,颜色变为 0 / 1 | 4 | 170 | 53 |
| DemoBuffers,箭头买入,颜色 1 卖出 | 1 和 3 | 152 | 39 |
EURUSD H1,2025.10.01 - 2026.10.01,模型为"1 minute OHLC"。五次运行中没有出现一次失败的请求;交易数的差异来自每个指标在没有持仓时给出信号的频率。
8. 读取其他周期上的指标
同样的读取方法也适用于更高周期上的指标,大多数过滤条件正是这样构建的("仅当价格位于 H4 的 200 EMA 之上时才买入")。创建句柄时指定周期,该句柄的 K 线 1 就是该周期最后一根已收盘的 K 线:
int h4=iMA(_Symbol,PERIOD_H4,200,0,MODE_EMA,PRICE_CLOSE); ... double ma[]; if(CopyBuffer(h4,0,1,1,ma)==1 && iClose(_Symbol,PERIOD_H4,1)>ma[0]) allowBuy=true;
有两点需要说明。第一,程序启动时其他周期的数据可能尚未就绪:在终端构建好历史数据之前,BarsCalculated() 返回 -1,CopyBuffer() 失败,因此需要重复检查,而不能当作"没有信号"。第二,在较低周期上做决策时,切勿读取较高周期的 K 线 0。在 H1 图表上,正在形成的 H4 K 线每小时都在变化,因此读取它的过滤条件在测试器中会显得完美(测试器已经知道这根 H4 K 线如何收尾),而在实时图表上的表现却会不同。
9. 程序
- Buffer Inspector Kb(脚本,MQL5\Scripts,在代码库中免费提供):把它拖到已经加载了该指标的图表上。它会检查每个窗口中的每个指标,把表格输出到"专家"日志,用 Comment() 显示在图表上,并为每个箭头缓冲区最近的几个箭头加上标签。如果在输入参数中填写指标名称,它会自行创建该指标,这对来自 Market 的已编译指标很有用。启用 "Only remove the labels" 运行它即可清理图表。
- DemoBuffers.mq5(指标,MQL5\Indicators):一个包含线、箭头、带颜色索引的彩色线和计算缓冲区的指标,供实验使用。源代码见下文。
- BufferSignalEA.mq5(EA,MQL5\Experts):按上述四条规则交易任意指标;指标、指标的输入参数、规则和缓冲区编号都是输入参数。源代码见下文。
结论
指标的缓冲区就是它的公共接口,即使没有源代码也是如此。读取全部缓冲区、按数值对它们分类、在图表上标注箭头,并检查数值在往回多远的范围内仍会变化,就能把"我该用哪个缓冲区编号?"从一个要问指标作者的问题,变成一次十秒钟的检查。同样的分类也是四条通用交易规则——箭头、水平、线交叉和颜色变化——的基础,它们让同一个 EA 能够在已收盘的 K 线上使用它从未见过的指标,从而使策略测试器显示的结果就是实时图表上将发生的情况。
源代码
将每个代码清单以其名称保存到所示的文件夹中(在终端中选择"文件 → 打开数据文件夹"),然后在 MetaEditor 中按 F7 编译。
DemoBuffers.mq5 (MQL5\Indicators)
//+------------------------------------------------------------------+ //| DemoBuffers.mq5 | //| Bohdan Klok | //| https://www.mql5.com/en/users/kbposht | //+------------------------------------------------------------------+ #property copyright "Bohdan Klok" #property link "https://www.mql5.com/en/users/kbposht" #property version "1.00" #property description "Example: one indicator with every kind of buffer - a line, arrows," #property description "a colour line with its colour index, and a calculation buffer that is not drawn." #property indicator_chart_window #property indicator_buffers 5 #property indicator_plots 3 //--- plot 0: a plain line (buffer 0) #property indicator_label1 "SMA" #property indicator_type1 DRAW_LINE #property indicator_color1 clrSilver #property indicator_width1 1 //--- plot 1: arrows (buffer 1), drawn only where the close crosses the SMA upward #property indicator_label2 "Cross up" #property indicator_type2 DRAW_ARROW #property indicator_color2 clrDodgerBlue #property indicator_width2 2 //--- plot 2: a colour line (buffer 2 = value, buffer 3 = colour index) #property indicator_label3 "Trend EMA" #property indicator_type3 DRAW_COLOR_LINE #property indicator_color3 clrSeaGreen,clrOrangeRed #property indicator_width3 2 //--- input parameters input int InpSmaPeriod=20; // SMA period input int InpEmaPeriod=50; // EMA period //--- indicator buffers double ExtSma[]; // 0: line double ExtCross[]; // 1: arrows, EMPTY_VALUE where there is none double ExtEma[]; // 2: colour line value double ExtEmaColor[]; // 3: colour index, 0 rising, 1 falling double ExtSlope[]; // 4: calculations, EMA change over one bar //+------------------------------------------------------------------+ //| Custom indicator initialization function | //+------------------------------------------------------------------+ int OnInit() { SetIndexBuffer(0,ExtSma,INDICATOR_DATA); SetIndexBuffer(1,ExtCross,INDICATOR_DATA); SetIndexBuffer(2,ExtEma,INDICATOR_DATA); SetIndexBuffer(3,ExtEmaColor,INDICATOR_COLOR_INDEX); SetIndexBuffer(4,ExtSlope,INDICATOR_CALCULATIONS); PlotIndexSetInteger(1,PLOT_ARROW,233); PlotIndexSetDouble(1,PLOT_EMPTY_VALUE,EMPTY_VALUE); PlotIndexSetInteger(0,PLOT_DRAW_BEGIN,InpSmaPeriod); PlotIndexSetInteger(2,PLOT_DRAW_BEGIN,InpEmaPeriod); IndicatorSetString(INDICATOR_SHORTNAME,StringFormat("DemoBuffers(%d,%d)",InpSmaPeriod,InpEmaPeriod)); return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Custom indicator iteration function | //+------------------------------------------------------------------+ int OnCalculate(const int rates_total, const int prev_calculated, const datetime &time[], const double &open[], const double &high[], const double &low[], const double &close[], const long &tick_volume[], const long &volume[], const int &spread[]) { if(rates_total<InpEmaPeriod+2) return(0); const double k=2.0/(InpEmaPeriod+1.0); int start=(prev_calculated>1) ? prev_calculated-1 : 1; if(prev_calculated==0) { ExtEma[0]=close[0]; ExtSlope[0]=0.0; ExtEmaColor[0]=0.0; } for(int i=start; i<rates_total; i++) { //--- SMA, empty until there are enough bars if(i>=InpSmaPeriod-1) { double sum=0.0; for(int j=0; j<InpSmaPeriod; j++) sum+=close[i-j]; ExtSma[i]=sum/InpSmaPeriod; } else ExtSma[i]=EMPTY_VALUE; //--- EMA, its slope and the colour of the slope ExtEma[i]=ExtEma[i-1]+k*(close[i]-ExtEma[i-1]); ExtSlope[i]=ExtEma[i]-ExtEma[i-1]; ExtEmaColor[i]=(ExtSlope[i]>=0.0) ? 0.0 : 1.0; //--- an arrow under the bar where the close crosses the SMA upward ExtCross[i]=EMPTY_VALUE; if(i>=InpSmaPeriod && ExtSma[i-1]!=EMPTY_VALUE && close[i-1]<ExtSma[i-1] && close[i]>=ExtSma[i]) ExtCross[i]=low[i]; } return(rates_total); } //+------------------------------------------------------------------+
BufferSignalEA.mq5 (MQL5\Experts)
//+------------------------------------------------------------------+ //| BufferSignalEA.mq5 | //| Bohdan Klok | //| https://www.mql5.com/en/users/kbposht | //+------------------------------------------------------------------+ #property copyright "Bohdan Klok" #property link "https://www.mql5.com/en/users/kbposht" #property version "1.00" #property description "Example: trades the signals of any indicator by four generic rules on its" #property description "buffers - an arrow appears, a level is crossed, one buffer crosses another, a colour" #property description "index becomes a value. Signals are read on the closed bar, once per bar." #include <Trade\Trade.mqh> //--- the four generic rules enum ENUM_SIGNAL_RULE { SIGNAL_OFF=0, // Disabled SIGNAL_ARROW=1, // Arrow appears in the buffer SIGNAL_CROSS_UP=2, // Buffer crosses the level upward SIGNAL_CROSS_DOWN=3, // Buffer crosses the level downward SIGNAL_LINE_UP=4, // Buffer crosses the second buffer upward SIGNAL_LINE_DOWN=5, // Buffer crosses the second buffer downward SIGNAL_BECOMES=6 // Buffer becomes equal to the level (colour index) }; //--- input parameters input string InpIndicator="Examples\\Fractals"; // Indicator name inside MQL5\Indicators input string InpParams=""; // Its inputs, comma separated (empty = defaults) input int InpSignalBar=3; // Signal bar (1 = last closed bar) input ENUM_SIGNAL_RULE InpBuyRule=SIGNAL_ARROW; // Buy rule input int InpBuyBuffer=1; // Buy: buffer input double InpBuyLevel=0.0; // Buy: level input int InpBuyBuffer2=0; // Buy: second buffer input ENUM_SIGNAL_RULE InpSellRule=SIGNAL_ARROW; // Sell rule input int InpSellBuffer=0; // Sell: buffer input double InpSellLevel=0.0; // Sell: level input int InpSellBuffer2=1; // Sell: second buffer input double InpLots=0.1; // Lot input int InpStopPoints=300; // Stop loss, points input int InpTakePoints=600; // Take profit, points input ulong InpMagic=20261010; // Magic number //--- global variables CTrade ExtTrade; int ExtHandle=INVALID_HANDLE; datetime ExtLastBar=0; //+------------------------------------------------------------------+ //| Indicator inputs from "14,0,1.5,true,text" | //+------------------------------------------------------------------+ int CreateByName(const string name,const string params) { MqlParam p[]; ArrayResize(p,1); p[0].type=TYPE_STRING; p[0].string_value=name; string parts[]; const int n=(StringLen(params)>0) ? StringSplit(params,',',parts) : 0; ArrayResize(p,n+1); for(int i=0; i<n; i++) { string s=parts[i]; StringTrimLeft(s); StringTrimRight(s); if(s=="true" || s=="false") { p[i+1].type=TYPE_BOOL; p[i+1].integer_value=(s=="true"); } else if(StringFind(s,".")>=0 && MathIsValidNumber(StringToDouble(s))) { p[i+1].type=TYPE_DOUBLE; p[i+1].double_value=StringToDouble(s); } else if(s=="0" || StringToInteger(s)!=0) { p[i+1].type=TYPE_INT; p[i+1].integer_value=StringToInteger(s); } else { p[i+1].type=TYPE_STRING; p[i+1].string_value=s; } } return(IndicatorCreate(_Symbol,_Period,IND_CUSTOM,ArraySize(p),p)); } //+------------------------------------------------------------------+ //| A value is empty if it is EMPTY_VALUE or not a number | //+------------------------------------------------------------------+ bool IsEmpty(const double v) { return(v==EMPTY_VALUE || !MathIsValidNumber(v)); } //+------------------------------------------------------------------+ //| Does the rule fire on the signal bar? | //+------------------------------------------------------------------+ bool RuleFires(const ENUM_SIGNAL_RULE rule,const int buffer,const double level,const int buffer2) { if(rule==SIGNAL_OFF) return(false); double a[],b[]; //--- two values: a[0] = the bar before the signal bar, a[1] = the signal bar if(CopyBuffer(ExtHandle,buffer,InpSignalBar,2,a)!=2) return(false); switch(rule) { case SIGNAL_ARROW: return(!IsEmpty(a[1]) && a[1]!=0.0); case SIGNAL_CROSS_UP: return(!IsEmpty(a[0]) && !IsEmpty(a[1]) && a[0]<level && a[1]>=level); case SIGNAL_CROSS_DOWN: return(!IsEmpty(a[0]) && !IsEmpty(a[1]) && a[0]>level && a[1]<=level); case SIGNAL_BECOMES: return(!IsEmpty(a[1]) && MathAbs(a[1]-level)<0.5 && (IsEmpty(a[0]) || MathAbs(a[0]-level)>=0.5)); case SIGNAL_LINE_UP: case SIGNAL_LINE_DOWN: { if(CopyBuffer(ExtHandle,buffer2,InpSignalBar,2,b)!=2) return(false); if(IsEmpty(a[0]) || IsEmpty(a[1]) || IsEmpty(b[0]) || IsEmpty(b[1])) return(false); if(rule==SIGNAL_LINE_UP) return(a[0]<b[0] && a[1]>=b[1]); return(a[0]>b[0] && a[1]<=b[1]); } } return(false); } //+------------------------------------------------------------------+ //| Is there an open position of this EA? | //+------------------------------------------------------------------+ bool HasPosition() { for(int i=PositionsTotal()-1; i>=0; i--) if(PositionGetSymbol(i)==_Symbol && PositionGetInteger(POSITION_MAGIC)==(long)InpMagic) return(true); return(false); } //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { if(InpSignalBar<1) { Print("Signal bar must be 1 or more: the forming bar can still change"); return(INIT_PARAMETERS_INCORRECT); } ExtHandle=CreateByName(InpIndicator,InpParams); if(ExtHandle==INVALID_HANDLE) { PrintFormat("Cannot load %s (error %d)",InpIndicator,GetLastError()); return(INIT_FAILED); } ExtTrade.SetExpertMagicNumber(InpMagic); ExtLastBar=0; return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { if(ExtHandle!=INVALID_HANDLE) IndicatorRelease(ExtHandle); } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //--- one decision per bar const datetime bar=iTime(_Symbol,_Period,0); if(bar==ExtLastBar) return; if(BarsCalculated(ExtHandle)<InpSignalBar+2) return; ExtLastBar=bar; const bool buy=RuleFires(InpBuyRule,InpBuyBuffer,InpBuyLevel,InpBuyBuffer2); const bool sell=RuleFires(InpSellRule,InpSellBuffer,InpSellLevel,InpSellBuffer2); if(buy==sell || HasPosition()) return; const double point=SymbolInfoDouble(_Symbol,SYMBOL_POINT); if(buy) { const double ask=SymbolInfoDouble(_Symbol,SYMBOL_ASK); ExtTrade.Buy(InpLots,_Symbol,ask,ask-InpStopPoints*point,ask+InpTakePoints*point); } else { const double bid=SymbolInfoDouble(_Symbol,SYMBOL_BID); ExtTrade.Sell(InpLots,_Symbol,bid,bid+InpStopPoints*point,bid-InpTakePoints*point); } } //+------------------------------------------------------------------+


