preview
Забытая классика объёма: индикатор "Finite Volume Elements" для современных рынков

Забытая классика объёма: индикатор "Finite Volume Elements" для современных рынков

MetaTrader 5Примеры |
77 0
Artyom Trishkin
Artyom Trishkin

Содержание


Введение

В мире технического анализа индикаторы объёма играют ключевую роль в понимании рыночной динамики и подтверждении ценовых движений. Одним из таких инструментов является индикатор Finite Volume Elements (FVE), впервые подробно описанный в апрельском номере журнала Technical Analysis of STOCKS & COMMODITIES в 2003 году. FVE был разработан как усовершенствованный способ оценки баланса между покупателями и продавцами, учитывая не только направление цены, но и интенсивность объёмов торгов.

В отличие от классических индикаторов объёма, таких как On Balance Volume (OBV) или Accumulation/Distribution, FVE учитывает не только разницу между ценой закрытия и открытия, но и фильтрует незначительные колебания, устраняя рыночный "шум". Это позволяет более точно определять периоды накопления и распределения, а также выявлять истинные тренды на рынке.

В условиях современных финансовых рынков, характеризующихся высокой волатильностью и частыми ложными сигналами, Finite Volume Elements становится особенно актуальным инструментом. Его способность фильтровать незначительные движения и фокусироваться на значимых изменениях объёма делает FVE полезным как для краткосрочных трейдеров, так и для долгосрочных инвесторов, стремящихся повысить точность своих торговых решений.

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


Finite Volume Elements

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

Для расчёта индикатора необходимо выполнить следующие шаги:

  • Определить типичные цены для текущего и предыдущего баров:
    CurrTP = (CurrHigh + CurrLow + CurrClose) / 3
    PrevTP = (PrevHigh + PrevLow + PrevClose) / 3
  • Рассчитать метрику движения цены (MF), которая учитывает не только закрытие, но и положение цены внутри бара, а также изменение типичной цены:
    MF = (Close - (High + Low) / 2) + (CurrTP - PrevTP)
    где
    CurrTP - типичная цена текущего бара
    PrevTP - типичная цена предыдущего бара
  • Определить направления движения (Direction):
    если MF превышает определённый порог (Cutoff), движение (Direction) считается восходящим (+1), если ниже отрицательного порога - нисходящим (-1), иначе - нейтральным (0)
  • Скорректировать объём:
    AdjVolume = Volume * Direction
    где
    Volume - объём текущего бара
    Direction - определённое на предыдущем шаге направление движения
  • Суммировать скорректированные объёмы за заданный период (Samples) и разделить на общий объём за этот же период:
    SumAdjVolume = AdjVolume_1 + AdjVolume_2 + ... + AdjVolume_N  
    SumVolume = Volume_1 + Volume_2 + ... + Volume_N  
    где
    N - количество баров в выбранном периоде (Samples)
  • Рассчитать итоговое значение FVE, выраженное в процентах:
    FVE = (SumAdjVolume / SumVolume) * 100%

Рассмотренные формулы позволяют последовательно рассчитать значение индикатора FVE для любого выбранного периода. Реализуем такой индикатор для платформы MetaTrader5.


Реализуем FVE для MetaTrader 5

Создадим индикатор, работающий в подокне графика. Индикатор будет использовать как реальный, так и тиковый объём, что важно для рынка Forex.

Входные параметры индикатора будут такими:

  • Samples — период расчёта (по умолчанию 22 бара),
  • Threshold (CutOff) — порог чувствительности, фильтрующий незначительные колебания (по умолчанию 0.3),
  • Used Volume — тип используемого объёма (реальный или тиковый).

В каталоге терминала \MQL5\Indicators\STOCKS_COMMODITIES\FiniteVolumeElement\ создадим новый индикатор с именем FVE.mq5:

//+------------------------------------------------------------------+
//|                                                          FVE.mq5 |
//|                                  Copyright 2026, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2026, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#property version   "1.00"
#property description       "Finite Volume Elements"
#property indicator_separate_window
#property indicator_buffers 3
#property indicator_plots   1
//--- plot FVE
#property indicator_label1  "FVE"
#property indicator_type1   DRAW_LINE
#property indicator_color1  clrRed
#property indicator_style1  STYLE_SOLID
#property indicator_width1  1

enum ENUM_USED_VOLUME   // Перечисление используемых объёмов
  {
   USED_VOLUME_REAL,    // Real Volume
   USED_VOLUME_TICK,    // Tick Volume
  };

//--- input parameters
input(name="Samples")      int               InpSamples     =  22;               // Период расчёта
input(name="Threshold")    double            InpCutOff      =  0.3;              // Порог чувствительности
input(name="Used Volume")  ENUM_USED_VOLUME  InpUsedVolume  =  USED_VOLUME_TICK; // Используемый объём

//--- indicator buffers
double         BufferFVE[];
double         BufferVolumePlusMinus[];
double         BufferVolumes[];

//--- global variables
int            samples;
double         cutoff;
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- indicator buffers mapping
   SetIndexBuffer(0,BufferFVE,INDICATOR_DATA);
   SetIndexBuffer(1,BufferVolumePlusMinus,INDICATOR_CALCULATIONS);
   SetIndexBuffer(2,BufferVolumes,INDICATOR_CALCULATIONS);

   PlotIndexSetDouble(0,PLOT_EMPTY_VALUE,EMPTY_VALUE);

//--- Проверка и установка периода и порога
   samples=(InpSamples<1? 22 : InpSamples);
   cutoff=InpCutOff/100.0;
//--- Установка имени индикатора и уровня 0
   IndicatorSetString(INDICATOR_SHORTNAME,StringFormat("FVE(%d,%.3f)",samples,InpCutOff));
   IndicatorSetInteger(INDICATOR_LEVELS,1);
   IndicatorSetDouble(INDICATOR_LEVELVALUE,0,0.0);

   ArraySetAsSeries(BufferFVE,true);
   ArraySetAsSeries(BufferVolumePlusMinus,true);
   ArraySetAsSeries(BufferVolumes,true);

