Разработка схемы для торговой системы типа "Три экрана Элдера" на MQL5

Схема для торговой системы "Три экрана Элдера"
Многие трейдеры при поиске или разработке торговой системы слышали о схеме "Три экрана", которую предложил когда-то Александр Элдер. Найдётся множество людей высказывающих своё мнение в интернете, что эта система не работает. И найдётся также много мнений, что из этого можно извлечь прибыль. Но необязательно верить ни тем ни другим. Всё всегда нужно проверять самому. А если Вы изучаете программирование, то всё тем более в Ваших руках, так как можно проверить работоспособность схемы на истории.

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

В эксперте из предыдущей статьи уже есть возможность включить/отключить уровни Stop Loss/Take Profit, Trailing Stop, наращивание объёма позиции, переворот позиции по противоположному сигналу. А все необходимы функции уже расставлены на своих местах. В итоге вся работа сводится к тому, что нужно всего лишь изменить список внешних параметров, добавив в него дополнительные опции и модифицировать некоторые уже имеющиеся функции.

В этой схеме в качестве примера сделаем так, чтобы сигналы на трёх таймфреймах формировались по индикатору Moving Average. После, для продолжения эксперимента с этой схемой, Вы можете вызывать любые другие индикаторы внеся небольшие изменения в код. Также сделаем так, чтобы таймфреймы для каждого "экрана" можно было устанавливать. А если в параметр, который отвечает за период индикатора, установлено нулевое значение, то это будет означать, что этот "экран" не используется. То есть, можно настроить систему на один или два таймфрейма.

Перед тем, как начать, сделайте копию папки с экспертом из предыдущей статьи и переименуйте её.

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

//+------------------------------------------------------------------+
//| ВНЕШНИЕ ПАРАМЕТРЫ ЭКСПЕРТА                                       |
//+------------------------------------------------------------------+
sinput long            MagicNumber    = 777;       // Magic Number
sinput int             Deviation      = 10;        // Deviation (p)
//---
input  ENUM_TIMEFRAMES TF01           = PERIOD_W1; // TF01
input  int             PeriodIndTF01  = 5;         // Period Indicator TF01
//---
input  ENUM_TIMEFRAMES TF02           = PERIOD_D1; // TF02
input  int             PeriodIndTF02  = 5;         // Period Indicator TF02
//---
input  ENUM_TIMEFRAMES TF03           = PERIOD_H4; // TF03
input  int             PeriodIndTF03  = 5;         // Period Indicator TF03
//---
input  double          TakeProfit     = 100;       // Take Profit (p)
input  double          StopLoss       = 50;        // Stop Loss (p)
input  double          TrailingSL     = 10;        // Step Trailing Stop (p)
input  bool            Reverse        = true;      // On/Off Reverse
input  double          Lot            = 0.1;       // Lot
input  double          Increase       = 0.1;       // Increase Volume
input  double          StepIncrease   = 10;        // Step Increase (p)
sinput bool            InfoPanel      = true;      // Info Panel
//---

Параметр AmountMove, переменную gAmountMove и функцию CorrectingParameters() я убрал для упрощения примера схемы. Те, кто заинтересован этим условием могут попробовать реализовать его самостоятельно. Также в файле ENUMS.mqh нужно убрать перечисление индикаторов, так как в этом эксперте будет использоваться только один индикатор.

В начале файла нужно добавить константу AVAL, значение которой будет использоваться вместо gAmountMove:

//---
#define AVAL 3 // Кол-во значений, которые нужно получить у индикаторов и баров
//---

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

//+------------------------------------------------------------------+
//| ГЛОБАЛЬНЫЕ ПЕРЕМЕННЫЕ                                            |
//+------------------------------------------------------------------+
int
hdlTF01=INVALID_HANDLE, // Хэндл индикатора на первом ТФ
hdlTF02=INVALID_HANDLE, // Хэндл индикатора на втором ТФ
hdlTF03=INVALID_HANDLE; // Хэндл индикатора на третьем ТФ
//---

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

Так как идентификаторы (PERIOD_MN1, PERIOD_W1, PERIOD_D1 и т.д.), которые относятся к перечислению таймфреймов имеют целочисленный тип (int) и перечислены от минимального к максимальному, то их можно проверять с помощью функций fmax() и fmin(). Обратитесь к Справке, чтобы узнать более подробную информацию об этих функциях.

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

