Индикатор кластерного объема (Footprint) в MQL5 на CopyTicks

Индикатор кластерного объема (Footprint) в MQL5 на CopyTicks КРЕДИТЫ, ИПОТЕКА и БАНКИ
Пошаговое руководство по созданию профессионального индикатора кластерного объема (Footprint/Delta) в MetaTrader 5 с использованием CopyTicks на MQL5.
Суть: Индикатор Footprint (кластерный объем) разделяет проторгованный объем внутри каждой свечи на покупки (Ask) и продажи (Bid) по каждой цене. В MQL5 это реализуется через функцию CopyTicksRange, которая извлекает точную тиковую историю для каждого бара, после чего данные группируются и динамически отрисовываются на графике.

Введение в кластерный анализ на MQL5

Анализ горизонтальных объемов и дельты (Footprint) — это стандарт де-факто для профессионального ордер-флоу анализа. В отличие от стандартных баров, кластерный график позволяет заглянуть внутрь свечи и увидеть борьбу покупателей и продавцов на каждом ценовом уровне. В терминале MetaTrader 5 мы имеем прямой доступ к реальной тиковой истории благодаря функциям CopyTicks и CopyTicksRange.

Если вам требуется выгрузить накопленные тиковые данные для глубокого анализа в Python или R, ознакомьтесь с руководством Экспорт тиковой истории из MT5 в CSV с помощью CopyTicksRange.

Для сохранения детальной статистики кластеров или отладочных логов в структурированном виде, вы можете использовать подход, описанный в статье Как переопределить FileOpen в MQL4 для сохранения логов в JSON.

На основе полученных кластерных объемов можно построить более сложные торговые сигналы. О том, как это сделать, читайте в нашей статье Как реализовать алгоритм Bid/Ask Imbalance в MQL5.

Исходный код

Ниже представлен полный код индикатора, который рассчитывает объемы Bid/Ask для каждого ценового уровня на последних InpMaxBars барах и отображает их в виде текстовых кластеров прямо на ценовом графике.

//+------------------------------------------------------------------+
//|                                                    Footprint.mq5 |
//|                                  Copyright 2024, Quant Developer |
//+------------------------------------------------------------------+
#property copyright "Quant Developer"
#property link      "https://finfluct.com"
#property version   "1.00"
#property indicator_chart_window
#property indicator_plots 0

struct PriceLevel
{
   double price;
   long bid_vol;
   long ask_vol;
};

input int InpMaxBars = 5;                  // Кол-во баров для расчета
input color InpAskColor = clrLimeGreen;    // Цвет Ask (Покупки)
input color InpBidColor = clrOrangeRed;    // Цвет Bid (Продажи)
input int InpFontSize = 8;                 // Размер шрифта

int OnInit()
{
   return(INIT_SUCCEEDED);
}

void OnDeinit(const int reason)
{
   ObjectsDeleteAll(0, "FP_");
   ChartRedraw();
}

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 < InpMaxBars) return(0);
   
   // Удаляем старые объекты перед перерисовкой
   ObjectsDeleteAll(0, "FP_");
   
   int start_idx = rates_total - InpMaxBars;
   
   for(int i = start_idx; i < rates_total; i++)
   {
      datetime t_start = time[i];
      // Конечная точка - начало следующего бара или текущее время + запас
      datetime t_end = (i < rates_total - 1) ? time[i+1] : TimeCurrent() + 3600;
      
      MqlTick ticks[];
      // Запрашиваем тиковую историю для текущего бара
      int copied = CopyTicksRange(_Symbol, ticks, COPY_TICKS_ALL, t_start * 1000, t_end * 1000);
      if(copied <= 0) continue;
      
      PriceLevel levels[];
      
      for(int t = 0; t < copied; t++)
      {
         double price = ticks[t].last;
         if(price == 0) price = ticks[t].bid; // Фолбэк на bid, если last равен 0
         if(price == 0) continue;
         
         // Округляем цену до шага цены инструмента
         double tick_size = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE);
         if(tick_size > 0)
         {
            price = MathRound(price / tick_size) * tick_size;
         }
         
         long vol = ticks[t].volume;
         if(vol == 0) vol = 1; // Минимальный объем
         
         int idx = GetPriceLevelIndex(levels, price);
         
         // Классификация тика: Buy (Ask) или Sell (Bid)
         if((ticks[t].flags & TICK_FLAG_BUY) != 0)
         {
            levels[idx].ask_vol += vol;
         }
         else if((ticks[t].flags & TICK_FLAG_SELL) != 0)
         {
            levels[idx].bid_vol += vol;
         }
         else
         {
            // Если флаги не определены, классифицируем по отношению к Bid/Ask
            if(ticks[t].last >= ticks[t].ask && ticks[t].ask > 0)
               levels[idx].ask_vol += vol;
            else
               levels[idx].bid_vol += vol;
         }
      }
      
      // Отрисовка полученных кластеров на графике
      int levels_count = ArraySize(levels);
      for(int k = 0; k < levels_count; k++)
      {
         string obj_name = "FP_" + IntegerToString(i) + "_" + DoubleToString(levels[k].price, _Digits);
         
         if(ObjectCreate(0, obj_name, OBJ_TEXT, 0, t_start, levels[k].price))
         {
            string text = IntegerToString(levels[k].bid_vol) + " x " + IntegerToString(levels[k].ask_vol);
            ObjectSetString(0, obj_name, OBJPROP_TEXT, text);
            
            // Выбор цвета в зависимости от перевеса (Delta)
            color text_color = (levels[k].ask_vol >= levels[k].bid_vol) ? InpAskColor : InpBidColor;
            ObjectSetInteger(0, obj_name, OBJPROP_COLOR, text_color);
            ObjectSetInteger(0, obj_name, OBJPROP_FONTSIZE, InpFontSize);
            ObjectSetInteger(0, obj_name, OBJPROP_ANCHOR, ANCHOR_LEFT);
         }
      }
   }
   
   ChartRedraw();
   return(rates_total);
}

// Поиск индекса ценового уровня или создание нового
int GetPriceLevelIndex(PriceLevel &levels[], double price)
{
   int size = ArraySize(levels);
   for(int i = 0; i < size; i++)
   {
      if(MathAbs(levels[i].price - price) < _Point / 2.0)
         return i;
   }
   ArrayResize(levels, size + 1);
   levels[size].price = price;
   levels[size].bid_vol = 0;
   levels[size].ask_vol = 0;
   return size;
}

Разбор параметров

  • InpMaxBars: Количество последних свечей, для которых будет рассчитываться и отображаться кластерный объем. Ограничение необходимо для предотвращения зависания терминала при глубокой истории.
  • InpAskColor: Цвет отображения объемов покупок (Ask), когда они доминируют на уровне.
  • InpBidColor: Цвет отображения объемов продаж (Bid), когда они превышают покупки.
  • InpFontSize: Размер шрифта для текстовых меток объемов на графике.

Как запустить

Для запуска индикатора выполните следующие шаги:

1. Откройте MetaEditor в MetaTrader 5 (клавиша F4).

2. Создайте новый пользовательский индикатор (Файл -> Новый -> Пользовательский индикатор) с именем Footprint.

3. Полностью замените сгенерированный код на представленный выше листинг.

4. Скомпилируйте код (клавиша F7).

5. Перетащите индикатор из навигатора терминала на график нужного инструмента. Убедитесь, что ваш брокер предоставляет реальную тиковую историю (доступно на большинстве счетов типа ECN/NDD).

Оцените статью
FinFluct