//--- Всё успешно
   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[])
  {
//--- Проверка количества доступных баров
   if(rates_total<samples+1)
      return(0);

//--- Массивы для расчёта - как таймсерии
   ArraySetAsSeries(high,true);
   ArraySetAsSeries(low,true);
   ArraySetAsSeries(close,true);
   ArraySetAsSeries(volume,true);
   ArraySetAsSeries(tick_volume,true);

//--- Проверка и расчёт количества просчитываемых баров
   int limit=rates_total-prev_calculated;

//--- Если очередной новый тик - ничего не делаем
   if(limit==0)
      return(rates_total);

//--- Если первый запуск или изменения исторических данных
   if(limit>1)
     {
      //--- расчёт начинаем от начала исторических данных,
      //--- инициализируем буферы индикатора нулевыми значениями
      limit=rates_total-samples-1;
      ArrayInitialize(BufferFVE,0);
      ArrayInitialize(BufferVolumePlusMinus,0);
      ArrayInitialize(BufferVolumes,0);
     }
//--- Расчёт индикатора (либо вся история, либо каждый очередной новый бар)
   for(int i=limit; i>=0; i--)
     {
      //--- Типичные цены для текущего и предыдущего баров
      double TP_curr=(high[i]+low[i]+close[i])/3.0;
      double TP_prev=(high[i+1]+low[i+1]+close[i+1])/3.0;
      
      //--- Рассчитываем текущие метрику и направление движения цены
      double MF=(close[i]-(high[i]+low[i])/2.0)+TP_curr-TP_prev;
      int FveFactor=(MF>cutoff*close[i]) ? 1 : (MF< -cutoff*close[i]) ? -1 : 0;
      
      //--- Записываем текущие скорректированный и общий объёмы в буферы
      long vol=Volume(i,volume,tick_volume);
      BufferVolumePlusMinus[i]=double(vol*FveFactor);
      BufferVolumes[i]=(double)vol;

      //--- Суммируем скорректированный и общий объём за samples баров
      double FVEsum=0, VolSum=0;
      for(int j=0;j<samples;j++)
        {
         int idx=i+j;
         FVEsum+=BufferVolumePlusMinus[idx]; // Сумма скорректированного объёма за samples баров 
         VolSum+=BufferVolumes[idx];         // Сумма общего объёма за samples баров
        }
      //--- Рассчитываем FVE
      BufferFVE[i]=(VolSum!=0 ? (FVEsum/VolSum)*100.0 : 0.0);
     }
//--- return value of prev_calculated for next call
   return(rates_total);
  }
//+------------------------------------------------------------------+
//| Возвращает объём в зависимости от выбранного в настройках        |
//+------------------------------------------------------------------+
long Volume(const int index,const long &volume_real[],const long &volume_tick[])
  {
   return(InpUsedVolume==USED_VOLUME_REAL ? volume_real[index] : volume_tick[index]);
  }
//+------------------------------------------------------------------+

Скомпилируем индикатор и запустим на графике со значением Threshold (InpCutOff) равным 0.2:

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


Индикатор FinVolEleLinRegSl на основе FVE

С целью более эффективного выявления рыночных прорывов Маркос Катсанос предложил индикатор FinVolEleLinRegSl, который является развитием индикатора Finite Volume Element. Если FVE позволяет определить, преобладают ли на рынке покупатели или продавцы, то FinVolEleLinRegSl дополняет этот анализ оценкой динамики изменений — то есть скорости и направления изменения как самого FVE, так и цены.

Для этого в FinVolEleLinRegSl рассчитываются наклоны (slope) линейной регрессии по FVE и по цене за определённый период. Это позволяет выявлять моменты, когда объём и цена начинают двигаться согласованно или, наоборот, расходятся, и это может быть признаком скорого изменения тренда или подтверждения текущего движения.

В той же папке \MQL5\Indicators\STOCKS_COMMODITIES\FiniteVolumeElement (FVE)\ создадим новый индикатор, тоже работающий в подокне, с именем FinVolEleLinRegSl.mq5:

//+------------------------------------------------------------------+
//|                                            FinVolEleLinRegSl.mq5 |
//|                                  Copyright 2026, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2026, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#property version   "1.00"
#property indicator_separate_window
#property description "Finite Volume Elements + Linear Regression Slope"
#property indicator_buffers 5
#property indicator_plots   2
//--- plot FVESlope
#property indicator_label1  "FVESlope"
#property indicator_type1   DRAW_LINE
#property indicator_color1  clrRed
#property indicator_style1  STYLE_SOLID
#property indicator_width1  1
//--- plot PriceSlope
#property indicator_label2  "PriceSlope"
#property indicator_type2   DRAW_LINE
#property indicator_color2  clrBlue
#property indicator_style2  STYLE_SOLID
#property indicator_width2  1

enum ENUM_USED_VOLUME   // Перечисление используемых объёмов
  {
   USED_VOLUME_REAL,    // Real Volume
   USED_VOLUME_TICK,    // Tick Volume
  };

//--- input parameters
input(name="Samples")         int            InpSamples           =  22;               // Период расчёта
input(name="Threshold")       double         InpCutOff            =  0.3;              // Порог чувствительности
input(name="SlopePeriod")     int            InpSlopePeriod       =  35;               // Период линейной регрессии
input(name="PriceSlopeFactor")double         InpPriceSlopeFactor  = 2500;              // Масштаб цены (индивидуально под инструмент)
input(name="Used Volume")  ENUM_USED_VOLUME  InpUsedVolume        =  USED_VOLUME_TICK; // Используемый объём

//--- indicator buffers
double         BufferFVESlope[];
double         BufferPriceSlope[];
double         BufferFVE[];
double         BufferVolumePlusMinus[];
double         BufferVolumes[];

//--- global variables
int            samples;
double         cutoff;
int            slope_period;
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
   //--- отображаемые буферы
   SetIndexBuffer(0,BufferFVESlope,INDICATOR_DATA);
   SetIndexBuffer(1,BufferPriceSlope,INDICATOR_DATA);
   //--- расчётные буферы
   SetIndexBuffer(2,BufferFVE,INDICATOR_CALCULATIONS);
   SetIndexBuffer(3,BufferVolumePlusMinus,INDICATOR_CALCULATIONS);
   SetIndexBuffer(4,BufferVolumes,INDICATOR_CALCULATIONS);

   PlotIndexSetDouble(0,PLOT_EMPTY_VALUE,EMPTY_VALUE);
   PlotIndexSetDouble(1,PLOT_EMPTY_VALUE,EMPTY_VALUE);

   samples=(InpSamples<1? 22 : InpSamples);
   cutoff=InpCutOff/100.0;
   slope_period=(InpSlopePeriod<2? 35 : InpSlopePeriod);

