English
preview
Знакомство с языком MQL5 (Часть 32): Освоение API и функции WebRequest в языке MQL5 (VI)

Знакомство с языком MQL5 (Часть 32): Освоение API и функции WebRequest в языке MQL5 (VI)

MetaTrader 5Торговые системы |
193 0
Israel Pelumi Abioye
Israel Pelumi Abioye

Введение

И снова приветствуем вас в Части 32 серии "Знакомство с языком MQL5"! В предыдущей статье я объяснил, как использовать функцию WebRequest и API для запроса свечных данных из внешних источников. Мы рассмотрели, как получить ответ сервера в виде необработанного текста, как аккуратно разделить его на отдельные свечи и как сохранить очищенные и упорядоченные значения свечей на вашем ПК в виде структурированного файла. К концу статьи у вас был полный файл с хорошо упорядоченными свечными данными, который можно в любой момент использовать снова, не обращаясь к серверу.

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

 

Настройка свойств индикатора

В этом разделе мы начнем создавать индикатор, который будет визуализировать свечные данные в свечном формате. Индикатор будет считывать данные непосредственно из файла, который мы создали ранее, поскольку индикаторы не могут самостоятельно использовать функцию WebRequest. Торговые советники могут запрашивать данные из внешних источников, а индикаторы – нет, поэтому использование файла является самым безопасным и эффективным способом передачи информации.

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

Пример:

// PROPERTY SETTINGS
#property indicator_separate_window
#property indicator_buffers 5
#property indicator_plots   1

// PLOT SETTINGS FOR THE CANDLES
#property indicator_label1  "BTCUSDT CANDLE BARS"
#property indicator_type1   DRAW_COLOR_CANDLES
#property indicator_color1  clrGreen, clrRed

//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- indicator buffers mapping
//---   
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate(const int32_t rates_total,
                const int32_t 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 int32_t &spread[])
  {
//---
//--- return value of prev_calculated for next call
   return(rates_total);
  }

Пояснение:

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

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

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

После настройки параметров графического построения индикатор объявляет буферы, в которых будут храниться значения свечей. Каждый буфер имеет свое назначение. Цены открытия, максимума, минимума и закрытия, а также числа, определяющие, будет ли каждая свеча отображаться в бычьем или медвежьем цвете, хранятся в отдельных буферах.

Каждый буфер связывается с индикатором в функции инициализации. Чтобы сопоставить буфер, достаточно сообщить MetaTrader, что определенный массив значений должен управлять визуальным элементом или отрисовывать часть свечи. Буфер цвета сопоставляется как буфер цветовых индексов, а буферы открытия, максимума, минимума и закрытия – как буферы данных. Этот последний буфер определяет, какой цвет должна использовать каждая свеча при отображении. После успешного сопоставления всех буферов индикатор завершает инициализацию и во время выполнения готов загружать данные. Этот подготовительный процесс гарантирует, что все правильно организовано, прежде чем позже будут заполнены значения свечей.

Аналогия:

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

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

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

Вывод:

Figure 1. Separate Window

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

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

 

Чтение файла и группировка данных времени свечей

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

Открытие файла

Сначала мы получаем доступ к файлу, содержащему сохраненную информацию о свечах, чтобы упорядочить данные о времени. После открытия файла мы просматриваем значения в столбце Time, перебирая строки в цикле. Мы можем объединить все времена открытия, отбирая только корректные временные значения. Хотя индикатору не нужны временные данные для отображения свечей, их группировка сохраняет структуру неизменной и может пригодиться, если впоследствии понадобится обратиться ко времени свечей для анализа или других расчетов.

Пример:
// PROPERTY SETTINGS
#property indicator_separate_window
#property indicator_buffers 5
#property indicator_plots   1

// PLOT SETTINGS FOR THE CANDLES
#property indicator_label1  "BTCUSDT CANDLE BARS"
#property indicator_type1   DRAW_COLOR_CANDLES
#property indicator_color1  clrGreen, clrRed

// INDICATOR BUFFERS
double bar_open[];
double bar_high[];
double bar_low[];
double bar_close[];
double ColorBuffer[];
string   SharedFilename = "BTCUSDTM30_MQL5.csv";
int time_handle;

//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- indicator buffers mapping
//---
   SetIndexBuffer(0, bar_open, INDICATOR_DATA);
   SetIndexBuffer(1, bar_high, INDICATOR_DATA);
   SetIndexBuffer(2, bar_low, INDICATOR_DATA);
   SetIndexBuffer(3, bar_close, INDICATOR_DATA);
   SetIndexBuffer(4, ColorBuffer, INDICATOR_COLOR_INDEX);
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate(const int32_t rates_total,
                const int32_t 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 int32_t &spread[])
  {
//---
//TIME
   time_handle = FileOpen(SharedFilename, FILE_READ | FILE_CSV | FILE_ANSI, ',');

   if(time_handle == INVALID_HANDLE)
     {
      Print("Failed to open file. Error: ", GetLastError());
     }

//--- return value of prev_calculated for next call
   return(rates_total);
  }

Пояснение:

Чтобы сохранить имя CSV-файла со свечными данными, сначала задается строковая переменная. Когда программе понадобится прочитать свечную информацию, это позволит ей понимать, какой файл открыть. Любая функция внутри индикатора сможет получить к этой переменной доступ, если она объявлена в глобальной области видимости. Затем хэндл файла сохраняется в указанной переменной. Хэндл открытого файла работает как идентификатор или ссылка. После успешного открытия файла программа использует этот хэндл, чтобы читать строки данных и получать доступ к каждому значению.

