Самообучение языку MQL5 с полного нуля - страница 72

 
Vasiliy Sokolov:

Так как совершенству нет предела, вставлю еще несколько замечаний по коду:

Я выделил два нетривиальных места желтым.

1) Обратите внимание, что код повторяется и первом if и в следующем else. Разница только в последней строке и конечном действии (OpenBUY, OpenSell).

2) Условия попадания в блок else не очевидны. Они из-за обилия ?? не просматриваются. В действительности, они зависят лишь от последней строчки:

 Это верный признак того, что здесь не хватате функции.

Нужно написать функцию, которая возвращает истину, если время для открытия позиции соответствует заданному (напишу ее чуть позже)

Да, Василий, Вы правы, действительно нужно было написать функции.

С уважением, Владимир.

 
Vasiliy Sokolov:
Кстати обратите внимание на общий размер Вашей программы. Он уже вполне значительный. Как Вам? Кстати, новичок такой объем кода написать уже не может: переменные начинают путаться, вложенность скобок зашкаливает, начинают лезть ошибки при компиляции как грибы после дождя. После компиляции программа такого объема у них начинает глючить и никто не может понять в чем дело. А у Вас все работает почему-то), и структурно понятно по функциям, что и как происходит. Красота одним словом.

Благодарю, Василий! Здесь большая доля Вашего труда, т.к. шаблон трейлинг стопа был предоставлен именно Вами. Мне осталось только заполнять функции кодом. Сейчас работаю над тралом. Кое-что уже сделано, но есть моменты, в которых мне нужно ещё разобраться, чтобы предоставить окончательный вариант советника на всеобщее обозрение.

С уважением, Владимир.

 

Я добавил несколько функций. У меня получился вот такой код:

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//--- Зададим условия для открытия позиций BUY и SELL   
   double price=SymbolInfoDouble(Symbol(),SYMBOL_ASK);
   double point=SymbolInfoDouble(Symbol(),SYMBOL_POINT);
   int digits=(int)SymbolInfoInteger(Symbol(),SYMBOL_DIGITS);
   price=NormalizeDouble(price,digits);
   //-- Если открытой позиции нет и время для открытия позволяет,
   //-- открывает BUY или SELL в зависимости от положения тика
   if(IsMainPositionOpen() == false && IsTimeForOpen())
   {
      if(TickUP()==(price+point))
         OpenBUY();
      if(TickDOWN()==(price-point))
         OpenSELL();
   }
   //-- Если наступило время закрытия позиции, закрываем все
   if(IsTimeForClose())
      CloseALL();

//+------------------------------------------------------------------+
//| Шаблон трейлинг стопа предоставленный Василием Соколовым         |
//+------------------------------------------------------------------+

//-- Выбираем позиции по текущему символу. Если позиции нет и выбирать нечего, то выходим!
   if(!PositionSelect(Symbol()))
      return;
//-- Стоп-лосс длинной позиции переставляем в безубыток и тралим его
   if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY)
     {
      SetBreakevenForBuyPosition(); // установить безубыток для Buy позиции
      TrailingStopLossForBuyPosition(); // перетащить Stop Loss для Buy позиции
     }
//-- Стоп-лосс короткой позиции переставляем в безубыток и тралим его
   else
      if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL)
        {
         SetBreakevenForSellPosition(); // установить безубыток для Sell позиции
         TrailingStopLossForSellPosition(); // перетащить Stop Loss для Sell позиции
        }
  }
  
//+------------------------------------------------------------------+
//| Возвращает истину, если позиция торгового эксперта уже открыта.  |
//| Возвращает ложь в противном случае.                              |
//+------------------------------------------------------------------+  
bool IsMainPositionOpen()
{
   //-- Если позиций нет - то и у эксперта позиции нет, возвращаем ложь.
   if(PositionSelect(Symbol()) == false)
      return false;
   //-- Позиция есть и ее мэджик совпадает с мэджиком эксперта - возвращаем истину
   if(PositionGetInteger(POSITION_MAGIC) == Magic_Number)
      return true;
   //-- Позиция есть но ее мэджик не совподает с мэджиком эксперта -
   //-- это чья-то другая позиция, позвращаем ложь
   else
      return false;
}
//+------------------------------------------------------------------+
//| Возвращает истину, если текущее время попадает в диапазон        |
//| разрешенного времени для открытия позиции. В противном случае    |
//| возвращает ложь.                                                 |
//+------------------------------------------------------------------+  
bool IsTimeForOpen()
  {
   MqlDateTime time_current,time_open,time_open1;
   TimeToStruct(TimeCurrent(),time_current);
   TimeToStruct((D'1970.01.01 09:00:00'),time_open);
   TimeToStruct((D'1970.01.01 09:01:00'),time_open1);
   if(time_current.hour == time_open.hour &&
      time_current.min >= time_open.min &&
      time_current.min < time_open1.min
      )
      return true;
   else
      return false;
   }
//+------------------------------------------------------------------+
//| Возвращает истину, если текущее время попадает в диапазон        |
//| времени для закрытия позиции. В противном случае возвращает ложь.|                                                 |
//+------------------------------------------------------------------+     
bool IsTimeForClose()
{
   MqlDateTime time_current,time_open,time_open1,time_close;
   TimeToStruct(TimeCurrent(),time_current);
   TimeToStruct((D'1970.01.01 23:50:00'), time_close);
   if(time_current.hour==time_close.hour && 
      time_current.min==time_close.min)
      return true;
   else
      return false;
}
Работа с мэджиком мне по-прежнему не понятна. На неттинге это бессмысленно. В любом случае Вы сможете легко убрать эту проверку так как она делается только в одной функции.
Файлы:
MrBrooklin.mq5  38 kb
 

OnInit блок тоже перемудрен и все равно написан не совсем корректно. Во-первых нужно стремится писать идентификаторы, а не числа. Возвращаеть не -1, а INIT_FAILED не 0, а INIT_SUCCEEDED. Во-вторых switch здесь излишен. Нужно писать либо if либо switch. Писать сначала одно, потому другое - масло масленное.

В третьих нужно контролировать все варианты типа счета. Есть демо, есть реал. А еще есть Contest. Но даже если бы третьего счета не было, должна стоять заглушка которая бы отлавливала все прочие варианты:

int OnInit()
  {
//--- Определим тип счёта на который устанавливаем советник: демо или реальный счет
   ENUM_ACCOUNT_TRADE_MODE account_type=(ENUM_ACCOUNT_TRADE_MODE)AccountInfoInteger(ACCOUNT_TRADE_MODE);
//--- теперь превратим значение перечисления в понятный вид
   string trade_mode;               //создадим переменную для торгового режима
   ACCOUNT_TRADE_MODE_CONTEST
   if(account_type==ACCOUNT_TRADE_MODE_REAL) //если торговый режим счёта - реальный
     {
      //--- выводим окно сообщений на торговом терминале и закрываем советник
      MessageBox("Работа на реальном счете запрещена, выходим!","Советник запущен на реальном счете");
      return(INIT_FAILED); //возвращаем для функции OnInit ненулевое значение означающее "неудачная инициализация"
     }
   if(account_type==ACCOUNT_TRADE_MODE_DEMO) //если торговый режим счёта - демо
     {
      //--- выводим окно сообщений на торговом терминале и продолжаем работу советника
      MessageBox("Работа на демо-счете разрешена!","Советник запущен на демо-счете");
      trade_mode="Счёт REAL";
      return(INIT_SUCCEEDED); //возвращаем для функции OnInit нулевое значение означающее "удачная инициализация"
     }
//-- Заглушка на случай непредвиденных вариантов. Всегда должна быть даже если варианта явно два.
   else
   {
      MessageBox("Неизвестный тип счета. Работа невозможна!");
      return(INIT_FAILED); //возвращаем для функции OnInit нулевое значение означающее "удачная инициализация"
   }
  }
 

Ну и вишенка на торте: комментарии. Когда мы пишем функции, то там много места, рука сама тянется снабдить нужные куски кода комментариями:

//+------------------------------------------------------------------+
//| Возвращает истину, если позиция торгового эксперта уже открыта.  |
//| Возвращает ложь в противном случае.                              |
//+------------------------------------------------------------------+  
bool IsMainPositionOpen()
{
   //-- Если позиций нет - то и у эксперта позиции нет, возвращаем ложь.
   if(PositionSelect(Symbol()) == false)
      return false;
   //-- Позиция есть и ее мэджик совпадает с мэджиком эксперта - возвращаем истину
   if(PositionGetInteger(POSITION_MAGIC) == Magic_Number)
      return true;
   //-- Позиция есть но ее мэджик не совподает с мэджиком эксперта -
   //-- это чья-то другая позиция, позвращаем ложь
   else
      return false;
}

Когда этот же код пишется "заодно" в теле какой-нибудь основной функции, то он становится как можно более коротким, и без комментариев:

if(PositionSelect(Symbol())==false
      && PositionGetInteger(POSITION_MAGIC)!=Magic_Number
      && time_current.hour==time_open.hour
      && time_current.min>=time_open.min
      && time_current.min<time_open1.min
      && (TickUP()==(price+point)))
     {OpenBUY();}

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

Третий момент: вот Вы пишите:

//+------------------------------------------------------------------+
/ | Expert initialization function                                   |
//+------------------------------------------------------------------+
/* Функция инициализации советника OnInit с типом данных int. Если возвращаемое значение для функции
   return(-1), то это "неудачная инициализация". Если возвращаемое значение для функции return(0), то
   это "удачная инициализация". INIT_SUCCEEDED означает, что инициализация прошла успешно и дальше
   можно продолжать тестирование эксперта. Этот код означает то же самое, что и нулевое значение, т.е.
   "удачная инициализация".
*/
int OnInit()
  {
  ...
  }

А зачем добавлять еще один тип комментария? Просто замените то, что в блоке на Ваш комментарий:

//+------------------------------------------------------------------------------------------------------+
//| Функция инициализации советника OnInit с типом данных int. Если возвращаемое значение для функции    |
//| return(-1), то это "неудачная инициализация". Если возвращаемое значение для функции return(0), то   |
//| это "удачная инициализация". INIT_SUCCEEDED означает, что инициализация прошла успешно и дальше      |
//| можно продолжать тестирование эксперта. Этот код означает то же самое, что и нулевое значение, т.е.  |
//| "удачная инициализация".                                                                             |
//+------------------------------------------------------------------------------------------------------+
int OnInit()
  {
  ...
  }

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

 

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

//+------------------------------------------------------------------+
//| Возвращает истину, если текущее время попадает в диапазон        |
//| времени для закрытия позиции. В противном случае возвращает ложь.|                                                 |
//+------------------------------------------------------------------+     
bool IsTimeForClose()
{
   MqlDateTime time_current,time_open,time_open1,time_close;
   TimeToStruct(TimeCurrent(),time_current);
   TimeToStruct((D'1970.01.01 23:50:00'), time_close);
   if(time_current.hour==time_close.hour && 
      time_current.min==time_close.min)
      return true;
   else
      return false;
}

Внутреннее содержимое я взял из Вашего кода. Ясно, что позиция будет закрываться только в течении одной минуты. В 23:50. Этот код будет срабатывать, но если что-то пойдет не так в 23:50, то позиция останется висеть в 23:51. Поэтому нужно хотя бы написать: 

time_current.min>=time_close.min)

И даже такой вариант не идеальный. Более мощное решение использовать торговые режимы. Впрочем это уже следующий уровень мастерства. Пока и данная конструкция будет справляться.

 
MrBrooklin:

Здравствуйте, Василий! Спасибо Вам большое за своевременные подсказки и поддержку. Очень сильно помогли мне в изучении языка программирования MQL5 Ваши сообщения о роли функций и принципах построения программного кода:

  1. https://www.mql5.com/ru/forum/352460/page28#comment_18636493
  2. https://www.mql5.com/ru/forum/352460/page28#comment_18637800
  3. https://www.mql5.com/ru/forum/352460/page29#comment_18641729
  4. https://www.mql5.com/ru/forum/352460/page52#comment_18694985

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

С уважением, Владимир.

Хорошая работа. У хороших кодеров обычно хорошие алгоритмисты, а у них постановщики задач, а у них постановщики целей... Все начинается с правильных целей, и правильных задач. Дом можно строить сразу и потом найти воду. Можно сперва найти воду и дом строить с учетом близкой воды. Цели / назначение, а постановка задачи это от возможностей.... У Вас сразу к алгоритму.... Но в общем очень даже!

 
Vasiliy Sokolov:

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

Внутреннее содержимое я взял из Вашего кода. Ясно, что позиция будет закрываться только в течении одной минуты. В 23:50. Этот код будет срабатывать, но если что-то пойдет не так в 23:50, то позиция останется висеть в 23:51. Поэтому нужно хотя бы написать: 

И даже такой вариант не идеальный. Более мощное решение использовать торговые режимы. Впрочем это уже следующий уровень мастерства. Пока и данная конструкция будет справляться.

Василий, Вы замечательный учитель!

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

С уважением, Владимир.

 

Немного отойду от темы и расскажу историю из жизни про учителя (преподавателя). У нас в институте, когда уже шла специализация, преподавал замечательный человек. Мы в то время проходили алгебру логики. Многие студенты долго не могли понять, как 1+1 может равняться 1? Если 1х1, то ясное дело будет равно 1. А тут на тебе!!! Этот преподаватель на простых примерах дал нам объяснение, что такое логическое "ИЛИ" и, что такое логическое "И", которое запомнил на всю жизнь.

Представьте себе, говорит преподаватель, что Вам нужно утром приехать в институт на занятия. До института можно доехать на троллейбусе ИЛИ на трамвае. Вы пришли на остановку, но там нет ни троллейбуса (условие false или тоже самое, что и 0), ни трамвая (условие false или тоже самое, что и 0). Естественно, что Вы не сможете приехать в институт (условие false или тоже самое, что и 0). Проверяем 0+0=0. Замечательно! Если же Вы пришли на остановку, а на ней стоит троллейбус (условие true или тоже самое, что и 1), ИЛИ трамвай (условие true или тоже самое, что и 1) , ИЛИ троллейбус и трамвай вместе взятые, то Вы обязательно приедете в институт и выполнится условие true или тоже самое, что и 1! Проверяем: 1+0=1, 0+1=1 и 1+1=1. Всё сходится!

На этих же примерах с троллейбусом и трамваем, он объяснил нам, что такое логическое И.

Вот что значит сила таланта преподавателя! На всю жизнь запомнил!

С уважением, Владимир.

 
Valeriy Yastremskiy:

Хорошая работа. У хороших кодеров обычно хорошие алгоритмисты, а у них постановщики задач, а у них постановщики целей... Все начинается с правильных целей, и правильных задач. Дом можно строить сразу и потом найти воду. Можно сперва найти воду и дом строить с учетом близкой воды. Цели / назначение, а постановка задачи это от возможностей.... У Вас сразу к алгоритму.... Но в общем очень даже!

Спасибо, Валерий, за участие в данной теме и конструктивный диалог.

С уважением, Владимир.

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