//--- Короткое имя индикатора
   IndicatorSetString(INDICATOR_SHORTNAME,
   StringFormat("FVELinReg(%d,%.3f,%d,%.1f)",samples,InpCutOff,slope_period,InpPriceSlopeFactor));
   
//--- Уровень 0 индикатора
   IndicatorSetInteger(INDICATOR_LEVELS,1);
   IndicatorSetDouble(INDICATOR_LEVELVALUE,0,0.0);

   ArraySetAsSeries(BufferFVE,true);
   ArraySetAsSeries(BufferVolumePlusMinus,true);
   ArraySetAsSeries(BufferVolumes,true);
   ArraySetAsSeries(BufferFVESlope,true);
   ArraySetAsSeries(BufferPriceSlope,true);

   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[])
  {
//--- Проверка количества доступных баров
   if(rates_total<samples+slope_period+1)
      return(0);

//--- Массивы для расчёта - как таймсерии
   ArraySetAsSeries(high,true);
   ArraySetAsSeries(low,true);
   ArraySetAsSeries(close,true);
   ArraySetAsSeries(volume,true);
   ArraySetAsSeries(tick_volume,true);

//--- Проверка и расчёт количества просчитываемых баров
   int limit=rates_total-prev_calculated;

//--- Если первый запуск или изменения исторических данных
   if(limit>1)
     {
      limit=rates_total-samples-slope_period-1;
      ArrayInitialize(BufferFVE,0);
      ArrayInitialize(BufferVolumePlusMinus,0);
      ArrayInitialize(BufferVolumes,0);
      ArrayInitialize(BufferFVESlope,0);
      ArrayInitialize(BufferPriceSlope,0);
     }
//--- Расчёт индикатора
   for(int i=limit; i>=0; i--)
     {
      //--- Типичные цены для текущего и предыдущего баров (FVE)
      double TP_curr=(high[i]+low[i]+close[i])/3.0;
      double TP_prev=(high[i+1]+low[i+1]+close[i+1])/3.0;
      
      //--- Рассчитываем текущие метрику и направление движения цены (FVE)
      double MF=(close[i]-(high[i]+low[i])/2.0)+TP_curr-TP_prev;
      int FveFactor=(MF>cutoff*close[i]) ? 1 : (MF< -cutoff*close[i]) ? -1 : 0;
      
      //--- Записываем текущие скорректированный и общий объёмы в буферы (FVE)
      long vol=Volume(i,volume,tick_volume);
      BufferVolumePlusMinus[i]=double(vol*FveFactor);
      BufferVolumes[i]=(double)vol;
      
      //--- Суммируем скорректированный и общий объём за samples баров (FVE)
      double FVEsum=0, VolSum=0;
      for(int j=0;j<samples;j++)
        {
         int idx=i+j;
         FVEsum+=BufferVolumePlusMinus[idx]; // Сумма скорректированного объёма за samples баров 
         VolSum+=BufferVolumes[idx];         // Сумма общего объёма за samples баров
        }
      //--- Рассчитываем FVE
      BufferFVE[i]=(VolSum!=0 ? (FVEsum/VolSum)*100.0 : 0.0);

      // --- Линейная регрессия по FVE и по цене
      if(i<=rates_total-samples-slope_period-1)
        {
         BufferFVESlope[i]=LinearRegSlope(BufferFVE,i,slope_period);
         BufferPriceSlope[i]=LinearRegSlope(close,i,slope_period)*InpPriceSlopeFactor;
        }
      else
        {
         BufferFVESlope[i]=EMPTY_VALUE;
         BufferPriceSlope[i]=EMPTY_VALUE;
        }
     }
   return(rates_total);
  }
//+------------------------------------------------------------------+
//| Возвращает объём в зависимости от выбранного в настройках        |
//+------------------------------------------------------------------+
long Volume(const int index,const long &volume_real[],const long &volume_tick[])
  {
   return(InpUsedVolume==USED_VOLUME_REAL ? volume_real[index] : volume_tick[index]);
  }
//+------------------------------------------------------------------+
//| Расчёт наклона линейной регрессии для массива buffer             |
//+------------------------------------------------------------------+
double LinearRegSlope(const double &buffer[],int start,int period)
  {
   bool as_series=ArrayGetAsSeries(buffer);
   double sumX=0,sumY=0,sumXY=0,sumXX=0;
   for(int i=0;i<period;i++)
     {
      double x=i;
      double y=(as_series ? buffer[start+period-1-i] : buffer[start+i]);
      sumX+=x;
      sumY+=y;
      sumXY+=x*y;
      sumXX+=x*x;
     }
   double denom=period*sumXX-sumX*sumX;
   if(denom==0) return(0.0);
   double slope=(period*sumXY-sumX*sumY)/denom;
   return(slope);
  }

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

Индикатор анализирует, как меняется соотношение объёма и цены на рынке. Сначала рассчитывается индикатор FVE, дающий данные о силе покупателей или продавцов. Затем определяется, ускоряется или замедляется импульс объёма, а также как быстро меняется сама цена. Для этого строятся две линии: одна отражает скорость изменения FVE, другая — скорость изменения цены:


Интерпретация индикаторов

Finite Volume Elements может использоваться как самостоятельный инструмент для определения фаз накопления и распределения, а также для фильтрации ложных сигналов других индикаторов. Основные его сигналы:
  • FVE > 0 — преобладают покупки, на рынке доминируют быки,
  • FVE < 0 — преобладают продажи, на рынке доминируют медведи,
  • Пересечение нулевой линии — потенциальный сигнал к смене тенденции.

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