Затем программа открывает файл с определенными параметрами, которые задают его структуру. Исходные данные сохраняются, поскольку мы читаем файл без записи в него. Поскольку файл обрабатывается как CSV, предполагается, что каждая строка содержит несколько значений, разделенных запятыми: время и ценовые уровни. Программа может корректно разделять и считывать каждое значение, используя кодировку ANSI и явно задавая запятую как разделитель.

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

Аналогия:

Представьте себе CSV-файл как сшиватель на полке, в котором все свечные данные аккуратно разложены по строкам и столбцам. Благодаря переменной с именем файла вы точно знаете, какой сшиватель взять: она работает как ярлык на сшивателе. Это похоже на ярлык, размещенный на видном месте: когда имя файла объявлено в глобальной области видимости, любой в библиотеке сможет увидеть его и взять нужный сшиватель.

Хэндл файла можно представить как ключ, который открывает сшиватель с хорошо организованными записями о свечах. Открыв файл в режиме чтения и указав, что это CSV с запятыми, вы сможете точно извлекать нужные значения. Вы можете просматривать данные о времени и ценах каждой свечи в понятном виде, потому что каждая запятая отделяет одно значение от следующего. Программа быстро проверяет, что доступ к сшивателю разрешен. Если доступ получить не удается, программа мгновенно сообщает об ошибке – как библиотекарь, который предупреждает, что сейчас сшиватель недоступен. Это гарантирует, что любая проблема будет выявлена сразу, до обработки данных.

Наконец, если файл вручную открыт на вашем ПК, программа не сможет получить к нему доступ – так же как вы не сможете читать содержимое сшивателя, пока его держит кто-то другой. Чтобы индикатор смог открыть файл и корректно проанализировать свечную информацию, убедитесь, что файл не занят.

Циклический проход по файлу

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

Figure 2. File Data

На изображении выше файл выглядит как аккуратная таблица со строками и столбцами, но код обрабатывает его так, как будто все набрано в одну строку.

Time, Open, High, Low, Close,
2025.12.12 05:30:00, 92133.19, 92478.34, 92133.18, 92444.42,
2025.12.12 06:00:00, 92444.41,92720, 92400, 92600.54,
2025.12.12 06:30:00, 92600.55, 92600.55, 92463.17, 92513.34,
2025.12.12 07:00:00, 92513.34, 92565.83, 92365.85, 92377.8,
2025.12.12 07:30:00, 92377.81, 92520.56, 92257.8, 92425.34,
2025.12.12 08:00:00, 92425.33, 92487.25, 92173.27, 92258.49,
2025.12.12 08:30:00, 92258.49, 92370.01, 92044.8, 92342.39,
2025.12.12 09:00:00, 92342.38, 92384.6, 92111.03, 92113.56,
2025.12.12 09:30:00, 92113.57, 92553.33, 92094, 92520.56,
2025.12.12 10:00:00, 92520.56, 92520.56, 92520.56, 92520.56

Алгоритм может легко отличить один элемент от другого, так как каждое значение в файле отделено запятой. Подсчет элементов, отделенных запятыми, показывает, что всего их 55: заголовки и полный набор данных для десяти свечей.

Примеры:

string   GlobalOpenTime[55];
Пример:
//TIME
   time_handle = FileOpen(SharedFilename, FILE_READ | FILE_CSV | FILE_ANSI, ',');

   if(time_handle == INVALID_HANDLE)
     {
      Print("Failed to open file. Error: ", GetLastError());
     }
     
     for(int i = 0; i < 55; i++)
     {

      GlobalOpenTime[i]  = FileReadString(time_handle);

     }

Пояснение:

Каждое из 55 строковых значений в массиве GlobalOpenTime представляет собой отдельный элемент, прочитанный из файла. Все элементы в файле, отделенные запятыми, включая заголовки и значения для десяти свечей, в сумме дают 55. Чтобы пройти по всем 55 элементам, используется цикл. На каждой итерации программа считывает один элемент из файла и сохраняет его в массиве в нужной позиции. Это означает, что первый элемент массива содержит первое значение из файла, второй – второе и т.д., пока последний элемент не будет содержать последнее значение.

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

Группировка всех времен свечей в один массив

Не различая, относится ли значение к open, high, low, close или time, цикл проходит по каждому элементу в файле один за другим. Все, что он делает, – считывает каждое значение ровно в том виде, в котором оно записано. Цель в том, чтобы при переборе элементов определить, какие из них относятся к столбцу Time, чтобы затем собрать их все. Мы можем использовать позиции элементов внутри цикла, чтобы определить, считывается ли сейчас значение времени, потому что файл организован как повторяющийся шаблон столбцов.

Согласно структуре файла, столбец Time всегда имеет индекс 0 в шаблоне значений, потому что он идет первым в каждой строке. Следующее значение времени всегда будет находиться ровно на пять позиций дальше предыдущего, потому что в каждой строке пять полей. Следовательно, если начать с индекса 0 и каждый раз прибавлять 5, мы будем последовательно получать значения времени из файла. Этот процесс повторяется, пока цикл не пройдет по всем 55 элементам файла. Мы соберем эти значения, преобразуем их из текста в тип данных datetime и сохраним в едином массиве, чтобы впоследствии упростить их использование в индикаторе.

Пример:

int index_time;
datetime bar1_time;
datetime bar2_time;
datetime bar3_time;
datetime bar4_time;
datetime bar5_time;
datetime bar6_time;
datetime bar7_time;
datetime bar8_time;
datetime bar9_time;
datetime bar10_time;

datetime OpenTime[10];

//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate(const int32_t rates_total,
                const int32_t 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 int32_t &spread[])
  {
//---
//TIME
   time_handle = FileOpen(SharedFilename, FILE_READ | FILE_CSV | FILE_ANSI, ',');

   if(time_handle == INVALID_HANDLE)
     {
      Print("Failed to open file. Error: ", GetLastError());
     }

//LOOPING THROGH THE FILE ELEMENTS
   for(int i = 0; i < 55; i++)
     {

      GlobalOpenTime[i]  = FileReadString(time_handle);

     }
//GROUPING ALL TIME TOGETHER
   index_time = 5;
   bar1_time = StringToTime(GlobalOpenTime[index_time]);
   index_time += 5;
   bar2_time = StringToTime(GlobalOpenTime[index_time]);
   index_time += 5;
   bar3_time = StringToTime(GlobalOpenTime[index_time]);
   index_time += 5;
   bar4_time = StringToTime(GlobalOpenTime[index_time]);
   index_time += 5;
   bar5_time = StringToTime(GlobalOpenTime[index_time]);
   index_time += 5;
   bar6_time = StringToTime(GlobalOpenTime[index_time]);
   index_time += 5;
   bar7_time = StringToTime(GlobalOpenTime[index_time]);
   index_time += 5;
   bar8_time = StringToTime(GlobalOpenTime[index_time]);
   index_time += 5;
   bar9_time = StringToTime(GlobalOpenTime[index_time]);
   index_time += 5;
   bar10_time = StringToTime(GlobalOpenTime[index_time]);

   OpenTime[0]  = bar1_time;
   OpenTime[1]  = bar2_time;
   OpenTime[2]  = bar3_time;
   OpenTime[3]  = bar4_time;
   OpenTime[4]  = bar5_time;
   OpenTime[5]  = bar6_time;
   OpenTime[6]  = bar7_time;
   OpenTime[7]  = bar8_time;
   OpenTime[8]  = bar9_time;
   OpenTime[9]  = bar10_time;
   FileClose(time_handle);

//--- return value of prev_calculated for next call
   return(rates_total);
  }

Пояснение:

Чтобы начать извлекать времена свечей, создается индекс, указывающий на первое фактическое значение времени в файле. Первое фактическое время свечи начинается с позиции 5, так как заголовки занимают первые пять позиций. Затем десять значений времени свечей, которые будут извлечены, временно сохраняются в нескольких переменных datetime. Чтобы получить исходное значение времени и преобразовать его в тип datetime, который понимает язык MQL5, программа использует индекс. После чтения этого значения индекс смещается на пять позиций, что гарантирует, что далее он будет указывать время следующей свечи. Этот шаблон повторяется, пока все десять значений времени свечей не будут успешно извлечены и преобразованы; каждый сдвиг на 5 ведет к следующему значению времени.

Десять значений datetime извлекаются и затем группируются в один массив. На время открытия любой свечи легко ссылаться по индексу, потому что позиции в массиве соответствуют порядку свечей – от самой ранней к самой поздней.  Файл закрывается, когда временные данные будут сгруппированы в массив. Закрытие файла предотвращает его блокировку, снижает вероятность ошибок и позволяет программе обрабатывать данные без проблем с доступом к файлу.

Аналогия:

Представьте себе длинную полку, заполненную аккуратно разложенными маленькими карточками. Каждая карточка содержит информацию из файла со свечными данными. Файл на вашем компьютере выглядит как таблица, но для программы это просто длинный ряд карточек, отделенных запятыми. Метки, например, заголовки столбцов, составляют первые пять карточек на полке. Фактические свечные данные начинаются после этих меток. Шестая карточка на полке соответствует первому времени свечи. Вы кладете палец на шестую карточку, чтобы начать собирать времена свечей. Ваш начальный индекс – это позиция, на которую указывает палец. Вы берете карточку, читаете на ней время, а затем преобразуете текст в формат даты, который понимает ваша система. Отложив эту карточку, вы фиксируете первое время свечи.

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

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

 

Чтение файла и группировка цен открытия свечей

Чтобы использовать цены открытия из файла в индикаторе, на следующем этапе нужно объединить их все в группу. Эта процедура похожа на то, что мы делали со временем свечей, но, поскольку цены в языке MQL5 представлены значениями double, мы преобразуем данные в числа с десятичной частью, а не в формат даты и времени. Чтобы прочитать файл снова, сначала создается новый хэндл файла. Это важно, потому что для извлечения данных файл нужно открыть повторно: ранее мы закрыли его после чтения времени. После открытия файла программа проходит по каждому его элементу. Заголовки и все значения для десяти свечей в сумме составляют 55 элементов.

Программа анализирует позицию каждого элемента в цикле, чтобы определить, какие из них соответствуют ценам открытия. Поскольку столбец Open во второй позиции первой строки, его индекс равен 1. Программа считывает значение по этому индексу, преобразует его из текста в число и сохраняет как цену открытия первой свечи. Затем программа увеличивает индекс на 5, чтобы перейти к следующей цене открытия, потому что информация каждой свечи занимает в файле пять элементов. Эта процедура повторяется: программа считывает каждую следующую цену открытия, преобразует ее в double, а затем последовательно помещает в массив, предназначенный для цен открытия. К моменту завершения цикла все десять цен открытия будут аккуратно собраны в едином массиве, где каждая позиция соответствует отдельной свече.

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

Пример:
//OPEN PRICE
string   GlobalOpenPrice[55];
int open_handle;
int index_open;
double bar1_open;
double bar2_open;
double bar3_open;
double bar4_open;
double bar5_open;
double bar6_open;
double bar7_open;
double bar8_open;
double bar9_open;
double bar10_open;
double OpenPrice[10];
//TIME
time_handle = FileOpen(SharedFilename, FILE_READ | FILE_CSV | FILE_ANSI, ',');

if(time_handle == INVALID_HANDLE)
  {
   Print("Failed to open file. Error: ", GetLastError());
  }

//LOOPING THROGH THE FILE ELEMENTS
for(int i = 0; i < 55; i++)
  {

   GlobalOpenTime[i]  = FileReadString(time_handle);

  }
//GROUPING ALL TIME TOGETHER
index_time = 5;
bar1_time = StringToTime(GlobalOpenTime[index_time]);
index_time += 5;
bar2_time = StringToTime(GlobalOpenTime[index_time]);
index_time += 5;
bar3_time = StringToTime(GlobalOpenTime[index_time]);
index_time += 5;
bar4_time = StringToTime(GlobalOpenTime[index_time]);
index_time += 5;
bar5_time = StringToTime(GlobalOpenTime[index_time]);
index_time += 5;
bar6_time = StringToTime(GlobalOpenTime[index_time]);
index_time += 5;
bar7_time = StringToTime(GlobalOpenTime[index_time]);
index_time += 5;
bar8_time = StringToTime(GlobalOpenTime[index_time]);
index_time += 5;
bar9_time = StringToTime(GlobalOpenTime[index_time]);
index_time += 5;
bar10_time = StringToTime(GlobalOpenTime[index_time]);

OpenTime[0]  = bar1_time;
OpenTime[1]  = bar2_time;
OpenTime[2]  = bar3_time;
OpenTime[3]  = bar4_time;
OpenTime[4]  = bar5_time;
OpenTime[5]  = bar6_time;
OpenTime[6]  = bar7_time;
OpenTime[7]  = bar8_time;
OpenTime[8]  = bar9_time;
OpenTime[9]  = bar10_time;

FileClose(time_handle);

// OPEN
open_handle = FileOpen(SharedFilename, FILE_READ | FILE_CSV | FILE_ANSI, ',');

if(open_handle == INVALID_HANDLE)
  {
   Print("Failed to open file. Error: ", GetLastError());
  }

for(int i = 0; i < 55; i++)
  {

   GlobalOpenPrice[i]  = FileReadString(open_handle);

  }

index_open = 6;

bar1_open = StringToDouble(GlobalOpenPrice[index_open]);
index_open += 5;
bar2_open = StringToDouble(GlobalOpenPrice[index_open]);
index_open += 5;
bar3_open = StringToDouble(GlobalOpenPrice[index_open]);
index_open += 5;
bar4_open = StringToDouble(GlobalOpenPrice[index_open]);
index_open += 5;
bar5_open = StringToDouble(GlobalOpenPrice[index_open]);
index_open += 5;
bar6_open = StringToDouble(GlobalOpenPrice[index_open]);
index_open += 5;
bar7_open = StringToDouble(GlobalOpenPrice[index_open]);
index_open += 5;
bar8_open = StringToDouble(GlobalOpenPrice[index_open]);
index_open += 5;
bar9_open = StringToDouble(GlobalOpenPrice[index_open]);
index_open += 5;
bar10_open = StringToDouble(GlobalOpenPrice[index_open]);

OpenPrice[0]  = bar1_open;
OpenPrice[1]  = bar2_open;
OpenPrice[2]  = bar3_open;
OpenPrice[3]  = bar4_open;
OpenPrice[4]  = bar5_open;
OpenPrice[5]  = bar6_open;
OpenPrice[6]  = bar7_open;
OpenPrice[7]  = bar8_open;
OpenPrice[8]  = bar9_open;
OpenPrice[9]  = bar10_open;
FileClose(open_handle);

Пояснение:

Сначала все элементы, считанные из файла, включая заголовки и свечные данные, временно сохраняются в массиве строк. Для доступа к файлу также создается хэндл файла. Создаются десять переменных для временного хранения цены открытия каждой свечи, одна переменная для отслеживания индекса цен открытия и структурированный массив для хранения всех десяти цен открытия для использования в индикаторе. После объявления необходимых переменных программа открывает файл в режиме CSV с правильной кодировкой и настройками совместного доступа. Если получить доступ к файлу не удается, программа выводит сообщение об ошибке и код ошибки, чтобы помочь определить причину – например, отсутствие файла или недостаточные права доступа.

После успешного открытия файла цикл проходит по каждому из его 55 элементов. Среди этих элементов – заголовки и все свечные данные. На каждой итерации цикла программа получает следующее значение из файла и сохраняет его как строку во временном массиве. К концу этого цикла программа соберет все исходные данные файла в правильном порядке. После чтения всех элементов программа устанавливает начальный индекс для цен открытия. Этот индекс соответствует первой цене открытия в файле. Первая цена открытия стоит на шестой позиции, потому что структура файла помещает заголовки и значение времени первой свечи в первые пять элементов.

