English 中文 Español Deutsch 日本語 Português 한국어 Français Italiano Türkçe
Создание тиковых индикаторов

Создание тиковых индикаторов

MetaTrader 5Индикаторы | 4 апреля 2010, 11:53
13 520 14
Denis Zyatkevich
Denis Zyatkevich

Введение

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

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

Создание тикового индикатора

Напишем на языке MQL5 индикатор, строящий тиковый график цены. Пример работы такого индикатора можно увидеть на рисунке:

Рис.1. Тиковый график. 

Рисунок 1. Тиковый график

Индикатор строит две линии: Bid и Ask. Отображение каждой из них можно отключать в настройках индикатора.

Цены, поступающие от брокера по текущему финансовому инструменту, индикатор записывает в файл в текстовом виде: серверное время котировки, цена Bid и цена Ask:

2010.03.26 19:43:02 1.33955 1.33968

Имя файла соответствует финансовому инструменту (например, EURUSD.txt). Файлы располагаются по следующему пути: каталог_установки_MT5\MQL5\Files. В настройках индикатора можно указать дополнительный каталог для файла, а также префикс в имени файла перед названием финансового инструмента (это может быть полезно, если на одном финансовом инструменте запущено несколько индикаторов, создающих такие файлы).

Для создания индикатора запускаем торговый терминал MetaTrader 5, при помощи клавиши F4 вызываем Редактор MetaQuotes Language и начинаем писать текст программы.

Укажем, что индикатор должен строиться в отдельном окне под графиком цены:

// индикатор выводится в отдельное окно
#property indicator_separate_window

На индикаторе будут отображены две линии: Bid и Ask, соответственно будет два графических построения:

// два графических построения: для линий Bid и Ask
#property indicator_plots 2

И два буфера, содержащие данные, предназначенные для отображения на графике:

// два индикаторных буфера
#property indicator_buffers 2

Для каждой из линий индикатора укажем тип линии DRAW_LINE - линия, цвет, стиль рисования STYLE_SOLID - сплошная линия, текстовую метку "Bid" или "Ask":

// для линии Bid тип линии построения - линия
#property indicator_type1 DRAW_LINE
// задание цвета для линии Bid
#property indicator_color1 Red
// задание стиля отрисовки линии Bid
#property indicator_style1 STYLE_SOLID
// задание текстовой метки для линии Bid
#property indicator_label1 "Bid"
// для линии Ask тип линии построения - линия
#property indicator_type2 DRAW_LINE
// задание цвета для линии Ask
#property indicator_color2 Blue
// задание стиля отрисовки линии Ask
#property indicator_style2 STYLE_SOLID
// задание текстовой метки для линии Ask
#property indicator_label2 "Ask"

 Укажем входные переменные, значения которых пользователь может изменять из меню свойств индикатора: 

// переменная BidLineEnable определяет, показывать ли линию Bid
input bool BidLineEnable=true; // Show Bid Line
// переменная AskLineEnable определяет, показывать ли линию Ask
input bool AskLineEnable=true; // Show Ask Line
// переменная path_prefix задает путь и префикс к имени файла
input string path_prefix=""; // FileName Prefix

Переменные BidLineEnable и AskLineEnable позволяют включать и выключать отображение линий Bid и Ask индикатора. Переменная path_prefix позволяет указать префикс в имени файла, который будет располагаться перед названием финансового инструмента. Также, при помощи этой переменной, можно указать путь к вложенному каталогу, например, если path_prefix="MyBroker/test_", путь для файлов будет следующий:  "каталог_установки_MT5\MQL5\Files\MyBroker", а для инструмента EUR/USD имя файла будет "test_EURUSD.txt".

На глобальном уровне объявим переменные, которые будут использоваться в различных функциях индикатора и значения которых будут сохраняться между вызовами индикатора:

// переменная tick_stored содержит количество хранящихся котировок
int ticks_stored;
// массивы BidBuffer[] и AskBuffer[] - буферы для индикаторных линий
double BidBuffer[],AskBuffer[];

Переменную tick_stored  будем использовать для хранения количества имеющихся в наличии котировок. Динамические массивы BidBuffer[] и AskBuffer[] будут являться индикаторными буферами, в них будут храниться ценовые данные, отображаемые на графике в виде линий Bid и Ask.

В функции OnInit указываем, что в массивах BidBuffer[]  и AskBuffer[]  содержаться данные для отрисовки. Указываем, что значения 0 в индикаторных буферах не нужно отображать на графике.

void OnInit()
  {
   // массив BidBuffer[] является индикаторным буфером
   SetIndexBuffer(0,BidBuffer,INDICATOR_DATA);
   // массив AskBuffer[] является индикаторным буфером
   SetIndexBuffer(1,AskBuffer,INDICATOR_DATA);
   // не отрисовываются нулевые значения линии Bid
   PlotIndexSetDouble(0,PLOT_EMPTY_VALUE,0);
   // не отрисовываются нулевые значения линии Ask
   PlotIndexSetDouble(1,PLOT_EMPTY_VALUE,0);
  }

Теперь создаем функцию OnCalculate, перечислим все параметры, которые передаются в эту функцию при ее вызове: 

int OnCalculate(const int rates_total,
                const int prev_calculated,
                const datetime &time[],
                const double &open[],
                const double &high[],
                const double &low[],
                const double &close[],
                const long &tick_volume[],
                const long &volume[],
                const int &spread[])
  {

Объявляем переменные:

// переменная file_handle - указатель (хэндл) файла;
// BidPosition и AskPosition - позиция начала цен Bid и Ask в строке;
// line_string_len - длина строки, прочитанной из файла, i - счетчик цикла;
int file_handle,BidPosition,AskPosition,line_string_len,i;
// переменная last_price_bid - последняя поступившая котировка Bid
double last_price_bid=SymbolInfoDouble(Symbol(),SYMBOL_BID);
// переменная last_price_ask - последняя поступившая котировка Ask
double last_price_ask=SymbolInfoDouble(Symbol(),SYMBOL_ASK);
// переменная filename - имя файла, file_buffer - буфер для
// записываемой в файл и читаемой из файла строки
string filename,file_buffer;

Переменная целого типа file_handle будет использоваться для хранения хэндла (указателя) файла в файловых операциях, BidPosition и AskPosition - для хранения позиции начала цен Bid и Ask в строке, line_string_len - для длины строки, прочитанной из файла, i  - в качестве счетчика цикла. В вещественных переменных last_price_bid и last_price_ask хранятся последние поступившие цены Bid и Ask. Строковая переменная filename используется для хранения имени файла, file_buffer - для строки, прочитанной из файла или записываемой в него.

Составляем имя файла из переменной path_prefix, названия финансового инструмента и символов ".txt". Использование функции StringConcatenate более предпочтительно, чем объединение строковых данных с помощью оператора сложения, так как работает быстрее и экономнее по памяти.

// формирование имени файла из переменной path_prefix, названия
// финансового инструмента и символов ".txt"
StringConcatenate(filename,path_prefix,Symbol(),".txt");

Открываем файл с помощью функции FileOpen для дальнейшей работы с ним:

// открытие файла для чтения и записи, кодировка ANSI, совместный доступ по чтению
file_handle=FileOpen(filename,FILE_READ|FILE_WRITE|FILE_ANSI|FILE_SHARE_READ);

Используем флаги FILE_READ - так как будет проводиться чтение из файла, FILE_WRITE - будет и запись, FILE_ANSI - используется кодировка ANSI (по умолчанию Unicode), FILE_SHARE_READ - разрешаем доступ к файлу для чтения другим приложениям во время работы с ним.

Если это первый запуск индикатора и данных еще нет (или была произведена смена периода графика):

// если это первый запуск функции OnCalculate, производится чтение котировок из файла
if(prev_calculated==0)
  {
   // чтение первой строки из файла и определение длинны строки
   line_string_len=StringLen(FileReadString(file_handle))+2;
   // если файл большой (содержит больше котировок, чем количество rates_total/2)
   if(FileSize(file_handle)>(ulong)line_string_len*rates_total/2)
     {
      // установка указателя файла для чтения последних rates_total/2 котировок
      FileSeek(file_handle,-line_string_len*rates_total/2,SEEK_END);
      // перемещение указателя файла в начало следующей строки
      FileReadString(file_handle);
     }
     // если файл небольшой
     else
     {
     // перемещение указателя файла в начало файла
     FileSeek(file_handle,0,SEEK_SET);
     }
   // сброс счетчика хранящихся котировок
   ticks_stored=0;
   // чтение до конца файла
   while(FileIsEnding(file_handle)==false)
     {
      // чтение строки из файла
      file_buffer=FileReadString(file_handle);
      // обработка прочитанной строки, если ее длина больше 6-ти символов
      if(StringLen(file_buffer)>6)
        {
         // нахождение начала цены Bid в строке
         BidPosition=StringFind(file_buffer," ",StringFind(file_buffer," ")+1)+1;
         // нахождение начала цены Ask в строке
         AskPosition=StringFind(file_buffer," ",BidPosition)+1;
         // если линия Bid отображается, занесение цены Bid в массив BidBuffer[]
       if(BidLineEnable) BidBuffer[ticks_stored]=StringToDouble(StringSubstr(file_buffer,BidPosition,AskPosition-BidPosition-1));
         // если линия Ask отображается, занесение цены Ask в массив AskBuffer[]
       if(AskLineEnable) AskBuffer[ticks_stored]=StringToDouble(StringSubstr(file_buffer,AskPosition));
         // увеличение счетчика хранящихся котировок
         ticks_stored++;
        }
     }
  }

Для того, чтобы количество хранящихся котировок не превышало количество баров на графике цены, ограничим количество загружаемых из файла котировок половиной количества баров на графике. Читаем первую строку из файла и определяем ее длину. К длине строки добавляем число два, так как в конце строки находятся еще два байта - коды 13 и 10 ("перевод строки" и "возврат каретки").

Будем считать, что средняя длина остальных строк файла такая же. Если длина файла больше, чем произведение длины одной строки на количество rates_total/2 (то есть, если файл содержит большее количество котировок, чем rates_total/2), то будет производиться чтение только последних rates_total/2 котировок. Для этого устанавливается файловый указатель на расстоянии произведения длины строки на rates_total/2 от конца файла и читаем одну строку для того, чтобы выровнять файловый указатель на границу начала строки.

Обращаю внимание, что в операторе if производится сравнение двух величин, одна из которых - длина файла - имеет тип ulong, а выражение в правой части - тип int. Поэтому производим явное приведение типа данных правой части выражения к типу ulong.

Если файл содержит меньшее количество котировок, чем rates_total/2, то перемещаем файловый указатель в начало файла. 

Сбрасываем счетчик котировок и читаем строки из файла, пока не дойдем до конца. Обрабатываем строки только размером более шести символов - это минимальная длина строки, содержащая по одному символу на дату, время, цены Bid и Ask и разделительные пробелы между ними. Извлекаем из строки, прочитанной из файла, значения цен Bid и Ask, если соответствующая линия должна отображаться. Увеличиваем счетчик котировок.

Если данные уже были прочитаны ранее, при помощи функции FileSeek перемещаем файловый указатель в конец файла (новые данные записываются в конец файла). При помощи функции StringConcatenate сформируем строку, которая будет записана в файл при помощи функции FileWrite. Заносим новые значения цен Bid и Ask в массивы BidBuffer[] и AskBuffer[], если соответствующая линия должна отображаться и увеличиваем счетчик котировок.

   // если данные из файла уже прочитаны ранее
else
  {
   // перемещение файлового указателя в конец файла
   FileSeek(file_handle,0,SEEK_END);
   // формирование строки, предназначенной для записи в файл
   StringConcatenate(file_buffer,TimeCurrent()," ",DoubleToString(last_price_bid,_Digits)," ",DoubleToString(last_price_ask,_Digits));
   // запись строки в файл
   FileWrite(file_handle,file_buffer);
   // если линия Bid отображается, добавление в массив BidBuffer[] последней цены Bid
   if(BidLineEnable) BidBuffer[ticks_stored]=last_price_bid;
   // если линия Ask отображается, добавление в массив AskBuffer[] последней цены Ask
   if(AskLineEnable) AskBuffer[ticks_stored]=last_price_ask;
   // увеличение счетчика хранящихся котировок
   ticks_stored++;
  }

Может возникнуть вопрос, почему бы не прочитать данные из файла в функции OnInit? В функции OnInit еще не определен размер динамических массивов BidBuffer[] и AskBuffer[], он назначается только при вызове функции OnCalculate.

Закрываем открытый ранее файл:

// закрытие файла
FileClose(file_handle);

Если после чтения данных из файла или добавления новых цен в массивы BidBuffer[]  и AskBuffer[]  количество хранящихся  котировок будет равняться количеству баров на ценовом графике или превысит его, будет удалена половина более старых котировок, а оставшиеся будут смещены на их место.

// если количество котировок больше, чем количество баров на графике
if(ticks_stored>=rates_total)
  {
   // удаление первых tick_stored/2 котировок и смещение остальных
   for(i=ticks_stored/2;i<ticks_stored;i++)
     {
      // если линия Bid отображается, смещение данных в массиве BidBuffer[] на tick_stored/2 вначало
      if(BidLineEnable) BidBuffer[i-ticks_stored/2]=BidBuffer[i];
      // если линия Ask отображается, смещение данных в массиве AskBuffer[] на tick_stored/2 вначало
      if(AskLineEnable) AskBuffer[i-ticks_stored/2]=AskBuffer[i];
     }
   // изменение счетчика котировок
   ticks_stored-=ticks_stored/2;
  }

Массивы буферов индикатора BidBuffer[]  и AskBuffer[]  не являются таймсериями, самый последний, "свежий" элемент в них имеет индекс ticks_stored-1, а самый последний, "свежий" бар на графике цены имеет индекс rates_total-1. Чтобы их совместить на одном уровне, смещаем линии индикатора при помощи функции PlotIndexSetInteger:

// смещение линии Bid для выравнивания с графиком цены
PlotIndexSetInteger(0,PLOT_SHIFT,rates_total-ticks_stored);
// смещение линии Ask для выравнивания с графиком цены
PlotIndexSetInteger(1,PLOT_SHIFT,rates_total-ticks_stored);

заносим в элемент массивов BidBuffer[]  и AskBuffer[]  под индексом rates_total-1 значения последних поступивших цен (если соответствующие линии должны отображаться) для отображения их в левом верхнем углу индикатора:

// если линия Bid отображается, занесение в последний элемент массива BidBuffer[]
// последней цены Bid для отображения ее в окне индикатора
if(BidLineEnable) BidBuffer[rates_total-1]=last_price_bid;
// если линия Ask отображается, занесение в последний элемент массива AskBuffer[]
// последней цены Ask для отображения ее в окне индикатора
if(AskLineEnable) AskBuffer[rates_total-1]=last_price_ask;

Завершаем выполнение функции OnCalculate (возвращаем количество баров, можно вернуть любое число, отличное от нуля) и закрываем фигурную скобку.

// возврат из функции OnCalculate(), возвращается значение, отличное от нуля
return(rates_total);
}

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

Создание индикатора "тиковых свечей"

Теперь напишем индикатор, строящий так называемые "тиковые свечи". В отличие от обычного свечного графика, в котором каждая свеча соответствует определенному интервалу времени, в индикаторе "тиковых свечей" каждая свеча не привязана ко времени, а включает в себя определенное количество котировок, поступивших от брокера. Вид такого индикатора изображен на рисунке:

Рис.2. Индикатор 'тиковых свечей'.

Рисунок 2. Индикатор "тиковых свечей"

Индикатор "тиковых свечей", так же, как и тиковый индикатор, рассмотренный ранее, записывает все приходящие котировки по данному инструменту в файл. Формат записи и расположение файла такие же. В параметрах индикатора можно указать путь и префикс имени файла, количество котировок, соответствующее одной свече, какие цены используются для построения индикатора - Bid или Ask.

Для написания индикатора запускаем торговый терминал MetaTrader 5 и из него запускаем Редактор MetaQuotes Language, нажав клавишу F4.

Укажем, что индикатор строится не на графике цены, а в отдельном окне под ним:

// индикатор выводится в отдельное окно
#property indicator_separate_window

Индикатор имеет только одно графическое построение: цветные свечи.

// используется одно графическое построение - цветные свечи
#property indicator_plots 1

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

// для свечей индикатора необходимо 4 буфера для цен OHLC и один - для индекса цвета
#property indicator_buffers 5

Укажем тип графического построения: DRAW_COLOR_CANDLES - разноцветные свечи.

// задание типа графического построения - цветные свечи
#property indicator_type1 DRAW_COLOR_CANDLES

Укажем цвета, которые будут использоваться при построении свечей:

// задание цветов для раскраски свечей
#property indicator_color1 Gray,Red,Green

Создадим данные перечислимого типа price_types, которые могут принимать значения Bid или Ask:

// объявление данных перечислимого типа
enum price_types
  {
   Bid,
   Ask
  };

Укажем входные переменные, значение которых пользователь может изменять из меню свойств индикатора:

// входная переменная ticks_in_candle указывает количество котировок,
// соответствующее одной свече
input int ticks_in_candle=16; //Tick Count in Candles
// входная переменная applied_price типа price_types указывает
// какие цены используются при построении индикатора - Bid или Ask
input price_types applied_price=0; // Price
// входная переменная path_prefix задает путь и префикс к имени файла
input string path_prefix=""; // FileName Prefix

Переменная ticks_in_candle  указывает количество котировок, соответствующее одной свече индикатора. Переменная applied_price  указывает, какие цены используются для построения графика: Bid или Ask. В переменной path_prefix можно указать каталог, в котором будет находиться создаваемый файл с историческими котировками и префикс в имени файла перед названием финансового инструмента.

На глобальном уровне объявим переменные, значения которых должны сохраняться при повторном вызове функций индикатора:

// переменная ticks_stored содержит количество хранящихся котировок
int ticks_stored;
// массив TicksBuffer[] используется для хранения поступивших цен,
// массивы OpenBuffer[], HighBuffer[], LowBuffer[] и CloseBuffer[]
// используются для хранения цен OHLC отображаемых свечей, массив
// ColorIndexBuffer[] используется для хранения индекса цвета свечей
double TicksBuffer[],OpenBuffer[],HighBuffer[],LowBuffer[],CloseBuffer[],ColorIndexBuffer[];

Переменная ticks_stored используется для хранения количества имеющихся котировок. Массив TicksBuffer[] используется для хранения значений цен поступивших котировок, а массивы OpenBuffer[], HighBuffer[], LowBuffer[] и CloseBuffer[] - для хранения значений цен открытия, максимальной, минимальной и цены закрытия свечей, которые будут отображаться на графике. Массив ColorIndexBuffer[] используется для хранения индексов цветов свечей.

В функции OnInit указываем, что массивы OpenBuffer[], HighBuffer[], LowBuffer[] и CloseBuffer[] используются как индикаторные буферы, массив ColorIndexBuffer[] содержит индексы цветов свечей, а массив TicksBuffer[]  используется для промежуточных вычислений:

void OnInit()
  {
   // массив OpenBuffer[] является индикаторным буфером
   SetIndexBuffer(0,OpenBuffer,INDICATOR_DATA);
   // массив HighBuffer[] является индикаторным буфером
   SetIndexBuffer(1,HighBuffer,INDICATOR_DATA);
   // массив LowBuffer[] является индикаторным буфером
   SetIndexBuffer(2,LowBuffer,INDICATOR_DATA);
   // массив CloseBuffer[] является индикаторным буфером
   SetIndexBuffer(3,CloseBuffer,INDICATOR_DATA);
   // массив ColorIndexBuffer[] является буфером индекса цвета
   SetIndexBuffer(4,ColorIndexBuffer,INDICATOR_COLOR_INDEX);
   // массив TicksBuffer[] служит для промежуточных вычислений
   SetIndexBuffer(5,TicksBuffer,INDICATOR_CALCULATIONS);

Укажем, что массивы OpenBuffer[], HighBuffer[], LowBuffer[], CloseBuffer[]  и ColorIndexBuffer[] будут таймсериями (то есть самые "свежие" данные имеют индекс 0):

   // индексация в массиве OpenBuffer[] будет производиться как в таймсериях
   ArraySetAsSeries(OpenBuffer,true);
   // индексация в массиве HighBuffer[] будет производиться как в таймсериях
   ArraySetAsSeries(HighBuffer,true);
   // индексация в массиве LowBuffer[] будет производиться как в таймсериях
   ArraySetAsSeries(LowBuffer,true);
   // индексация в массиве CloseBuffer[] будет производиться как в таймсериях
   ArraySetAsSeries(CloseBuffer,true);
   // индексация в массиве ColorIndexBuffer[] будет производиться как в таймсериях
   ArraySetAsSeries(ColorIndexBuffer,true);

Укажем, что значения 0 индикаторных буферов не должны отображаться на графике:

   // нулевые значения в графическом построении 0 (цены Open) не отрисовываются
   PlotIndexSetDouble(0,PLOT_EMPTY_VALUE,0);
   // нулевые значения в графическом построении 1 (цены High) не отрисовываются
   PlotIndexSetDouble(1,PLOT_EMPTY_VALUE,0);
   // нулевые значения в графическом построении 2 (цены Low) не отрисовываются
   PlotIndexSetDouble(2,PLOT_EMPTY_VALUE,0);
   // нулевые значения в графическом построении 3 (цены Close) не отрисовываются
   PlotIndexSetDouble(3,PLOT_EMPTY_VALUE,0);
  }

На этом написание функции OnInit завершено (в конце ставим закрывающую фигурную скобку).

Пишем функцию OnCalculate. Укажем все параметры, которые передаются в функцию при ее вызове.