В модификациях FVE с анализом наклона (как в FinVolEleLinRegSl) Маркос Катсанос предлагал использовать наклон (slope) для фильтрации ложных сигналов и для более раннего выявления изменений в динамике объёма и цены. Например, если наклон FVE становится отрицательным, это может быть предупреждением о скором ослаблении тренда, даже если сам FVE ещё положителен. В индикаторе FinVolEleLinRegSl кроме наклона линии FVE отображается и наклон линии цены Close. Это позволяет анализировать взаимное расположение линий наклона линейной регрессии FVE и цены и выявлять схождения/расхождения цены и объёмов. Например, если линия наклона цены растёт, а линия наклона объёмов падает, то это может означать, что движение цены не подкрепляется объёмами, и вскоре может быть разворот тенденции или остановка направленного движения.

Варианты интерпретации индикатора FinVolEleLinRegSl, предложенные Маркосом Катсаносом:

  • Основной сигнал индикатора — это пересечение линиями FVESlope и PriceSlope нулевого уровня 
    • когда обе линии пересекают ноль снизу вверх, это трактуется как ранний сигнал начала восходящего тренда, особенно если движение подтверждается объёмом,
    • пересечение нуля сверху вниз обеими линиями — сигнал к возможному развороту вниз,
  • Согласованное движение линий (обе линии растут и находятся выше нуля или обе падают и ниже нуля) подтверждает силу текущего тренда,
  • Дивергенция между линиями (например, PriceSlope продолжает расти, а FVESlope начинает снижаться) может указывать на ослабление тренда и возможный разворот. 
    Автор отмечал, что такие расхождения часто предшествуют коррекциям или смене направления движения,
  • Использование наклона (slope) позволяет фильтровать ложные сигналы классического FVE и получать более ранние предупреждения о смене рыночной динамики.

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

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

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

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

Давайте протестируем в советнике некоторые аспекты интерпретации двух этих индикаторов.


Тестируем в советнике

Для тестирования будем использовать только один индикатор — FinVolEleLinRegSl, так как он является производным от FVE, и показывает две линии — величину линейной регрессии наклона FVE и величину линейной регрессии наклона цены.

Для спецификации сигналов индикатора FinVolEleLinRegSl рассмотрим две таблицы: состояния одной (и каждой) линии индикатора и сводную таблицу взаимных расположений двух линий.

Таблица состояний линий индикатора:

Состояние  Описание   Расшифровка
GROWING Прямая линия вверх Стабильный направленный рост. Сила быков сохраняется.
FALLING Прямая линия вниз Стабильное направленное падение. Давление медведей сохраняется.
TURN_TO_GROW Форма «V» (минимум) Точка разворота: наклон сменился с падения на рост.
TURN_TO_FALL Форма «Λ» (максимум) Точка разворота: наклон сменился с роста на падение.

Каждая из двух линий индикатора может иметь такие состояния. Для определения общего сигнала индикатора необходимо анализировать взаимное расположение двух его линий.

Таблица взаимных расположений линий индикатора:

№  Состояние FVE   Состояние Price Итоговое состояние индикатора  Тип и расшифровка сигнала
1 GROWING GROWING FVE_GROWING_PRICE_GROWING [Бычий] Тренд вверх: объем подтверждает рост цены.
2 GROWING FALLING FVE_GROWING_PRICE_FALLING [Бычий] Конвергенция: закупка на падении цены.
3 GROWING TURN_TO_GROW FVE_GROWING_PRICE_TURN_GROW [Бычий] Подтверждение: цена начала расти вслед за объемом.
4 GROWING TURN_TO_FALL FVE_GROWING_PRICE_TURN_FALL [Медвежий] Скрытая дивергенция: цена падает при росте объема.
5 FALLING FALLING FVE_FALLING_PRICE_FALLING [Медвежий] Тренд вниз: объем подтверждает падение цены.
6 FALLING GROWING FVE_FALLING_PRICE_GROWING [Медвежий] Дивергенция: продажи актива на росте цены.
7 FALLING TURN_TO_FALL FVE_FALLING_PRICE_TURN_FALL [Медвежий] Подтверждение: цена начала падать вслед за объемом.
8 FALLING TURN_TO_GROW FVE_FALLING_PRICE_TURN_GROW [Бычий] Скрытая конвергенция: цена растет при падении объема.
9 TURN_TO_GROW TURN_TO_GROW FVE_TURN_GROW_PRICE_TURN_GROW [Бычий] Синхронный минимум («V»): сильный импульс на покупку.
10 TURN_TO_GROW GROWING FVE_TURN_GROW_PRICE_GROWING [Бычий] Импульс объема: FVE развернулся вверх вдогонку цене.
11 TURN_TO_GROW FALLING FVE_TURN_GROW_PRICE_FALLING [Бычий] Начало конвергенции: объем пошел вверх (FVE «V»).
12 TURN_TO_GROW TURN_TO_FALL FVE_TURN_GROW_PRICE_TURN_FALL [Бычий] Зеркальное схождение: разворот навстречу друг другу.
13 TURN_TO_FALL TURN_TO_FALL FVE_TURN_FALL_PRICE_TURN_FALL [Медвежий] Синхронный максимум («Λ»): сильный импульс на продажу.
14 TURN_TO_FALL FALLING FVE_TURN_FALL_PRICE_FALLING [Медвежий] Импульс объема: FVE развернулся вниз вдогонку цене.
15 TURN_TO_FALL GROWING FVE_TURN_FALL_PRICE_GROWING [Медвежий] Начало дивергенции: объем пошел вниз (FVE «Λ»).
16 TURN_TO_FALL TURN_TO_GROW FVE_TURN_FALL_PRICE_TURN_GROW [Медвежий] Зеркальное расхождение: разворот в разные стороны.

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

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

Советник для тестов возьмём из CodeBase: https://www.mql5.com/ru/code/63916. Это простой советник, торгующий по сигналам от стандартных индикаторов Williams' Percent Range (WPR) и полос Боллинджера (BB).

Сохраним советник в папке \MQL5\Experts\STOCKS_COMMODITIES\FiniteVolumeElement (FVE)\ под именем ExpFVEWPRBB.mq5.

Добавим в него отключаемую фильтрацию сигналов по индикатору FinVolEleLinRegSl. Для указания типов объёмов индикатора и возврата состояния его линий определим перечисления:

//+------------------------------------------------------------------+
//|                                                  ExpFVEWPRBB.mq5 |
//|                                  Copyright 2026, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2026, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#property version   "1.00"

//+------------------------------------------------------------------+
//| Включаемые файлы                                                 |
//+------------------------------------------------------------------+
#include <Trade\Trade.mqh>
#include <Arrays\ArrayLong.mqh>

//+------------------------------------------------------------------+
//| Перечисления                                                     |
//+------------------------------------------------------------------+
//--- Типы сигналов
enum ENUM_SIGNAL_TYPE
  {
   SIGNAL_TYPE_NONE,                                                 // Нет сигнала
   SIGNAL_TYPE_LONG,                                                 // Сигнал на покупку
   SIGNAL_TYPE_SHORT,                                                // Сигнал на покупку
  };
  
//--- Перечисление используемых объёмов
enum ENUM_USED_VOLUME
  {
   USED_VOLUME_REAL,                                                 // Real Volume
   USED_VOLUME_TICK,                                                 // Tick Volume
  };

//--- Состояния линий FVESlope и PriceSlope
enum ENUM_LINE_SLOPE_STATE
  {
   LINE_SLOPE_STATE_UNKNOWN,                                         // Состояние не определено
   LINE_SLOPE_STATE_FLAT,                                            // Низкая активность
   LINE_SLOPE_STATE_GROWING,                                         // Объёмы растут
   LINE_SLOPE_STATE_FALLING,                                         // Объёмы падают
   LINE_SLOPE_STATE_TURN_TO_GROW,                                    // Переход к росту объёмов
   LINE_SLOPE_STATE_TURN_TO_FALL                                     // Переход к снижению объёмов
  };
  
//--- Взаимные состояния линии FVESlope и PriceSlope
enum ENUM_FVE_STATE
  {
   FVE_STATE_UNKNOWN,                                                // Состояние не определено
   
   //--- Группа свойств FVE GROWING (Рост)
   FVE_STATE_FVE_GROWING_PRICE_GROWING,                              // FVE растет, Цена растет
   FVE_STATE_FVE_GROWING_PRICE_FALLING,                              // FVE растет, Цена падает
   FVE_STATE_FVE_GROWING_PRICE_TURN_GROW,                            // FVE растет, Цена развернулась вверх
   FVE_STATE_FVE_GROWING_PRICE_TURN_FALL,                            // FVE растет, Цена развернулась вниз
   
   //--- Группа свойств FVE FALLING (Падение)
   FVE_STATE_FVE_FALLING_PRICE_FALLING,                              // FVE падает, Цена падает
   FVE_STATE_FVE_FALLING_PRICE_GROWING,                              // FVE падает, Цена растет
   FVE_STATE_FVE_FALLING_PRICE_TURN_FALL,                            // FVE падает, Цена развернулась вниз
   FVE_STATE_FVE_FALLING_PRICE_TURN_GROW,                            // FVE падает, Цена развернулась вверх
   
   //--- Группа свойств FVE TURN_GROW (Разворот вверх)
   FVE_STATE_FVE_TURN_GROW_PRICE_GROWING,                            // FVE развернулся вверх, Цена растет
   FVE_STATE_FVE_TURN_GROW_PRICE_FALLING,                            // FVE развернулся вверх, Цена падает
   FVE_STATE_FVE_TURN_GROW_PRICE_TURN_GROW,                          // FVE развернулся вверх, Цена развернулась вверх
   FVE_STATE_FVE_TURN_GROW_PRICE_TURN_FALL,                          // FVE развернулся вверх, Цена развернулась вниз
   
   //--- Группа свойств FVE TURN_FALL (Разворот вниз)
   FVE_STATE_FVE_TURN_FALL_PRICE_FALLING,                            // FVE развернулся вниз, Цена падает
   FVE_STATE_FVE_TURN_FALL_PRICE_GROWING,                            // FVE развернулся вниз, Цена растет
   FVE_STATE_FVE_TURN_FALL_PRICE_TURN_FALL,                          // FVE развернулся вниз, Цена развернулась вниз
   FVE_STATE_FVE_TURN_FALL_PRICE_TURN_GROW                           // FVE развернулся вниз, Цена развернулась вверх
  };

//+------------------------------------------------------------------+
//| Структуры                                                        |
//+------------------------------------------------------------------+

Во входных параметрах добавим параметры индикатора, а в глобальных переменных — переменные для хранения скорректированных значений входных параметров:

//+------------------------------------------------------------------+
//| Структуры                                                        |
//+------------------------------------------------------------------+
//--- Структура позиций
struct SData
  {
   CArrayLong  list_tickets;                                         // Список тикетов открытых позиций
   double      total_volume;                                         // Общий объём открытых позиций
  };

//--- Структура данных позиций по типам
struct SDataPositions
  {
   SData       Buy;                                                  // Данные позиций Buy
   SData       Sell;                                                 // Данные позиций Sell
  }Data;

//+------------------------------------------------------------------+
//| Макроподстановки                                                 |
//+------------------------------------------------------------------+
#define  DATA_COUNT        3                                         // Количество получаемых данных от индикаторов (3 и более)
#define  ENV_ATTEMPTS      3                                         // Количество попыток ожидания получения окружения
#define  ENV_WAIT_ATTEMPT  1000                                      // Количество миллисекунд ожидания обновления окружения
#define  SPREAD_MLTP       3                                         // Множитель спреда для дистанции стоп-приказов

//+------------------------------------------------------------------+
//| Входные параметры                                                |
//+------------------------------------------------------------------+
//--- WPR
input int                  InpPeriodWPR      =  32;                  /* WPR calculation period */                             // Период расчёта WPR
input double               InpOverboughtWPR  = -20;                  /* WPR Overbought Level */                               // Уровень перекупленности WPR
input double               InpOversoldWPR    = -80;                  /* WPR Oversold Level */                                 // Уровень перепроданности WPR
//--- BB
input int                  InpPeriodBB       =  58;                  /* BB calculation period */                              // Период расчёта BB
input double               InpDeviationBB    =  2.0;                 /* BB deviations */                                      // Отклонения BB
input int                  InpShiftBB        =  0;                   /* BB shift */                                           // Сдвиг BB
input ENUM_APPLIED_PRICE   InpPriceBB        =  PRICE_CLOSE;         /* BB applied price */                                   // Цена расчёта BB
//--- ATR
input int                  InpPeriodATR      =  64;                  /* ATR calculation period */                             // Период расчёта ATR