Затем программа считывает значение по этому первому индексу как цену открытия первой свечи. Строковое значение преобразуется в double, чтобы его можно было отобразить в индикаторе или использовать в расчетах. После того как первая цена открытия сохранена, а индекс увеличен на 5, программа переходит к цене открытия следующей свечи. Этот процесс повторяется для всех десяти свечей: каждое преобразование и сохранение выполняются одно за другим. К моменту завершения этой фазы каждая из десяти цен открытия будет временно сохранена в отдельной переменной и преобразована в соответствующий числовой тип.

На следующем этапе все десять цен открытия объединяются в единый массив. Первая позиция массива содержит цену открытия первой свечи, вторая – второй и т.д. вплоть до десятой свечи. Каждый элемент этого массива соответствует отдельной свече. Благодаря такой структуре цену открытия любой свечи легко получить по ее позиции в массиве. В конце файл закрывается. Закрытие файла имеет решающее значение, потому что оно гарантирует освобождение всех ресурсов, необходимых для доступа к файлу. Кроме того, это поможет избежать конфликтов или сбоев в случае открытия файла вручную, пока программа все еще работает. Цены открытия теперь надежно сохранены в массиве и готовы к использованию в вычислениях, построении индикатора или другой обработке.

Аналогия:

Представьте себе большую книжную полку с длинным рядом карточек. Каждая карточка представляет собой фрагмент информации из файла со свечными данными. Часть первых карточек – это заголовки, а остальные карточки содержат реальные свечные данные. Ваша задача – собрать только цены открытия для каждой из десяти свечей и сохранить их в аккуратной отдельной коробке для удобного доступа. Сначала вы готовите пустую коробку для хранения цен открытия. Затем вы с помощью программы "открываете" книжную полку – как шкаф, который нужно открыть, чтобы добраться до карточек. Если шкаф открыть не удается, программа уведомит вас об этом, чтобы вы могли устранить проблему перед тем, как продолжить.

После этого вы последовательно просматриваете каждую из 55 карточек, одну за другой, и временно складываете их в стопку, чтобы все было хорошо видно. В эту стопку теперь собраны и заголовки, и все данные с книжной полки. Как вы знаете, шестая карточка содержит первую цену открытия. Это связано с тем, что первые пять карточек – это заголовки и время первой свечи. Вы берете шестую карточку, читаете число на ней, а затем подходящим образом форматируете его, чтобы ваша «коробка» могла его хранить. Это становится первой ценой открытия в вашей коробке.

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

 

Чтение файла и группировка цен максимума свечей

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

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

Пример:
//HIGH PRICE 
string   GlobalHighPrice[55];
int index_high;
double bar1_high;
double bar2_high;
double bar3_high;
double bar4_high;
double bar5_high;
double bar6_high;
double bar7_high;
double bar8_high;
double bar9_high;
double bar10_high;
double HighPrice[10];
// HIGH
int high_handle = FileOpen(SharedFilename, FILE_READ | FILE_CSV | FILE_ANSI, ',');

if(high_handle == INVALID_HANDLE)
  {
   Print("Failed to open file. Error: ", GetLastError());
  }

for(int i = 0; i < 55; i++)
  {

   GlobalHighPrice[i]  = FileReadString(high_handle);

  }

index_high = 7;

bar1_high = StringToDouble(GlobalHighPrice[index_high]);
index_high += 5;
bar2_high = StringToDouble(GlobalHighPrice[index_high]);
index_high += 5;
bar3_high = StringToDouble(GlobalHighPrice[index_high]);
index_high += 5;
bar4_high = StringToDouble(GlobalHighPrice[index_high]);
index_high += 5;
bar5_high = StringToDouble(GlobalHighPrice[index_high]);
index_high += 5;
bar6_high = StringToDouble(GlobalHighPrice[index_high]);
index_high += 5;
bar7_high = StringToDouble(GlobalHighPrice[index_high]);
index_high += 5;
bar8_high = StringToDouble(GlobalHighPrice[index_high]);
index_high += 5;
bar9_high = StringToDouble(GlobalHighPrice[index_high]);
index_high += 5;
bar10_high = StringToDouble(GlobalHighPrice[index_high]);

HighPrice[0]  = bar1_high;
HighPrice[1]  = bar2_high;
HighPrice[2]  = bar3_high;
HighPrice[3]  = bar4_high;
HighPrice[4]  = bar5_high;
HighPrice[5]  = bar6_high;
HighPrice[6]  = bar7_high;
HighPrice[7]  = bar8_high;
HighPrice[8]  = bar9_high;
HighPrice[9]  = bar10_high;

FileClose(high_handle);

Пояснение:

Сначала все необработанные значения из файла кратковременно хранятся в глобальном массиве строк. Для хранения преобразованной цены максимума для каждой из десяти свечей также объявлены отдельные переменные типа double. Наконец, создается специальный массив типа double для хранения всех цен максимума для дальнейшего доступа. Используя хэндл, который обеспечивает безопасный доступ для чтения, программа открывает CSV-файл. Если файл открыть не удается, выводится сообщение об ошибке с соответствующим кодом. После открытия файла программа считывает каждый из 55 элементов как строку и сохраняет его во временном глобальном массиве. В этом массиве содержатся все значения свечей и заголовки.

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

Аналогия:

Представьте файл как длинную полку с карточками, на каждой из которых есть информация о свече. Цены максимума похожи на определенную подборку карточек, которую вы хотите взять с полки. Начиная с первой карточки с ценой максимума, вы поднимаете ее, читаете и преобразуете в формат, который можете использовать. Следующую карточку с ценой максимума вы получаете, сдвигаясь по полке на заранее заданное число карточек; этот процесс повторяется, пока у вас не окажутся все десять. Когда вы закончите, вы кладете все эти карточки с максимумами в специальную коробку, чтобы легко получать к ним доступ, когда они нужны.

 

