从基础到中级:指标(三)·综合运用
布林带指标缓冲区的初始化挂接
在 MT5 自定义指标里,先把三条线(中轨、上轨、下轨)的缓冲区声明好,运行时才能往里写值。下面这段代码用 double 数组 MiddleBuffer / UpperBuffer / LowerBuffer 承接布林带输出,并用 g_bb_handle 保存 iBands 返回的句柄,初值一律设为 INVALID_HANDLE,避免野指针。 OnInit 里第一件事是 SetIndexBuffer 把三个数组绑到 BASE_LINE、UPPER_BAND、LOWER_BAND,类型标 INDICATOR_DATA;紧接着 ArraySetAsSeries(..., true) 让索引 0 对应最新 K 线,这是 MT5 时间序列的坑,不设置会导致画线整体偏移一根。 绘图属性全靠 PlotIndexSetInteger 在运行时从 input 搬进去:颜色、线型(下轨默认 STYLE_DOT 点线)、线宽分别映射 Inp*Color / Inp*Style / Inp*Width。是否显示某条带由 inp_BB_Show_Middle 等布尔量控制,条件为真写 DRAW_LINE,否则 DRAW_NONE——这样不用重编译就能隐掉某条轨。 指标名用 SMT_PREFIX + SMT_BB_NAME 拼接周期与偏差,例如 "SMT_BB(20,2.0)",在副图左上角直接读出参数。开 MT5 把这段塞进自己的 iBands 封装,改 InpBBPeriod 从 20 调到 50,能明显看到轨道对贵金属 1H 图的反应变迟钝,这是验证参数灵敏度的直接办法。外汇与贵金属杠杆高,轨道变宽不代表风险降低,仓位仍需自管。
input ENUM_LINE_STYLE InpLowerStyle = STYLE_DOT; class=class="str">"cmt">//================================================== class="macro">#include <Anhnt/Configuration/NamingConfiguration.mqh> class=class="str">"cmt">//================================================== class=class="str">"cmt">// 指示器缓冲区 class=class="str">"cmt">//================================================== class="type">class="kw">double MiddleBuffer[]; class="type">class="kw">double UpperBuffer[]; class="type">class="kw">double LowerBuffer[]; class=class="str">"cmt">//================================================== class=class="str">"cmt">// 全局变量 class=class="str">"cmt">//================================================== class="type">int g_bb_handle = INVALID_HANDLE; class=class="str">"cmt">//--- 我们将保留布林线指标中的数值数量 class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int OnInit() { class=class="str">"cmt">//================================================== class=class="str">"cmt">// 设置缓冲区 class=class="str">"cmt">//================================================== SetIndexBuffer(BASE_LINE, MiddleBuffer, INDICATOR_DATA); SetIndexBuffer(UPPER_BAND, UpperBuffer, INDICATOR_DATA); SetIndexBuffer(LOWER_BAND, LowerBuffer, INDICATOR_DATA); ArraySetAsSeries(MiddleBuffer, true); ArraySetAsSeries(UpperBuffer, true); ArraySetAsSeries(LowerBuffer, true); class=class="str">"cmt">//================================================== class=class="str">"cmt">// 将 INPUT 值应用于绘图(运行时安全方法) class=class="str">"cmt">//================================================== PlotIndexSetInteger(BASE_LINE, PLOT_LINE_COLOR, InpMiddleColor); PlotIndexSetInteger(UPPER_BAND, PLOT_LINE_COLOR, InpUpperColor); PlotIndexSetInteger(LOWER_BAND, PLOT_LINE_COLOR, InpLowerColor); PlotIndexSetInteger(BASE_LINE, PLOT_LINE_STYLE, InpMiddleStyle); PlotIndexSetInteger(UPPER_BAND, PLOT_LINE_STYLE, InpUpperStyle); PlotIndexSetInteger(LOWER_BAND, PLOT_LINE_STYLE, InpLowerStyle); PlotIndexSetInteger(BASE_LINE, PLOT_LINE_WIDTH, InpMiddleWidth); PlotIndexSetInteger(UPPER_BAND, PLOT_LINE_WIDTH, InpUpperWidth); PlotIndexSetInteger(LOWER_BAND, PLOT_LINE_WIDTH, InpLowerWidth); PlotIndexSetInteger( BASE_LINE, PLOT_DRAW_TYPE, inp_BB_Show_Middle ? DRAW_LINE : DRAW_NONE ); PlotIndexSetInteger( UPPER_BAND, PLOT_DRAW_TYPE, inp_BB_Show_Upper ? DRAW_LINE : DRAW_NONE ); PlotIndexSetInteger( LOWER_BAND, PLOT_DRAW_TYPE, inp_BB_Show_Lower ? DRAW_LINE : DRAW_NONE ); class="type">class="kw">string name = SMT_PREFIX + SMT_BB_NAME + "(" + (class="type">class="kw">string)InpBBPeriod + "," + DoubleToString(InpBBDeviation, class="num">1) + ")";
「布林带句柄的初始化与首帧数据搬运」
在 MT5 自定义指标里,iBands 句柄必须在 OnInit 阶段建好,否则后续 OnCalculate 永远拿不到轨道数据。下面这段把品种、周期、周期数、偏移、标准差倍数和应用价格一股脑传给 iBands,拿到 INVALID_HANDLE 就直接 INIT_FAILED 退出,避免空句柄在行情里裸奔。 初始化成功后别急着算,OnCalculate 里先用 BarsCalculated 等 iBands 至少攒够 InpBBPeriod 根 K 线——比如默认 20 周期布林带,calculated 小于等于 20 时直接 return 0,防止中轨数组前半段全是 EMPTY_VALUE 导致画图断裂。 首帧(prev_calculated==0)用 MathMin(calculated, rates_total) 控量,一次性把 BASE_LINE、UPPER_BAND、LOWER_BAND 三路缓冲拷进指标数组。这一步做完 return rates_total,MT5 才会把整条缓冲当已计算并提交渲染。外汇与贵金属杠杆高,布林带轨道仅描述波动区间,突破方向倾向需结合量价确认。
IndicatorSetString(INDICATOR_SHORTNAME, name); class=class="str">"cmt">//================================================== class=class="str">"cmt">// 创建 iBands 句柄 class=class="str">"cmt">//================================================== g_bb_handle = iBands( _Symbol, _Period, InpBBPeriod, InpBBShift, InpBBDeviation, inp_Applied_Price ); if(g_bb_handle == INVALID_HANDLE) { Print("iBand_Display INIT FAILED. Unable to create iBands handle. GetLastError = ", GetLastError()); class="kw">return INIT_FAILED; } Print("iBand_Display INIT SUCCESS"); class="kw">return INIT_SUCCEEDED; } class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int OnCalculate( const class="type">int rates_total, const class="type">int prev_calculated, const class="type">class="kw">datetime &time[], const class="type">class="kw">double &open[], const class="type">class="kw">double &high[], const class="type">class="kw">double &low[], const class="type">class="kw">double &close[], const class="type">long &tick_volume[], const class="type">long &volume[], const class="type">int &spread[] ) { class=class="str">"cmt">//--- 等待 iBands 准备就绪 class="type">int calculated = BarsCalculated(g_bb_handle); if(calculated <= InpBBPeriod) class="kw">return class="num">0; class=class="str">"cmt">//--- 该代码块在指标最初连接到图表时执行 if(prev_calculated == class="num">0) { ArrayInitialize(MiddleBuffer, EMPTY_VALUE); ArrayInitialize(UpperBuffer, EMPTY_VALUE); ArrayInitialize(LowerBuffer, EMPTY_VALUE); class="type">int to_copy = MathMin(calculated, rates_total); class=class="str">"cmt">// 一次性复制所有可用数据 CopyBuffer(g_bb_handle, BASE_LINE, class="num">0, to_copy, MiddleBuffer); CopyBuffer(g_bb_handle, UPPER_BAND, class="num">0, to_copy, UpperBuffer); CopyBuffer(g_bb_handle, LOWER_BAND, class="num">0, to_copy, LowerBuffer); class="kw">return rates_total; } class=class="str">"cmt">//--- 每打开一个新的条形图块,都会执行该代码块 if(prev_calculated != rates_total && prev_calculated != class="num">0) {
◍ 把工具请下神坛
上面这段 NEXT RUNS 逻辑只刷新索引 0 的条形图,也就是实时那一根。CopyBuffer 每次只取 1 根,MiddleBuffer[0]、UpperBuffer[0]、LowerBuffer[0] 各自赋值,数组序列由系统处理,别手动移位,否则实时带会错位。 FillArraysFromBuffers 现在挂着没用,但预留了 shift 与 amount 参数,将来给 EA 拉历史带宽很方便:BASE_LINE、UPPER_BAND、LOWER_BAND 三个缓冲区一次性拷回,任意一根返回小于 0 就直接 false,省得在策略层重复判错。 布林带自定义指标写到这层就算能跑通了。MT5 里外汇和贵金属波动常超两三倍标准差,高杠杆下止损被扫的概率不低,指标只是把带宽画出来,真下单前自己核一遍周期和点差。
class=class="str">"cmt">//================================================== class=class="str">"cmt">// NEXT RUNS: class=class="str">"cmt">// 只更新最新的条形图(索引 class="num">0)。 class=class="str">"cmt">// 不要手动移动数组(序列会处理)。 class=class="str">"cmt">//================================================== class="type">class="kw">double tmp[class="num">1]; if(CopyBuffer(g_bb_handle, BASE_LINE, class="num">0, class="num">1, tmp) > class="num">0) MiddleBuffer[class="num">0] = tmp[class="num">0]; if(CopyBuffer(g_bb_handle, UPPER_BAND, class="num">0, class="num">1, tmp) > class="num">0) UpperBuffer[class="num">0] = tmp[class="num">0]; if(CopyBuffer(g_bb_handle, LOWER_BAND, class="num">0, class="num">1, tmp) > class="num">0) LowerBuffer[class="num">0] = tmp[class="num">0]; class="kw">return rates_total; } class="kw">return rates_total; } class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool FillArraysFromBuffers( class="type">class="kw">double &base_values [], class=class="str">"cmt">// 中部缓冲区 class="type">class="kw">double &upper_values [], class=class="str">"cmt">// 上层缓冲区 class="type">class="kw">double &lower_values [], class=class="str">"cmt">// LowerBuffer class="type">int shift, class=class="str">"cmt">// shift = class="num">0 → realtime class="type">int ind_handle, class="type">int amount ) { class=class="str">"cmt">// NOTE: class=class="str">"cmt">// 目前未使用。 class=class="str">"cmt">// 按计划保留用于未来的辅助/EA 逻辑。 ResetLastError(); if(CopyBuffer(ind_handle, BASE_LINE, -shift, amount, base_values) < class="num">0) class="kw">return false; if(CopyBuffer(ind_handle, UPPER_BAND, -shift, amount, upper_values) < class="num">0) class="kw">return false; if(CopyBuffer(ind_handle, LOWER_BAND, -shift, amount, lower_values) < class="num">0) class="kw">return false; class="kw">return true; }