//--- FinVolEleLinRegSl
input int                  InpSamples        =  22;                  /* FVERegSl Samples */                                   // Период расчёта
input double               InpCutOff         =  0.3;                 /* FVERegSl Threshold */                                 // Порог чувствительности
input int                  InpSlopePeriod    =  35;                  /* FVERegSl SlopePeriod */                               // Период линейной регрессии
input double               InpPriceSlopeFactor= 2500;                /* FVERegSl PriceSlopeFactor */                          // Масштаб цены (индивидуально под инструмент)
input ENUM_USED_VOLUME     InpUsedVolume     =  USED_VOLUME_TICK;    /* FVERegSl Used Volume */                               // Используемый объём
input bool                 InpUseFilterByFVE =  true;                /* Use signal filtering by FVERegSl */                   // Использовать фильтрацию сигналов по FVERegSl

//--- Торговля
input double               InpVolume         =  0.1;                 /* Position volume */                                    // Объем позиции
sinput ulong               InpDeviation      =  10;                  /* Slippage (in points) */                               // Проскальзывание (в пунктах)
sinput ulong               InpMagic          =  123456;              /* Magic number */                                       // Магик
input int                  InpStopLoss       =  -1;                  /* Stop loss (in points), 0 - none, -1 - half of BB */   // Stop loss (в пунктах), 0 - отсутствует, -1 - половина BB
input int                  InpTakeProfit     =  -1;                  /* Take profit (in points), 0 - none, -1 - ATR value */  // Take profit (в пунктах), 0 - отсутствует, -1 - значение ATR
input double               InpSLMltp         =  2.6;                 /* Stop loss size multiplier, if SL==-1 */               // Множитель размера Stop loss, если Stop loss==-1
input double               InpTPMltp         =  1.3;                 /* Take profit size multiplier, if TP==-1 */             // Множитель размера Take profit, если Take profit==-1

//+------------------------------------------------------------------+
//| Глобальные переменные                                            |
//+------------------------------------------------------------------+
CTrade   trade;                                                      // Объект торгового класса
int      handle_wpr;                                                 // Хэндл индикатора WPR
int      handle_bb;                                                  // Хэндл индикатора BB
int      handle_atr;                                                 // Хэндл индикатора ATR
int      handle_fve;                                                 // Хэндл индикатора FVERegSl
double   wpr[DATA_COUNT]={};                                         // Массив значений WPR
double   bb0[DATA_COUNT]={};                                         // Массив значений BB, буфер 0 (Upper)
double   bb1[DATA_COUNT]={};                                         // Массив значений BB, буфер 1 (Lower)
double   bb2[DATA_COUNT]={};                                         // Массив значений BB, буфер 2 (Middle)
double   atr[DATA_COUNT]={};                                         // Массив значений ATR
double   fv0[DATA_COUNT]={};                                         // Массив значений FVESlope
double   fv1[DATA_COUNT]={};                                         // Массив значений FVEPriceSlope
MqlRates prc[DATA_COUNT]={};                                         // Массив цен и времени

int      period_wpr;                                                 // Период расчёта WPR
double   overbought_wpr;                                             // Уровень перекеупленности WPR
double   oversold_wpr;                                               // Уровень перепроданности WPR

int      period_bb;                                                  // Период расчёта BB
double   deviation_bb;                                               // Отклонения BB
int      shift_bb;                                                   // Сдвиг BB

int      period_atr;                                                 // Период расчёта ATR

int      samples;                                                    // Период расчёта FVERegSl
double   cutoff;                                                     // Порог чувствительности FVERegSl
int      slope_period;                                               // Период линейной регрессии FVERegSl

double   lot;                                                        // Объём позиции
string   program_name;                                               // Имя программы
int      prev_total;                                                 // Количество позиций на прошлой проверке
bool     netto;                                                      // Признак нетто-счёта

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+

В обработчике OnInit() советника скорректируем входные параметры индикатора и создадим его хэндл:

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- Если счёт не с типом хеджинг - ставим флаг и сообщаем о некорректной работе советника
   netto=false;
   if(AccountInfoInteger(ACCOUNT_MARGIN_MODE)!=ACCOUNT_MARGIN_MODE_RETAIL_HEDGING)
     {
      Print("The advisor is designed for use on a hedging account. Correct operation on a netting account is not guaranteed.");
      netto=true;
     }
   
//--- Устанавливаем и корректируем входные переменные индикаторов
//--- WPR
   period_wpr=(InpPeriodWPR<1 ? 14 : InpPeriodWPR);
   overbought_wpr=(InpOverboughtWPR<-99  ? -99  : InpOverboughtWPR> 0   ?  0  : InpOverboughtWPR);
   oversold_wpr  =(InpOversoldWPR  <-100 ? -100 : InpOversoldWPR  >-1   ? -1  : InpOversoldWPR);
   if(overbought_wpr<=oversold_wpr)
      overbought_wpr+=1;
//--- BB
   period_bb=(InpPeriodBB<2 ? 20 : InpPeriodBB);
   deviation_bb=InpDeviationBB;
   shift_bb=InpShiftBB;
//--- ATR
   period_atr=(InpPeriodATR<1 ? 14 : InpPeriodATR);
//--- FVERegSl
   samples=(InpSamples<1? 22 : InpSamples);
   cutoff=InpCutOff/100.0;
   slope_period=(InpSlopePeriod<2? 35 : InpSlopePeriod);
   
//--- Инициализируем массивы значений индикаторов
   ArrayInitialize(wpr,EMPTY_VALUE);
   ArrayInitialize(bb0,EMPTY_VALUE);
   ArrayInitialize(bb1,EMPTY_VALUE);
   ArrayInitialize(bb2,EMPTY_VALUE);
   ArrayInitialize(atr,EMPTY_VALUE);
   ArrayInitialize(fv0,EMPTY_VALUE);
   ArrayInitialize(fv1,EMPTY_VALUE);
   ZeroMemory(prc);