int OnCalculate(const int rates_total,
                const int prev_calculated,
                const datetime& time[],
                const double& open[],
                const double& high[],
                const double& low[],
                const double& close[],
                const long& tick_volume[],
                const long& volume[],
                const int& spread[])
  {

Объявляем переменные, которые будут использоваться в функции OnInit.

// переменная file_handle - указатель (хэндл) файла;
// BidPosition и AskPosition - позиция начала цен Bid и Ask в строке;
// line_string_len - длина строки, прочитанной из файла,
// CandleNumber - номер свечи, для которой определяются значения цен OHLC,
// i - счетчик цикла;
int file_handle,BidPosition,AskPosition,line_string_len,CandleNumber,i;
// переменная last_price_bid - последняя поступившая котировка Bid
double last_price_bid=SymbolInfoDouble(Symbol(),SYMBOL_BID);
// переменная last_price_ask - последняя поступившая котировка Ask
double last_price_ask=SymbolInfoDouble(Symbol(),SYMBOL_ASK);
// переменная filename - имя файла, file_buffer - буфер для
// записываемой в файл и читаемой из файла строки
string filename,file_buffer;

Переменная целого типа file_handle использоваться для хранения хэндла (указателя) файла в файловых операциях, BidPosition и AskPosition - для хранения позиции начала цен Bid и Ask в строке, line_string_len - для длины строки, прочитанной из файла CandleNumber - номера свечи, для которой определяются значения цен OHLC, i - в качестве счетчика цикла.

В вещественных переменных last_price_bid и last_price_ask хранятся последние поступившие цены Bid и Ask. Строковая переменная filename используется для хранения имени файла, file_buffer - для строки, прочитанной из файла или записываемой в него.

Так как размер массива TicksBuffer[]  не устанавливается автоматически, в отличие от массивов OpenBuffer[]HighBuffer[]LowBuffer[], CloseBuffer[] и ColorIndexBuffer[], являющихся индикаторными буферами, установим размер массива TicksBuffer[]  такой же, как и размер массивов OpenBuffer[], HighBuffer[], LowBuffer[], CloseBuffer[]  и ColorIndexBuffer[]:

// установка размера массива TicksBuffer[]
ArrayResize(TicksBuffer,ArraySize(CloseBuffer));

Составляем имя файла из переменной path_prefix, названия финансового инструмента и символов ".txt":

// формирование имени файла из переменной path_prefix, названия
// финансового инструмента и символов ".txt"
StringConcatenate(filename,path_prefix,Symbol(),".txt");

Открываем файл (значения используемых флагов описано при создании предыдущего индикатора).

// открытие файла для чтения и записи, кодировка ANSI, совместный доступ по чтению
file_handle=FileOpen(filename,FILE_READ|FILE_WRITE|FILE_ANSI|FILE_SHARE_READ);

Если функция OnCalculate вызвана первый раз и в массиве TicksBuffer[]  еще нет данных, загружаем их из файла:

if(prev_calculated==0)
  {
   // чтение первой строки из файла и определение длинны строки
   line_string_len=StringLen(FileReadString(file_handle))+2;
   // если файл большой (содержит больше котировок, чем количество rates_total/2)
   if(FileSize(file_handle)>(ulong)line_string_len*rates_total/2)
     {
      // установка указателя файла для чтения последних rates_total/2 котировок
      FileSeek(file_handle,-line_string_len*rates_total/2,SEEK_END);
      // перемещение указателя файла в начало следующей строки
      FileReadString(file_handle);
     }
   // если файл небольшой
   else
     {
      // перемещение указателя файла в начало файла
      FileSeek(file_handle,0,SEEK_SET);
     }
   // сброс счетчика хранящихся котировок
   ticks_stored=0;
   // чтение до конца файла
   while(FileIsEnding(file_handle)==false)
     {
      // чтение строки из файла
      file_buffer=FileReadString(file_handle);
      // обработка прочитанной строки, если ее длина больше 6-ти символов
      if(StringLen(file_buffer)>6)
        {
         // нахождение начала цены Bid в строке
         BidPosition=StringFind(file_buffer," ",StringFind(file_buffer," ")+1)+1;
         // нахождение начала цены Ask в строке
         AskPosition=StringFind(file_buffer," ",BidPosition)+1;
         // если используются цены Bid, занесение цены Bid в массив TicksBuffer[]
         if(applied_price==0) 
         TicksBuffer[ticks_stored]=StringToDouble(StringSubstr(file_buffer,BidPosition,AskPosition-BidPosition-1));
         // если используются цены Ask, занесение цены Ask в массив TicksBuffer[]
         if(applied_price==1) 
         TicksBuffer[ticks_stored]=StringToDouble(StringSubstr(file_buffer,AskPosition));
         // увеличение счетчика хранящихся котировок
         ticks_stored++;
        }
     }
  }

Чтение котировок из файла более подробно описывалось выше при создании предыдущего индикатора, здесь оно происходит подобным образом.

Если котировки загружены в массив TicksBuffer[]  ранее, дописываем в конец файла новое значение цен, а также заносим новую цену в массив TicksBuffer[]  и увеличиваем счетчик котировок:

// если данные из файла уже прочитаны ранее
else
  {
   // перемещение файлового указателя в конец файла
   FileSeek(file_handle,0,SEEK_END);
   // формирование строки, предназначенной для записи в файл
   StringConcatenate(file_buffer,TimeCurrent()," ",DoubleToString(last_price_bid,_Digits)," ",DoubleToString(last_price_ask,_Digits));
   // запись строки в файл
   FileWrite(file_handle,file_buffer);
   // если используются цены Bid, добавление в массив TicksBuffer[] последней цены Bid
   if(applied_price==0) TicksBuffer[ticks_stored]=last_price_bid;
   // если используются цены Ask, добавление в массив TicksBuffer[] последней цены Ask
   if(applied_price==1) TicksBuffer[ticks_stored]=last_price_ask;
   // увеличение счетчика хранящихся котировок
   ticks_stored++;
  }

Закрываем файл:

// закрытие файла
FileClose(file_handle);

Если количество хранящихся котировок достигло количества баров на ценовом графике или стало превышать его, удаляем половину самых старых данных, а оставшиеся данные сдвигаем:

// если количество котировок больше или равно количеству баров на графике
if(ticks_stored>=rates_total)
  {
   // удаление первых tick_stored/2 котировок и смещение остальных
   for(i=ticks_stored/2;i<ticks_stored;i++)
     {
      // смещение данных в массиве TicksBuffer[] на tick_stored/2 вначало
      TicksBuffer[i-ticks_stored/2]=TicksBuffer[i];
     }
   // изменение счетчика котировок
   ticks_stored-=ticks_stored/2;
  }

Вычисляем значения цен OHLC для каждой свечи и заносим эти значения в соответствующие буферы индикатора:

// занесение в CandleNumber номера несуществующей свечи
CandleNumber=-1;
// перебор всех имеющихся ценовых данных для формирования свечей
for(i=0;i<ticks_stored;i++)
  {
   // если уже ведется формирование данной свечи
   if(CandleNumber==(int)(MathFloor((ticks_stored-1)/ticks_in_candle)-MathFloor(i/ticks_in_candle)))
     {
      // текущая котировка пока является ценой закрытия текущей свечи
      CloseBuffer[CandleNumber]=TicksBuffer[i];
      // если текущая котировка больше максимальной цены текущей свечи, то это будет новое значение максимальной цены свечи
      if(TicksBuffer[i]>HighBuffer[CandleNumber]) HighBuffer[CandleNumber]=TicksBuffer[i];
      // если текущая котировка меньше минимальной цены текущей свечи, то это будет новое значение минимальной цены свечи
      if(TicksBuffer[i]<LowBuffer[CandleNumber]) LowBuffer[CandleNumber]=TicksBuffer[i];
      // если свеча растущая, то она будет иметь цвет с индексом 2 (зеленый)
      if(CloseBuffer[CandleNumber]>OpenBuffer[CandleNumber]) ColorIndexBuffer[CandleNumber]=2;
      // если свеча падающая, то она будет иметь цвет с индексом 1 (красный)
      if(CloseBuffer[CandleNumber]<OpenBuffer[CandleNumber]) ColorIndexBuffer[CandleNumber]=1;
      // если цены открытия и закрытия свечи равны между собой, то свеча будет иметь цвет с индексом 0 (серый)
      if(CloseBuffer[CandleNumber]==OpenBuffer[CandleNumber]) ColorIndexBuffer[CandleNumber]=0;
     }
   // если эта свеча еще не расчитывалась
   else
     {
      // определение номера свечи
      CandleNumber=(int)(MathFloor((ticks_stored-1)/ticks_in_candle)-MathFloor(i/ticks_in_candle));
      // текущая котировка будет являться ценой открытия свечи
      OpenBuffer[CandleNumber]=TicksBuffer[i];
      // текущая котировка будет являться максимальной ценой свечи
      HighBuffer[CandleNumber]=TicksBuffer[i];
      // текущая котировка будет являться минимальной ценой свечи
      LowBuffer[CandleNumber]=TicksBuffer[i];
      // текущая котировка пока является ценой закрытия текущей свечи
      CloseBuffer[CandleNumber]=TicksBuffer[i];
      // свеча будет иметь цвет с индексом 0 (серый)
      ColorIndexBuffer[CandleNumber]=0;
     }
  }

Завершаем выполнение функции OnCalculate, возвращаем любое ненулевое значение, что означает, что ценовые данные в массиве TicksBuffer[]  уже есть и читать их при следующем вызове функции не нужно. В конце ставим закрывающую фигурную скобку.

// возврат из функции OnCalculate(), возвращается значение, отличное от нуля
return(rates_total);
}

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

Заключение

В этой статье было описано создание двух тиковых индикаторов: тикового графика и индикатора "тиковых свечей". 

Прикрепленные файлы |
tickindicator.mq5 (8.33 KB)
Последние комментарии | Перейти к обсуждению на форуме трейдеров (14)
Denis Kirichenko
Denis Kirichenko | 27 дек. 2012 в 07:14

А так?

//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const datetime &time[],
                const double &open[],
                const double &high[],
                const double &low[],
                const double &close[],
                const long &tick_volume[],
                const long &volume[],
                const int &spread[])
  {
//---
   if(prev_calculated==0)
     {
      ArrayInitialize(BidBuffer,0);
      ArrayInitialize(AskBuffer,0);

     }
.....
  }
Denis Zyatkevich
Denis Zyatkevich | 25 мар. 2014 в 22:16

При изменении периода графика происходит изменение размера динамических массивов BidBuffer[] и AskBuffer[], связанных с индикаторными буферами. После этого массивы оказываются заполнены "случайными" значениями, которые отличаются от тех значений, которые былы занесены ранее; они также отличаются от значения 0, для которого указано отсутствие отрисовки. Поэтому каждый раз перед заполнением массивов, следует их инициализировать значением 0 (как написал denkir):

if(prev_calculated==0)
  {
   ArrayInitialize(BidBuffer,0);
   ArrayInitialize(AskBuffer,0);

Прилагаю исправленный вариант индикатора.

Эдуард
Эдуард | 14 сент. 2020 в 06:20

Спасибо!
Очень интересно. В tickcandels возможно тоже надо добавить   

   if(prev_calculated==0)
     {
      //*************
      ArrayInitialize(TicksBuffer,0);
      ArrayInitialize(OpenBuffer,0);
      ArrayInitialize(HighBuffer,0);
      ArrayInitialize(LowBuffer,0);
      ArrayInitialize(CloseBuffer,0);
      //*************
Александр
Александр | 17 апр. 2024 в 15:50

Добрый день!

Спасибо за статью и коды. Скажите, пожалуйста, почему получается так.

Беру ваш тиковый график, без изменений компилирую и вставляю на EURUSD M1. Внизу открывается окно, меняются значения ask и bid белым цветом (в левом верхнем углу), а график внизу из красной и синей полос не появляется. 

Затем я отодвигаю график влево, достаточно, чтобы не было видно текущей свечи вверху и графики внизу появляются. Возвращаю вправо - вверху видно текущую свечу, но нет графиков внизу. 

Александр
Александр | 18 апр. 2024 в 16:04
Увидел выше ссылку на исправленный индикатор. Скачал, спасибо, всё работает ) 
Пример написания игры "Змейка" на MQL5 Пример написания игры "Змейка" на MQL5
В статье рассматривается пример написания игры "Змейка". Создание игр в 5-ой версии языка MQL стало возможным, в первую очередь, благодаря обработке событий. Поддержка объектно-ориентированного программирования значительно упрощает данный процесс. Также вы узнаете особенности обработки событий, примеры работы со стандартной библиотекой MQL5 и способы периодического вызова функций.
Стили рисования в MQL5 Стили рисования в MQL5
В MQL4 есть 6 типов графического отображения индикаторов, а MQL5 доступно уже 18 стилей рисования. Поэтому, возможно, стоит написать статью о стилях рисования в MQL5. В данной статье мы рассмотрим подробности работы со стилями графического отображения индикаторов. Кроме того, мы создадим индикатор для иллюстрации всех этих стилей и уточним особенности графических построений.
MQL5 для "чайников": Как проектировать и конструировать классы объектов MQL5 для "чайников": Как проектировать и конструировать классы объектов
На примере создания программы визуального программирования показано, как проектировать и конструировать классы на MQL5. Статья предназначена для начинающих разработчиков приложений МТ5. Предлагается простая и понятная технология создания собственных классов без глубокого погружения в теорию объектно-ориентированного программирования.
Создание индикатора с возможностями графического управления Создание индикатора с возможностями графического управления
Те, кто более-менее знаком с рыночными настроениями не понаслышке, знает индикатор MACD, или полное название Схождение Расхождение Скользящих Средних, и знает его как мощный инструмент анализа движения цены, которым пользуются трейдеры с первых моментов появления методов компьютерного анализа. В данной статье мы рассмотрим возможные модификации MACD и реализуем их в одном индикаторе с возможностью графического переключения между модификациями.