Может ли чайник написать робота? Что два байта переслать! Глава 5.

28 января 2020, 06:54
Programmer96
[Удален]
0
123

Учимся читать

   Умение читать код обязательно. Да, научившись писать первые слова вы, безусловно, можете и прочесть их. А как быть если вам понадобится прочесть что-то чужое, да  состоящее не из нескольких слов, а  из 3-4 фраз? А что делать если это "рассказ" на нескольких страницах?  Без чтения никак.
   За многие годы написаны тысячи кодов, в них отображены многие сотни различных алгоритмов. Прежде чем изобретать свой велосипед, стоит ознакомиться со множеством "велосипедов" написанных другими авторами. Во-первых это оградит вас от стука в открытую дверь - многие задачи, которые вам сейчас кажутся непостижимо сложными, уже давным давно решены, достаточно прочесть и забрать готовое решение.  Во-вторых, открыв в терминале закладку "Библиотека" и ознакомившись всего лишь с несколькими работами, вы найдете множество готовых решений, писать которые вы еще и не думали. Забираем, кладем в закрома - эти "кирпичи" пригодятся вам при строительстве вашего первого большого робота.
_______________________________________________________________________________________________________________________

Содержание 

  • Введение
  • Первый шаг
  • Основы языка
  • Переменные 
  • Функции
  • Учимся читать
  • Обучение методом "Ломаем/строим". Модификация чужого кода
  • Работа над ошибками
  • Как найти "иголку" в стоге кода функции которую якобы можно использовать "как есть"?
  • Графический интерфейс
  • Пишем простой советник с нуля
  • Пишем сеточный советник с Мартингейлом
  • Перечень вложений

     Никулин предложил Вицину тренироваться на кошках.
Я с ним согласен, а в качестве "кошки" предлагаю очень простой советник, который искать не нужно - он есть в каждом терминале, на нем и будем учиться вначале читать, а в следующей главе переделывать код.

    Для того чтобы вывести советник в редактор, достаточно сделать то что на картинке ниже и кликнуть Enter.

    Открыли? Отлично.
Прежде чем читать, предлагаю сделать следующую практическую работу (это уже должно быть вам посильно):

  1. Переформатировать код
  2. Выделить все функции
  3. Переставить функции местами в том порядке как они исполняются

  1. Классическое форматирование Метаквот выглядит красиво, но затрудняет чтение. Я предлагаю иной вариант форматирования кода. Что именно имеется в виду, вы уже видели по ранее опубликованным фрагментам кодам. Кроме всего прочего подобный вариант форматирования облегчает поиск ошибок, которые неизбежны не только у начинающих.
Основные особенности моего варианта форматирования кода:
   - если после условия следует одиночный оператор, то я его размещаю в той же строке, что и условие. Примерно так:

if(slVal>0) sl=NormalizeDouble(slVal,Digits);

   - если имеется не только одиночный оператор, но и else после которого тоже следует одиночный оператор, то пишу так:

if(slVal>0) sl=NormalizeDouble(slVal,Digits); else sl=0;

    -  Если после условия следует блок операторов, то открывающую фигурную скобку печатаю через 1 пробел после условия, а закрывающую печатаю точно с тем же отступом, что и начало условия. Между фигурными скобками размещается блок операторов. После закрывающей скобки пишу //-- и начальный фрагмент условия. Показываю:

         if(stLev>0) {             if(sl>0 && Bid-sl<stLev) sl=0;             if(tp>0 && tp-Bid<stLev) tp=0;          } //-- if(stLev>0)

   - если после условия следует else с блоком операторов, то пишу так:

if(lot>=minLot) {         price=NormalizeDouble(Ask,Digits);    ordNumber=OrderSend(Symbol(),OP_BUY,lot,price,SleepPage,sl,tp,comment,mag,0,clrBlue); //Открыть сделку         if(ordNumber>0) return(ordNumber);          else { //----------------------- после else следует блок операторов -----------------------------             err=GetLastError();                Print("_2_ Can't oppen Buy, Error: ",err);                   return(0);          } //-- else } else return(0); // после else следует одиночный оператор Это else к условию if(lot>=minLot)


   2. Выделяем функции как показано ниже или как вам нравится. Важно чтобы вы четко видели начало и конец каждой функции.

// Функция ND это аналог выражения NormalizeDouble(v,Digits); //========================= ND ============== Начало функции double ND(double v) { // Между фигурными скобками тело функции    return(NormalizeDouble(v,Digits));   } //-- ND //========================= // ND // ======== Конец функции


   3. Переставляем функции местами.
В советнике, на котором мы будет тренироваться в чтении, есть только одна обязательная стандартная функция - OnTick()
Прочитав все то что написано в Глобальном пространстве, программа переходит к исполнению функции  OnTick()
Если в OnTick() есть вызов пользовательских функций - они вызываются в том порядке как это прописано в OnTick(). В нашем тренировочном советнике они имеются и из OnTick() вызываются.

Так как функция OnTick() исполняется первой, то и переставим ее на первое место. Остальные можно оставить как они есть. Порядок их вызова можно отследить поиском (клавиши Ctrl + F)

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

//+------------------------------------------------------------------+ //|                                               Moving Average.mq4 | //|                   Copyright 2005-2014, MetaQuotes Software Corp. | //|                                              http://www.mql4.com | //+------------------------------------------------------------------+ #property copyright   "2005-2014, MetaQuotes Software Corp." #property link        "http://www.mql4.com" #property description "Moving Average sample expert advisor" #define MAGICMA  20131111 // Директива макроподстановки мейджика ( OrderMagicNumber() ) //--- Inputs input double Lots          =0.1; input double MaximumRisk   =0.02; input double DecreaseFactor=3; input int    MovingPeriod  =12; input int    MovingShift   =6; //================================= OnTick ================================================ void OnTick() { //---    if(Bars<100 || IsTradeAllowed()==false) return; // если в истории <100 баров - возврат //--- Вызов функций: 1.CalculateCurrentOrders() 2.CheckForOpen() 3.else CheckForClose()    if(CalculateCurrentOrders(Symbol())==0) CheckForOpen();    else                                    CheckForClose(); //--- } //-- OnTick //============================== // OnTick // =========================================== //=============== CalculateCurrentOrders ==== Считает количество открытых бай/селл сделок int CalculateCurrentOrders(string symbol) {    int buys=0,sells=0; //---    for(int i=0;i<OrdersTotal();i++) {       if(OrderSelect(i,SELECT_BY_POS,MODE_TRADES)==false) break;       if(OrderSymbol()==Symbol() && OrderMagicNumber()==MAGICMA) {          if(OrderType()==OP_BUY)  buys++;          if(OrderType()==OP_SELL) sells++;       } //-- if(OrderSymbol    } //-- for //--- Возврат результата подсчета    if(buys>0) return(buys);    else       return(-sells); } //-- CalculateCurrentOrders //============================== // CalculateCurrentOrders // =========================== //============================== LotsOptimized ========= Рассчитывает объем сделки ====== double LotsOptimized() {    double lot=Lots;    int    orders=HistoryTotal();     // history orders total    int    losses=0;                  // number of losses orders without a break //--- select lot size    lot=NormalizeDouble(AccountFreeMargin()*MaximumRisk/1000.0,1); //--- calcuulate number of losses orders without a break    if(DecreaseFactor>0) {       for(int i=orders-1;i>=0;i--) {          if(OrderSelect(i,SELECT_BY_POS,MODE_HISTORY)==false) {             Print("Error in history!");             break;          } //-- if(OrderSelect          if(OrderSymbol()!=Symbol() || OrderType()>OP_SELL) continue;          //---          if(OrderProfit()>0) break;          if(OrderProfit()<0) losses++;       } //-- for(int       if(losses>1) lot=NormalizeDouble(lot-lot*losses/DecreaseFactor,1);    } //-- if(DecreaseFactor>0) //--- return lot size    if(lot<0.1) lot=0.1;    return(lot); } //-- LotsOptimized() //============================== // LotsOptimized // ====================================    //======= CheckForOpen ========================= Проверка условий для открытия сделки === void CheckForOpen() {    double ma;    int    res; //--- торговать только на первом тике нового бара    if(Volume[0]>1) return; //--- Получаем значение Moving Average    ma=iMA(NULL,0,MovingPeriod,MovingShift,MODE_SMA,PRICE_CLOSE,0); //--- sell-условяи    if(Open[1]>ma && Close[1]<ma) {       res=OrderSend(Symbol(),OP_SELL,LotsOptimized(),Bid,3,0,0,"",MAGICMA,0,Red);       return;    } //-- if(Open[1]>ma //--- buy-условия    if(Open[1]<ma && Close[1]>ma) {       res=OrderSend(Symbol(),OP_BUY,LotsOptimized(),Ask,3,0,0,"",MAGICMA,0,Blue);       return;    } //-- if(Open[1]<ma //--- } //-- CheckForOpen() //============================== // CheckForOpen // ===================================== //======= CheckForClose ========================= Проверка условий для ЗАКРЫТИЯ сделки == void CheckForClose() {    double ma; //--- Разрешить закрытие только только на первом тике нового бара    if(Volume[0]>1) return; //--- Получаем значение Moving Average      ma=iMA(NULL,0,MovingPeriod,MovingShift,MODE_SMA,PRICE_CLOSE,0); //---    for(int i=0;i<OrdersTotal();i++) {       if(OrderSelect(i,SELECT_BY_POS,MODE_TRADES)==false) break;       if(OrderMagicNumber()!=MAGICMA || OrderSymbol()!=Symbol()) continue; //--- Проверить тип ордера       if(OrderType()==OP_BUY) {          if(Open[1]>ma && Close[1]<ma) {             if(!OrderClose(OrderTicket(),OrderLots(),Bid,3,White))                Print("OrderClose error ",GetLastError());          } //-- if(Open[1]>ma             break;       } //-- if(OrderType()==OP_BUY)       if(OrderType()==OP_SELL) {          if(Open[1]<ma && Close[1]>ma) {             if(!OrderClose(OrderTicket(),OrderLots(),Ask,3,White))                Print("OrderClose error ",GetLastError());          } //--             break;       } //-- if(OrderType()==OP_SELL    } //-- for //--- } //-- CheckForClose //============================== // CheckForClose // ====================================

   

                                                             Читаем код начиная с функции OnTick()                                                

//================================= ===============================================
void OnTick() {
//--- 
   if(Bars<100 || IsTradeAllowed()==false) return; 
   // если в истории<100 баров или торговля запрещена - возврат
   
// Вызов функций: 1.CalculateCurrentOrders() 2.CheckForOpen() 3.else CheckForClose() 
   if(CalculateCurrentOrders(Symbol())==0) CheckForOpen();
   else                                    CheckForClose();
// Если CalculateCurrentOrders венула ноль - вызвать CheckForOpen();
// иначе вызвать CheckForClose(); 
//---
 } //-- OnTick
//============================== // OnTick // ===========================================

//=============== CalculateCurrentOrders ==== Считает количество открытых бай/селл сделок
int CalculateCurrentOrders(string symbol) {
   int buys=0,sells=0; // объявление переменных в которые будет записываться результат
//---
   for(int i=0;i<OrdersTotal();i++) { //Прокрутить цикл от 0 до кол.ордеров в терминале 
      if(OrderSelect(i,SELECT_BY_POS,MODE_TRADES)==false) break; 
       // Если ордер в спике открытых сделках не выбран - выйти из цикла  
      if(OrderSymbol()==Symbol() && OrderMagicNumber()==MAGICMA) { 
      // Если символьное имя сделуи == симв. имени графика И мейджик == заданному
         if(OrderType()==OP_BUY)  buys++; // Если сделка бай - увеличить buys на 1
         if(OrderType()==OP_SELL) sells++; //Если сделка селл - увеличить sells на 1
      } //-- if(OrderSymbol
   } //-- for
//--- Возврат результата подсчета 
   if(buys>0) return(buys);  // Если есть бай-сделки - вернуть их количество и выйти
   else       return(-sells);// иначе вернуть количество селл-сделок со знаком минус
 } //-- CalculateCurrentOrders
//============================== // CalculateCurrentOrders // =========================== 

//============================== LotsOptimized ========= Рассчитывает объем сделки ======
double LotsOptimized() { // Объявлениет функции
 // Объявление и инициализация переменных.
   double lot=Lots;              // lot= заданному в меню;
   int    orders=HistoryTotal(); // orders= количеству ордеров в истории;
   int    losses=0;              // количество убытоных =0;
//--- 
   lot=NormalizeDouble(AccountFreeMargin()*MaximumRisk/1000.0,1);
// Рассчитываем объем lot исходя из суммы свободной маржи и заданного в меню MaximumRisk   

   if(DecreaseFactor>0) { // Если в меню задан DecreaseFactor >0 
// Считаем количество убыточных сделок до команды break 
      for(int i=orders-1;i>=0;i--) {
         if(OrderSelect(i,SELECT_BY_POS,MODE_HISTORY)==false) { 
                             //Если ордер в истории не выбран 
            Print("Error in history!"); // собщить об ошибке
            break;                      // и выйти из цикла
         } //-- if(OrderSelect
         if(OrderSymbol()!=Symbol() || OrderType()>OP_SELL) continue;
          // Если(симв.имя ордера на то что у графика ИЛИ это отложенный ордер) пропустить
// OP_BUY ==0 OP_SELL ==1 OP_BUYLIMIT ==2 OP_SELLLIMIT ==3 OP_BUYSTOP ==4 OP_SELLSTOP ==5            
         //--- 
         if(OrderProfit()>0) break; // если найден профитный ордер - выйти из цикла  
         if(OrderProfit()<0) losses++; //если найдер убыточный - добавить 1 к losses 
      } //-- for(int
      if(losses>1) lot=NormalizeDouble(lot-lot*losses/DecreaseFactor,1);
       // если(кол.убыточных>1) из ранее расчитанного lot вычесть lot*losses/DecreaseFactor       
   } //-- if(DecreaseFactor>0)
   
//--- Вернуть рассчитанный объем сделки
   if(lot<0.1) lot=0.1; // если lot менее 0,1 - lot=0,1;
   return(lot); // Вернуть объем lot
 } //-- LotsOptimized()
//============================== // LotsOptimized // ====================================
  
//======= CheckForOpen ========================= Проверка условий для открытия сделки ===
void CheckForOpen() {
   double ma;
   int    res;
//--- торговать только на первом тике нового бара
   if(Volume[0]>1) return;
// Получаем текущую цену MA в переменную ma передав параметры MovingPeriod и MovingShift  
   ma=iMA(NULL,0,MovingPeriod,MovingShift,MODE_SMA,PRICE_CLOSE,0);
//--- Условия для открытия селл
   if(Open[1]>ma && Close[1]<ma) { 
    //ели (цена открытия 1 бара >ma и цена закрытия 1 бара <ma) { 
      res=OrderSend(Symbol(),OP_SELL,LotsOptimized(),Bid,3,0,0,"",MAGICMA,0,Red);
    //открыть селл объемом расссчитанным в LotsOptimized() по цене Bid 
      return; // выйти из функции
   } //-- if(Open[1]>ma
//--- Условия для открытия бай
   if(Open[1]<ma && Close[1]>ma) { // Все то же самое, но в противоположную сторону
      res=OrderSend(Symbol(),OP_BUY,LotsOptimized(),Ask,3,0,0,"",MAGICMA,0,Blue);
     // открыть бай-сделку 
      return;
   } //-- if(Open[1]<ma
//---
 } //-- CheckForOpen()
//============================== // CheckForOpen // =====================================
 
//======= CheckForClose ========================= Проверка условий для ЗАКРЫТИЯ сделки ==
void CheckForClose() {
   double ma;
//--- Разрешить закрытие только только на первом тике нового бара
   if(Volume[0]>1) return;
//--- Получаем значение MA
   ma=iMA(NULL,0,MovingPeriod,MovingShift,MODE_SMA,PRICE_CLOSE,0);
//---
   for(int i=0;i<OrdersTotal();i++) {
      if(OrderSelect(i,SELECT_BY_POS,MODE_TRADES)==false) break;
       // если нет открытых сделок - выйти из цикла
      if(OrderMagicNumber()!=MAGICMA || OrderSymbol()!=Symbol()) continue;
       // если не тот мейджик или не то символьное.имя сделки - пропустить
//--- Проверить тип ордера
      if(OrderType()==OP_BUY) { // если открыта бай-сделка
         if(Open[1]>ma && Close[1]<ma) { 
 // если условия противоположны тем что были нужны для открытия - закрыть сделку  
            if(!OrderClose(OrderTicket(),OrderLots(),Bid,3,White))
 // Приказ на закрытие сделки 
               Print("OrderClose error ",GetLastError());
 // !OrderClose == OrderClose вернула false, т.е. сделка не закрыта. 
 // Если так - сообщить об ошибке
         } //-- if(Open[1]>ma
            break; // Выйти из цикла
      } //-- if(OrderType()==OP_BUY)
      
      if(OrderType()==OP_SELL) { // если открыта селл-сделка
// Все то же самое, но в противоположную сторону      
         if(Open[1]<ma && Close[1]>ma) {
            if(!OrderClose(OrderTicket(),OrderLots(),Ask,3,White))
               Print("OrderClose error ",GetLastError());
         } //--
            break;
      } //-- if(OrderType()==OP_SELL
   } //-- for
//---
 } //-- CheckForClose
//============================== // CheckForClose // ====================================

   

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

res=OrderSend(Symbol(),OP_SELL,LotsOptimized(),Bid,3,0,0,"",MAGICMA,0,Red);

   Это приказ на открытие по рынку селл-сделки.
Переменная res принимает значение возвращаемое функцией  OrderSend() - номер открытой сделки. Если res == 0 - сделка не открылась.
   А что в скобках?
       В документации вы увидите такой текст (здесь часть):

int  OrderSend( 
   string   symbol,              // символ 
   int      cmd,                 // торговая операция 
   double   volume,              // количество лотов 
   double   price,               // цена 
   int      slippage,            // проскальзывание 
   double   stoploss,            // stop loss 
   double   takeprofit,          // take profit 
   string   comment=NULL,        // комментарий 
   int      magic=0,             // идентификатор 
   datetime expiration=0,        // срок истечения ордера 
   color    arrow_color=clrNONE  // цвет 
   );

  Переформатирую в одну строку, как вы это и должны помнить.
Параметры сделки (условно):
   открываем селл-слелку по EURUSD объемом 0,25 лота, по цене 1.3458, допустимым просказьзыванием 15п,
   стоплосс 1.3500, тейк 1.3300, комментом "openBuy", мейджиком 123, без срока истечения, нарисовать красную стрелку:

res=OrderSend("EURUSD",OP_SELL,0.25,1.3458,15,1.3500,1.3300,"openBuy",123,0,Red);

   или:

string symbol="EURUSD";
int ordType=OP_SELL;
double Lot=0.25;
double openPrice=1.3458; // условно. Нужно открывать по BID
int sleepPage=15;
double SL=1.3500;
double TP=1.3300; 
string comment="openBuy";
int magNumber=123; 

 res=OrderSend(symbol,ordType,Lot,openPrice,sleepPage,SL,TP,comment,magNumber,0,Red);

  Для бай ordType=OP_BUY; Открывать по цене Ask, SL и TP в другую сторону.

   Теперь понятно как открыть рыночную сделку?  Надеюсь...
Но вам обязательно нужно знать  и как закрывать сделки, и как отложенные ордера устанавливать.
  А поэтому открываем в Документации раздел "Торговые функции" и плотно присаживаемся (не на один час).
Изучить нужно все, но в первую очередь:
    OrderSend   -  эта функция открывает сделки по рынку и устанавливает отложенные ордера
    OrderClose  - эта функция закрывает сделки
   
OrderDelete - эта функция удаляет отложенные ордера

  Если что-то не сможете понять в Документации - ищите соотвествующий раздел в Учебнике.
  Не найдете и там нужное вам объяснение - спрашивайте в комментариях.

                                      Это очень важно!      Знать обязательно!                                              
                                                 Не зная, не напишете советник                                                   


  Код прочтен и понят.  И что же он делает?

  • Советник работает одним ордером
  • Стопов и тейков нет. Открые длинной сделки закрывает короткую и наоборот.
  • Управления позволяющего запретить бай или селл нет.
  • Профитность советника зависит от настроек МА, но в большей степени от удачи.
    Но, как бы вы ни строили этого робота, приличный профит он вряд-ли выдаст.
  • Если советник немного переделать, то профитность может быть значительно повышена.
    Именно этим и займемся в следующей главе названной "Ломаем/Строим"
_____________________________________________________

    Пожалуйста, не стесняйтесь писать комментарии.
Неважно что это - отзыв или вопрос. Важно (для меня) - буду видеть что к работе есть интерес.
На все вопросы обязательно отвечу. При этом поправлю текст с тем чтобы вновь о том же не спрашивали.


   Букварь публикуется "с листа".
Это значит что правки неизбежны. Изучив главу 15 (например) не поленитесь пролистать предыдущие.
Высока вероятность  того что там появилась новая важная информация или поправлены ошибки.


   Если это кому то интересно, то по ссылке ниже вы найдете все мои посты
         опубликованные в блоге начиная с августа 2014
                  Ранее опубликованные посты

   Не все в них так, то таких которые категорически нужно удалить не обнаружил (пока не обнаружил)



Поделитесь с друзьями: