English 中文 Español Deutsch 日本語 Português 한국어 Français Italiano Türkçe
Рецепты MQL5 - Разработка мультивалютного индикатора для анализа расхождения цен

Рецепты MQL5 - Разработка мультивалютного индикатора для анализа расхождения цен

MetaTrader 5Примеры | 23 февраля 2014, 08:37
5 751 19
Anatoli Kazharski
Anatoli Kazharski

Введение

В этой статье рассмотрим разработку мультивалютного индикатора для анализа расхождения цен за указанный период времени. Многие основные моменты уже рассматривались в предыдущей статье по программированию мультивалютных индикаторов Рецепты MQL5 - Разработка мультивалютного индикатора волатильности на MQL5. Поэтому на этот раз будем останавливаться только на новых функциях и тех функциях, которые претерпели сильные изменения. Если вы впервые рассматриваете тему программирования мультивалютных индикаторов, то рекомендуется в первую очередь прочитать предыдущую статью.

В этой статье мы рассмотрим следующие вопросы:

  • изменение свойств графика;
  • обработка событий CHARTEVENT_OBJECT_DRAG (перетаскивание графического объекта) и CHARTEVENT_CHART_CHANGE (изменение размеров графика или изменение свойств графика через диалог свойств);
  • отрисовка индикаторных буферов более, чем одним цветом;
  • определение максимумов и минимумов в индикаторных буферах в зоне видимости для установки максимума/минимума графика;
  • инверсия ряда.

Для индикатора объем кода в итоге получится довольно большой, около 1500 строк. Поэтому распределим все функции по отдельным категориям в файлы и будем подключать их к главному файлу проекта. Всего получится три категории функций для внешних файлов:

  • Checks.mqh - функции для различных проверок и загрузки доступных данных;
  • Objects.mqh - функции для управления графическими объектами;
  • Chart.mqh - функции для управления свойствами графика.

Функции, которые не относятся к перечисленным выше категориям, оставим в главном файле.


Процесс разработки индикатора

Далее приступим к программированию индикатора. Сначала нужно создать новый проект. Для этого создайте в директории Metatrader 5\MQL5\Indicators папку с именем индикатора, а в ней папку Include, в которой будут размещаться подключаемые файлы. Далее создайте главный файл в папке индикатора. Это можно сделать вручную, создав текстовый файл с расширением *.mq5 или с помощью Мастера MQL5 по готовому шаблону. Кроме основных функций программы OnInit(), OnDeinit() и OnCalculate(), будут еще использоваться OnChartEvent() и OnTimer().

Так же как и в предыдущей статье, кроме текущего символа будем выводить на график данные еще пяти указанных во внешних параметрах символов. Но на этот раз вместо рассчитанных по какой-либо формуле значений, будем выводить на график чистые ценовые данные. Тип отображения данных предоставим на выбор пользователю. То есть, во внешних параметрах можно будет выбрать в выпадающем списке один из трех вариантов: Линия, Бары или Японские свечи.

Если бы нужно было отобразить данные только в виде линий одним цветом, то в свойствах индикатора в разделе специфических параметров (#property) в самом начале программы достаточно было бы указать количество буферов, равное количеству символов. Но так как есть еще два режима для отрисовки графических серий в виде баров и свечей, то для двухцветного режима буферов понадобится больше: по четыре буфера для формирования каждой серии и по одному буферу для установки цвета по условию для каждого элемента в графической серии.

Для каждой графической серии в специфических параметрах программы необходимо определить цвета. Для этого достаточно просто перечислить их через запятую. Первым идет цвет, который используется в одноцветном режиме. В двухцветном режиме он будет использоваться для окраски баров/свечей, направленных вверх. Второй цвет будет использоваться только в двухцветном режиме для тех баров/свечей, которые направлены вниз.

Ниже показан код с перечислением специфических параметров:

#property indicator_chart_window // Выводить индикатор в окно графика
#property indicator_buffers 25   // Количество буферов для расчета индикатора
#property indicator_plots   5    // Количество графических серий
//--- Цвета индикаторных буферов
#property indicator_color1  clrDodgerBlue,C'0,50,100'
#property indicator_color2  clrLimeGreen,C'20,80,20'
#property indicator_color3  clrGold,C'160,140,0'
#property indicator_color4  clrAqua,C'0,140,140'
#property indicator_color5  clrMagenta,C'130,0,130'

С помощью директивы #define объявим константы, а с помощью командной строки #include подключим файлы с функциями, о которых уже было написано выше, и класс для работы с холстом из Стандартной библиотеки:

//--- Константы 
#define RESET           0 // Возврат терминалу команды на пересчет индикатора
#define SYMBOLS_COUNT   5 // Количество символов
//--- Подключим класс для работы с холстом
#include <Canvas\Canvas.mqh>
//--- Подключаем свои библиотеки
#include "Include/Checks.mqh"
#include "Include/Chart.mqh"
#include "Include/Objects.mqh"

Для создания выпадающих списков во внешних параметрах для выбора типа отрисовки и режима начальной точки расхождения цен, создадим перечисления ENUM_DRAWTYPE и ENUM_START_POINT:

//--- Тип отрисовки ценовых данных
enum ENUM_DRAWTYPE
  {
   LINE   =0,  // Линия
   BARS   =1,  // Бары
   CANDLES=2   // Японские свечи
  };
//--- Режим начальной точки расхождения цен
enum ENUM_START_POINT
  {
   VERTICAL_LINE=0,  // Вертикальная линия
   MONTH        =1,  // Месяц
   WEEK         =2,  // Неделя
   DAY          =3,  // День
   HOUR         =4   // Час
  };

О типах отображения данных уже рассказывалось выше, теперь немного подробнее о том, что означает начальная точка расхождения цен.

Всего создадим пять режимов: Вертикальная линия, Месяц, Неделя, День и Час. Для режима Вертикальная линия при загрузке индикатора на график будет установлена вертикальная линия. Перемещением этой линии указываться бар, на котором цены всех символов будут смыкаться в одну точку. Этой точкой опоры будет уровень цены открытия указанного бара для текущего символа. Любой другой режим будет указывать программе, что цены должны смыкаться каждый раз в начале указанного периода. То есть, в начале каждого месяца, в начале каждой недели, в начале каждого дня или в начале каждого часа.

Ниже можно ознакомиться со списком внешних параметров индикатора:

//--- Внешние параметры
input  ENUM_DRAWTYPE    DrawType             =CANDLES;       // Тип отрисовки
input  ENUM_START_POINT StartPriceDivergence =VERTICAL_LINE; // Начало расхождения цен
input  bool             TwoColor             =false;         // Двухцветные бары/свечи
sinput string dlm01=""; //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
input  string           Symbol02             ="GBPUSD";      // Символ 2
input  bool             Inverse02            =false;         // Инверсия символа 2
input  string           Symbol03             ="AUDUSD";      // Символ 3
input  bool             Inverse03            =false;         // Инверсия символа 3
input  string           Symbol04             ="NZDUSD";      // Символ 4
input  bool             Inverse04            =false;         // Инверсия символа 4
input  string           Symbol05             ="USDCAD";      // Символ 5
input  bool             Inverse05            =false;         // Инверсия символа 5
input  string           Symbol06             ="USDCHF";      // Символ 6
input  bool             Inverse06            =false;         // Инверсия символа 6

Нумерация символов начинается с двойки, так как единица - это текущий символ на графике.

Для каждого подключаемого символа можно включить инверсию. Инверсия означает, что данные символа будут отображаться перевернутыми. Это может быть полезным, когда в списке используемых для анализа символов присутствуют такие, у которых одна и та же валюта (например, доллар США) может быть как базовой, так и котируемой. Например, в валютной паре EURUSD, доллар является котируемой валютой, а в валютной паре USDCHF - базовой. В таком случае, если на графике текущий символ EURUSD, то для USDCHF можно включить инверсию, получив, таким образом, более удобное представление данных для анализа.

Ниже представлен список глобальных переменных и массивов:

//--- Структура массивов индикаторных буферов
struct buffers
  {
   double            open[];   // Буфер для цен открытия
   double            high[];   // Буфер для цен максимумов
   double            low[];    // Буфер для цен минимумов
   double            close[];  // Буфер для цен закрытия
   double            icolor[]; // Буфер для определения цвета элемента
  };
buffers           buffer_data[SYMBOLS_COUNT];
//--- Загрузка класса
CCanvas           canvas;
//--- Переменные/массивы для копирования данных из OnCalculate()
int               OC_rates_total     =0; // Размер входных таймсерий
int               OC_prev_calculated =0; // Обработано баров на предыдущем вызове
datetime          OC_time[];             // Время открытия
double            OC_open[];             // Цены открытия
double            OC_high[];             // Максимальные цены
double            OC_low[];              // Минимальные цены
double            OC_close[];            // Цены закрытия
long              OC_tick_volume[];      // Тиковые объемы
long              OC_volume[];           // Реальные объемы
int               OC_spread[];           // Спред

//--- Для хранения и проверки времени первого бара в терминале
datetime          series_first_date[SYMBOLS_COUNT];
datetime          series_first_date_last[SYMBOLS_COUNT];
//--- Массив времени бара, от которого начинать отрисовку
datetime          limit_time[SYMBOLS_COUNT];
//--- Массив названий символов
string            symbol_names[SYMBOLS_COUNT];
//--- Массив флагов инверсии символов
bool              inverse[SYMBOLS_COUNT];
//--- Цвета линий индикатора
color             line_colors[SYMBOLS_COUNT]={clrDodgerBlue,clrLimeGreen,clrGold,clrAqua,clrMagenta};
//--- Строка, символизирующая отсутствие символа
string            empty_symbol="EMPTY";
//--- Свойства графика
int               window_number              =WRONG_VALUE;               // Номер окна индикатора
int               chart_width                =0;                         // Ширина графика
int               chart_height               =0;                         // Высота графика
int               last_chart_width           =0;                         // Последняя в памяти ширина графика
int               last_chart_height          =0;                         // Последняя в памяти высота графика
int               chart_center_x             =0;                         // Центр графика по горизонтали
int               chart_center_y             =0;                         // Центр графика по вертикали
color             color_bar_up               =clrRed;                    // Цвет бара вверх
color             color_bar_down             =C'100,0,0';                // Цвет бара вниз
string            indicator_shortname        ="MS_PriceDivergence";      // Короткое имя индикатора
string            prefix                     =indicator_shortname+"_";   // Префикс для объектов
//--- Имя вертикальной линии начальной точки расхождения цен
string            start_price_divergence=prefix+"start_price_divergence";
//--- Свойства холста
string            canvas_name             =prefix+"canvas";          // Название холста
color             canvas_background       =clrBlack;                 // Цвет фона холста
uchar             canvas_opacity          =190;                      // Степень прозрачности
int               font_size               =16;                       // Размер шрифта
string            font_name               ="Calibri";                // Шрифт
ENUM_COLOR_FORMAT clr_format              =COLOR_FORMAT_ARGB_RAW;    // Компоненты цвета должны быть корректно заданы пользователем
//--- Сообщения холста
string            msg_prepare_data        ="Подготовка данных! Пожалуйста, подождите...";
string            msg_not_synchronized    ="Данные не синхронизированы! Пожалуйста, подождите...";
string            msg_load_data           ="";
string            msg_sync_update         ="";
string            msg_last                ="";
//---
ENUM_TIMEFRAMES   timeframe_start_point  =Period();    // Таймфрейм для точки расхождения цен
datetime          first_period_time      =NULL;        // Время первого указанного периода данных на графике
double            divergence_price       =0.0;         // Цена начальной точки расхождения цен
datetime          divergence_time        =NULL;        // Время начальной точки расхождения цен
double            symbol_difference[SYMBOLS_COUNT];    // Разница в цене относительно текущего символа
double            inverse_difference[SYMBOLS_COUNT];   // Разница, которая образуется при расчете инверсии

Далее рассмотрим функции, которые используются во время инициализации индикатора. В принципе, больших изменений по сравнению с тем, что было в функции OnInit() в индикаторе из предыдущей статьи, нет.

Добавим проверку на то, где используется индикатор. Дело в том, что в тестере на текущий момент разработчиками терминала реализованы не все возможности для управления свойствами графика, поэтому ограничимся использованием индикатора только вне тестера. Для этого напишем простую функцию CheckTesterMode(). Располагаться она будет в файле Checks.mqh:

//+------------------------------------------------------------------+
//| Проверяет, используется ли индикатор в тестере                   |
//+------------------------------------------------------------------+
bool CheckTesterMode()
  {
//--- Если индикатор был запущен в тестере, сообщим, что он не предназначен для использования в тестере
   if(MQLInfoInteger(MQL_TESTER) || 
      MQLInfoInteger(MQL_VISUAL_MODE) || 
      MQLInfoInteger(MQL_OPTIMIZATION))
     {
      Comment("На данный момент индикатор <- "+MQLInfoString(MQL_PROGRAM_NAME)+" -> не предназначен для использования в тестере!");
      return(false);
     }
//---
   return(true);
  }

Еще одна новая функция SetBarsColors() предназначена для установки цвета барам/свечам текущего символа, располагается она в файле Chart.mqh.

//+------------------------------------------------------------------+
//| Устанавливает цвета баров текущего символа                       |
//+------------------------------------------------------------------+
void SetBarsColors()
  {
//--- Цвет бара вверх, тени и окантовки тела бычьей свечи
   ChartSetInteger(0,CHART_COLOR_CHART_UP,color_bar_up);
//--- Цвет тела бычьей свечи
   ChartSetInteger(0,CHART_COLOR_CANDLE_BULL,color_bar_up);
//--- Цвет линии графика и японских свечей "Доджи"
   ChartSetInteger(0,CHART_COLOR_CHART_LINE,color_bar_up);
//--- Если включен двухцветный режим
   if(TwoColor)
     {
      //--- Цвет бара вниз, тени и окантовки тела медвежьей свечи
      ChartSetInteger(0,CHART_COLOR_CHART_DOWN,color_bar_down);
      //--- Цвет тела медвежьей свечи
      ChartSetInteger(0,CHART_COLOR_CANDLE_BEAR,color_bar_down);
     }
//--- Если двухцветный режим отключен
   else
     {
      //--- Цвет бара вниз, тени и окантовки тела медвежьей свечи
      ChartSetInteger(0,CHART_COLOR_CHART_DOWN,color_bar_up);
      //--- Цвет тела медвежьей свечи
      ChartSetInteger(0,CHART_COLOR_CANDLE_BEAR,color_bar_up);
     }
  }

Во время инициализации нужно определить, какой режим выбран во внешнем параметре StartPriceDivergence. Если выбран режим Вертикальная линия, то в глобальной переменной timeframe_start_point останется значение по умолчанию, то есть текущий таймфрейм. Иначе будет определен таймфрейм, соответствующий выбору. Для этого напишем функцию InitStartPointTF():

//+------------------------------------------------------------------+
//| Определяет таймфрейм для режима начальной точки цен              |
//+------------------------------------------------------------------+
void InitStartPointTF()
  {
//--- Если режим вертикальной линии, выйдем
   if(StartPriceDivergence==VERTICAL_LINE)
      return;
//--- Иначе определим период
   switch(StartPriceDivergence)
     {
      case MONTH : timeframe_start_point=PERIOD_MN1; break;
      case WEEK  : timeframe_start_point=PERIOD_W1;  break;
      case DAY   : timeframe_start_point=PERIOD_D1;  break;
      case HOUR  : timeframe_start_point=PERIOD_H1;  break;
     }
  }

Функция CheckInputParameters() в отличие от предыдущей статьи теперь выглядит так:

//+------------------------------------------------------------------+
//| Проверяет входные параметры на корректность                      |
//+------------------------------------------------------------------+
bool CheckInputParameters()
  {
//--- Если сейчас режим не вертикальной линии
   if(StartPriceDivergence!=VERTICAL_LINE)
     {
      //--- Если текущий период больше либо равен указанному для начальной точки расхождения цен, сообщим об этом и выйдем
      if(PeriodSeconds()>=PeriodSeconds(timeframe_start_point))
        {
         Print("Текущий таймфрейм должен быть меньше, чем в параметре Start Price Divergence!");
         Comment("Текущий таймфрейм должен быть меньше, чем в параметре Start Price Divergence!");
         return(false);
        }
     }
//---
   return(true);
  }

Инициализация массивов выглядит примерно так же, как и в предыдущей статье. Изменились только названия массивов и их количество.

//+------------------------------------------------------------------+
//| Первая инициализация массивов                                    |
//+------------------------------------------------------------------+
void InitArrays()
  {
   ArrayInitialize(limit_time,NULL);
   ArrayInitialize(symbol_difference,0.0);
   ArrayInitialize(inverse_difference,0.0);
   ArrayInitialize(series_first_date,NULL);
   ArrayInitialize(series_first_date_last,NULL);
//---
   for(int s=0; s<SYMBOLS_COUNT; s++)
     {
      ArrayInitialize(buffer_data[s].open,EMPTY_VALUE);
      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);
     }
  }
//+------------------------------------------------------------------+
//| Инициализирует массив символов                                   |
//+------------------------------------------------------------------+
void InitSymbolNames()
  {
   symbol_names[0]=AddSymbolToMarketWatch(Symbol02);
   symbol_names[1]=AddSymbolToMarketWatch(Symbol03);
   symbol_names[2]=AddSymbolToMarketWatch(Symbol04);
   symbol_names[3]=AddSymbolToMarketWatch(Symbol05);
   symbol_names[4]=AddSymbolToMarketWatch(Symbol06);
  }
//+------------------------------------------------------------------+
//| Инициализирует массив инверсий                                   |
//+------------------------------------------------------------------+
void InitInverse()
  {
   inverse[0]=Inverse02;
   inverse[1]=Inverse03;
   inverse[2]=Inverse04;
   inverse[3]=Inverse05;
   inverse[4]=Inverse06;
  }

Существенные изменения были произведены в функции SetIndicatorProperties(). Фактически это новая функция. Теперь в зависимости от того, какой режим выбран для отображения данных, во время инициализации устанавливаются соответствующие свойства.

//+------------------------------------------------------------------+
//| Устанавливает свойства индикатора                                |
//+------------------------------------------------------------------+
void SetIndicatorProperties()
  {
//--- Установим короткое имя
   IndicatorSetString(INDICATOR_SHORTNAME,indicator_shortname);
//--- Установим количество знаков
   IndicatorSetInteger(INDICATOR_DIGITS,_Digits);
//---  В режиме линия нужен только один буфер, отображающий цену открытия
   if(DrawType==LINE)
     {
      for(int s=0; s<SYMBOLS_COUNT; s++)
         SetIndexBuffer(s,buffer_data[s].close,INDICATOR_DATA);
     }
//--- В других режимах используем все цены для построения 
//    баров/свеч и дополнительный буфер для двухцветного режима
   else if(DrawType==BARS || DrawType==CANDLES)
     {
      for(int s=0; s<SYMBOLS_COUNT; s++)
        {
         static int buffer_number=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++;
        }
     }
//--- Установим метки для текущего таймфрейма
//    В режиме линия только цена открытия
   if(DrawType==LINE)
     {
      for(int s=0; s<SYMBOLS_COUNT; s++)
         PlotIndexSetString(s,PLOT_LABEL,symbol_names[s]+",Close");
     }
//--- В других режимах все цены баров/свеч
//    В качестве разделителя используется ";"
   else if(DrawType==BARS || DrawType==CANDLES)
     {
      for(int s=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");
        }
     }
//--- Установим тип линий для индикаторных буферов
//    Линия
   if(DrawType==LINE)
      for(int s=0; s<SYMBOLS_COUNT; s++)
         PlotIndexSetInteger(s,PLOT_DRAW_TYPE,DRAW_LINE);
//--- Бары
   if(DrawType==BARS)
      for(int s=0; s<SYMBOLS_COUNT; s++)
         PlotIndexSetInteger(s,PLOT_DRAW_TYPE,DRAW_COLOR_BARS);
//--- Свечи
   if(DrawType==CANDLES)
      for(int s=0; s<SYMBOLS_COUNT; s++)
         PlotIndexSetInteger(s,PLOT_DRAW_TYPE,DRAW_COLOR_CANDLES);

//--- Установим тип линий для данных текущего символа
//    Линия
   if(DrawType==LINE)
      ChartSetInteger(0,CHART_MODE,CHART_LINE);
//--- Бары
   if(DrawType==BARS)
      ChartSetInteger(0,CHART_MODE,CHART_BARS);
//--- Свечи
   if(DrawType==CANDLES)
      ChartSetInteger(0,CHART_MODE,CHART_CANDLES);

//--- Установим толщину линий
   for(int s=0; s<SYMBOLS_COUNT; s++)
      PlotIndexSetInteger(s,PLOT_LINE_WIDTH,1);
//--- Установим цвет линий для режима Линия
   if(DrawType==LINE)
      for(int s=0; s<SYMBOLS_COUNT; s++)
         PlotIndexSetInteger(s,PLOT_LINE_COLOR,line_colors[s]);
//--- Установим показ данных в Окне Данных только существующих символов
   for(int s=0; s<SYMBOLS_COUNT; s++)
     {
      if(symbol_names[s]!=empty_symbol)
         PlotIndexSetInteger(s,PLOT_SHOW_DATA,true);
      else
         PlotIndexSetInteger(s,PLOT_SHOW_DATA,false);
     }
//--- Пустое значение для построения, для которого нет отрисовки
   for(int s=0; s<SYMBOLS_COUNT; s++)
      PlotIndexSetDouble(s,PLOT_EMPTY_VALUE,EMPTY_VALUE);
  }

И, наконец, еще одна новая функция SetDivergenceLine() для использования в OnInit(). Она устанавливает вертикальную зеленую линию для управления началом расхождения цен в режиме Вертикальная линия.

//+------------------------------------------------------------------+
//| Устанавливает вертикальную линию для начальной точки цен         |
//+------------------------------------------------------------------+
void SetDivergenceLine()
  {
//--- Если вертикальной линии нет, установим ее
   if(StartPriceDivergence==VERTICAL_LINE && ObjectFind(0,start_price_divergence)<0)
      //--- Установим вертикальную линию на истинном баре
      CreateVerticalLine(0,0,TimeCurrent()+PeriodSeconds(),start_price_divergence,
                         2,STYLE_SOLID,clrGreenYellow,true,true,false,"","\n");
//--- Если не в режиме вертикальной линии
   if(StartPriceDivergence!=VERTICAL_LINE)
      DeleteObjectByName(start_price_divergence);
  }

А ниже представлено то, как выглядит все выше описанное в функции OnInit(). Когда все распределено по отдельным функциям и файлам, читать программу становится очень просто.

//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- Проверим, не используется ли сейчас индикатор в тестере
   if(!CheckTesterMode())
      return(INIT_FAILED);
//--- Установим цвет барам/свечам
   SetBarsColors();
//--- Определим период для начальной точки расхождения цен
   InitStartPointTF();
//--- Проверим корректность входных параметров
   if(!CheckInputParameters())
      return(INIT_PARAMETERS_INCORRECT);
//--- Включим таймер с интервалом 1 секунда
   EventSetMillisecondTimer(1000);
//--- Установим шрифт для отображения на холсте
   canvas.FontSet(font_name,font_size,FW_NORMAL);
//--- Инициализация массивов
   InitArrays();
//--- Инициализируем массив символов 
   InitSymbolNames();
//--- Инициализируем массив инверсий
   InitInverse();
//--- Установим свойства индикатора
   SetIndicatorProperties();
//--- Установим вертикальную линию начала расхождения цен
   SetDivergenceLine();
//--- Очистим комментарий
   Comment("");
//--- Обновим график
   ChartRedraw();
//--- Инициализация прошла успешно
   return(INIT_SUCCEEDED);
  }

В функции OnCalculate() код программы остался практически неизменным. В предыдущей статье после того, как все проверки на доступность данных были произведены, программа сначала в первом цикле заполняла вспомогательные массивы и только потом уже готовыми данными заполняла индикаторные буферы. В этот раз все уложим в один цикл.

Функции для проверки и загрузки данных я сделал более жесткими. Теперь каждое значение, которое нужно получить, проходит через указанное количество попыток с остановкой цикла, если значение получено. И так как теперь есть режимы, где понадобится определять начало того или иного периода (месяц, неделя, день, час), то получать время начала периода мы будем через старший таймфрейм. Поэтому была написана дополнительная функция подобная LoadAndFormData(), которая имеет похожее название LoadAndFormDataHighTF(). Ее код очень похож на исходный аналог, поэтому я не буду его приводить здесь.

Контрольную проверку на доступность данных текущего и старшего таймфрейма я реализовал в одной функции CheckAvailableData():

//+------------------------------------------------------------------+
//| Проверяет количество доступных данных у всех символов            |
//+------------------------------------------------------------------+
bool CheckAvailableData()
  {
   int attempts=100;
   
//---
   for(int s=0; s<SYMBOLS_COUNT; s++)
     {
      //--- Если такой символ есть
      if(symbol_names[s]!=empty_symbol)
        {
datetime time[];                    // Массив для проверки количества баров
   int      total_period_bars   =0;    // Количество баров текущего периода
   datetime terminal_first_date =NULL; // Первая дата имеющихся данных текущего периода в терминале
         //--- Получим первую дату данных текущего периода в терминале
         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)==0 || ArraySize(time)<total_period_bars)
           {
            msg_last=msg_prepare_data;
            ShowCanvasMessage(msg_prepare_data);
            OC_prev_calculated=0;
            return(false);
           }
        }
     }
//--- Если в режиме вертикальной линии для начальной точки расхождения цен, выходим
   if(StartPriceDivergence==VERTICAL_LINE)
      return(true);
   else
     {
      datetime time[];                    // Массив для проверки количества баров
      int      total_period_bars   =0;    // Количество баров текущего периода
      datetime terminal_first_date =NULL; // Первая дата имеющихся данных текущего периода в терминале
      //--- Получим первую дату данных текущего периода в терминале
      for(int i=0; i<attempts; i++)
         if((terminal_first_date=(datetime)SeriesInfoInteger(Symbol(),Period(),SERIES_FIRSTDATE))>0)
            break;
      //--- Получим количество доступных баров от указанной даты
      for(int i=0; i<attempts; i++)
         if((total_period_bars=(int)SeriesInfoInteger(Symbol(),timeframe_start_point,SERIES_BARS_COUNT))>0)
            break;
      //--- Проверим готовность данных баров
      for(int i=0; i<attempts; i++)
         //--- Скопируем указанное количество данных
         if(CopyTime(Symbol(),timeframe_start_point,
            terminal_first_date+PeriodSeconds(timeframe_start_point),TimeCurrent(),time)>0)
            break;
      //--- Если скопировано меньше данных, значит нужно совершить еще одну попытку
      if(ArraySize(time)<=0 || total_period_bars<=0)
        {
         msg_last=msg_prepare_data;
         ShowCanvasMessage(msg_prepare_data);
         OC_prev_calculated=0;
         return(false);
        }
     }
//---
   return(true);
  }

Функция FillIndicatorBuffers() существенно усложнилась для текущей задачи. Это связано с тем, что теперь есть несколько режимов, и для каждого из них нужно произвести свои действия. По сути, все можно разделить на четыре этапа.

  • Получение данных указанного символа;
  • Получение данных старшего таймфрейма, определение времени и ценового уровня, где цены всех символов смыкаются;
  • Расчет значений и заполнение индикаторного буфера;
  • Контрольная проверка.

Код функции подробно прокомментирован для изучения:

//+------------------------------------------------------------------+
//| Заполняет индикаторные буферы                                    |
//+------------------------------------------------------------------+
void FillIndicatorBuffers(int i,int s,datetime const &time[])
  {
   MqlRates    rates[];             // Структура данных
   double      period_open[];       // Цена открытия бара в начале периода расхождения цен
   datetime    period_time[];       // Время начала периода расхождения цен
   int         attempts=100;        // Количество попыток копирования
   datetime    high_tf_time=NULL;   // Время бара старшего таймфрейма

//--- Если находимся вне зоны "истинных" баров символа, выйдем
   if(time[i]<limit_time[s])
      return;
//--- Сбросим последнюю ошибку
   ResetLastError();
//--- Получим данные текущего бара указанного символа
   for(int j=0; j<attempts; j++)
      if(CopyRates(symbol_names[s],Period(),time[i],1,rates)==1)
        { ResetLastError(); break; }
//--- Если не удалось получить данные, выйдем
   if(ArraySize(rates)<1 || GetLastError()!=0)
      return;
//--- Если текущее время раньше, чем время первого периода или
//    время бара неравно времени бара текущего символа или
//    получены пустые значения
   if(rates[0].time==NULL || 
      time[i]!=rates[0].time || 
      time[i]<first_period_time || 
      rates[0].low==EMPTY_VALUE || 
      rates[0].open==EMPTY_VALUE ||
      rates[0].high==EMPTY_VALUE ||
      rates[0].close==EMPTY_VALUE)
     {
      //--- Запишем пустое значение
      if(DrawType!=LINE)
        {
         buffer_data[s].low[i]   =EMPTY_VALUE;
         buffer_data[s].open[i]  =EMPTY_VALUE;
         buffer_data[s].high[i]  =EMPTY_VALUE;
        }
      buffer_data[s].close[i]=EMPTY_VALUE;
      return;
     }
//--- Если в режиме вертикальной линии для начальной точки расхождения цен
   if(StartPriceDivergence==VERTICAL_LINE)
     {
      //--- Получим время линии
      divergence_time=(datetime)ObjectGetInteger(0,start_price_divergence,OBJPROP_TIME);
      //--- Получим время первого бара
      first_period_time=time[0];
     }
//--- Если в других режимах, то будем отслеживать начало периода
   else
     {
      //--- Если зашли сюда в первый раз, запомним данные первого бара старшего таймфрейма
      if(divergence_time==NULL)
        {
         ResetLastError();
         //--- Получим время открытия первого бара старшего таймфрейма
         for(int j=0; j<attempts; j++)
            if(CopyTime(Symbol(),timeframe_start_point,time[0]+PeriodSeconds(timeframe_start_point),1,period_time)==1)
              { ResetLastError(); break; }
         //--- Если не удалось получить цену/время, выйдем
         if(ArraySize(period_time)<1 || GetLastError()!=0)
            return;
         //--- Иначе запомним время первого бара старшего таймфрейма
         else
            first_period_time=period_time[0];
        }
      //--- Если время текущего бара на текущем таймфрейме раньше, чем время первого бара старшего таймфрейма
      if(time[i]<first_period_time)
         high_tf_time=first_period_time;
      //--- Иначе будем получать данные последнего бара старшего таймфрейма относительно текущего бара текущего таймфрейма
      else
         high_tf_time=time[i];
      //--- Обнулим последнюю ошибку
      ResetLastError();
      //--- Получим цену открытия первого бара старшего таймфрейма
      for(int j=0; j<attempts; j++)
         if(CopyOpen(Symbol(),timeframe_start_point,high_tf_time,1,period_open)==1)
           { ResetLastError(); break; }
      //--- Получим время открытия первого бара старшего таймфрейма
      for(int j=0; j<attempts; j++)
         if(CopyTime(Symbol(),timeframe_start_point,high_tf_time,1,period_time)==1)
           { ResetLastError(); break; }
      //--- Если не удалось получить цену/время, выйдем
      if(ArraySize(period_open)<1 || ArraySize(period_time)<1 || GetLastError()!=0)
         return;
      //--- Если время текущего таймфрейма раньше, чем время начала первого периода или
      //    время указанного периода неравно тому, что в памяти
      if(time[i]<first_period_time || divergence_time!=period_time[0])
        {
         symbol_difference[s]  =0.0; // Обнулим разницу цен символов
         inverse_difference[s] =0.0; // Обнулим разницу инверсии
         //--- Запомним время начала расхождения цен
         divergence_time=period_time[0];
         //--- Запомним уровень начала расхождения цен
         divergence_price=period_open[0];
         //--- Установим вертикальную линию в начале периода расхождения цен
         CreateVerticalLine(0,0,period_time[0],start_price_divergence+"_"+TimeToString(divergence_time),
                            2,STYLE_SOLID,clrWhite,false,false,true,TimeToString(divergence_time),"\n");
        }
     }
//--- Если в режиме вертикальной линии и время бара раньше, чем время линии
   if(StartPriceDivergence==VERTICAL_LINE && time[i]<divergence_time)
     {
      //--- Держим нулевые значения разницы
      symbol_difference[s]  =0.0;
      inverse_difference[s] =0.0;
      //--- Для режима отрисовки Линия только цена открытия
      if(DrawType==LINE)
         buffer_data[s].close[i]=rates[0].close-symbol_difference[s];
      //--- Для других режимов отрисовки все цены
      else
        {
         buffer_data[s].low[i]   =rates[0].low-symbol_difference[s];
         buffer_data[s].open[i]  =rates[0].open-symbol_difference[s];
         buffer_data[s].high[i]  =rates[0].high-symbol_difference[s];
         buffer_data[s].close[i] =rates[0].close-symbol_difference[s];
         //--- Установим цвет текущему элементу индикаторного буфера
         SetBufferColorIndex(i,s,rates[0].close,rates[0].open);
        }
     }
//--- Если в других режимах
   else
     {
      //--- Если нужна инверсия данных символа
      if(inverse[s])
        {
         //--- Если начался новый период, пересчитаем переменные для расчета
         if(symbol_difference[s]==0.0)
           {
            //--- Если режим вертикальной линии
            if(StartPriceDivergence==VERTICAL_LINE)
              {
               //--- Рассчитаем разницу
               symbol_difference[s]  =rates[0].open-OC_open[i];
               inverse_difference[s] =OC_open[i]-(-OC_open[i]);
              }
            //--- Для других режимов
            else
              {
               //--- Рассчитаем разницу
               symbol_difference[s]  =rates[0].open-divergence_price;
               inverse_difference[s] =divergence_price-(-divergence_price);
              }
           }
         //--- Для режима Линия только цена закрытия
         if(DrawType==LINE)
            buffer_data[s].close[i]=-(rates[0].close-symbol_difference[s])+inverse_difference[s];
         //--- Для других режимов все цены
         else
           {
            buffer_data[s].low[i]   =-(rates[0].low-symbol_difference[s])+inverse_difference[s];
            buffer_data[s].open[i]  =-(rates[0].open-symbol_difference[s])+inverse_difference[s];
            buffer_data[s].high[i]  =-(rates[0].high-symbol_difference[s])+inverse_difference[s];
            buffer_data[s].close[i] =-(rates[0].close-symbol_difference[s])+inverse_difference[s];
            //--- Установим цвет текущему элементу индикаторного буфера
            SetBufferColorIndex(i,s,rates[0].close,rates[0].open);
           }
        }
      //--- Если без инверсии, то в начале периода нужно вычитать только разницу между ценами символов
      else
        {
         //--- Если начался новый период
         if(symbol_difference[s]==0.0)
           {
            //--- Если режим вертикальной линии
            if(StartPriceDivergence==VERTICAL_LINE)
               symbol_difference[s]=rates[0].open-OC_open[i];
            //--- Для других режимов
            else
               symbol_difference[s]=rates[0].open-divergence_price;
           }
         //--- Для режима отрисовки Линия только цена открытия
         if(DrawType==LINE)
            buffer_data[s].close[i]=rates[0].close-symbol_difference[s];
         //--- Для других режимов отрисовки все цены
         else
           {
            buffer_data[s].low[i]   =rates[0].low-symbol_difference[s];
            buffer_data[s].open[i]  =rates[0].open-symbol_difference[s];
            buffer_data[s].high[i]  =rates[0].high-symbol_difference[s];
            buffer_data[s].close[i] =rates[0].close-symbol_difference[s];
            //--- Установим цвет текущему элементу индикаторного буфера
            SetBufferColorIndex(i,s,rates[0].close,rates[0].open);
           }
        }
     }
//--- Контрольная проверка рассчитанных значений
//    Для режима Линия только цена открытия
   if(DrawType==LINE)
     {
      //--- Если текущее время раньше, чем время первого периода или
      //    время бара неравно времени бара текущего символа, запишем пустое значение
      if(time[i]!=rates[0].time || time[i]<first_period_time)
         buffer_data[s].close[i]=EMPTY_VALUE;
     }
//--- Для других режимов все цены
   else
     {
      //--- Если текущее время раньше, чем время первого периода или
      //    время бара неравно времени бара текущего символа или
      //    получены пустые значения
      if(rates[0].time==NULL || 
         time[i]!=rates[0].time || 
         time[i]<first_period_time || 
         rates[0].low==EMPTY_VALUE || 
         rates[0].open==EMPTY_VALUE ||
         rates[0].high==EMPTY_VALUE ||
         rates[0].close==EMPTY_VALUE)
        {
         //--- Запишем пустое значение
         buffer_data[s].low[i]   =EMPTY_VALUE;
         buffer_data[s].open[i]  =EMPTY_VALUE;
         buffer_data[s].high[i]  =EMPTY_VALUE;
         buffer_data[s].close[i] =EMPTY_VALUE;
        }
     }
  }

При изучении функции выше вы должны были заметить еще одну пользовательскую функцию SetBufferColorIndex(). С помощью этой функции устанавливается цвет в цветовой индикаторный буфер.

//+------------------------------------------------------------------+
//| Устанавливает цвет элементу буфера по условию                    |
//+------------------------------------------------------------------+
void SetBufferColorIndex(int i,int symbol_number,double close,double open)
  {
//--- Если включен двухцветный режим, проверим условие
   if(TwoColor)
     {
      //--- Если цена закрытия больше цены открытия, то это бар вверх и используем первый цвет
      if(close>open)
         buffer_data[symbol_number].icolor[i]=0;
      //--- иначе это бар вниз и используем второй цвет
      else
         buffer_data[symbol_number].icolor[i]=1;
     }
//--- Если одноцветный режим, то используем для всех баров/свечей первый цвет
   else
      buffer_data[symbol_number].icolor[i]=0;
  }

После того, как индикаторные буферы заполнены, нужно определить максимум и минимум из всех тех значений, которые в текущий момент видны в окне графика. В MQL5 есть возможность получать номер первого видимого бара в окне графика, а также количество видимых баров. Эти возможности и будем использовать в еще одной пользовательской функции CorrectChartMaxMin(). Функцию можно разделить на несколько этапов:

  • Определение номера первого и последнего видимых баров;
  • Определение максимума и минимума видимых баров текущего символа;
  • Определение максимума и минимума среди всех массивов символов;
  • Установка максимума и минимума в свойствах графика.

Ниже представлен код функции CorrectChartMaxMin(), которая находится в файле Chart.mqh.

//+------------------------------------------------------------------+
//| Корректирует максимум/минимум графика относительно всех буферов  |
//+------------------------------------------------------------------+
void CorrectChartMaxMin()
  {
   double low[];                  // Массив минимумов
   double high[];                 // Массив максимумов
   int    attempts          =10;  // Кол-во попыток
   int    array_size        =0;   // Размер массива для рисования
   int    visible_bars      =0;   // Количество видимых баров
   int    first_visible_bar =0;   // Номер первого видимого бара
   int    last_visible_bar  =0;   // Номер последнего видимого бара
   double max_price         =0.0; // Максимальная цена
   double min_price         =0.0; // Минимальная цена
   double offset_max_min    =0.0; // Отступ от максимума/минимума графика
//--- Обнулим последнюю ошибку
   ResetLastError();
//--- Количество видимых баров
   visible_bars=(int)ChartGetInteger(0,CHART_VISIBLE_BARS);
//--- Номер первого видимого бара
   first_visible_bar=(int)ChartGetInteger(0,CHART_FIRST_VISIBLE_BAR);
//--- Номер последнего видимого бара
   last_visible_bar=first_visible_bar-visible_bars;
//--- Если есть ошибка, выйдем
   if(GetLastError()!=0)
      return;
//--- Если значение некорректно, исправим
   if(last_visible_bar<0)
      last_visible_bar=0;
//--- Получим максимум и минимум текущего символа на видимой части графика
   for(int i=0; i<attempts; i++)
      if(CopyHigh(Symbol(),Period(),last_visible_bar,visible_bars,high)==visible_bars)
         break;
   for(int i=0; i<attempts; i++)
      if(CopyLow(Symbol(),Period(),last_visible_bar,visible_bars,low)==visible_bars)
         break;
//--- Выйдем, если данные не были получены
   if(ArraySize(high)<=0 || ArraySize(low)<=0)
      return;
//--- Если данные получены, определим в массивах текущего символа максимум и минимум
   else
     {
      min_price=low[ArrayMinimum(low)];
      max_price=high[ArrayMaximum(high)];
     }
//--- Получим максимальную и минимальную цены на всех ценовых массивах
   for(int s=0; s<SYMBOLS_COUNT; s++)
     {
      //--- Если такого символа нет, то перейти к следующему
      if(symbol_names[s]==empty_symbol)
         continue;
      //---
      datetime time[];         // Массив времени
      int      bars_count=0; // Количество баров для расчета
      //--- Установим нулевой размер массивам
      ArrayResize(high,0);
      ArrayResize(low,0);
      //--- Получим время первого видимого на графике бара
      for(int i=0; i<attempts; i++)
         if(CopyTime(Symbol(),Period(),last_visible_bar,visible_bars,time)==visible_bars)
            break;
      //--- Если данных меньше, чем видимых баров на графике, выйдем
      if(ArraySize(time)<visible_bars)
         return;
      //--- Если время первого "истинного" бара больше, чем
      //    время первого видимого бара на графике, то
      //    получим доступное количество баров текущего в цикле символа
      if(limit_time[s]>time[0])
        {
         //--- Получим размер массива
         array_size=ArraySize(time);
         //--- Получим количество баров от первого истинного
         if((bars_count=Bars(Symbol(),Period(),limit_time[s],time[array_size-1]))<=0)
            return;
        }
      //--- Иначе получим видимое количество баров на графике
      else
         bars_count=visible_bars;
      //--- Установим индексацию, как в таймсериях
      ArraySetAsSeries(low,true);
      ArraySetAsSeries(high,true);
      //--- Скопируем данные из индикаторных буферов
      //    Все режимы кроме Линия
      if(DrawType!=LINE)
        {
         ArrayCopy(low,buffer_data[s].low);
         ArrayCopy(high,buffer_data[s].high);
        }
      //--- Если в режиме Линия
      else
        {
         ArrayCopy(low,buffer_data[s].close);
         ArrayCopy(high,buffer_data[s].close);
        }
      //--- Получим размер массива
      array_size=ArraySize(high);
      //--- Заполним пустые значения,
      //    чтобы они не учитывались в определении максимума/минимума
      for(int i=0; i<array_size; i++)
        {
         if(high[i]==EMPTY_VALUE)
            high[i]=max_price;
         if(low[i]==EMPTY_VALUE)
            low[i]=min_price;
        }
      //--- Определим максимум/минимум, учитывая инверсию
      if(inverse[s])
        {
         //--- Если ошибок нет, запомним значения
         if(ArrayMaximum(high,last_visible_bar,bars_count)>=0 && 
            ArrayMinimum(low,last_visible_bar,bars_count)>=0)
           {
            max_price=fmax(max_price,low[ArrayMaximum(low,last_visible_bar,bars_count)]);
            min_price=fmin(min_price,high[ArrayMinimum(high,last_visible_bar,bars_count)]);
           }
        }
      else
        {
         //--- Если ошибок нет, запомним значения
         if(ArrayMinimum(low,last_visible_bar,bars_count)>=0 && 
            ArrayMaximum(high,last_visible_bar,bars_count)>=0)
           {
            min_price=fmin(min_price,low[ArrayMinimum(low,last_visible_bar,bars_count)]);
            max_price=fmax(max_price,high[ArrayMaximum(high,last_visible_bar,bars_count)]);
           }
        }
     }
//--- Рассчитаем отступ (3%) от верха и низа графика
   offset_max_min=((max_price-min_price)*3)/100;
//--- Включим режим фиксированного масштаба графика
   ChartSetInteger(0,CHART_SCALEFIX,true);
//--- Установим максимум/минимум
   ChartSetDouble(0,CHART_FIXED_MAX,max_price+offset_max_min);
   ChartSetDouble(0,CHART_FIXED_MIN,min_price-offset_max_min);
//--- Обновим график
   ChartRedraw();
  }

Описанную выше функцию мы будем использовать при обработке события перетаскивания вертикальной линии (и, разумеется, при расчете значений индикатора в OnCalculate):

//+------------------------------------------------------------------+
//| ChartEvent function                                              |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,
                  const long &lparam,
                  const double &dparam,
                  const string &sparam)
  {
//--- Событие перетаскивания графического объекта
   if(id==CHARTEVENT_OBJECT_DRAG)
     {
      //--- Если сейчас режим вертикальной линии для начальной точки расхождения цен, обновим индикаторные буферы
      if(StartPriceDivergence==VERTICAL_LINE)
         OnCalculate(OC_rates_total,
                     0,
                     OC_time,
                     OC_open,
                     OC_high,
                     OC_low,
                     OC_close,
                     OC_tick_volume,
                     OC_volume,
                     OC_spread);
     }
//--- Изменение размеров графика или изменение свойств графика через диалог свойств
   if(id==CHARTEVENT_CHART_CHANGE)
      //--- Скорректируем максимум и минимум графика относительно значений в индикаторных буферах
      CorrectChartMaxMin();
  }

Все функции готовы. Более подробно с подробно прокомментированным кодом можно ознакомиться в приложении к статье.

Продемонстрируем, что же в итоге получилось. По умолчанию во внешних параметрах установлены символы GBPUSD, AUDUSD, NZDUSD, USDCAD, USDCHF. На скриншоте ниже показан недельный график EURUSD в режиме Вертикальная линия с отключенной инверсией:

Недельный таймфрейм в режиме "Вертикальная линия"

Рис. 1 - Недельный таймфрейм в режиме "Вертикальная линия"

На скриншоте ниже 30-ти минутный таймфрейм в режиме День, но только на этот раз у символов с базовой валютой USD включен режим инверсии. В данном случае это USDCAD (светло-голубые свечи) и USDCHF (сиреневые свечи).

30-минутный таймфрейм в режиме "День"

Рис. 2 - 30-минутный таймфрейм в режиме "День"


Заключение

Думаю, получился довольно интересный и информативный инструмент для мультивалютного анализа расхождения цен. Этот индикатор можно неограниченно развивать в лучшую сторону.

Спасибо за внимание!

Прикрепленные файлы |
Последние комментарии | Перейти к обсуждению на форуме трейдеров (19)
Anatoli Kazharski
Anatoli Kazharski | 26 нояб. 2014 в 10:47
Argo:
Много уважаемый автор мне в целом все понравилось . Вы помогли мне решить ряд задач не прибегая к "интенсивной мозговой деятельности". Но пожалуйста либо перепишите код либо опишите как , с вашей точки зрения, его устанавливать, а то мне понадобилось 2 часа для того чтобы подправить код  и установить его по классической схеме. Да и в обсуждении можно поменьше сленга здесь не все ВОЛКИ программирования, к которым я себя тоже не отношу. 

А что не получалось и что пришлось исправлять?

И еще я пишу серию статей и в одной из них хочу поднять вопрос корреляции и хотел бы сделать ссылку на ваш материал, а для иллюстрации своих слов вставить скриншот с изображением индюка написанного по вашему алгоритму с моими изменениями. Для этого прошу вашего разрешения. Обещаю что код данного индикатора публиковаться не будет. 

Любой код из статей используйте в своих целях так, как вам хочется. Для этого код и выкладывается, чтобы каждый мог его использовать в своих целях. Просто, когда будете писать статью, указывайте откуда были взяты примеры. Это общепринятые везде правила.

Anatoli Kazharski
Anatoli Kazharski | 26 нояб. 2014 в 10:48
Edic:
Глянул статью. Там вроде  не обойдена проблемма "пустых" баров.  Без этого, думаю, мультивалютку трогать не стоит.
Эта статья была продолжением темы. Посмотрите также и предыдущую: Рецепты MQL5 - Разработка мультивалютного индикатора волатильности на MQL5.
Edic-
Edic- | 26 нояб. 2014 в 17:00
tol64:
Эта статья была продолжением темы. Посмотрите также и предыдущую: Рецепты MQL5 - Разработка мультивалютного индикатора волатильности на MQL5.
Спасибо. Теперь ясно.
Rashid Umarov
Rashid Umarov | 26 нояб. 2014 в 17:03
Edic-:
Спасибо. Теперь ясно.

Самое интересное, что в обсуждаемой статье сразу говорится об этом

В этой статье рассмотрим разработку мультивалютного индикатора для анализа расхождения цен за указанный период времени. Многие основные моменты уже рассматривались в предыдущей статье по программированию мультивалютных индикаторов Рецепты MQL5 - Разработка мультивалютного индикатора волатильности на MQL5. Поэтому на этот раз будем останавливаться только на новых функциях и тех функциях, которые претерпели сильные изменения. Если вы впервые рассматриваете тему программирования мультивалютных индикаторов, то рекомендуется в первую очередь прочитать предыдущую статью.

Vasily Pototsky
Vasily Pototsky | 22 окт. 2020 в 13:07

Интересная статья, спасибо. Но прошло некоторое время и возможно что то изменилось в mql. Теперь индикатор выдаёт такую ошибку в файле Checks.mqh и зависает. Подскажите, в чём тут причина?


Ошибка


Ошибка 2

Обновление на новый MetaTrader 4 билд 600 и выше Обновление на новый MetaTrader 4 билд 600 и выше
В новой версии терминала MetaTrader 4 была изменена структура хранения пользовательских данных. Если раньше все программы, шаблоны, профили и т.д. хранились прямо в папке установки терминала, то теперь данные, необходимые для работы конкретного пользователя терминала, хранятся в отдельной специальной папке, называемой каталог данных. В этой статье собраны ответы на популярные вопросы.
Структура данных в MetaTrader 4 build 600 и выше Структура данных в MetaTrader 4 build 600 и выше
Начиная с 600 билда MetaTarder 4, изменилась структура, а также место хранения файлов клиентского терминала. Теперь MQL4-программы разнесены по отдельным каталогам в зависимости от типа программы (эксперты, индикаторы, скрипты). Данные терминала в большинстве случаев теперь хранятся в специальном каталоге данных отдельно от места установки терминала. В данной статье мы подробно опишем, каким образом осуществляется перенос данных, а также причины введения такой системы хранения.
Видео: Как работают торговые сигналы на платформе MetaTrader Видео: Как работают торговые сигналы на платформе MetaTrader
Короткий видеоролик за 15 минут расскажет и покажет, что такое торговые сигналы, как оформить на них подписку и как самому стать Поставщиком. В видео максимально подробно показаны все особенности нашего сервиса. После внимательного просмотра материала вы сможете самостоятельно подписаться на любой Сигнал из обширной базы или начать продавать собственные сигналы.
SQL и MQL5: Работаем с базой данных SQLite SQL и MQL5: Работаем с базой данных SQLite
Данная статья рассчитана на программистов, проявившим интерес к использованию SQL в своих проектах. В статье читателям представляется функциональность SQLite, а также рассматриваются ее преимущества. Статья не требует знание функций SQLite, но минимальные знания SQL приветствуются.