Чтение файла и группировка цен минимума и закрытия свечей

Следующим шагом является группировка цен минимума и закрытия из файла. Эта процедура почти такая же, как и при работе с ценами открытия и максимумами. Сначала мы повторно открываем файл с отдельным хэндлом для каждого набора данных. Затем при проходе по всему файлу мы считываем каждый элемент как строку во временные массивы хранения. Чтобы выделить каждое значение минимума, мы начинаем с индекса, который соответствует столбцу Low, и продвигаемся с фиксированным шагом. После преобразования из строки в double каждое значение хранится в отдельном массиве минимумов. Для цен закрытия мы начинаем с индекса, который соответствует столбцу Close, продолжаем в том же пошаговом порядке, преобразуем каждое значение в double и сохраняем его в массиве цен закрытия.

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

Пример:
//LOW PRICE
string   GlobalLowPrice[55];
int index_low;
double bar1_low;
double bar2_low;
double bar3_low;
double bar4_low;
double bar5_low;
double bar6_low;
double bar7_low;
double bar8_low;
double bar9_low;
double bar10_low;
double LowPrice[10];

//CLOSE PRICE
string   GlobalClosePrice[55];
int index_close;
double bar1_close;
double bar2_close;
double bar3_close;
double bar4_close;
double bar5_close;
double bar6_close;
double bar7_close;
double bar8_close;
double bar9_close;
double bar10_close;
double ClosePrice[10];
//LOW
int low_handle = FileOpen(SharedFilename, FILE_READ | FILE_CSV | FILE_ANSI, ',');

if(low_handle == INVALID_HANDLE)
  {
   Print("Failed to open file. Error: ", GetLastError());
  }

for(int i = 0; i < 55; i++)
  {

   GlobalLowPrice[i]  = FileReadString(low_handle);

  }

index_low = 8;

bar1_low = StringToDouble(GlobalLowPrice[index_low]);
index_low += 5;
bar2_low = StringToDouble(GlobalLowPrice[index_low]);
index_low += 5;
bar3_low = StringToDouble(GlobalLowPrice[index_low]);
index_low += 5;
bar4_low = StringToDouble(GlobalLowPrice[index_low]);
index_low += 5;
bar5_low = StringToDouble(GlobalLowPrice[index_low]);
index_low += 5;
bar6_low = StringToDouble(GlobalLowPrice[index_low]);
index_low += 5;
bar7_low = StringToDouble(GlobalLowPrice[index_low]);
index_low += 5;
bar8_low = StringToDouble(GlobalLowPrice[index_low]);
index_low += 5;
bar9_low = StringToDouble(GlobalLowPrice[index_low]);
index_low += 5;
bar10_low = StringToDouble(GlobalLowPrice[index_low]);


LowPrice[0]   = bar1_low;
LowPrice[1]   = bar2_low;
LowPrice[2]   = bar3_low;
LowPrice[3]   = bar4_low;
LowPrice[4]   = bar5_low;
LowPrice[5]   = bar6_low;
LowPrice[6]   = bar7_low;
LowPrice[7]   = bar8_low;
LowPrice[8]   = bar9_low;
LowPrice[9]   = bar10_low;

FileClose(low_handle);

//CLOSE
int close_handle = FileOpen(SharedFilename, FILE_READ | FILE_CSV | FILE_ANSI, ',');

if(close_handle == INVALID_HANDLE)
  {
   Print("Failed to open file. Error: ", GetLastError());
  }

for(int i = 0; i < 55; i++)
  {

   GlobalClosePrice[i]  = FileReadString(close_handle);

  }

index_close = 9;

bar1_close = StringToDouble(GlobalClosePrice[index_close]);
index_close += 5;
bar2_close = StringToDouble(GlobalClosePrice[index_close]);
index_close += 5;
bar3_close = StringToDouble(GlobalClosePrice[index_close]);
index_close += 5;
bar4_close = StringToDouble(GlobalClosePrice[index_close]);
index_close += 5;
bar5_close = StringToDouble(GlobalClosePrice[index_close]);
index_close += 5;
bar6_close = StringToDouble(GlobalClosePrice[index_close]);
index_close += 5;
bar7_close = StringToDouble(GlobalClosePrice[index_close]);
index_close += 5;
bar8_close = StringToDouble(GlobalClosePrice[index_close]);
index_close += 5;
bar9_close = StringToDouble(GlobalClosePrice[index_close]);
index_close += 5;
bar10_close = StringToDouble(GlobalClosePrice[index_close]);

ClosePrice[0] = bar1_close;
ClosePrice[1] = bar2_close;
ClosePrice[2] = bar3_close;
ClosePrice[3] = bar4_close;
ClosePrice[4] = bar5_close;
ClosePrice[5] = bar6_close;
ClosePrice[6] = bar7_close;
ClosePrice[7] = bar8_close;
ClosePrice[8] = bar9_close;
ClosePrice[9] = bar10_close;

FileClose(close_handle);

Пояснение:

