Меньше кода, больше прока.. пишем советник

Проект изначально анонсирован мною на форуме mql5.com.

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

В отличии от местно-принятой практики, проектирование будет вестись сверху-вниз. От желаемых use-case пользователя, а не от инженерных основ терминала. То есть сначала пишем основной(и единственный) для конечного пользователя файл советника, убираем оттуда всё явно лишнее, и для того чтобы ЭТО заработало пишем библиотеку. Затем добавляем новый use-case и модернизируем библиотеку.

на правах контр-тезиса и полемики с https://www.mql5.com/ru/articles/5654 и советниками г-на Карпутова

конечно, перед тем как вообще приступать надо установить «линию партии» и «цель-коммунизм» :

  • пользователю для реализации стратегии достаточно 100 строк. (помимо комментариев, input и прочих #property).
  • при этом число новых для него «сущностей» (функций/классов/методов/констант) должно сводится к минимуму.
  • библиотека должна содержать счётное кол-во файлов.
  • потенциально пригодна для GUI
  • расширяется за счёт плагинов

Цели достижимы ? тяжело, но в принципе ДА. По некоторым путям есть наработки и идеи. Но готового решения пока нет :-)

Текущий шажок - организовать получение данных экпертом. Таким образом чтобы пользователю было просто их описывать, но при этом сохранились «зацепки» для метаданных и дальнейших расширений. (забегая вперёд - по дальнейшему коду уже внедрять часть GUI, по крайней мере отладочную)

На правах инициатора, набросал простейший use-case - торгуем по пересечению двух MA

соответсвенно часть с input выглядит так:

/** ------- ПАРАМЕТРЫ СОВЕТНИКА ------
**/
input ENUM_APPLIED_PRICE FAST_MA_PRICE=PRICE_CLOSE;
input ENUM_MA_METHOD FAST_MA_METHOD=MODE_EMA;
input int FAST_MA_PERIOD=14;
input int FAST_MA_SHIFT=0;
 
input ENUM_APPLIED_PRICE SLOW_MA_PRICE=PRICE_CLOSE;
input ENUM_MA_METHOD SLOW_MA_METHOD=MODE_SMA;
input int SLOW_MA_PERIOD=54;
input int SLOW_MA_SHIFT=0;

по задумке, следующее что должен сделать пользователь - описать какие именно данные он из input получает. Хотя-бы перечислить:

// просто перечисляем идентификаторы данных
// всех которые нужны для принятия решений
enum ENUM_SERIES {
   FAST_MA,       // id. значений FAST_MA
   SLOW_MA,       // id. значений SLOW_MA
   TOTAL_SERIES   // последний элемент перечисления = кол-во элементов
};

и объяснить как он эти данные вычисляет/получает (получилось длинно, но сразу на оба терминала)

/// вычисление данных
/// эксперт будет обращаться к функции каждый раз когда ему необходимы данные
/// управление кешированием и очерёдность(взаимозависимость) вычислений лежит на верхнем уровне
/// @arg ea - эксперт
/// @arg id - ид.серии данных
/// @arg shift - сдвиг в серии
/// @arg data[] - кешированные результаты
/// @return double - конкретное значение для [id][shift] или EMPTY_VALUE если не может быть вычилено
/// если данные могут быть кешированы, они должны быть сохраненны в массиве data
double GetData(EA *ea,int id,int shift,double &data[])
{
#ifdef __MQL4__
   // для 4-ки всё просто - по идентификаторам серии и бара получить данные
   switch ((ENUM_SERIES)id) {
      case FAST_MA:
         return data[shift]=iMA(ea.Symbol,ea.Period,FAST_MA_PERIOD,0,FAST_MA_METHOD,FAST_MA_PRICE,shift);
      case SLOW_MA:
         return data[shift]=iMA(ea.Symbol,ea.Period,SLOW_MA_PERIOD,0,SLOW_MA_METHOD,SLOW_MA_PRICE,shift);
   }
   return EMPTY_VALUE;
#else
   // для 5-ки несколко сложнее (и кстати не проверено) - надо ещё заводить хендлы стандартных индикаторов
   // и проводить (возможно)лишнее копирование
   static d_fast_ma=0;
   static d_slow_ma=0;
   if (d_fast_ma==0) d_fast_ma=iMA(ea.Symbol,ea.Period,FAST_MA_PERIOD,0,FAST_MA_METHOD,FAST_MA_PRICE,shift);
   if (d_slow_ma==0) d_slow_ma=iMA(ea.Symbol,ea.Period,SLOW_MA_PERIOD,0,SLOW_MA_METHOD,SLOW_MA_PRICE,shift);  
   double tmp[1];
   switch((ENUM_SERIES)id) {
      case FAST_MA: CopyBuffer(d_fast_ma,0,shift,1,tmp); return data[shift]=tmp[0];
      case SLOW_MA: CopyBuffer(d_slow_ma,0,shift,1,tmp); return data[shift]=tmp[0];
   }
   return EMPTY_VALUE;
#endif
}

и наконец описать торговый сигнал :

/// генерация(вычисление) сигнала при открытии бара
/// @arg ea - эксперт
/// @arg shift - номер бара в таймсерии. Типично будет 0
/// @return int - сигнал OP_BUY или OP_SELL или -1 если сигнала нет 
int SignalOfCross(EA *ea,int shift)
{
   if (FAST_MA_PRICE!=PRICE_OPEN || SLOW_MA_PRICE!=PRICE_OPEN) shift++;
   if (ea.CrossedUp(FAST_MA,SLOW_MA,shift)) {
      return OP_BUY;
   }
   if (ea.CrossedDn(FAST_MA,SLOW_MA,shift)) {
      return OP_SELL;
   }
   return -1;
}

В ПРИНЦИПЕ ВСЁ. Этого достаточно чтобы реализовать советник и это явно не требует от пользователя чтения тонн документации. От него нужен базовый уровень владения MQL. А всё остальное должна делать библиотека (или вот, модное слово - engine). Это она должна строится под юзера, а не он учить очередной многотомник API

кстати вот OnInit :

int OnInit()
{
   ea = new EA();
   // настраиваем таймфрейм "по умолчанию"
   //   символ и период - текущие
   //   TOTAL_SERIES наборов данных и кешируем по 30 в каждом
   //   для получения данных служит GetData
   ea.SetupTimeframe(_Symbol,_Period,TOTAL_SERIES,30,GetData);
   // настраиваем сигнал "по умолчанию"
   ea.SetupSignal(SignalOfCross);
   // ------ настройки завершены ------
   // остальная часть одинакова для всех советников
   int ret;
   if ((ret=ea.OnInit())!=INIT_SUCCEEDED) {
      return ret;
   }
   EventSetTimer(60);
 
   return(INIT_SUCCEEDED);
}

Оригинальный пост: https://www.mql5.com/ru/forum/305859