preview
Индикатор сезонности по часам, дням недели и месяца

Индикатор сезонности по часам, дням недели и месяца

MetaTrader 5Торговые системы |
339 0
Yevgeniy Koshtenko
Yevgeniy Koshtenko

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

В этом руководстве мы создадим индикатор сезонности на языке MQL5 для платформы MetaTrader 5. Наш индикатор будет анализировать исторические данные цен, чтобы выявить среднюю доходность для дней месяца (с 1 по 31), дней недели (от понедельника до воскресенья) или часов дня (с 0 до 23). Результаты будут отображаться в виде гистограммы в отдельном окне графика, с линией прогноза, соединяющей значения сезонности, и точками, выделяющими ожидаемые значения на следующих барах. Кроме того, индикатор выведет текстовую статистику с прогнозами, лучшими и худшими периодами. Мы разберём процесс разработки шаг за шагом, объясняя каждую часть кода, чтобы вы могли не только использовать индикатор, но и адаптировать его под свои идеи. Это будет путешествие в мир программирования и финансового анализа, где код становится мостом между данными и торговыми решениями.


Для чего изучать сезонность

Сезонность — это как скрытый пульс рынка. Она возникает из-за множества факторов: поведения трейдеров, экономических событий, даже человеческой психологии. Например, в конце месяца крупные фонды могут закрывать позиции, вызывая предсказуемые движения цен. Или в определённые часы дня активность на рынке возрастает из-за открытия торговых сессий в Лондоне или Нью-Йорке. Наш индикатор поможет выявить эти закономерности, превратив хаотичные данные в понятные графики и числа. Это не магия, а наука о данных, которая работает на трейдера.

Индикатор, который мы создадим, будет универсальным. Он позволит выбрать тип сезонности, настроить количество анализируемых баров, отображать доходность в процентах или абсолютных значениях, а также включать или отключать прогноз на графике. Гибкость — ключ к тому, чтобы индикатор стал полезным инструментом для разных рынков и таймфреймов, будь то дневной график EUR/USD или часовой график золота.


Определяем структуру индикатора

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

//+------------------------------------------------------------------+
//|                                         SeasonalityIndicator.mq5 |
//|                                  Copyright 2025, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2025, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#property version   "1.00"
#property indicator_separate_window
#property indicator_buffers 3
#property indicator_plots   3

//--- plot Seasonality
#property indicator_label1  "Seasonality"
#property indicator_type1   DRAW_HISTOGRAM
#property indicator_color1  clrDodgerBlue
#property indicator_style1  STYLE_SOLID
#property indicator_width1  2

//--- plot Forecast Line
#property indicator_label2  "Forecast"
#property indicator_type2   DRAW_LINE
#property indicator_color2  clrRed
#property indicator_style2  STYLE_DASH
#property indicator_width2  2

//--- plot Forecast Points
#property indicator_label3  "Forecast Points"
#property indicator_type3   DRAW_ARROW
#property indicator_color3  clrOrange
#property indicator_width3  3

В этом коде мы задаём основную информацию об индикаторе: авторские права, ссылку на сайт MetaQuotes и версию. Директива #property indicator_separate_window указывает, что индикатор будет в отдельном окне. Мы определяем три буфера и три графика. Гистограмма (DRAW_HISTOGRAM) окрашивается в голубой цвет, линия прогноза (DRAW_LINE) — в красный пунктир, а точки прогноза (DRAW_ARROW) — в оранжевый цвет с шириной три пикселя. Эти настройки задают визуальный стиль, который сделает индикатор понятным и привлекательным.


Настраиваем входные параметры

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

//--- Входные параметры
enum ENUM_SEASONALITY_TYPE
{
   SEASONALITY_DAYS_OF_MONTH = 0,  // Дни месяца (1-31)
   SEASONALITY_DAYS_OF_WEEK = 1,   // Дни недели (Пн-Вс)
   SEASONALITY_HOURS = 2           // Часы (0-23)
};

input ENUM_SEASONALITY_TYPE SeasonalityType = SEASONALITY_DAYS_OF_MONTH; // Тип сезонности
input int BarsToAnalyze = 1000;                                          // Количество баров для анализа
input bool ShowPercentage = true;                                        // Показывать в процентах
input bool ShowPositiveOnly = false;                                     // Показывать только положительные значения
input bool ShowForecastOnChart = true;                                   // Показывать прогноз на графике
input color ForecastColor = clrRed;                                      // Цвет линии прогноза
input color ForecastPointsColor = clrOrange;                             // Цвет точек прогноза

Перечисление ENUM_SEASONALITY_TYPE задаёт три варианта сезонности. По умолчанию выбран анализ по дням месяца. Параметр BarsToAnalyze установлен на 1000 баров, что соответствует примерно четырём годам данных на дневном графике. Параметр ShowPercentage по умолчанию включён, чтобы доходность отображалась в процентах, что интуитивно понятнее. Параметры ShowPositiveOnly и ShowForecastOnChart дают дополнительную гибкость, а цвета можно изменить через интерфейс MetaTrader.


Определяем буферы и переменные

Для хранения данных индикатор использует три буфера: один для гистограммы, второй для линии прогноза и третий для точек прогноза. Также нужны глобальные переменные: массив для хранения средней доходности по периодам, массив для подписей периодов (например, "Пн" или "1") и переменная для количества периодов, которое зависит от типа сезонности (31 для дней месяца, 7 для дней недели, 24 для часов).

//--- Буферы индикатора
double SeasonalityBuffer[];
double ForecastLineBuffer[];
double ForecastPointsBuffer[];

//--- Глобальные переменные
double seasonality_data[];
string period_labels[];
int periods_count;

Буферы будут содержать данные для отрисовки, а переменные seasonality_data и period_labels помогут хранить и отображать результаты анализа. Переменная periods_count определяет, сколько периодов анализируется, и задаётся в зависимости от типа сезонности.


Инициализация индикатора

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

int OnInit()
{
   SetIndexBuffer(0, SeasonalityBuffer, INDICATOR_DATA);
   SetIndexBuffer(1, ForecastLineBuffer, INDICATOR_DATA);
   SetIndexBuffer(2, ForecastPointsBuffer, INDICATOR_DATA);
   
   PlotIndexSetInteger(2, PLOT_ARROW, 159);
   
   PlotIndexSetInteger(1, PLOT_LINE_COLOR, ForecastColor);
   PlotIndexSetInteger(2, PLOT_LINE_COLOR, ForecastPointsColor);
   
   switch(SeasonalityType)
   {
      case SEASONALITY_DAYS_OF_MONTH:
         periods_count = 31;
         ArrayResize(period_labels, periods_count);
         for(int i = 0; i < periods_count; i++)
            period_labels[i] = IntegerToString(i + 1);
         IndicatorSetString(INDICATOR_SHORTNAME, "Сезонность по дням месяца");
         break;
         
      case SEASONALITY_DAYS_OF_WEEK:
         periods_count = 7;
         ArrayResize(period_labels, periods_count);
         period_labels[0] = "Пн";
         period_labels[1] = "Вт";
         period_labels[2] = "Ср";
         period_labels[3] = "Чт";
         period_labels[4] = "Пт";
         period_labels[5] = "Сб";
         period_labels[6] = "Вс";
         IndicatorSetString(INDICATOR_SHORTNAME, "Сезонность по дням недели");
         break;
         
      case SEASONALITY_HOURS:
         periods_count = 24;
         ArrayResize(period_labels, periods_count);
         for(int i = 0; i < periods_count; i++)
            period_labels[i] = IntegerToString(i) + ":00";
         IndicatorSetString(INDICATOR_SHORTNAME, "Сезонность по часам");
         break;
   }
   
   ArrayResize(seasonality_data, periods_count);
   ArrayInitialize(seasonality_data, 0.0);
   
   IndicatorSetInteger(INDICATOR_DIGITS, ShowPercentage ? 2 : _Digits);
   
   return(INIT_SUCCEEDED);
}

Эта функция задаёт основу работы индикатора. Например, для дней недели создаётся массив подписей от "Пн" до "Вс", а для часов — от "0:00" до "23:00". Название индикатора меняется, чтобы пользователь сразу понял, какой тип сезонности выбран. Это делает индикатор интуитивно понятным.


Расчёт сезонности

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

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

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(rates_total < BarsToAnalyze + 1)
      return(0);

   ArrayInitialize(seasonality_data, 0.0);
   
   double period_returns[];
   int period_counts[];
   ArrayResize(period_returns, periods_count);
   ArrayResize(period_counts, periods_count);
   ArrayInitialize(period_returns, 0.0);
   ArrayInitialize(period_counts, 0);
   
   int start_pos = MathMax(0, rates_total - BarsToAnalyze - 1);
   
   for(int i = start_pos; i < rates_total - 1; i++)
   {
      double return_value = 0.0;
      if(close[i] != 0)
         return_value = (close[i+1] - close[i]) / close[i];
      
      int period_index = GetPeriodIndex(time[i]);
      
      if(period_index >= 0 && period_index < periods_count)
      {
         period_returns[period_index] += return_value;
         period_counts[period_index]++;
      }
   }
   
   for(int i = 0; i < periods_count; i++)
   {
      if(period_counts[i] > 0)
      {
         seasonality_data[i] = period_returns[i] / period_counts[i];
         if(ShowPercentage)
            seasonality_data[i] *= 100.0;
         if(ShowPositiveOnly && seasonality_data[i] < 0)
            seasonality_data[i] = 0.0;
      }
   }
   
   for(int i = 0; i < rates_total; i++)
   {
      int period_index = GetPeriodIndex(time[i]);
      if(period_index >= 0 && period_index < periods_count)
      {
         SeasonalityBuffer[i] = seasonality_data[period_index];
         ForecastLineBuffer[i] = seasonality_data[period_index];
      }
      else
      {
         SeasonalityBuffer[i] = 0.0;
         ForecastLineBuffer[i] = 0.0;
      }
      ForecastPointsBuffer[i] = EMPTY_VALUE;
   }
   
   if(ShowForecastOnChart)
      DrawForecastOnChart(rates_total, time);
   
   return(rates_total);
}

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


Определение периода

Чтобы понять, к какому периоду относится бар, создаём функцию GetPeriodIndex. Она преобразует время бара в индекс периода в зависимости от типа сезонности. Для дней месяца она берёт число дня и вычитает единицу, чтобы получить индекс от 0 до 30. Для дней недели она преобразует значение дня недели (где воскресенье — 0, а понедельник — 1) в порядок, где понедельник — 0, а воскресенье — 6. Для часов просто возвращается значение часа от 0 до 23.

int GetPeriodIndex(datetime bar_time)
{
   MqlDateTime dt;
   TimeToStruct(bar_time, dt);
   
   switch(SeasonalityType)
   {
      case SEASONALITY_DAYS_OF_MONTH:
         return(dt.day - 1);
         
      case SEASONALITY_DAYS_OF_WEEK:
         return(dt.day_of_week == 0 ? 6 : dt.day_of_week - 1);
         
      case SEASONALITY_HOURS:
         return(dt.hour);
   }
   
   return(-1);
}

Эта функция — как компас, который помогает индикатору понять, к какому "ящику" отнести данные бара. Без неё мы бы не смогли связать цену с конкретным днём или часом.


Отрисовка прогноза

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

void DrawForecastOnChart(int rates_total, const datetime &time[])
{
   if(rates_total < 3)
      return;
   
   datetime current_time = time[rates_total - 1];
   int current_period = GetPeriodIndex(current_time);
   int next_period1 = GetNextPeriod(current_period);
   int next_period2 = GetNextPeriod(next_period1);
   
   int forecast_pos1 = rates_total - 2;
   int forecast_pos2 = rates_total - 1;
   
   if(next_period1 >= 0 && next_period1 < periods_count)
   {
      ForecastLineBuffer[forecast_pos1] = seasonality_data[next_period1];
      ForecastPointsBuffer[forecast_pos1] = seasonality_data[next_period1];
   }
   
   if(next_period2 >= 0 && next_period2 < periods_count)
   {
      ForecastLineBuffer[forecast_pos2] = seasonality_data[next_period2];
      ForecastPointsBuffer[forecast_pos2] = seasonality_data[next_period2];
   }
   
   if(rates_total >= 3)
   {
      int current_pos = rates_total - 3;
      int current_period_idx = GetPeriodIndex(time[current_pos]);
      
      if(current_period_idx >= 0 && current_period_idx < periods_count)
      {
         double current_value = seasonality_data[current_period_idx];
         double next_value = (next_period1 >= 0) ? seasonality_data[next_period1] : current_value;
         
         ForecastLineBuffer[current_pos] = current_value;
         if(forecast_pos1 < rates_total)
            ForecastLineBuffer[forecast_pos1] = next_value;
      }
   }
}

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


Вычисление следующего периода

Функция GetNextPeriod определяет, какой период следует за текущим. Она использует модульную арифметику, чтобы обеспечить циклический переход: после 31-го дня месяца идёт 1-й, после воскресенья — понедельник, после 23:00 — 0:00.

int GetNextPeriod(int current_period)
{
   if(current_period < 0)
      return(-1);
   
   switch(SeasonalityType)
   {
      case SEASONALITY_DAYS_OF_MONTH:
         return((current_period + 1) % 31);
         
      case SEASONALITY_DAYS_OF_WEEK:
         return((current_period + 1) % 7);
         
      case SEASONALITY_HOURS:
         return((current_period + 1)rate_total % 24);
   }
   
   return(-1);
}

Эта функция гарантирует, что прогноз всегда смотрит на правильные будущие периоды, сохраняя логику циклического времени. Она действует как точный календарь рынка, обеспечивая, чтобы индикатор корректно переходил от одного периода к следующему, будь то смена дней месяца, дней недели или часов. Представьте, что рынок — это гигантские часы, где каждый тик отсчитывает не только время, но и потенциальные движения цены. После 31-го дня месяца функция плавно возвращает нас к 1-му дню, после воскресенья переходит к понедельнику, а после 23:00 — к 0:00. Такой циклический подход отражает реальную структуру времени на финансовых рынках, позволяя индикатору предсказывать, какие периоды могут быть значимыми для трейдера.


Отображение статистики

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

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

Завершает сообщение полная статистика, где для каждого периода указывается его подпись — например, "Пн" или "15" — и средняя доходность. Этот текст появляется в правом верхнем углу графика, словно отчёт учёного, который раскрывает тайны рыночных ритмов, делая их понятными и полезными для принятия торговых решений.


Заключение

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

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

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

Прикрепленные файлы |
Seasonaly_Ind.mq5 (31.38 KB)
Возможности Мастера MQL5, которые вам нужно знать (Часть 47): Обучение с подкреплением (алгоритм временных различий) Возможности Мастера MQL5, которые вам нужно знать (Часть 47): Обучение с подкреплением (алгоритм временных различий)
Temporal Difference (TD, временные различия) — еще один алгоритм обучения с подкреплением, который обновляет Q-значения на основе разницы между прогнозируемыми и фактическими вознаграждениями во время обучения агента. Особое внимание уделяется обновлению Q-значений без учета их пар "состояние-действие" (state-action). Как обычно, мы рассмотрим, как этот алгоритм можно применить в советнике, собранном с помощью Мастера.
Реализация криптографического алгоритма SHA-256 с нуля на MQL5 Реализация криптографического алгоритма SHA-256 с нуля на MQL5
Создание интеграций с криптовалютными биржами без DLL-файлов долгое время было сложной задачей, но это решение обеспечивает полную основу для прямого подключения к рынку.
Анализ временных разрывов цен в MQL5 (Часть II): Создаем тепловую карту распределения ликвидности во времени Анализ временных разрывов цен в MQL5 (Часть II): Создаем тепловую карту распределения ликвидности во времени
Подробное руководство по созданию индикатора тепловой карты для MetaTrader 5, который визуализирует временное распределение цены в виде тепловой карты. Статья раскрывает математическую основу анализа временной плотности, где каждый ценовой уровень окрашивается от красного (минимальное время пребывания) до синего (максимальное время пребывания).
Искусство ведения логов (Часть 1): Основные понятия и первые шаги в MQL5 Искусство ведения логов (Часть 1): Основные понятия и первые шаги в MQL5
Добро пожаловать в новое приключение! Данная статья открывает специальный цикл, в котором мы будем пошагово создавать библиотеку для манипуляций с журналами, предназначенную для тех, кто занимается разработкой на языке MQL5.