Сначала объявляются два строковых массива с 55 элементами, чтобы временно хранить все данные о минимумах и ценах закрытия из файла. Начальные позиции данных минимумов и цен закрытия в файле хранятся в целочисленных переменных. Минимум и цена закрытия для каждой свечи хранятся в отдельных переменных типа double. Чтобы собрать все десять минимумов и цен закрытия в организованном виде, в конце создаются два массива. Для открытия файла в режиме "только чтение" используется хэндл файла (для блока минимумов). Программа проверяет, корректно ли открыт файл, и если нет, выводит сообщение об ошибке вместе с кодом. После открытия файла цикл считывает каждый из 55 элементов и сохраняет его в виде строки в массиве данных минимумов.

Затем код устанавливает индекс на соответствующий столбец набора данных, чтобы определить начальное положение минимальных цен в файле. Каждая цена минимума извлекается из временного массива по этому индексу, преобразуется из строки в double (подходящий числовой формат для расчетов) и затем сохраняется в отдельной переменной, представляющей эту свечу. После того как значение получено, индекс увеличивается на фиксированный шаг 5, что соответствует переходу к минимуму следующей свечи в серии. Эта процедура повторяется до тех пор, пока все десять цен минимума не будут преобразованы и сохранены. Наконец, файл закрывается для освобождения ресурсов после того, как отдельные цены минимума свечей будут объединены в итоговый массив LowPrice для более быстрого доступа.

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

 

Визуализация свечных данных в графическом формате

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

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

Пример:

int start_chart_index;
int current_index;
start_chart_index = rates_total - 10;

for(int i = 0; i < 10; i++)
  {

   current_index = start_chart_index + i;
   bar_close[current_index] = ClosePrice[i];
   bar_open[current_index] = OpenPrice[i];
   bar_high[current_index] = HighPrice[i];
   bar_low[current_index] = LowPrice[i];

// SET COLOR: GREEN FOR BULLISH, RED FOR BEARISH
   if(ClosePrice[i] >= OpenPrice[i])
     {
      ColorBuffer[current_index] = 0; // Index 0 of indicator_color1 (clrGreen)
     }
   else
     {
      ColorBuffer[current_index] = 1; // Index 1 of indicator_color1 (clrRed)
     }
  }

Результат:

Figure 3. Indicator Window

Пояснение:

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

На каждой итерации цикла значения открытия, максимума, минимума и закрытия свечи распределяются по соответствующим буферам для отображения. Этот этап гарантирует, что ценовые данные будут точно представлены на графике. Программа также определяет цвет свечи, анализируя соотношение между ценами открытия и закрытия. Свеча окрашивается в один цвет и считается бычьей, если цена закрытия больше или равна цене открытия; если цена закрытия ниже, это означает медвежью свечу, у которой другой цвет. Все десять свечей, каждая с точной информацией о цене и цвете, последовательно отображаются на графике с помощью этого метода. Преобразуя упорядоченные данные из массивов в визуальное представление, этот метод позволяет графику точно отображать местоположение, размер и цвет свечей, давая четкое представление о недавней рыночной активности.

Аналогия:

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

По мере движения вдоль полки каждая свеча тщательно размещается в следующем слоте, так что все десять последних свечей оказываются правильно организованными, с нужными цветами и значениями. Поскольку в окне индикатора не указаны максимальная и минимальная цены, свечи на изображении выше едва видны. Свечи сейчас "сжаты" из‑за использования произвольно высокого значения. Нам нужен способ динамически изменять окно, потому что рынок постоянно меняется, а сам метод прост.

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

Пример:
 int highest_index;
 int lowest_index;
 double max_level;
 double min_level;
//VISUALIZING IN CANDLE FORMAT
highest_index = ArrayMaximum(HighPrice,0,WHOLE_ARRAY);
lowest_index = ArrayMinimum(LowPrice,0,WHOLE_ARRAY);

max_level = HighPrice[highest_index] + (HighPrice[highest_index] - LowPrice[lowest_index]);
min_level = LowPrice[lowest_index] - (HighPrice[highest_index] - LowPrice[lowest_index]);

IndicatorSetDouble(INDICATOR_MAXIMUM, max_level);  // maximum value
IndicatorSetDouble(INDICATOR_MINIMUM,min_level);  // minimum value

start_chart_index = rates_total - 10;

for(int i = 0; i < 10; i++)  // START FROM SECOND BAR
  {

   current_index = start_chart_index + i;
   bar_close[current_index] = ClosePrice[i];
   bar_open[current_index] = OpenPrice[i];
   bar_high[current_index] = HighPrice[i];
   bar_low[current_index] = LowPrice[i];

// SET COLOR: GREEN FOR BULLISH, RED FOR BEARISH
   if(ClosePrice[i] >= OpenPrice[i])
     {
      ColorBuffer[current_index] = 0; // Index 0 of indicator_color1 (clrGreen)
     }
   else
     {
      ColorBuffer[current_index] = 1; // Index 1 of indicator_color1 (clrRed)
     }
  }

Результат:

Figure 4. Candle Visualization

Пояснение:

Сначала индексы максимальных и минимальных значений в массивах свечей сохраняются в двух целочисленных переменных, при этом в одной переменной будет храниться позиция максимальной цены среди всех свечей, а в другой – минимальной. Кроме того, свеча с максимальной ценой определяется функцией, которая просматривает массив максимальных цен, чтобы найти индекс наибольшего значения. Аналогичным образом, другая функция находит свечу с минимальной ценой – ищет в массиве минимумов индекс наименьшего значения.

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

Наконец, эти вычисленные максимальный и минимальный уровни применяются к окну индикатора. Это настраивает вертикальный диапазон окна так, что все свечные данные, независимо от колебаний цен, аккуратно помещаются внутри и хорошо видны. Теперь индикатор будет показывать все десять свечей в окне с корректным масштабом.

Аналогия:

Представьте десять книг на полке, каждая из которых символизирует свечу. В каждой книге есть самая верхняя страница и самая нижняя страница. Сначала вы не знаете, сколько места нужно оставить на полке над самой высокой книгой и под самой низкой. Если интервал задан неправильно, книги выглядят тесно и их трудно разглядеть. Следующий этап – изменить пропорции полки так, чтобы самая высокая книга удобно помещалась, а самая маленькая не была зажата внизу. Аналогичным образом программа определяет диапазон окна индикатора по значениям максимумов и минимумов свечей, а затем немного расширяет его, чтобы свечи отображались четко и сбалансированно.

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

Пример:
bool   DataLoaded = false; // Flag to ensure data is loaded only once
//VISUALIZING IN CANDLE FORMAT
highest_index = ArrayMaximum(HighPrice,0,WHOLE_ARRAY);
lowest_index = ArrayMinimum(LowPrice,0,WHOLE_ARRAY);

max_level = HighPrice[highest_index] + (HighPrice[highest_index] - LowPrice[lowest_index]);
min_level = LowPrice[lowest_index] - (HighPrice[highest_index] - LowPrice[lowest_index]);

IndicatorSetDouble(INDICATOR_MAXIMUM, max_level);  // maximum value
IndicatorSetDouble(INDICATOR_MINIMUM,min_level);  // minimum value

DataLoaded = true;

if(!DataLoaded || rates_total < 10)
  {
   return(0); // Not enough bars yet or data loading failed
  }

ArrayFill(bar_open, 0, rates_total, EMPTY_VALUE);
ArrayFill(bar_high, 0, rates_total, EMPTY_VALUE);
ArrayFill(bar_low, 0, rates_total, EMPTY_VALUE);
ArrayFill(bar_close, 0, rates_total, EMPTY_VALUE);

start_chart_index = rates_total - 10;

for(int i = 0; i < 10; i++)  // START FROM SECOND BAR
  {

   current_index = start_chart_index + i;

   bar_close[current_index] = ClosePrice[i];
   bar_open[current_index] = OpenPrice[i];
   bar_high[current_index] = HighPrice[i];
   bar_low[current_index] = LowPrice[i];

// SET COLOR: GREEN FOR BULLISH, RED FOR BEARISH
   if(ClosePrice[i] >= OpenPrice[i])
     {
      ColorBuffer[current_index] = 0; // Index 0 of indicator_color1 (clrGreen)
     }
   else
     {
      ColorBuffer[current_index] = 1; // Index 1 of indicator_color1 (clrRed)
     }
  }

Результат:

Figure 3. BTCUSDT M30 Bars

Пояснение:

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

Примечание:

Советник WebRequest должен быть активен, чтобы онлайн‑обновления работали корректно. Новые рыночные данные регулярно запрашиваются и добавляются в файл. Когда файл изменяется, индикатор автоматически перезагружает данные и обновляет отображение свечей самыми свежими значениями.


Заключение

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

Перевод с английского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/en/articles/20591

Прикрепленные файлы |
Нейросети в трейдинге: Масштабируемые трансформеры со структурной декомпозицией признаков (Основные компоненты) Нейросети в трейдинге: Масштабируемые трансформеры со структурной декомпозицией признаков (Основные компоненты)
Продолжаем разработку FAT: декомпозиция признаков по полям, отдельные проекции Query/Key/Value и параметрическое масштабирование внимания. Описаны OpenCL‑кернел многоголового внимания, свёрточный объект CNeuronFieldAwareConv и интеграция в CNeuronMHFAT с контролем памяти и градиентов. Читатель получает готовые компоненты для MQL5‑моделей, устойчивость к шуму и масштабируемость по числу полей и голов.
Разработка пользовательского индикатора матрицы эффективности торгового счёта Разработка пользовательского индикатора матрицы эффективности торгового счёта
Этот индикатор выступает в роли средства контроля за соблюдением дисциплины, отслеживая в режиме реального времени состояние счета, прибыль/убыток и просадку и отображая панель показателей эффективности. Он может помочь трейдерам сохранять преемственность, избегать чрезмерной торговли и соблюдать правила отбора, установленные проп-трейдинговыми фирмами.
Знакомство с языком MQL5 (Часть 31): Освоение API и функции WebRequest в языке MQL5 (V) Знакомство с языком MQL5 (Часть 31): Освоение API и функции WebRequest в языке MQL5 (V)
Узнайте, как использовать функцию WebRequest и вызовы внешних API, чтобы получать свежие свечные данные, преобразовывать каждое значение в пригодный тип и аккуратно сохранять информацию в табличном виде. Этот шаг закладывает основу для создания индикатора, который визуализирует данные в свечном формате.
От новичка до эксперта: Разработка стратегии торговли по зонам ликвидности От новичка до эксперта: Разработка стратегии торговли по зонам ликвидности
Торговля в зонах ликвидности обычно ведется путем ожидания возврата цены и повторного тестирования интересующей зоны, часто путем размещения отложенных ордеров в этих областях. В этой статье мы используем MQL5, чтобы воплотить эту концепцию в жизнь, демонстрируя, как такие зоны могут быть определены программно и как можно систематически применять управление рисками. Присоединяйтесь к обсуждению, поскольку мы исследуем как логику торговли на основе ликвидности, так и ее практическую реализацию.