English Español Deutsch 日本語
preview
Торговля на разрывах справедливой стоимости (FVG)/дисбалансах шаг за шагом: Подход Smart Money

Торговля на разрывах справедливой стоимости (FVG)/дисбалансах шаг за шагом: Подход Smart Money

MetaTrader 5Трейдинг | 18 сентября 2024, 16:30
75 17
Allan Munene Mutiiria
Allan Munene Mutiiria

Введение

Мы рассмотрим основные шаги по созданию и развитию советника на основе стратегии разрыва справедливой стоимости (Fair Value Gap, FVG)/дисбаланса, а также подход Smart Money. По сути, путь к созданию советника, основанного на стратегии FVG, представляет собой слияние искусства и науки, обычно требующее от трейдера умения не только анализировать свечи, но и рисовать для визуализации концептуальных уровней. Оглавление:

  1. Определение дисбаланса
  2. Описание торговой стратегии
  3. План торговой стратегии
  4. Торговая система на MQL5
  5. Результаты тестера стратегий
  6. Заключение


В нашей работе мы будем активно использовать язык MetaQuotes Language 5 (MQL5) в качестве нашей базовой среды разработки и запускать файлы в торговом терминале MetaTrader 5 (MT5).


Определение FVG/дисбаланса

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

Описание торговой стратегии FVG

Стратегия объединяет концепцию оценки справедливой стоимости с дисбалансами свечей для выявления потенциальных торговых возможностей.

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

Ниже показано, как обнаружить FVG на графике:

  • Обнаружение большой свечи: это первое, что нужно сделать. Идеальное соотношение тела и тени - 70%.
  • Изучение соседних свечей: рассмотрите свечи слева и справа от найденной. Большая свеча не должна полностью перекрываться соседними. Допустимы небольшие наложения на верхней и нижней сторонах большой свечи. Разрыв справедливой стоимости должен находиться между тенями соседних свечей.
  • Определение разницы справедливой стоимости: На последнем этапе необходимо определить разницу в справедливой стоимости и отобразить ее на ценовом графике. Разрывом справедливой стоимости будет диапазон цен между максимумом и минимумом предыдущей свечи при медвежьем тренде. Именно здесь проявляется дисбаланс рынка и может возникнуть возможность для торговли. Аналогично для бычьего тренда, но с противоположными условими.



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

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

  • Медвежий (недооцененный) FVG

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

  • Бычий (переоцененный) FVG

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


План торговой стратегии FVG

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

  • Бычий FVG: Согласно стратегии, нам необходимо найти бычью свечу, демонстрирующую значительное движение цены, а затем оценить соседние свечи — слева и справа. Если условия соблюдены, мы получаем разницу третьей и первой свечей. Если разница не находится в пределах ограниченных предопределенных точек, у нас есть бычий FVG, которым мы будем торговать соответствующим образом. FVG становится нашей точкой интереса, и мы документируем ее в алгоритме. Одновременно мы рисуем на графике FVG, окрашенный в зеленый/лаймовый цвет, с предопределенной длиной для целей визуализации, сигнализируя о том, что мы нашли бычий FVG и готовы торговать по нему. Итак, теперь, если цена возвращается и касается нижней области FVG, мы отправляем мгновенный рыночный ордер на покупку. Тейк-профит будет установлен в верхней части FVG, а стоп-лосс — ниже цены открытия ордера с соотношением риска к прибыли 1:3. Однако если цена не возвращается к конфигурации FVG по истечении предопределенного периода, мы исключаем ее из нашего внимания.


То же самое делается применительно ко всем остальным бычьим конфигурациям FVG.

  • Медвежий FVG: Опять же, нам необходимо найти медвежью свечу, демонстрирующую значительное движение цены, а затем оценить соседние свечи — слева и справа. Если условия соблюдены, мы получаем разницу третьей и первой свечей. Если разница не находится в пределах ограниченных предопределенных точек, у нас есть медвежий FVG, которым мы будем торговать соответствующим образом. FVG становится нашей точкой интереса, и мы документируем ее в алгоритме. Одновременно мы рисуем на графике FVG, окрашенный в красный/томатный цвет, с предопределенной длиной для целей визуализации, сигнализируя о том, что мы нашли медвежий FVG и готовы торговать по нему. Итак, теперь, если цена возвращается и касается верхней области FVG, мы отправляем мгновенный рыночный ордер на продажу. Тейк-профит будет установлен в нижней части FVG, а стоп-лосс — выше цены открытия ордера с соотношением риска к прибыли 1:10. Однако если цена не возвращается к конфигурации FVG по истечении предопределенного периода, мы исключаем ее из нашего внимания.


То же самое делается применительно ко всем остальным медвежьим конфигурациям FVG.


Торговая система FVG в MQL5

С теорией покончено. Давайте создадим советника на языке MQL5 для MetaTrader 5. 

В терминале MetaTrader 5 выберите "Сервис" > "Редактор MetaQuotes Language" или просто нажмите F4. Откроется среда разработки на MQL5, которая позволяет писать торговых роботов, технические индикаторы, скрипты и библиотеки функций.


Теперь нажмите "Создать", выберите "Советник (шаблон)" и кликните "Далее".


ОТКРЫВАЕМ НОВЫЙ ФАЙЛ

Укажите желаемое имя файла советника, нажмите "Далее" > "Далее" > "Готово". Мы готовы к воплощению стратегии FVG в коде.

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

#include <Trade/Trade.mqh>
CTrade obj_Trade;

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

#define FVG_Prefix "FVG REC "
#define CLR_UP clrLime
#define CLR_DOWN clrRed

Ниже приведена иллюстрация важности вышеупомянутых параметров.


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

int minPts = 100;
int FVG_Rec_Ext_Bars = 10;

Ниже представлена иллюстрация. Мы определяем minPts для минимальных точек и FVG_Rec_Bars для диапазона в барах, в котором будет отображаться длина прямоугольника.


Наконец, мы определяем четыре массива переменных типа string, integer, datetime и boolean, которые будут содержать и хранить наши данные, используемые при создании советника. Это, опять же, глобальные переменные.

string totalFVGs[];
int barINDICES[];
datetime barTIMEs[];
bool signalFVGs[];

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

Сначала мы получаем все видимые столбцы на графике и сообщаем их количество.

   int visibleBars = (int)ChartGetInteger(0,CHART_VISIBLE_BARS);
   Print("Total visible bars on chart = ",visibleBars);

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

   if (ObjectsTotal(0,0,OBJ_RECTANGLE)==0){
      Print("No FVGs Found, Resizing storage arrays to 0 now!!!");
      ArrayResize(totalFVGs,0);
      ArrayResize(barINDICES,0);
      ArrayResize(signalFVGs,0);
   }

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

   ObjectsDeleteAll(0,FVG_Prefix);

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

Пошаговое описание:

Для поиска бычьей конфигурации FVG мы берем минимум первого бара с индексом i, технически это наш бар номер 0, если предположить, что у нас уже есть конфигурация из 3 баров. Берем максимум третьего бара с индексом i+2 и получаем разницу их разрывов в виде пунктов.

      double low0 = iLow(_Symbol,_Period,i);
      double high2 = iHigh(_Symbol,_Period,i+2);
      double gap_L0_H2 = NormalizeDouble((low0 - high2)/_Point,_Digits);

Та же логика применима и при поиске медвежьих конфигураций. Для поиска медвежьей конфигурации FVG мы берем максимум первого бара с индексом i, технически это наш бар номер 0, если предположить, что у нас уже есть конфигурация из 3 баров. Берем минимум третьего бара с индексом i+2 и получаем разницу их разрывов в виде пунктов.

      double high0 = iHigh(_Symbol,_Period,i);
      double low2 = iLow(_Symbol,_Period,i+2);
      double gap_H0_L2 = NormalizeDouble((low2 - high0)/_Point,_Digits);

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

  1. FVG_UP - проверяет, что минимум индекса столбца i больше максимума индекса бара i+2, и в то же время вычисленные точки разрыва больше минимально допустимых точек прямоугольника.

  2. FVG_DOWN - проверяет, что максимум индекса столбца i меньше минимума индекса бара i+2, и в то же время вычисленные точки разрыва больше минимально допустимых точек прямоугольника.

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

      bool FVG_UP = low0 > high2 && gap_L0_H2 > minPts;
      bool FVG_DOWN = low2 > high0 && gap_H0_L2 > minPts;

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

Мы определяем переменную time1 типа данных datetime, в которой мы храним время для центрального бара, который является средним или вторым баром, с индексом i+1, где будет начинаться прямоугольник.

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

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

         datetime time1 = iTime(_Symbol,_Period,i+1);
         double price1 = FVG_UP ? high2 : high0;

После определения первых координат мы определяем вторые координаты. 

Мы определяем переменную time2 типа datetime, в которой мы храним время для конечного бара, то есть бара, на котором заканчивается нарисованный прямоугольник. Это достигается простым добавлением к time1 (начальному времени) количества баров для расширения прямоугольника.

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

         datetime time2 = time1 + PeriodSeconds(_Period)*FVG_Rec_Ext_Bars;
         double price2 = FVG_UP ? low0 : low2;

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


Получив координаты конфигурации FVG, нам нужно дать имя этому прямоугольному объекту FVG.

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

         string fvgNAME = FVG_Prefix+"("+TimeToString(time1)+")";

Нам также необходимо назначить разные цвета нашим конфигурациям FVG, чтобы мы могли различать бычьи и медвежьи конфигурации. Бычьим конфигурациям назначается предопределенный бычий цвет, то есть CLR_UP, а медвежьим — предопределенный медвежий цвет, то есть CLR_DOWN.

         color fvgClr = FVG_UP ? CLR_UP : CLR_DOWN;

У нас есть все необходимое, чтобы нарисовать соответствующую конфигурацию FVG на графике. 

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

void CreateRec(string objName,datetime time1,double price1,
               datetime time2, double price2,color clr){
   if (ObjectFind(0,objName) < 0){
      ObjectCreate(0,objName,OBJ_RECTANGLE,0,time1,price1,time2,price2);
      
      ObjectSetInteger(0,objName,OBJPROP_TIME,0,time1);
      ObjectSetDouble(0,objName,OBJPROP_PRICE,0,price1);
      ObjectSetInteger(0,objName,OBJPROP_TIME,1,time2);
      ObjectSetDouble(0,objName,OBJPROP_PRICE,1,price2);
      ObjectSetInteger(0,objName,OBJPROP_COLOR,clr);
      ObjectSetInteger(0,objName,OBJPROP_FILL,true);
      ObjectSetInteger(0,objName,OBJPROP_BACK,false);
      
      ChartRedraw(0);
   }
}

Затем используем созданную функцию для создания соответствующего прямоугольника FVG на графике, передав предварительно созданные параметры.

         CreateRec(fvgNAME,time1,price1,time2,price2,fvgClr);

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

         ArrayResize(totalFVGs,ArraySize(totalFVGs)+1);
         ArrayResize(barINDICES,ArraySize(barINDICES)+1);

После изменения размера массивов хранения данных мы добавляем новые данные в массив.

         totalFVGs[ArraySize(totalFVGs)-1] = fvgNAME;
         barINDICES[ArraySize(barINDICES)-1] = i+1;

Мы успешно создали конфигурации FVG для всех видимых баров на графике. Ниже представлен результат.


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

Ниже представлена пошаговая процедура:

Пройдемся по всем созданным конфигурациям FVG, используя функцию цикла for.

   for (int i=ArraySize(totalFVGs)-1; i>=0; i--){// ... }

Получаем/извлекаем данные конфигурации.

      string objName = totalFVGs[i];
      string fvgNAME = ObjectGetString(0,objName,OBJPROP_NAME);
      int barIndex = barINDICES[i];
      datetime timeSTART = (datetime)ObjectGetInteger(0,fvgNAME,OBJPROP_TIME,0);
      datetime timeEND = (datetime)ObjectGetInteger(0,fvgNAME,OBJPROP_TIME,1);
      double fvgLOW = ObjectGetDouble(0,fvgNAME,OBJPROP_PRICE,0);
      double fvgHIGH = ObjectGetDouble(0,fvgNAME,OBJPROP_PRICE,1);
      color fvgColor = (color)ObjectGetInteger(0,fvgNAME,OBJPROP_COLOR);

Пройдем по всем барам расширения прямоугольника, то есть по длине выбранного прямоугольника.

      for (int k=barIndex-1; k>=(barIndex-FVG_Rec_Ext_Bars); k--){//... }

Получим данные по бару.

         datetime barTime = iTime(_Symbol,_Period,k);
         double barLow = iLow(_Symbol,_Period,k);
         double barHigh = iHigh(_Symbol,_Period,k);

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

         if (k==0){
            Print("OverFlow Detected @ fvg ",fvgNAME);
            UpdateRec(fvgNAME,timeSTART,fvgLOW,barTime,fvgHIGH);
            break;
         }

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

void UpdateRec(string objName,datetime time1,double price1,
               datetime time2, double price2){
   if (ObjectFind(0,objName) >= 0){
      ObjectSetInteger(0,objName,OBJPROP_TIME,0,time1);
      ObjectSetDouble(0,objName,OBJPROP_PRICE,0,price1);
      ObjectSetInteger(0,objName,OBJPROP_TIME,1,time2);
      ObjectSetDouble(0,objName,OBJPROP_PRICE,1,price2);
      
      ChartRedraw(0);
   }
}

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

         if ((fvgColor == CLR_DOWN && barHigh > fvgHIGH) ||
            (fvgColor == CLR_UP && barLow < fvgLOW)
         ){
            Print("Cut Off @ bar no: ",k," of Time: ",barTime);
            UpdateRec(fvgNAME,timeSTART,fvgLOW,barTime,fvgHIGH);
            break;
         }


После того, как все настроено, мы изменяем размер массивов хранения до нуля, чтобы подготовиться к разделу OnTick.

   ArrayResize(totalFVGs,0);
   ArrayResize(barINDICES,0);

Полный код OnInit, отвечающий за создание настроек FVG на видимых барах графика и их соответствующее обновление, будет выглядеть следующим образом:

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit(){

   int visibleBars = (int)ChartGetInteger(0,CHART_VISIBLE_BARS);
   Print("Total visible bars on chart = ",visibleBars);
   
   if (ObjectsTotal(0,0,OBJ_RECTANGLE)==0){
      Print("No FVGs Found, Resizing storage arrays to 0 now!!!");
      ArrayResize(totalFVGs,0);
      ArrayResize(barINDICES,0);
      ArrayResize(signalFVGs,0);
   }
   
   ObjectsDeleteAll(0,FVG_Prefix);
   
   for (int i=0; i<=visibleBars; i++){
      //Print("Bar Index = ",i);
      double low0 = iLow(_Symbol,_Period,i);
      double high2 = iHigh(_Symbol,_Period,i+2);
      double gap_L0_H2 = NormalizeDouble((low0 - high2)/_Point,_Digits);
      
      double high0 = iHigh(_Symbol,_Period,i);
      double low2 = iLow(_Symbol,_Period,i+2);
      double gap_H0_L2 = NormalizeDouble((low2 - high0)/_Point,_Digits);
      
      bool FVG_UP = low0 > high2 && gap_L0_H2 > minPts;
      bool FVG_DOWN = low2 > high0 && gap_H0_L2 > minPts;
      
      if (FVG_UP || FVG_DOWN){
         Print("Bar Index with FVG = ",i+1);
         datetime time1 = iTime(_Symbol,_Period,i+1);
         double price1 = FVG_UP ? high2 : high0;
         datetime time2 = time1 + PeriodSeconds(_Period)*FVG_Rec_Ext_Bars;
         double price2 = FVG_UP ? low0 : low2;
         string fvgNAME = FVG_Prefix+"("+TimeToString(time1)+")";
         color fvgClr = FVG_UP ? CLR_UP : CLR_DOWN;
         CreateRec(fvgNAME,time1,price1,time2,price2,fvgClr);
         Print("Old ArraySize = ",ArraySize(totalFVGs));
         ArrayResize(totalFVGs,ArraySize(totalFVGs)+1);
         ArrayResize(barINDICES,ArraySize(barINDICES)+1);
         Print("New ArraySize = ",ArraySize(totalFVGs));
         totalFVGs[ArraySize(totalFVGs)-1] = fvgNAME;
         barINDICES[ArraySize(barINDICES)-1] = i+1;
         ArrayPrint(totalFVGs);
         ArrayPrint(barINDICES);
      }
   }
   
   for (int i=ArraySize(totalFVGs)-1; i>=0; i--){
      string objName = totalFVGs[i];
      string fvgNAME = ObjectGetString(0,objName,OBJPROP_NAME);
      int barIndex = barINDICES[i];
      datetime timeSTART = (datetime)ObjectGetInteger(0,fvgNAME,OBJPROP_TIME,0);
      datetime timeEND = (datetime)ObjectGetInteger(0,fvgNAME,OBJPROP_TIME,1);
      double fvgLOW = ObjectGetDouble(0,fvgNAME,OBJPROP_PRICE,0);
      double fvgHIGH = ObjectGetDouble(0,fvgNAME,OBJPROP_PRICE,1);
      color fvgColor = (color)ObjectGetInteger(0,fvgNAME,OBJPROP_COLOR);
      
      Print("FVG NAME = ",fvgNAME," >No: ",barIndex," TS: ",timeSTART," TE: ",
            timeEND," LOW: ",fvgLOW," HIGH: ",fvgHIGH," CLR = ",fvgColor);
      for (int k=barIndex-1; k>=(barIndex-FVG_Rec_Ext_Bars); k--){
         datetime barTime = iTime(_Symbol,_Period,k);
         double barLow = iLow(_Symbol,_Period,k);
         double barHigh = iHigh(_Symbol,_Period,k);
         //Print("Bar No: ",k," >Time: ",barTime," >H: ",barHigh," >L: ",barLow);
         
         if (k==0){
            Print("OverFlow Detected @ fvg ",fvgNAME);
            UpdateRec(fvgNAME,timeSTART,fvgLOW,barTime,fvgHIGH);
            break;
         }
         
         if ((fvgColor == CLR_DOWN && barHigh > fvgHIGH) ||
            (fvgColor == CLR_UP && barLow < fvgLOW)
         ){
            Print("Cut Off @ bar no: ",k," of Time: ",barTime);
            UpdateRec(fvgNAME,timeSTART,fvgLOW,barTime,fvgHIGH);
            break;
         }
      }
      
   }
   
   ArrayResize(totalFVGs,0);
   ArrayResize(barINDICES,0);

   return(INIT_SUCCEEDED);
}

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

Ниже перечислены небольшие различия в используемом цикле.

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

   for (int i=0; i<=FVG_Rec_Ext_Bars; i++){//... }

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

      double low0 = iLow(_Symbol,_Period,i+1);
      double high2 = iHigh(_Symbol,_Period,i+2+1);

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

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

      bool fvgExist = false;

Мы снова проходим по предопределенным барам, начиная с бара, предшествующего текущему, то есть 0 + 1 = 1, получая максимальную и минимальную цены выбранного бара, и если какая-либо из цен совпадает с координатами второй точки конфигурации, она все еще находится в пределах диапазона, следовательно, по ней всё еще можно торговать, и поэтому мы устанавливаем переменную fvgExist в значение true.

      for (int k=1; k<=FVG_Rec_Ext_Bars; k++){
         double barLow = iLow(_Symbol,_Period,k);
         double barHigh = iHigh(_Symbol,_Period,k);
         
         if (barHigh == fvgLow || barLow == fvgLow){
            //Print("Found: ",fvgNAME," @ bar ",k);
            fvgExist = true;
            break;
         }
      }

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

      double Ask = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK),_Digits);
      double Bid = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID),_Digits);

После этого используем операторы if, чтобы проверить, является ли выбранная конфигурация FVG медвежьей и находится ли текущая цена Bid выше верхних координат настройки, и в то же время это первый торговый сигнал для конфигурации. Когда все условия будут выполнены, откроем ордер на продажу объемом 0,01. Цена открытия — это текущая рыночная цена Bid с уровнем тейк-профита на нижних координатах конфигурации FVG и стоп-лоссом при соотношении риска к прибыли 1:10 выше цены входа. После открытия позиции мы устанавливаем данные сигнала по индексу на true, чтобы не открывать никакую другую позицию на основе этой конкретной конфигурации на следующем тике.

      if (fvgColor == CLR_DOWN && Bid > fvgHigh && !signalFVGs[j]){
         Print("SELL SIGNAL For (",fvgNAME,") Now @ ",Bid);
         double SL_sell = Ask + NormalizeDouble((((fvgHigh-fvgLow)/_Point)*10)*_Point,_Digits);
         double trade_lots = Check1_ValidateVolume_Lots(0.01);
         
         if (Check2_Margin(ORDER_TYPE_SELL,trade_lots) &&
             Check3_VolumeLimit(trade_lots) &&
             Check4_TradeLevels(POSITION_TYPE_SELL,SL_sell,fvgLow)){
            obj_Trade.Sell(trade_lots,_Symbol,Bid,SL_sell,fvgLow);
            signalFVGs[j] = true;
         }
         ArrayPrint(totalFVGs,_Digits," [< >] ");
         ArrayPrint(signalFVGs,_Digits," [< >] ");
      }


К бычьему подтверждению применяются противоположные условия.

      else if (fvgColor == CLR_UP && Ask < fvgLow && !signalFVGs[j]){
         Print("BUY SIGNAL For (",fvgNAME,") Now @ ",Ask);
         double SL_buy = Bid - NormalizeDouble((((fvgHigh-fvgLow)/_Point)*10)*_Point,_Digits);
         double trade_lots = Check1_ValidateVolume_Lots(0.01);

         if (Check2_Margin(ORDER_TYPE_BUY,trade_lots) &&
             Check3_VolumeLimit(trade_lots) &&
             Check4_TradeLevels(POSITION_TYPE_BUY,SL_buy,fvgHigh)){
            obj_Trade.Buy(trade_lots,_Symbol,Ask,SL_buy,fvgHigh);
            signalFVGs[j] = true;
         }
         ArrayPrint(totalFVGs,_Digits," [< >] ");
         ArrayPrint(signalFVGs,_Digits," [< >] ");
      }


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

      if (fvgExist == false){
         bool removeName = ArrayRemove(totalFVGs,0,1);
         bool removeTime = ArrayRemove(barTIMEs,0,1);
         bool removeSignal = ArrayRemove(signalFVGs,0,1);
         if (removeName && removeTime && removeSignal){
            Print("Success removing the FVG DATA from the arrays. New Data as Below:");
            Print("FVGs: ",ArraySize(totalFVGs)," TIMEs: ",ArraySize(barTIMEs),
                     " SIGNALs: ",ArraySize(signalFVGs));
            ArrayPrint(totalFVGs);
            ArrayPrint(barTIMEs);
            ArrayPrint(signalFVGs);
         }
      }


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

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick(){
   
   for (int i=0; i<=FVG_Rec_Ext_Bars; i++){
      double low0 = iLow(_Symbol,_Period,i+1);
      double high2 = iHigh(_Symbol,_Period,i+2+1);
      double gap_L0_H2 = NormalizeDouble((low0 - high2)/_Point,_Digits);
      
      double high0 = iHigh(_Symbol,_Period,i+1);
      double low2 = iLow(_Symbol,_Period,i+2+1);
      double gap_H0_L2 = NormalizeDouble((low2 - high0)/_Point,_Digits);
      
      bool FVG_UP = low0 > high2 && gap_L0_H2 > minPts;
      bool FVG_DOWN = low2 > high0 && gap_H0_L2 > minPts;
      
      if (FVG_UP || FVG_DOWN){
         datetime time1 = iTime(_Symbol,_Period,i+1+1);
         double price1 = FVG_UP ? high2 : high0;
         datetime time2 = time1 + PeriodSeconds(_Period)*FVG_Rec_Ext_Bars;
         double price2 = FVG_UP ? low0 : low2;
         string fvgNAME = FVG_Prefix+"("+TimeToString(time1)+")";
         color fvgClr = FVG_UP ? CLR_UP : CLR_DOWN;
         
         if (ObjectFind(0,fvgNAME) < 0){
            CreateRec(fvgNAME,time1,price1,time2,price2,fvgClr);
            Print("Old ArraySize = ",ArraySize(totalFVGs));
            ArrayResize(totalFVGs,ArraySize(totalFVGs)+1);
            ArrayResize(barTIMEs,ArraySize(barTIMEs)+1);
            ArrayResize(signalFVGs,ArraySize(signalFVGs)+1);
            Print("New ArraySize = ",ArraySize(totalFVGs));
            totalFVGs[ArraySize(totalFVGs)-1] = fvgNAME;
            barTIMEs[ArraySize(barTIMEs)-1] = time1;
            signalFVGs[ArraySize(signalFVGs)-1] = false;
            ArrayPrint(totalFVGs);
            ArrayPrint(barTIMEs);
            ArrayPrint(signalFVGs);
         }
      }
   }
   
   for (int j=ArraySize(totalFVGs)-1; j>=0; j--){
      bool fvgExist = false;
      string objName = totalFVGs[j];
      string fvgNAME = ObjectGetString(0,objName,OBJPROP_NAME);
      double fvgLow = ObjectGetDouble(0,fvgNAME,OBJPROP_PRICE,0);
      double fvgHigh = ObjectGetDouble(0,fvgNAME,OBJPROP_PRICE,1);
      color fvgColor = (color)ObjectGetInteger(0,fvgNAME,OBJPROP_COLOR);
      
      for (int k=1; k<=FVG_Rec_Ext_Bars; k++){
         double barLow = iLow(_Symbol,_Period,k);
         double barHigh = iHigh(_Symbol,_Period,k);
         
         if (barHigh == fvgLow || barLow == fvgLow){
            //Print("Found: ",fvgNAME," @ bar ",k);
            fvgExist = true;
            break;
         }
      }
      
      //Print("Existence of ",fvgNAME," = ",fvgExist);
      
      double Ask = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK),_Digits);
      double Bid = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID),_Digits);
      
      if (fvgColor == CLR_DOWN && Bid > fvgHigh && !signalFVGs[j]){
         Print("SELL SIGNAL For (",fvgNAME,") Now @ ",Bid);
         double SL_sell = Ask + NormalizeDouble((((fvgHigh-fvgLow)/_Point)*10)*_Point,_Digits);
         double trade_lots = Check1_ValidateVolume_Lots(0.01);
         
         if (Check2_Margin(ORDER_TYPE_SELL,trade_lots) &&
             Check3_VolumeLimit(trade_lots) &&
             Check4_TradeLevels(POSITION_TYPE_SELL,SL_sell,fvgLow)){
            obj_Trade.Sell(trade_lots,_Symbol,Bid,SL_sell,fvgLow);
            signalFVGs[j] = true;
         }
         ArrayPrint(totalFVGs,_Digits," [< >] ");
         ArrayPrint(signalFVGs,_Digits," [< >] ");
      }
      else if (fvgColor == CLR_UP && Ask < fvgLow && !signalFVGs[j]){
         Print("BUY SIGNAL For (",fvgNAME,") Now @ ",Ask);
         double SL_buy = Bid - NormalizeDouble((((fvgHigh-fvgLow)/_Point)*10)*_Point,_Digits);
         double trade_lots = Check1_ValidateVolume_Lots(0.01);

         if (Check2_Margin(ORDER_TYPE_BUY,trade_lots) &&
             Check3_VolumeLimit(trade_lots) &&
             Check4_TradeLevels(POSITION_TYPE_BUY,SL_buy,fvgHigh)){
            obj_Trade.Buy(trade_lots,_Symbol,Ask,SL_buy,fvgHigh);
            signalFVGs[j] = true;
         }
         ArrayPrint(totalFVGs,_Digits," [< >] ");
         ArrayPrint(signalFVGs,_Digits," [< >] ");
      }
      
      if (fvgExist == false){
         bool removeName = ArrayRemove(totalFVGs,0,1);
         bool removeTime = ArrayRemove(barTIMEs,0,1);
         bool removeSignal = ArrayRemove(signalFVGs,0,1);
         if (removeName && removeTime && removeSignal){
            Print("Success removing the FVG DATA from the arrays. New Data as Below:");
            Print("FVGs: ",ArraySize(totalFVGs)," TIMEs: ",ArraySize(barTIMEs),
                     " SIGNALs: ",ArraySize(signalFVGs));
            ArrayPrint(totalFVGs);
            ArrayPrint(barTIMEs);
            ArrayPrint(signalFVGs);
         }
      }      
   }
   
}

Мы создали торговую систему согласно концепции Smart Money, основанную на стратегии FVG/дисбаланса для генерации торговых сигналов.


Результаты стратегии FVG в тестере

Вот результаты тестирования в тестере стратегий.

  • График баланса/эквити:


  • Результаты тестирования на истории:



Заключение

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

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

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

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

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

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


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

Прикрепленные файлы |
FVG_SMC_EA.mq5 (21.23 KB)
Последние комментарии | Перейти к обсуждению на форуме трейдеров (17)
Daniel Conte
Daniel Conte | 25 июл. 2024 в 10:06
Спасибо за такую подробную информацию
Allan Munene Mutiiria
Allan Munene Mutiiria | 25 июл. 2024 в 17:34
Daniel Conte #:
Спасибо за такую подробную информацию.

Спасибо и добро пожаловать за дибэк и признание @Daniel Conte

Taku001
Taku001 | 19 авг. 2024 в 20:55
Большое спасибо за прекрасную работу. Но я заметил, что Check2_Margin, Check3_VolumeLimit и Check4_TradeLevels являются необъявленными идентификаторами.
Allan Munene Mutiiria
Allan Munene Mutiiria | 19 авг. 2024 в 22:05
Taku001 #:
Большое спасибо за прекрасную работу. Но я заметил, что Check2_Margin, Check3_VolumeLimit и Check4_TradeLevels являются необъявленными идентификаторами.
@Taku001 спасибо за добрый отзыв и признание. Эти проверки соответствуют требованиям к марже, объему и торговым уровням и обеспечивают отсутствие ошибок перед размещением сделок соответственно. Их декларирование производится в прикрепленных файлах, которые доступны внизу статьи в виде вложений, предпочтительно в виде ZIP-файлов. Надеюсь, это поможет. Спасибо.
Taku001
Taku001 | 21 авг. 2024 в 10:07
Allan Munene Mutiiria #:
@Taku001 спасибо за добрый отзыв и признание. Проверки проводятся на соответствие требованиям к марже, объему и торговым уровням, а также на отсутствие ошибок перед размещением сделок соответственно. Их декларация содержится в прикрепленных файлах, которые доступны внизу статьи в виде вложений, предпочтительно в формате ZIP. Надеюсь, это поможет. Спасибо.
Спасибо Аллану. Вы - звезда. Продолжайте в том же духе
Особенности написания Пользовательских Индикаторов Особенности написания Пользовательских Индикаторов
Написание пользовательских индикаторов в торговой системе MetaTrader 4
Построение модели ограничения тренда свечей (Часть 2): Объединение нативных индикаторов Построение модели ограничения тренда свечей (Часть 2): Объединение нативных индикаторов
В статье рассматривается использование встроенных индикаторов MetaTrader 5 для отсеивания нетрендовых сигналов. Продолжая предыдущую статью, мы рассмотрим, как это сделать с помощью кода MQL5, чтобы воплотить нашу идею в виде программы.
Особенности написания экспертов Особенности написания экспертов
Написание и тестирование экспертов в торговой системе MetaTrader 4.
Пользовательские индикаторы (Часть 1): Пошаговое руководство по разработке простых индикаторов на MQL5 Пользовательские индикаторы (Часть 1): Пошаговое руководство по разработке простых индикаторов на MQL5
В этой статье рассказывается о том, как писать пользовательские индикаторы на языке MQL5. Это вводная часть, в которой вы познакомитесь с основами создания простых пользовательских индикаторов. В ней продемонстрирован практический подход к программированию различных пользовательских индикаторов. Материал предназначен для тех, кто еще в начал пути по изучению программирования на MQL5.