//--- Создаём хэндлы индикаторов
//--- WPR
   handle_wpr=iWPR(Symbol(),PERIOD_CURRENT,period_wpr);
   if(handle_wpr==INVALID_HANDLE)
     {
      PrintFormat("%s: Failed to create iWPR(%d) handle",__FUNCTION__,period_wpr);
      return INIT_FAILED;
     }
//--- BB
   handle_bb=iBands(Symbol(),PERIOD_CURRENT,period_bb,shift_bb,deviation_bb,InpPriceBB);
   if(handle_bb==INVALID_HANDLE)
     {
      PrintFormat("%s: Failed to create iBands(%d,%d,%.3f,%s) handle",__FUNCTION__,period_bb,shift_bb,deviation_bb,EnumToString(InpPriceBB));
      return INIT_FAILED;
     }
//--- ATR
   handle_atr=iATR(Symbol(),PERIOD_CURRENT,period_atr);
   if(handle_atr==INVALID_HANDLE)
     {
      PrintFormat("%s: Failed to create iATR(%d) handle",__FUNCTION__,period_atr);
      return INIT_FAILED;
     }
//--- FVERegSl
   handle_fve=iCustom(Symbol(),PERIOD_CURRENT,"FinVolEleLinRegSl",samples,cutoff,slope_period,InpPriceSlopeFactor,InpUsedVolume);
   if(handle_fve==INVALID_HANDLE)
     {
      PrintFormat("%s: Failed to create FinVolEleLinRegSl(%d,%.3f,%d,%.1f) handle",__FUNCTION__,samples,cutoff,slope_period,InpPriceSlopeFactor);
      return INIT_FAILED;
     }

//--- Имя программы и количество позиций на прошлой проверке
   program_name=MQLInfoString(MQL_PROGRAM_NAME);
   prev_total=0;
   
//--- Автоматическая установка типа заполнения
   trade.SetTypeFilling(GetTypeFilling());
//--- Установка магика
   trade.SetExpertMagicNumber(InpMagic);
//--- Установка проскальзывания
   trade.SetDeviationInPoints(InpDeviation);
//--- Установка лота с корректировкой введённого значения
   lot=CorrectLots(InpVolume);
   
//--- Всё успешно
   PrintFormat("%s::%s: Initialization was successful",program_name,__FUNCTION__);
   return(INIT_SUCCEEDED);
  }

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

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

В обработчике OnTick() советника сигнал, полученный от индикаторов WPR и Bollinger Bandsкорректируем по состоянию индикатора FinVolEleLinRegSl:

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//--- Получаем в массивы данные трёх баров индикаторов и цен
   if(!CopyIndicatorsData(1) || !CopyPricesData(1))
      return;
   
//--- Заполнение списков тикетов позиций
   int positions_total=PositionsTotal();
   if(prev_total!=positions_total)
     {
      if(!FillingListTickets(Symbol(),InpMagic))
         return;
      prev_total=positions_total;
     }
   
//--- Получаем сигналы от индикаторов
   ENUM_SIGNAL_TYPE signal_wpr=SignalWPR();
   ENUM_SIGNAL_TYPE signal_bb=SignalBB();
//--- Общий сигнал
   ENUM_SIGNAL_TYPE signal=(signal_wpr==signal_bb ? signal_wpr : SIGNAL_TYPE_NONE);
   
//--- Если используется фильтрация сигналов советника,
   if(InpUseFilterByFVE)
     {
      //--- корректируем сигнал по состоянию индикатора FinVolEleLinRegSl
      signal=SignalByFVELinRegSl(signal,0);
     }
     
//--- Торгуем по сигналам
   TradeProcess(signal);
  }

Функция фильтрации согналов советника по состоянию линий индикатора FinVolEleLinRegSl:

//+------------------------------------------------------------------+
//| Сигнал FinVolEleLinRegSl на указанном по индексу баре            |
//+------------------------------------------------------------------+
ENUM_SIGNAL_TYPE SignalByFVELinRegSl(const ENUM_SIGNAL_TYPE signal,const int index)
  {
//--- Получаем состояние индикатора FVELinRegSl на указанном индексе
   ENUM_FVE_STATE fve_state=StateFVELinRegSl(index);

//--- Раскомментировав строки, получим вывод состояния индикатора в комментарии на графике
   /*
   static string text="";
   string txt=StringFormat("\nStateFVE: %s (%s)",StringSubstr(EnumToString(fve_state),10),FVEStateDescription(fve_state));
   if(text!=txt)
     {
      text=txt;
      Comment(text);
     }
   */
//--- В зависимости от направления сигнала просто ограничиваем противоположные сигналы по фильтру
   switch(signal)
     {
      //--- при сигнале на покупку
      case SIGNAL_TYPE_LONG   :
        //--- если состояние фильтра показывает противоположный сигнал - делаем основной сигнал отсутствующим
        switch(fve_state)
          {
           case FVE_STATE_FVE_FALLING_PRICE_FALLING      :  //[Медвежий] Подтвержденное падение (Trend)
           case FVE_STATE_FVE_FALLING_PRICE_GROWING      :  //[Медвежий] Устойчивая дивергенция (Distribution)
           case FVE_STATE_FVE_FALLING_PRICE_TURN_FALL    :  //[Медвежий] Цена подтвердила падение объема
           case FVE_STATE_FVE_GROWING_PRICE_TURN_FALL    :  //[Медвежий] Скрытая дивергенция (Price Reversal)
           case FVE_STATE_FVE_TURN_FALL_PRICE_FALLING    :  //[Медвежий] Импульс объема вдогонку цене
           case FVE_STATE_FVE_TURN_FALL_PRICE_GROWING    :  //[Медвежий] Начало дивергенции (FVE Top)
           case FVE_STATE_FVE_TURN_FALL_PRICE_TURN_FALL  :  //[Медвежий] Синхронный разворот вниз (Strong Signal)
           case FVE_STATE_FVE_TURN_FALL_PRICE_TURN_GROW  :  //[Медвежий] Зеркальное расхождение (Volume Reversal)
             return SIGNAL_TYPE_NONE;
           break;
           //--- в ином случае, если основной сигнал совпадает с направлением сигнала по фильтру, возвращаем сигнал без изменений
           default: return signal;
          }
        break;
      //--- при сигнале на продажу
      case SIGNAL_TYPE_SHORT  :
        //--- если состояние фильтра показывает противоположный сигнал - делаем основной сигнал отсутствующим
        switch(fve_state)
          {
           case FVE_STATE_FVE_GROWING_PRICE_GROWING      :  //[Бычий] Подтвержденный рост (Trend)
           case FVE_STATE_FVE_GROWING_PRICE_FALLING      :  //[Бычий] Устойчивая конвергенция (Accumulation)
           case FVE_STATE_FVE_GROWING_PRICE_TURN_GROW    :  //[Бычий] Цена подтвердила рост объема
           case FVE_STATE_FVE_FALLING_PRICE_TURN_GROW    :  //[Бычий] Скрытая конвергенция (Price Reversal)
           case FVE_STATE_FVE_TURN_GROW_PRICE_GROWING    :  //[Бычий] Импульс объема вдогонку цене
           case FVE_STATE_FVE_TURN_GROW_PRICE_FALLING    :  //[Бычий] Начало конвергенции (FVE Bottom)
           case FVE_STATE_FVE_TURN_GROW_PRICE_TURN_GROW  :  //[Бычий] Синхронный разворот вверх (Strong Signal)
           case FVE_STATE_FVE_TURN_GROW_PRICE_TURN_FALL  :  //[Бычий] Зеркальное схождение (Volume Reversal)
             return SIGNAL_TYPE_NONE;
           break;
           //--- в ином случае, если основной сигнал совпадает с направлением сигнала по фильтру, возвращаем сигнал без изменений
           default: return signal;
          }
        break;
      //--- возвращаем сигнал без изменений
      default: return signal;
     }
  }

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

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

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