//---
// Переменная для определения минимального ТФ
ENUM_TIMEFRAMES gTFmin=NULL;
//---

Так как эксперт может быть настроен на работу не только по трём таймфреймам, но и на одном или двух, то нужно учесть все варианты при определении минимального таймфрейма. Ниже представлен код функции MinTF():

//+------------------------------------------------------------------+
//| ОПРЕДЕЛЕНИЕ МИНИМАЛЬНОГО ТФ ДЛЯ ПРОВЕРКИ НОВОГО БАРА             |
//+------------------------------------------------------------------+
void MinTF()
  {
// Условия для одного таймфрейма
   if(PeriodIndTF01>0 && PeriodIndTF02==0 && PeriodIndTF03==0)
     { gTFmin=TF01; return; }
//---
   if(PeriodIndTF01==0 && PeriodIndTF02>0 && PeriodIndTF03==0)
     { gTFmin=TF02; return; }
//---
   if(PeriodIndTF01==0 && PeriodIndTF02==0 && PeriodIndTF03>0)
     { gTFmin=TF03; return; }
//---
// Условия для двух таймфреймов
   if(PeriodIndTF01>0 && PeriodIndTF02>0 && PeriodIndTF03==0)
     { gTFmin=fmin(TF01,TF02); return; }
//---
   if(PeriodIndTF01==0 && PeriodIndTF02>0 && PeriodIndTF03>0)
     { gTFmin=fmin(TF02,TF03); return; }
//---
   if(PeriodIndTF01>0 && PeriodIndTF02==0 && PeriodIndTF03>0)
     { gTFmin=fmin(TF01,TF03); return; }
//---
// Условия для трёх таймфреймов
// Определение минимального ТФ в два этапа
   if(PeriodIndTF01>0 && PeriodIndTF02>0 && PeriodIndTF03>0)
     {
      gTFmin=fmin(TF01,TF02);
      gTFmin=fmin(gTFmin,TF03); return;
     }
  }
//---

Функцию MinTF() нужно будет вызывать при инициализации эксперта в функции OnInit(). Значение переменной gTFmin затем используется в функциях NewBar() и GetDataBars().

Функция GetHandlesIndicators() теперь выглядит так, как показано ниже. Для каждого индикатора указываются свой период и таймфрейм.

//+------------------------------------------------------------------+
//| ПОЛУЧАЕТ ХЭНДЛЫ ИНДИКАТОРОВ                                      |
//+------------------------------------------------------------------+
void GetHandlesIndicators()
  {
// Получим хэндлы индикаторов, которые указаны в параметрах
   if(PeriodIndTF01>0 && hdlTF01==INVALID_HANDLE)
     { hdlTF01=iMA(_Symbol,TF01,PeriodIndTF01,0,MODE_SMA,PRICE_CLOSE); }
//---
   if(PeriodIndTF02>0 && hdlTF02==INVALID_HANDLE)
     { hdlTF02=iMA(_Symbol,TF02,PeriodIndTF02,0,MODE_SMA,PRICE_CLOSE); }
//---
   if(PeriodIndTF03>0 && hdlTF03==INVALID_HANDLE)
     { hdlTF03=iMA(_Symbol,TF03,PeriodIndTF03,0,MODE_SMA,PRICE_CLOSE); }
//---
// Если неудалось получить хендл индикатора для первого таймфрейма
   if(PeriodIndTF01>0 && hdlTF01==INVALID_HANDLE)
     { Print("Не удалось получить хэндл индикатора для TF01!"); }
//---
// Если неудалось получить хендл индикатора для второго таймфрейма
   if(PeriodIndTF02>0 && hdlTF02==INVALID_HANDLE)
     { Print("Не удалось получить хэндл индикатора для TF02!"); }
//---
// Если неудалось получить хендл индикатора для третьего таймфрейма
   if(PeriodIndTF01>0 && hdlTF03==INVALID_HANDLE)
     { Print("Не удалось получить хэндл индикатора для TF03!"); }
  }
//---

В файле ARRAYS.mqh нужно добавить массивы для значений индикаторов (для каждого таймфрейма отдельно):

//---
// Массив для значений индикатора
double bfr_ind_TF01[],bfr_ind_TF02[],bfr_ind_TF03[];
//---

Функция GetDataIndicators() для получения значений индикаторов теперь выглядит так, как показано ниже. Осуществляется проверка валидности хэндлов и, если всё в порядке, то массивы заполняются значениями индикаторов.

//+------------------------------------------------------------------+
//| ПОЛУЧАЕТ ЗНАЧЕНИЯ ИНДИКАТОРОВ                                    |
//+------------------------------------------------------------------+
bool GetDataIndicators()
  {
// Если хэндлы индикаторов не получены, то...
   if((PeriodIndTF01>0 && hdlTF01==INVALID_HANDLE) ||
      (PeriodIndTF02>0 && hdlTF02==INVALID_HANDLE) ||
      (PeriodIndTF03>0 && hdlTF03==INVALID_HANDLE)
     )
     {
      // ...попробуем получить их ещё раз
      GetHandlesIndicators();
     }
//---
// Если используется первый ТФ и хэндл индикатора был получен
   if(PeriodIndTF01>0 && hdlTF01!=INVALID_HANDLE)
     {
      // Установим обратный порядок индексации (... 3 2 1 0)
      ArraySetAsSeries(bfr_ind_TF01,true);
      //---
      // Получим значения индикатора
      if(CopyBuffer(hdlTF01,0,0,AVAL,bfr_ind_TF01)<AVAL)
        {
         Print("Не удалось скопировать значения ("+
               _Symbol+"; "+TFtoS(_Period)+") в массив bfr_ind_TF01! Ошибка ("+
               IS(GetLastError())+"): "+ErrorDesc(GetLastError())+"");
         //---
         return(false);
        }
     }
//---
// Если используется второй ТФ и хэндл индикатора был получен
   if(PeriodIndTF02>0 && hdlTF02!=INVALID_HANDLE)
     {
      // Установим обратный порядок индексации (... 3 2 1 0)
      ArraySetAsSeries(bfr_ind_TF02,true);
      //---
      // Получим значения индикатора
      if(CopyBuffer(hdlTF02,0,0,AVAL,bfr_ind_TF02)<AVAL)
        {
         Print("Не удалось скопировать значения ("+
               _Symbol+"; "+TFtoS(_Period)+") в массив bfr_ind_TF02! Ошибка ("+
               IS(GetLastError())+"): "+ErrorDesc(GetLastError())+"");
         //---
         return(false);
        }
     }
//---
// Если используется третий ТФ и хэндл индикатора был получен
   if(PeriodIndTF03>0 && hdlTF03!=INVALID_HANDLE)
     {
      // Установим обратный порядок индексации (... 3 2 1 0)
      ArraySetAsSeries(bfr_ind_TF03,true);
      //---
      // Получим значения индикатора
      if(CopyBuffer(hdlTF03,0,0,AVAL,bfr_ind_TF03)<AVAL)
        {
         Print("Не удалось скопировать значения ("+
               _Symbol+"; "+TFtoS(_Period)+") в массив bfr_ind_TF03! Ошибка ("+
               IS(GetLastError())+"): "+ErrorDesc(GetLastError())+"");
         //---
         return(false);
        }
     }
//---
   return(true);
  }
//---

В функции GetSignal() также, как и при определении минимального таймфрейма учитываются все возможные варианты состояний внешних параметров, которые относятся к формированию условий на открытие позиций. Ниже представлен её код:

//+------------------------------------------------------------------+
//| ВОЗВРАЩАЕТ СИГНАЛ                                                |
//+------------------------------------------------------------------+
int GetSignal()
  {
// Сигнал на продажу (1):
// Если текущее значение индикаторов на сформировавшихся барах
// ниже, чем предыдущие, то это сигнал на продажу
//---
// Условия для одного таймфрейма
   if(PeriodIndTF01>0 && PeriodIndTF02==0 && PeriodIndTF03==0)
     {
      if(bfr_ind_TF01[1]<bfr_ind_TF01[2]) { return(1); }
     }
//---
   if(PeriodIndTF01==0 && PeriodIndTF02>0 && PeriodIndTF03==0)
     {
      if(bfr_ind_TF02[1]<bfr_ind_TF02[2]) { return(1); }
     }
//---
   if(PeriodIndTF01==0 && PeriodIndTF02==0 && PeriodIndTF03>0)
     {
      if(bfr_ind_TF03[1]<bfr_ind_TF03[2]) { return(1); }
     }
//---
// Условия для двух таймфреймов
   if(PeriodIndTF01>0 && PeriodIndTF02>0 && PeriodIndTF03==0)
     {
      if(bfr_ind_TF01[1]<bfr_ind_TF01[2] && bfr_ind_TF02[1]<bfr_ind_TF02[2])
        {
         return(1);
        }
     }
//---
   if(PeriodIndTF02>0 && PeriodIndTF03>0 && PeriodIndTF01==0)
     {
      if(bfr_ind_TF02[1]<bfr_ind_TF02[2] && bfr_ind_TF03[1]<bfr_ind_TF03[2])
        {
         return(1);
        }
     }
//---
   if(PeriodIndTF01>0 && PeriodIndTF03>0 && PeriodIndTF02==0)
     {
      if(bfr_ind_TF01[1]<bfr_ind_TF01[2] && bfr_ind_TF03[1]<bfr_ind_TF03[2])
        {
         return(1);
        }
     }
//---
// Условия для трёх таймфреймов
   if(PeriodIndTF01>0 && PeriodIndTF02>0 && PeriodIndTF03>0)
     {
      if(bfr_ind_TF01[1]<bfr_ind_TF01[2] &&
         bfr_ind_TF02[1]<bfr_ind_TF02[2] &&
         bfr_ind_TF03[1]<bfr_ind_TF03[2]
         )
        {
         return(1);
        }
     }
//---
// Сигнал на покупку (0):
// Если текущее значение индикаторов на сформировавшихся барах
// выше, чем предыдущие, то это сигнал на покупку
//---
// Условия для одного таймфрейма
   if(PeriodIndTF01>0 && PeriodIndTF02==0 && PeriodIndTF03==0)
     {
      if(bfr_ind_TF01[1]>bfr_ind_TF01[2]) { return(0); }
     }
//---
   if(PeriodIndTF01==0 && PeriodIndTF02>0 && PeriodIndTF03==0)
     {
      if(bfr_ind_TF02[1]>bfr_ind_TF02[2]) { return(0); }
     }
//---
   if(PeriodIndTF01==0 && PeriodIndTF02==0 && PeriodIndTF03>0)
     {
      if(bfr_ind_TF03[1]>bfr_ind_TF03[2]) { return(0); }
     }
//---
// Условия для двух таймфреймов
   if(PeriodIndTF01>0 && PeriodIndTF02>0 && PeriodIndTF03==0)
     {
      if(bfr_ind_TF01[1]>bfr_ind_TF01[2] && bfr_ind_TF02[1]>bfr_ind_TF02[2])
        {
         return(0);
        }
     }
//---
   if(PeriodIndTF02>0 && PeriodIndTF03>0 && PeriodIndTF01==0)
     {
      if(bfr_ind_TF02[1]>bfr_ind_TF02[2] && bfr_ind_TF03[1]>bfr_ind_TF03[2])
        {
         return(0);
        }
     }
//---
   if(PeriodIndTF01>0 && PeriodIndTF03>0 && PeriodIndTF02==0)
     {
      if(bfr_ind_TF01[1]>bfr_ind_TF01[2] && bfr_ind_TF03[1]>bfr_ind_TF03[2])
        {
         return(0);
        }
     }
//---
// Условия для трёх таймфреймов
   if(PeriodIndTF01>0 && PeriodIndTF02>0 && PeriodIndTF03>0)
     {
      if(bfr_ind_TF01[1]>bfr_ind_TF01[2] &&
         bfr_ind_TF02[1]>bfr_ind_TF02[2] &&
         bfr_ind_TF03[1]>bfr_ind_TF03[2]
         )
        {
         return(0);
        }
     }
//---
   return(-1);
  }
//---

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

Далее оптимизируем параметры и посмотрим на результаты. Настройки тестера установим, как на картинке ниже:

Настройки тестера

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

Настройки эксперта

Оптимизация будет длиться приблизительно один час на двухъядерном компьютере. Ниже представлен График оптимизации:

График оптимизации

Для теста выберем результат по максимальному значению фактора восстановления:

Результат теста по максимальному значению фактора восстановления

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

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




Скачать архив с файлами эксперта exp3tf.zip
Скачать сет с настройками эксперта

Комментариев нет :

Отправить комментарий