从基础到中级:指标(三)·综合运用
📘

从基础到中级:指标(三)·综合运用

第 3/3 篇

布林带指标缓冲区的初始化挂接

在 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 图的反应变迟钝,这是验证参数灵敏度的直接办法。外汇与贵金属杠杆高,轨道变宽不代表风险降低,仓位仍需自管。

MQL5 / C++
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 才会把整条缓冲当已计算并提交渲染。外汇与贵金属杠杆高,布林带轨道仅描述波动区间,突破方向倾向需结合量价确认。

MQL5 / C++
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 里外汇和贵金属波动常超两三倍标准差,高杠杆下止损被扫的概率不低,指标只是把带宽画出来,真下单前自己核一遍周期和点差。

MQL5 / C++
  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;
}

常见问题

先在全局用 double 数组加 SetIndexBuffer 绑定到对应画线索引,再在 OnInit 里用 iBands 拿句柄并做 ArraySetAsSeries,避免首帧空数据导致数组越界。
多为句柄未初始化完就拷贝数据,需在首帧用 CopyBuffer 前判断句柄!=INVALID_HANDLE,并等 bars>=带宽周期再搬运。
可以,小布能读取你贴的代码或品种页逻辑,标出句柄初始化缺失、缓冲区未绑定等常见坑,并给出修改建议。
大概率要,黄金和欧美波动结构不同,默认20周期2倍偏差在贵金属上可能频繁假突破,建议按品种回看再定。
不算,收口仅代表波动压缩,开口方向受消息和流动性影响,外汇贵金属高风险,需结合量价或更高周期确认。