С полным кодом советника можно ознакомиться в прикреплённых к статье файлах.

Для правильной работы советника файл индикатора должен находиться в папке расположения советника: \MQL5\Experts\STOCKS_COMMODITIES\FiniteVolumeElement (FVE)\. Файл индикатора тоже можно найти в прикреплённых к статье файлах.

Скомпилируем советник, установим в тестере стратегий параметры тестирования на дневном графике EURUSD с 01.01.2023 по текущую дату:

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

После запуска тестирования за указанный период времени, видим такие результаты:

Теперь включим фильтрацию сигналов советника по индикатору FinVolEleLinRegSl:

и запустим тот же тест ещё раз. 

Теперь видим такой результат:

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

Здесь мы смогли избавиться от одного убыточного периода. При этом количество сделок стало меньше, что естественно, так как некоторые сделки не были открыты из-за работы фильтра. Соответственно, общая прибыль стала меньше при практически одинаковой просадке (с фильтром она всё-таки чуть меньше). Количество убыточных трейдов тоже незначительно уменьшилось.

Хороший ли это результат — судить вам. Но повторю: фильтр абсолютно грубый. Он просто отключает сигналы советника в случае, если направление сигнала не совпадает с направлением состояния фильтра.

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

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


Заключение

Сегодня мы рассмотрели индикатор Finite Volume Elements и его расширенную версию Finite Volume Elements + Linear Regression Slope. В результате простого тестирования получили незначительное улучшение работы советника со снижением просадки, но с уменьшением прибыльности. Но всё же кажется, что этот индикатор вполне заслуживает внимания. Несмотря на то, что идея индикатора появилась много лет назад, благодаря современным торговым платформам, таким как MetaTrader 5, мы можем использовать проверенные временем инструменты в новых условиях, наглядно тестируя и адаптируя ТС под свои задачи и стратегии.

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


Программы, используемые в статье:

#
 Имя Тип
Описание
 1  FVE.mqh  Индикатор  Индикатор Finite Volume Elements
 2  FinVolEleLinRegSl.mqh  Индикатор  Индикатор Finite Volume Elements + Linear Regression Slope
 3  ExpFVEWPRBB.mqh  Советник  Советник для тестирования индикаторов


Прикрепленные файлы |
FVE.mq5 (11.45 KB)
ExpFVEWPRBB.mq5 (115.3 KB)
Нейросети в трейдинге: Асинхронная обработка событий в потоковых моделях (EVA-Flow) Нейросети в трейдинге: Асинхронная обработка событий в потоковых моделях (EVA-Flow)
В статье знакомимся с фреймворком EVA-Flow для низколатентной и высокочастотной оценки оптического потока на основе событийных данных. Модель сочетает адаптивное представление потока через Unified Voxel Grid с пространственно-временной рекуррентной архитектурой SMR, обеспечивая стабильное и точное прогнозирование движения в режиме реального времени.
Внедряем систему непрерывной адаптации LLM для алгоритмического трейдинга Внедряем систему непрерывной адаптации LLM для алгоритмического трейдинга
SEAL (Self-Evolving Adaptive Learning) — система непрерывной адаптации LLM для алгоритмического трейдинга, решающая проблему быстрой деградации моделей на меняющихся рынках. Вместо периодического переобучения, которое занимает часы и стирает старые паттерны, SEAL учится на каждой закрытой сделке, сохраняя приоритетную память важных примеров и автоматически запуская инкрементальный файнтьюнинг при падении точности или смене рыночного режима.
Особенности написания экспертов Особенности написания экспертов
Написание и тестирование экспертов в торговой системе MetaTrader 4.
Нейросети в трейдинге: Возмущённые модели пространства состояний для анализа рыночной динамики (Окончание) Нейросети в трейдинге: Возмущённые модели пространства состояний для анализа рыночной динамики (Окончание)
В статье представлена адаптация фреймворка P-SSE для задач анализа финансовых рынков. Реализованные решения обеспечивают последовательную обработку локальных событий, аккумулируя их в согласованное представление рыночной динамики. Подход позволяет прогнозировать изменения рынка на заданный горизонт планирования, сохраняя высокую чувствительность к микроимпульсам и минимизируя вычислительные затраты.