MQL5 Cookbook: 开发多品种指标分析价格偏离·进阶篇
📊

MQL5 Cookbook: 开发多品种指标分析价格偏离·进阶篇

(2/3)· 1500 行指标拆成三份头文件,拖一下图表就能重算偏离,老手也常卡在缓存区配色

实战向 第 2/3 篇
不少人把多品种指标当静态面板用,改个周期就手动重加载,结果偏离阈值算错还以为是行情问题。拖拽对象触发的事件不处理,图表缩放后最高最低线直接飞出可视区。这篇要解决的正是这类「能动却不跟手」的坑。

多品种背离指标的基础变量与运行限制

写多品种价格背离指标,第一步是把状态字符串和核心数组先声明好。下面这组全局变量里,msg_not_synchronized 用于提醒数据未同步,symbol_difference[] 和 inverse_difference[] 都按 SYMBOLS_COUNT 长度开路,分别存相对主图的价差和反转计算差值;divergence_price、divergence_time 则锁定背离起点。 指标不能在策略测试器里跑,这是硬约束。CheckTesterMode() 用 MQLInfoInteger 同时判断 MQL_TESTER、MQL_VISUAL_MODE、MQL_OPTIMIZATION 三个标志,任一为真就 Comment 提示并 return false。实盘加载前先在 MT5 里点开「专家顾问测试」面板验证,若看到那句 not intended to be used in Strategy Tester,说明编译逻辑已生效。 K 线配色交给 SetBarsColors() 处理。它通过 ChartSetInteger(0, CHART_COLOR_CHART_UP, color_bar_up) 等调用直接改当前图表(句柄 0)的阳线、蜡烛体、折线色;若 TwoColor 开关打开,再补设 CHART_COLOR_CHART_DOWN 和 bear candlestick 的体色。外汇与贵金属波动剧烈、杠杆风险高,这类视觉改写只辅助读图,不替代仓位管理。

MQL5 / C++
class="type">class="kw">string msg_not_synchronized ="Unsynchronized data! Please wait...";
class="type">class="kw">string msg_load_data ="";
class="type">class="kw">string msg_sync_update ="";
class="type">class="kw">string msg_last ="";
class=class="str">"cmt">//---
ENUM_TIMEFRAMES timeframe_start_point =Period(); class=class="str">"cmt">// Timeframe for the price divergence starting point
class="type">class="kw">datetime first_period_time =NULL; class=class="str">"cmt">// Time of the first specified period on chart
class="type">class="kw">double divergence_price =class="num">0.0; class=class="str">"cmt">// Price of the price divergence starting point
class="type">class="kw">datetime divergence_time =NULL; class=class="str">"cmt">// Time of the price divergence starting point
class="type">class="kw">double symbol_difference[SYMBOLS_COUNT]; class=class="str">"cmt">// Difference in price relative to the current symbol
class="type">class="kw">double inverse_difference[SYMBOLS_COUNT]; class=class="str">"cmt">// Difference that is formed when calculating inversion
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Checks if indicator is used in Strategy Tester |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CheckTesterMode()
  {
class=class="str">"cmt">//--- Report that indicator is not intended to be used in Strategy Tester
   if(MQLInfoInteger(MQL_TESTER) ||
      MQLInfoInteger(MQL_VISUAL_MODE) ||
      MQLInfoInteger(MQL_OPTIMIZATION))
     {
      Comment("Currently, the <- "+MQLInfoString(MQL_PROGRAM_NAME)+" -> indicator is not intended to be used in Strategy Tester!");
      class="kw">return(false);
     }
class=class="str">"cmt">//---
   class="kw">return(true);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Sets colors for the current symbol bars |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void SetBarsColors()
  {
class=class="str">"cmt">//--- Color for the up bar, shadows and body borders of bull candlesticks
   ChartSetInteger(class="num">0,CHART_COLOR_CHART_UP,color_bar_up);
class=class="str">"cmt">//--- Body class="type">color of a bull candlestick
   ChartSetInteger(class="num">0,CHART_COLOR_CANDLE_BULL,color_bar_up);
class=class="str">"cmt">//--- Line chart class="type">color and class="type">color of "Doji" Japanese candlesticks
   ChartSetInteger(class="num">0,CHART_COLOR_CHART_LINE,color_bar_up);
class=class="str">"cmt">//--- For two-class="type">color mode
   if(TwoColor)
     {
      class=class="str">"cmt">//--- Color for the down bar, shadows and body borders of bear candlesticks
      ChartSetInteger(class="num">0,CHART_COLOR_CHART_DOWN,color_bar_down);
      class=class="str">"cmt">//--- Body class="type">color of a bear candlestick

「起点周期与参数校验的落地逻辑」

指标初始化时,先根据起点模式选定参考周期:月线对应 PERIOD_MN1,周线 PERIOD_W1,日线 PERIOD_D1,小时线 PERIOD_H1;若用户选了垂直线模式,则直接跳过周期绑定。 CheckInputParameters 会拦掉一个常见误用——当当前图表周期秒数大于等于起点周期秒数(PeriodSeconds() >= PeriodSeconds(timeframe_start_point))时,向终端打印并 Comment 提示,返回 false 终止加载。也就是说,挂 H1 图却把起点设成 HOUR,指标起不来。 InitArrays 负责把 limit_time、symbol_difference、inverse_difference 等数组清零或置空,再按 SYMBOLS_COUNT 循环把每个品种的 buffer_data[s].open 填成 EMPTY_VALUE,避免残留数据污染首根K线计算。 双色关闭时的配色分支也在这里:下跌K线的影线、边框与实体都指向 color_bar_up,等于把空头蜡烛画成多头色,回测或肉眼复盘时要留意这种反直觉设置。

MQL5 / C++
ChartSetInteger(class="num">0,CHART_COLOR_CANDLE_BEAR,color_bar_down);
 }
class=class="str">"cmt">//--- If two-class="type">color mode is turned off
 else
  {
   class=class="str">"cmt">//--- Color for the down bar, shadows and body borders of bear candlesticks
   ChartSetInteger(class="num">0,CHART_COLOR_CHART_DOWN,color_bar_up);
   class=class="str">"cmt">//--- Body class="type">color of a bear candlestick
   ChartSetInteger(class="num">0,CHART_COLOR_CANDLE_BEAR,color_bar_up);
  }
 }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Identifies timeframe for the price starting point mode              |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void InitStartPointTF()
  {
class=class="str">"cmt">//--- Exit if vertical line mode is selected
   if(StartPriceDivergence==VERTICAL_LINE)
      class="kw">return;
class=class="str">"cmt">//--- Otherwise define the timeframe
   class="kw">switch(StartPriceDivergence)
     {
      case MONTH : timeframe_start_point=PERIOD_MN1; class="kw">break;
      case WEEK  : timeframe_start_point=PERIOD_W1;  class="kw">break;
      case DAY   : timeframe_start_point=PERIOD_D1;  class="kw">break;
      case HOUR  : timeframe_start_point=PERIOD_H1;  class="kw">break;
     }
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Checks input parameters for correctness                            |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CheckInputParameters()
  {
class=class="str">"cmt">//--- For all other modes except the &class="macro">#x27;Vertical Line&class="macro">#x27;
   if(StartPriceDivergence!=VERTICAL_LINE)
     {
      class=class="str">"cmt">//--- If the current period is greater than or equal to the specified period of the price divergence starting point, report of it and exit
      if(PeriodSeconds()>=PeriodSeconds(timeframe_start_point))
        {
         Print("Current timeframe should be less than one specified in the Start Price Divergence parameter!");
         Comment("Current timeframe should be less than one specified in the Start Price Divergence parameter!");
         class="kw">return(false);
        }
     }
class=class="str">"cmt">//---
   class="kw">return(true);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| First initialization of arrays                                    |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void InitArrays()
  {
   ArrayInitialize(limit_time,NULL);
   ArrayInitialize(symbol_difference,class="num">0.0);
   ArrayInitialize(inverse_difference,class="num">0.0);
   ArrayInitialize(series_first_date,NULL);
   ArrayInitialize(series_first_date_last,NULL);
class=class="str">"cmt">//---
   for(class="type">int s=class="num">0; s<SYMBOLS_COUNT; s++)
     {
      ArrayInitialize(buffer_data[s].open,EMPTY_VALUE);

◍ 缓冲区与多品种初始化的底层接线

多品种指标在 MT5 里先把每个品种的 OHLC 缓冲区和颜色索引全部灌成 EMPTY_VALUE,否则历史不足的周期会画出 0 轴附近的脏线。外层两层循环对 SYMBOLS_COUNT 个品种、每品种 4 个价格数组加 1 个颜色数组做 ArrayInitialize,是避免图形撕裂的基础动作。 InitSymbolNames 把 Symbol02~Symbol06 共 5 个外部品种经 AddSymbolToMarketWatch 写入 symbol_names 数组,InitInverse 则同步装入 Inverse02~Inverse06 的反向开关。这两步决定了后面绘图时取的是直盘报价还是倒置报价,贵金属与交叉盘混排时尤其要核对。 SetIndicatorProperties 里按 DrawType 分流缓冲区:LINE 模式只绑 close 一个缓冲,省资源;BARS / CANDLES 模式用静态计数器 buffer_number 顺序绑 open/high/low/close 各一个 INDICATOR_DATA 再加一个 INDICATOR_COLOR_INDEX,每品种吃掉 5 个句柄。开 MT5 把 SYMBOLS_COUNT 改成你实际品种数,若超出 5 却没扩数组,绑定会越界报错。

MQL5 / C++
  ArrayInitialize(buffer_data[s].high,EMPTY_VALUE);
  ArrayInitialize(buffer_data[s].low,EMPTY_VALUE);
  ArrayInitialize(buffer_data[s].close,EMPTY_VALUE);
  ArrayInitialize(buffer_data[s].icolor,EMPTY_VALUE);
   }
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Initializes array of symbols                                      |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void InitSymbolNames()
  {
   symbol_names[class="num">0]=AddSymbolToMarketWatch(Symbol02);
   symbol_names[class="num">1]=AddSymbolToMarketWatch(Symbol03);
   symbol_names[class="num">2]=AddSymbolToMarketWatch(Symbol04);
   symbol_names[class="num">3]=AddSymbolToMarketWatch(Symbol05);
   symbol_names[class="num">4]=AddSymbolToMarketWatch(Symbol06);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Initializes array of inversions                                   |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void InitInverse()
  {
   inverse[class="num">0]=Inverse02;
   inverse[class="num">1]=Inverse03;
   inverse[class="num">2]=Inverse04;
   inverse[class="num">3]=Inverse05;
   inverse[class="num">4]=Inverse06;
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Sets indicator properties                                         |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void SetIndicatorProperties()
  {
class=class="str">"cmt">//--- Set the class="type">short name
   IndicatorSetString(INDICATOR_SHORTNAME,indicator_shortname);
class=class="str">"cmt">//--- Set the number of decimal digits
   IndicatorSetInteger(INDICATOR_DIGITS,_Digits);
class=class="str">"cmt">//---  In the &class="macro">#x27;Line&class="macro">#x27; mode we need only one buffers that displays the open price
   if(DrawType==LINE)
     {
      for(class="type">int s=class="num">0; s<SYMBOLS_COUNT; s++)
         SetIndexBuffer(s,buffer_data[s].close,INDICATOR_DATA);
     }
class=class="str">"cmt">//--- In other modes we use all prices for drawing 
class=class="str">"cmt">//    bars/candlesticks and additional buffer for the two-class="type">color mode
   else if(DrawType==BARS || DrawType==CANDLES)
     {
      for(class="type">int s=class="num">0; s<SYMBOLS_COUNT; s++)
        {
         class="kw">static class="type">int buffer_number=class="num">0;
         SetIndexBuffer(buffer_number,buffer_data[s].open,INDICATOR_DATA);
         buffer_number++;
         SetIndexBuffer(buffer_number,buffer_data[s].high,INDICATOR_DATA);
         buffer_number++;
         SetIndexBuffer(buffer_number,buffer_data[s].low,INDICATOR_DATA);
         buffer_number++;
         SetIndexBuffer(buffer_number,buffer_data[s].close,INDICATOR_DATA);
         buffer_number++;
         SetIndexBuffer(buffer_number,buffer_data[s].icolor,INDICATOR_COLOR_INDEX);
         buffer_number++;
        }
     }

多品种绘图标签与图表模式的绑定逻辑

在 MT5 多品种指标里,绘图模式直接决定标签怎么写、柱子怎么画。LINE 模式只取收盘价,所以给每个品种缓冲区设的 PLOT_LABEL 就是「品种名,Close」;而 BARS 和 CANDLES 要把开高低收全甩进数据窗,标签用分号隔开四个价格字段。 绘图类型靠 PlotIndexSetInteger 逐个缓冲区指派:DRAW_LINE、DRAW_COLOR_BARS、DRAW_COLOR_CANDLES 三选一,同时用 ChartSetInteger(0,CHART_MODE,...) 把主图自身也切成对应样式,否则会出现指标画蜡烛、主图却是线图的错位。 线宽统一压成 1,LINE 模式下再按 line_colors 数组上色;最后循环里只给 symbol_names 不等于空字符串的缓冲区开 PLOT_SHOW_DATA,避免废符号污染数据窗。外汇与贵金属波动剧烈、杠杆高风险,参数改动后请在策略测试器用历史数据验证显示逻辑。

MQL5 / C++
class=class="str">"cmt">//--- Set labels for the current timeframe
class=class="str">"cmt">//    In the &class="macro">#x27;Line&class="macro">#x27; mode only opening price is used
   if(DrawType==LINE)
     {
      for(class="type">int s=class="num">0; s<SYMBOLS_COUNT; s++)
         PlotIndexSetString(s,PLOT_LABEL,symbol_names[s]+",Close");
     }
class=class="str">"cmt">//--- In other modes all prices of bars/candlesticks
class=class="str">"cmt">//    ";" is used as a separator
   else if(DrawType==BARS || DrawType==CANDLES)
     {
      for(class="type">int s=class="num">0; s<SYMBOLS_COUNT; s++)
        {
         PlotIndexSetString(s,PLOT_LABEL,
                           symbol_names[s]+",Open;"+
                           symbol_names[s]+",High;"+
                           symbol_names[s]+",Low;"+
                           symbol_names[s]+",Close");
        }
     }
class=class="str">"cmt">//--- Set the type of lines for indicator buffers
class=class="str">"cmt">//--- Line
   if(DrawType==LINE)
      for(class="type">int s=class="num">0; s<SYMBOLS_COUNT; s++)
         PlotIndexSetInteger(s,PLOT_DRAW_TYPE,DRAW_LINE);
class=class="str">"cmt">//--- Bars
   if(DrawType==BARS)
      for(class="type">int s=class="num">0; s<SYMBOLS_COUNT; s++)
         PlotIndexSetInteger(s,PLOT_DRAW_TYPE,DRAW_COLOR_BARS);
class=class="str">"cmt">//--- Candlesticks
   if(DrawType==CANDLES)
      for(class="type">int s=class="num">0; s<SYMBOLS_COUNT; s++)
         PlotIndexSetInteger(s,PLOT_DRAW_TYPE,DRAW_COLOR_CANDLES);
class=class="str">"cmt">//--- Set the type of lines for data of current symbol
class=class="str">"cmt">//--- Line
   if(DrawType==LINE)
      ChartSetInteger(class="num">0,CHART_MODE,CHART_LINE);
class=class="str">"cmt">//--- Bars
   if(DrawType==BARS)
      ChartSetInteger(class="num">0,CHART_MODE,CHART_BARS);
class=class="str">"cmt">//--- Candlesticks
   if(DrawType==CANDLES)
      ChartSetInteger(class="num">0,CHART_MODE,CHART_CANDLES);
class=class="str">"cmt">//--- Set the line width
   for(class="type">int s=class="num">0; s<SYMBOLS_COUNT; s++)
      PlotIndexSetInteger(s,PLOT_LINE_WIDTH,class="num">1);
class=class="str">"cmt">//--- Set the line class="type">color for the &class="macro">#x27;Line&class="macro">#x27; mode
   if(DrawType==LINE)
      for(class="type">int s=class="num">0; s<SYMBOLS_COUNT; s++)
         PlotIndexSetInteger(s,PLOT_LINE_COLOR,line_colors[s]);
class=class="str">"cmt">//--- Display data in Data Window only for existing symbols
   for(class="type">int s=class="num">0; s<SYMBOLS_COUNT; s++)
     {
      if(symbol_names[s]!=empty_symbol)
         PlotIndexSetInteger(s,PLOT_SHOW_DATA,true);

「初始化里如何埋好背离起点」

指标启动阶段就把背离起点画好,能省掉实盘里反复手动标线的麻烦。OnInit 里先拦掉策略测试器之外的非法环境,再依次跑颜色、起点周期、参数校验,最后挂 1 秒毫秒定时器 EventSetMillisecondTimer(1000) 保活刷新。 SetDivergenceLine 的逻辑很直接:当模式是 VERTICAL_LINE 且图上还没这根线(ObjectFind 返回负值),就用 CreateVerticalLine 在 TimeCurrent()+PeriodSeconds() 处落一根绿黄色竖线;若不是该模式,则顺手 DeleteObjectByName 清掉旧对象,避免不同模式切换留垃圾。 CheckAvailableData 给数据等待留了 100 次尝试上限(attempts=100),对多品种同时拉历史的外汇/贵金属指标来说,这能避免在流动性差的品种上卡死初始化。MT5 里开多符号背离指标时,建议先把 SYMBOLS_COUNT 和这个尝试次数对着经纪商可见品种数调一遍。 外汇与贵金属波动剧烈、杠杆风险高,竖线仅作起点参照,不代表任何方向确定性。

MQL5 / C++
else
      PlotIndexSetInteger(s,PLOT_SHOW_DATA,false);
   }
class=class="str">"cmt">//--- Empty value for plotting where nothing will be drawn
   for(class="type">int s=class="num">0;s<SYMBOLS_COUNT; s++)
      PlotIndexSetDouble(s,PLOT_EMPTY_VALUE,EMPTY_VALUE);
   }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Sets vertical line for price divergence starting point           |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void SetDivergenceLine()
  {
class=class="str">"cmt">//--- If there is no vertical line yet, set it
   if(StartPriceDivergence==VERTICAL_LINE && ObjectFind(class="num">0,start_price_divergence)<class="num">0)
     class=class="str">"cmt">//--- Place a vertical line on the true bar
     CreateVerticalLine(class="num">0,class="num">0,TimeCurrent()+PeriodSeconds(),start_price_divergence,
                        class="num">2,STYLE_SOLID,clrGreenYellow,true,true,false,"","\n");
class=class="str">"cmt">//--- For all other modes except the &class="macro">#x27;Vertical Line&class="macro">#x27;
   if(StartPriceDivergence!=VERTICAL_LINE)
      DeleteObjectByName(start_price_divergence);
   }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Custom indicator initialization function                         |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnInit()
  {
class=class="str">"cmt">//--- Check if indicator is currently being used in Strategy Tester
   if(!CheckTesterMode())
      class="kw">return(INIT_FAILED);
class=class="str">"cmt">//--- Set the class="type">color for bars/candlesticks
   SetBarsColors();
class=class="str">"cmt">//--- Define the timeframe for the price divergence starting point
   InitStartPointTF();
class=class="str">"cmt">//--- Check input parameters for correctness
   if(!CheckInputParameters())
      class="kw">return(INIT_PARAMETERS_INCORRECT);
class=class="str">"cmt">//--- Set the timer at class="num">1-second intervals
   EventSetMillisecondTimer(class="num">1000);
class=class="str">"cmt">//--- Set the font to be displayed on the canvas
   canvas.FontSet(font_name,font_size,FW_NORMAL);
class=class="str">"cmt">//--- Initialization of arrays
   InitArrays();
class=class="str">"cmt">//--- Initialize the array of symbols 
   InitSymbolNames();
class=class="str">"cmt">//--- Initialize the array of inversions
   InitInverse();
class=class="str">"cmt">//--- Set indicator properties
   SetIndicatorProperties();
class=class="str">"cmt">//--- Set vertical line of the price divergence start
   SetDivergenceLine();
class=class="str">"cmt">//--- Clear the comment
   Comment("");
class=class="str">"cmt">//--- Refresh the chart
   ChartRedraw();
class=class="str">"cmt">//--- Initialization completed successfully
   class="kw">return(INIT_SUCCEEDED);
   }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Checks the amount of available data for all symbols              |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CheckAvailableData()
  {
   class="type">int attempts=class="num">100;

class=class="str">"cmt">//---
   for(class="type">int s=class="num">0;s<SYMBOLS_COUNT; s++)
     {
     class=class="str">"cmt">//--- If this symbol is available
     if(symbol_names[s]!=empty_symbol)
       {

◍ 数据齐了才能画背离线

MT5 里跨周期或换品种读取历史时,最容易踩的坑是:终端还没把全部 bar 拉下来,指标就急着算背离,结果前面几百根 K 线时间是空的。下面这段逻辑先摸清「终端里这个品种当前周期到底存了多少根」,再决定要不要继续。 datetime time[]; // 装时间数组,用来核对实际拷贝到的 bar 数 int total_period_bars = 0; // 当前周期应有的总 bar 数 datetime terminal_first_date = NULL; // 终端里该周期最早一条数据的日期 // 先问终端:这个周期最早数据是哪天 time_terminal_first_date=(datetime)SeriesInfoInteger(symbol_names[s],Period(),SERIES_TERMINAL_FIRSTDATE); // 从最早那天算到此刻,共有多少根 total_period_bars=Bars(symbol_names[s],Period(),terminal_first_date,TimeCurrent()); // 尝试多次拷贝,直到拷贝到的数量够为止 for(int i=0; i<attempts; i++) { if(CopyTime(symbol_names[s],Period(),0,total_period_bars,time)) { if(ArraySize(time)>=total_period_bars) break; } } // 一根没拷到或数量不够,打提示并退出,等下一轮重来

if(ArraySize(time)==0ArraySize(time)<total_period_bars)

{ msg_last=msg_prepare_data; ShowCanvasMessage(msg_prepare_data); OC_prev_calculated=0; return(false); } 外汇和贵金属行情在跳空或刚开盘时,服务器推送延迟会让 Bars() 返回的值比真实可见 K 线少几根,概率不低。把 attempts 设成 3~5 次重试,比一次性失败更稳。 如果 StartPriceDivergence 设成 VERTICAL_LINE,数据齐了直接返回 true 去画竖线;否则走另一段:用 SeriesInfoInteger(Symbol(),Period(),SERIES_FIRSTDATE) 拿本图表周期首根日期,同样套一层重试循环。开 MT5 把这段粘进指标 OnInit 之前跑一遍,能在图表左上角看到是否卡在「准备数据」状态。

MQL5 / C++
class="type">class="kw">datetime time[];                      class=class="str">"cmt">// Array for checking the number of bars
   class="type">int      total_period_bars   =class="num">0;      class=class="str">"cmt">// Number of bars of the current period
   class="type">class="kw">datetime terminal_first_date =NULL;   class=class="str">"cmt">// First date of the current time frame data available in the terminal
         class=class="str">"cmt">//--- Get the first date of the current time frame data in the terminal
         terminal_first_date=(class="type">class="kw">datetime)SeriesInfoInteger(symbol_names[s],Period(),SERIES_TERMINAL_FIRSTDATE);
         class=class="str">"cmt">//--- Get the number of available bars from the date specified
         total_period_bars=Bars(symbol_names[s],Period(),terminal_first_date,TimeCurrent());
         class=class="str">"cmt">//--- Check the readiness of bar data
         for(class="type">int i=class="num">0; i<attempts; i++)
           {
            class=class="str">"cmt">//--- Copy the specified amount of data
            if(CopyTime(symbol_names[s],Period(),class="num">0,total_period_bars,time))
              {
               class=class="str">"cmt">//--- If the required amount has been copied, terminate the loop
               if(ArraySize(time)>=total_period_bars)
                  class="kw">break;
              }
           }
         class=class="str">"cmt">//--- If the amount of data copied is not sufficient, one more attempt is required
         if(ArraySize(time)==class="num">0 || ArraySize(time)<total_period_bars)
           {
            msg_last=msg_prepare_data;
            ShowCanvasMessage(msg_prepare_data);
            OC_prev_calculated=class="num">0;
            class="kw">return(false);
           }
         }
     }
class=class="str">"cmt">//--- Exit if current mode is vertical line of the price divergence starting point
   if(StartPriceDivergence==VERTICAL_LINE)
      class="kw">return(true);
   else
      {
       class="type">class="kw">datetime time[];                      class=class="str">"cmt">// Array for checking the number of bars
       class="type">int      total_period_bars   =class="num">0;      class=class="str">"cmt">// Number of bars of the current period
       class="type">class="kw">datetime terminal_first_date =NULL;   class=class="str">"cmt">// First date of the current time frame data available in the terminal
       class=class="str">"cmt">//--- Get the first date of the current time frame data in the terminal
       for(class="type">int i=class="num">0; i<attempts; i++)
          if((terminal_first_date=(class="type">class="kw">datetime)SeriesInfoInteger(Symbol(),Period(),SERIES_FIRSTDATE))>class="num">0)
             class="kw">break;
       class=class="str">"cmt">//--- Get the number of available bars from the date specified
把多品种偏离诊断交给小布
这些多品种偏离的实时计算与阈值提示,小布盯盘已内置在对应品种页,打开即可看到跨品种乖离,你只管判断方向。

常见问题

前者是用户拖拽图表对象(如线、标签)时触发,后者在缩放、改属性等图表变更时触发;进阶指标通常两者都捕获,才能保持偏离线跟随视区。
每个序列除价格数据缓存外,还需一个颜色索引缓存来区分阳线阴线,所以 N 个品种在双色下缓存区接近 2N 而非 N。
不用自己写,小布盯盘的品种页已聚合主要交叉盘的实时偏离与乖离,写指标更适合做定制化策略回测。
多是时间轴数组未用 ArraySetAsSeries 对齐,或拷贝历史数据时方向弄反,导致新柱画到左边、偏离符号整体反向。
编译隔离、便于多人改图形逻辑而不动数据检查,也方便把图表管理函数复用到别的 EA 项目里。