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

 

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

В отличии от местно-принятой практики, проектирование будет вестись сверху-вниз. От желаемых 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);
}
  

..

Библиотека для простого и быстрого создания программ для MetaTrader (Часть I). Концепция, организация данных, первые результаты
Библиотека для простого и быстрого создания программ для MetaTrader (Часть I). Концепция, организация данных, первые результаты
  • www.mql5.com
Разбирая огромное количество торговых стратегий, множество заказов на изготовление программ для терминалов MT5 и MT4, просматривая огромное разнообразие различных сайтов по тематике скриптов, индикаторов и роботов для MetaTrader, я пришёл к выводу, что всё это многообразие в подавляющем своём большинстве строится на фактически одних и тех же...
 
Пожалуйста вставьте все свои коды правильно: ну невозможно смотреть на это серое уныние. Есть же четкий план действий: нажали на кнопку в редакторе, в полученное поле вставить код. Вы же упорно вставляете полотно текста и потом пытаетесь применить к этому полотну стиль «код»
 
Vladimir Karputov:
Пожалуйста вставьте все свои коды правильно: ну невозможно смотреть на это серое уныние. Есть же четкий план действий: нажали на кнопку в редакторе, в полученное поле вставить код. Вы же упорно вставляете полотно текста и потом пытаетесь применить к этому полотну стиль «код»

он (код) там криво вставляется. один фрагмент ещё можно как-то бороть, а чуть более - уже мука..

то есть про "серое уныние", это не ко мне - это к веб-мастерам. Как, в 2019г, при обилии средств, они этого добились - загадка :-)

но чтобы впредь было веселее, более-менее большие посты буду сначала писать в вики, а сюда переносить копи-пастой и снижу объём публикуемого кода.

 
Maxim Kuznetsov:

он (код) там криво вставляется. один фрагмент ещё можно как-то бороть, а чуть более - уже мука..

то есть про "серое уныние", это не ко мне - это к веб-мастерам. Как, в 2019г, при обилии средств, они этого добились - загадка :-)

но чтобы впредь было веселее, более-менее большие посты буду сначала писать в вики, а сюда переносить копи-пастой и снижу объём публикуемого кода.

Видимо нужно сейчас скопировать блок с кодом и вставить в "Блокнот"

Потом скопировать с блокнота и вставить назад, но перед этим создать новый блок для "нового" кода 

 

я создавал основу шаблон для советника и отдельно делал файл стратегий. 

Вы думаете пользователю все просто?

минимальные знания программирования все равно нужны! 

И никакие "справки" инструкции, видео, ничего не спасает. 

Пользователю потом нужно будет от Вас, чтобы Вы педалили ему бесплатно стратегии. 

И читать они справку не будут. 

 
Vitaly Muzichenko:

Видимо нужно сейчас скопировать блок с кодом и вставить в "Блокнот"

Потом скопировать с блокнота и вставить назад, но перед этим создать новый блок для "нового" кода 

немного помучался, но вроде как "расцветил" :-)

камешек (точнее булыжничек) в огород упомянутым веб-мастерам: при копи-пасте во встроенный редактор, точнее очевидно при первой "расцветке", редактор самовольно выкусывает часть кода который ему не нравится. В частности он самовольно редактировал  "if ( (ret=ea.OnInit())!=INIT_SECEEDED) {..}" . Видимо дивный алгоритм highlight считает что OnInit только один и перегружать в классе его нельзя. 

 
Это псевдокод или уже для применения? В функции GetData нельзя использовать "статики" для "хэндлов" индикаторов (d_fast_ma, d_slow_ma), потому что пользователь возьмет еще пару "машек" для фильтрации или еще чего с другими параметрами (на другом символе, например). Нужно либо кешировать "хэндлы" в каком-то объекте, либо (если на эффективность не смотреть) получать "хэндл" каждый раз заново - они должны кешироваться самим терминалом при равных параметрах.
 
Maxim Kuznetsov:

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

1) пользователю для реализации стратегии достаточно 100 строк. (помимо комментариев, input и прочих #property).

2) при этом число новых для него "сущностей" (функций/классов/методов/констант) должно сводится к минимуму.

3) библиотека должна содержать счётное кол-во файлов.

4) потенциально пригодна для GUI

5) расширяется за счёт плагинов


3) Ну а какая разница сколько файлов содержит библиотека? 1, 10, 100, 1000? Все должно быть сделано для удобства того, кто эту либу разрабатывает. Если ему удобно все разместить по мелким файлам - пожалуйста. Если привык все кодить в одном - пожалуйста. По большому счету не составляет труда собрать мегафайл из кучи разрозненных автоматическими средствами. Так что на счет этого пункта я бы не упорствовал. 

4) Пригодна для GUI - не совсем понятно, как это. Библиотека это изолированный слой бизнес-логики если по-взрослому. Этот слой не должен зависит от внешнего GUI вообще никак. Это GUI должно зависить от этого слоя.

 
Maxim Kuznetsov:
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);  


У Вас изначально подход чисто процедурный: что вижу, о том пою. А если среднюю нужно рассчитать не на ценах, а допустим на объеме, или другом индикаторе? Снова заставлять переписывать пользователя? Расчет средней - это алгоритм. То к чему Вы хотите применить алгоритм - это данные. Вы смешиваете алгоритм с данными, вместо того что бы сразу это разграничить (проде этого псевдокода):

int period_ma = 12;
int shift = 3;
double fast_ma = SMA(Close, period, shift);

Ой, индекс серии забыл. - А на самом деле его и не должно быть. Нужно все рассчитывать в кольцевом буфере комбинируя алгоритмы в конвейеры.

 
Maxim Kuznetsov:

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

Мой не заслужено забыли. А зря, там много чего.

 
Vasiliy Sokolov:

У Вас изначально подход чисто процедурный: что вижу, о том пою. А если среднюю нужно рассчитать не на ценах, а допустим на объеме, или другом индикаторе? Снова заставлять переписывать пользователя? Расчет средней - это алгоритм. То к чему Вы хотите применить алгоритм - это данные. Вы смешиваете алгоритм с данными, вместо того что бы сразу это разграничить (проде этого псевдокода):

Ой, индекс серии забыл. - А на самом деле его и не должно быть. Нужно все рассчитывать в кольцевом буфере комбинируя алгоритмы в конвейеры.

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

в use-case стиль принципиально процедурный, как самый распространённый. Потенциальные пользователи (начинающие программисты) именно так пишут. 


Причина обращения: