Краткий курс программирования ЕА на MQL5 без ООП и стандартной библиотеки

  • Автор темы Автор темы AlexeyVik
  • Дата начала Дата начала

AlexeyVik

Программист mql4 mql5
Здравствуйте уважаемые посетители данной ветки форума. Этот курс предназначен для начинающих знакомство с языком программирования MQL5.
Надеюсь, с азами программирования вы знакомы. Знаете что такое переменная, типы переменных, видимость переменных. Знаете что такое функция, пользовательская функция и прочие минимальные знания программирования вам не чужды.
Для примера напишем советник по избитой теме торговли, основанную на пересечении двух индикаторов Moving Average.
[IMPORTANT]Замечание: Наш советник предназначен для обучения и не будет, не нацелен на извлечение прибыли.[/IMPORTANT]
Итак, начинаем:
Также надеюсь, знаете, как создать новый файл будущего советника. Меню «Файл» затем «Создать» отметить, что создаём советник и далее. Либо через контекстное меню «Новый файл»... и так далее. MetaEditor в помощь нам, создаёт такой код.
Код:
Expand Collapse Copy
//+------------------------------------------------------------------+
//|                                                    MyFirstEA.mq5 |
//|                                                         Viktorov |
//|                                                [email protected] |
//+------------------------------------------------------------------+
#property copyright "Viktorov"
#property link      "[email protected]"
#property version   "1.00"
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//---
   
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---
   
  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   
  }
//+------------------------------------------------------------------+
Теперь начинаем вставлять свой код. Для начала нам надо задать параметры торговли и используемых индикаторов.
Обращение к индикаторам в MQL5 происходит не так как в MQL4, поэтому надо объявить переменные, в которых будем хранить хендлы индикаторов. А поскольку индикаторов у нас два, то и хендлов должно быть два.
Затем в функции int OnInit() получим хендлы необходимых индикаторов.

В итоге наш код будет выглядеть уже вот так.
Код:
Expand Collapse Copy
//+------------------------------------------------------------------+
//|                                                    MyFirstEA.mq5 |
//|                                                         Viktorov |
//|                                                [email protected] |
//+------------------------------------------------------------------+
#property copyright "Viktorov"
#property link      "[email protected]"
#property version   "1.00"

//---- input parameters
input double               lot            =  0.1;  // Размер лота
input int                  tacke          =  400;  // TackeProfit
input int                  loss           =  200;  // StopLoss
input int                  periodFastMa   =  8;    // Период усреднения быстрой МА
input ENUM_MA_METHOD       metodFastMa    =  1;    // Метод усреднения быстрой МА
input ENUM_APPLIED_PRICE   priceFastMA    =  1;    // Используемая цена быстрой МА
input int                  periodSlowMa   =  13;   // Период усреднения медленной МА
input ENUM_MA_METHOD       metodSlowMa    =  1;    // Метод усреднения медленной МА
input ENUM_APPLIED_PRICE   priceSlowMA    =  1;    // Используемая цена медленной МА

int handleFastMA  // хендл быстрой МА
  , handleSlowMA; // хендл медленной МА

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//---
   handleFastMA = iMA(_Symbol, PERIOD_CURRENT, periodFastMa, 0, metodFastMa, priceFastMA);
   handleSlowMA = iMA(_Symbol, PERIOD_CURRENT, periodSlowMa, 0, metodSlowMa, priceSlowMA);
   if(handleFastMA == INVALID_HANDLE || handleSlowMA == INVALID_HANDLE)
   return(INIT_FAILED);
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---
   
  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   
  }
//+------------------------------------------------------------------+
Теперь надо как-то определить, что произошло пересечение линий индикаторов. Для этого напишем пользовательскую функцию определяющую факт пересечения, назовём её Crossing. Возвращать функция будет 0, 1 или -1 следовательно тип функции будет int.
Код:
Expand Collapse Copy
int Crossing()
{
   double   fastMA[2], slowMA[2];
   CopyBuffer(handleFastMA, 0, 1, 2, fastMA);
   CopyBuffer(handleSlowMA, 0, 1, 2, slowMA);
   
   if(fastMA[0] < slowMA[0] && fastMA[1] > slowMA[1])
    return(0);
   if(fastMA[0] > slowMA[0] && fastMA[1] < slowMA[1])
    return(1);

  return(-1);
}/*******************************************************************/
В данной функции в заранее объявленные массивы копируются значения индикаторов на первом и втором барах. Затем сравнивается положение линий. Если на втором баре fastMA была ниже slowMA а на первом стало наоборот, следовательно, быстрая МА пересекла медленную МА снизу вверх. И наоборот, если на втором баре fastMA была выше slowMA а стало наоборот, то быстрая МА пересекла медленную МА сверху вниз.
Поместим её в самый конец нашего кода.
Теперь самое время попытаться получить результат вызова этой функции. Вставим в функцию void OnTick() вызов нашей функции и примем значение в переменную int isCrossed. А для контроля вместо открытия позиции просто напечатаем текст "Откроем позицию Buy" или "Откроем позицию Sell".

Теперь код функции OnTick() выглядит так
Код:
Expand Collapse Copy
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   int isCrossed = Crossing();
    if(isCrossed == 0)
     Print("Откроем позицию Buy");
    if(isCrossed == 1)
     Print("Откроем позицию Sell");
  }
//+------------------------------------------------------------------+
Но вот незадача… Получается что пересечение действительно на каждом тике пока не закроется текущий бар… Следовательно откроется несколько позиций подряд. Чтобы это предотвратить надо организовать открытие позиций только один раз на баре в момент его первого тика. Для этого напишем ещё одну пользовательскую функцию.

Выглядит она так.
Код:
Expand Collapse Copy
bool newBar()
{
 static datetime timeLastBar;
  MqlRates mqlRates[];
   int s = 0;
    do
     {
      s++;
     }
    while(CopyRates(_Symbol, PERIOD_CURRENT, 0, 1, mqlRates) < 0 && s < 7);
   bool ret = timeLastBar != mqlRates[0].time;
   if(ret)
    timeLastBar = mqlRates[0].time;
   return(ret);
}/*******************************************************************/
В этой функции объявлена статическая static переменная типа datetime timeLastBar которая хранит время открытия бара на котором было последнее обращение к этой функции. Для определения времени открытия текущего бара будем использовать функцию CopyRates() и помещать результат в массив структур типа MqlRates. Затем полученное время последнего бара сравнивается с временем хранящимся в переменной timeLastBar и если оно изменилось в переменную запишется время последнего бара и функция вернёт true. Если-же время не изменилось, возвращается false.
Поместим его опять-же в самый конец нашего кода и исправим вызов функции Crossing()

Таким образом, вызов функции выглядит так.

Код:
Expand Collapse Copy
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   int isCrossed = Crossing();
   if(newBar())
    {
     if(isCrossed == 0)
     Print("Откроем позицию Buy");
     if(isCrossed == 1)
     Print("Откроем позицию Sell");
    }
  }

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

AlexeyVik

Программист mql4 mql5
И вот теперь самое трудное и новое. Будем писать функции открытия и модификации позиций.
Начнём с того, что определим имя функции, типы и имена входящих параметров, объявим и обнулим переменные структур которые используются в функции проверки OrderCheck( ) и функции запроса OrderSend()
Код:
Expand Collapse Copy
bool openPosition(ENUM_ORDER_TYPE type // Тип ордера
                 , double volume       // Запрашиваемый объем сделки в лотах
                 , int sl              // Stop Loss ордера в пунктах
                 , int tp              // Take Profit ордера в пунктах
                 , int magic = 0       // MagickNumber ордера
                 , string comm = NULL  // комментарий ордера/позиции
                 )
{
 MqlTradeCheckResult checkResult;
   ZeroMemory(checkResult);
 MqlTradeRequest     request;
  ZeroMemory(request);
 MqlTradeResult      tradeResult;
    ZeroMemory(tradeResult);
  
  return(false);
}/*******************************************************************/
Затем объявим переменную для хранения актуальных цен и заполним структуру mqlTick.
Код:
Expand Collapse Copy
 MqlTick             mqlTick;
      int s = 0;
       do
        {
         s++;
        }
       while(!SymbolInfoTick(_Symbol, mqlTick) && s < 7);
        double price = type == ORDER_TYPE_BUY ? mqlTick.ask : mqlTick.bid;
      double stop = type == ORDER_TYPE_BUY ? price-(sl*_Point) : price+(sl*_Point)
           , take = type == ORDER_TYPE_BUY ? price+(tp*_Point) : price-(tp*_Point);
[NOTE]Важно:
Для правильного заполнения поля type_filling нам необходимо знать режимы заключения сделок по конкретному инструменту.
[/NOTE]
Получим это свойство в переменную типа перечисления.
Код:
Expand Collapse Copy
     ENUM_SYMBOL_TRADE_EXECUTION exec = (ENUM_SYMBOL_TRADE_EXECUTION)SymbolInfoInteger(_Symbol, SYMBOL_TRADE_EXEMODE);
Теперь самое время заполнять структуру запроса. От режима заключения сделок зависит не только поле type_filling но и как заполнять поля sl и tp
Если тип исполнения Market то стопы и тейки можно устанавливать только после открытия позиции посредством модификации позиции. Соответственно при заполнении учтём этот факт.

Заполнение структуры:
Код:
Expand Collapse Copy
    request.action         =  TRADE_ACTION_DEAL;
     request.symbol         =  _Symbol;
      request.volume         =  volume;
       request.price          =  price;
        request.sl             =  exec == SYMBOL_TRADE_EXECUTION_MARKET ? 0.0 : sl == 0 ? 0.0 : NormalizeDouble(stop, _Digits);
         request.tp             =  exec == SYMBOL_TRADE_EXECUTION_MARKET ? 0.0 : tp == 0 ? 0.0 : NormalizeDouble(take, _Digits);
        request.deviation      =  555;
       request.type           =  type;
      request.type_filling   =  exec == SYMBOL_TRADE_EXECUTION_MARKET ? ORDER_FILLING_FOK : ORDER_FILLING_RETURN;
     request.magic          =  magic;
    request.comment        =  comm;
ВСЁ… Теперь проверим достаточность средств и правильность заполнения структуры запроса и отправим запрос на исполнение.
И последнее, если тип исполнения Market то после открытия позиции надо будет её выбрать для дальнейшей работы с ней и установить Stop Loss и Take Profit.
Так выглядит код открытия позиции.
Код:
Expand Collapse Copy
bool openPosition(ENUM_ORDER_TYPE type // Тип ордера
                 , double volume       // Запрашиваемый объем сделки в лотах
                 , int sl              // Stop Loss ордера в пунктах
                 , int tp              // Take Profit ордера в пунктах
                 , int magic = 0       // MagickNumber ордера
                 , string comm = NULL  // комментарий ордера/позиции
                 )
{
 MqlTradeCheckResult checkResult;
   ZeroMemory(checkResult);
 MqlTradeRequest     request;
  ZeroMemory(request);
 MqlTradeResult      tradeResult;
    ZeroMemory(tradeResult);
 MqlTick             mqlTick;
      int s = 0;
       do
        {
         s++;
        }
       while(!SymbolInfoTick(_Symbol, mqlTick) && s < 7);
        double price = type == ORDER_TYPE_BUY ? mqlTick.ask : mqlTick.bid;
      double stop = type == ORDER_TYPE_BUY ? price-(sl*_Point) : price+(sl*_Point)
           , take = type == ORDER_TYPE_BUY ? price+(tp*_Point) : price-(tp*_Point);
     ENUM_SYMBOL_TRADE_EXECUTION exec = (ENUM_SYMBOL_TRADE_EXECUTION)SymbolInfoInteger(_Symbol, SYMBOL_TRADE_EXEMODE);
    request.action         =  TRADE_ACTION_DEAL;
     request.symbol         =  _Symbol;
      request.volume         =  volume;
       request.price          =  price;
        request.sl             =  exec == SYMBOL_TRADE_EXECUTION_MARKET ? 0.0 : sl == 0 ? 0.0 : NormalizeDouble(stop, _Digits);
         request.tp             =  exec == SYMBOL_TRADE_EXECUTION_MARKET ? 0.0 : tp == 0 ? 0.0 : NormalizeDouble(take, _Digits);
        request.deviation      =  555;
       request.type           =  type;
      request.type_filling   =  exec == SYMBOL_TRADE_EXECUTION_MARKET ? ORDER_FILLING_FOK : ORDER_FILLING_RETURN;
     request.magic          =  magic;
    request.comment        =  comm;
     if(!OrderCheck(request, checkResult))
      Print(__FUNCTION__, " ", checkResult.retcode, " ", request.volume, " ", checkResult.margin_free-checkResult.margin);
    else
      {
       if(!OrderSend(request, tradeResult))
        Print(__FUNCTION__, " ", tradeResult.retcode);
       if(exec == SYMBOL_TRADE_EXECUTION_MARKET)
        {
         int n = 0;
          do
           {
            n++;
           }
          while(!PositionSelectByTicket(tradeResult.order) && n < 5);
          sl_tp_Modification(stop, take, tradeResult.order);
        }
       return(true);
      }
  return(false);
}/*******************************************************************/
Поскольку модификация позиций выполняется той-же функцией OrderSend() подробно разбирать её не имеет смысла. Отличие только в заполняемых полях структуры.

Так выглядит код модификации позиции.
Код:
Expand Collapse Copy
bool sl_tp_Modification(double stopLoss
                       , double takeProfit
                       , ulong  ticket
                       )
{
 bool ret = false;
 MqlTradeRequest     request;
 MqlTradeCheckResult checkResult;
 MqlTradeResult      tradeResult;
  ZeroMemory(request);
   ZeroMemory(checkResult);
    ZeroMemory(tradeResult);
   if(PositionSelectByTicket(ticket))
    {
     request.action         =  TRADE_ACTION_SLTP;
      request.symbol         =  PositionGetString(POSITION_SYMBOL);
       request.sl             =  stopLoss;
      request.tp             =  takeProfit;
     request.position       =  ticket;
    if(!OrderCheck(request, checkResult))
     {
     Print("****** StopLoss ", stopLoss);
     Print("****** Take_Profit ", takeProfit);
     Print("****** ticket ", ticket);
     return(ret);
     }
    }
  ret = OrderSend(request, tradeResult);
  if(!ret)  Print(__FUNCTION__, " ", tradeResult.retcode);
   return(ret);
}/*******************************************************************/
Вот теперь можно внести изменения и вместо печати в журнал открывать позиции.
Код:
Expand Collapse Copy
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   int isCrossed = Crossing();
   if(newBar())
    {
     if(isCrossed == 0)
      openPosition(ORDER_TYPE_BUY, lot, loss, tacke);
     if(isCrossed == 1)
      openPosition(ORDER_TYPE_SELL, lot, loss, tacke);
    }
  }
[IMPORTANT]Полный код советника в прикреплённом файле.[/IMPORTANT]
 

Вложения

Последнее редактирование:

avr52

Новичок форума
Очень интересный и поучительный материал. Спасибо. Вот бы еще примеры, а лучше много примеров.
 

AlexeNP

Гуру форума
bool newBar()
{
static datetime timeLastBar;
MqlRates mqlRates[];
int s = 0;
do
{
s++;
}
while(CopyRates(_Symbol, PERIOD_CURRENT, 0, 1, mqlRates) < 0 && s < 7);
bool ret = timeLastBar != mqlRates[0].time;
if(ret)
timeLastBar = mqlRates[0].time;
return(ret);
}
не проще ли в этой функции использовать SeriesInfoInteger
и эту функцию нужно в первый раз в OnInit запустить... а то мало ли что

Код:
Expand Collapse Copy
bool NewBar()
  {
//---
   static long last_bar;
   long cur_bar=SeriesInfoInteger(_Symbol,PERIOD_CURRENT,SERIES_LASTBAR_DATE);
   if(last_bar<cur_bar)
     {
      last_bar=cur_bar;
      return(true);
     }
   return(false);
//---
  }
 

AlexeyVik

Программист mql4 mql5
не проще ли в этой функции использовать SeriesInfoInteger
и эту функцию нужно в первый раз в OnInit запустить... а то мало ли что

Код:
Expand Collapse Copy
bool NewBar()
  {
//---
   static long last_bar;
   long cur_bar=SeriesInfoInteger(_Symbol,PERIOD_CURRENT,SERIES_LASTBAR_DATE);
   if(last_bar<cur_bar)
     {
      last_bar=cur_bar;
      return(true);
     }
   return(false);
//---
  }
Когда-то давным-давно я применял эту ……… Но неоднократно получал время 0 и выясняя причину на форуме mql5 понял, что перед тем как пользоваться этим надо обратиться к свойствам символа. То-есть «подёргать кота за причинное место». Потому я от этого отказался и завернул CopyRates в цикл do while.
А в принципе, нет никакой разницы как получить время текущего бара.
Что касается запуска в OnInit(), это зависит от стратегии. Если надо при запуске подождать нового бара, то конечно надо. Но если надо сразу выполнить весь код, то этого делать не надо.
 

AlexeyVik

Программист mql4 mql5
Больше всего в программировании я ненавижу писать комментарии. А простенький советник вот уже есть. Ведь главное не запастись кучей примеров, а понять что хочешь и свериться с полученным результатом. Если результат не тот, что ожидался, ищем ошибку алгоритма.
Обязательно научитесь пользоваться отладчиком.
 

Ugar

Гуру форума
Когда-то давным-давно я применял эту ……… Но неоднократно получал время 0 и выясняя причину на форуме mql5 понял, что перед тем как пользоваться этим надо обратиться к свойствам символа. То-есть «подёргать кота за причинное место». Потому я от этого отказался и завернул CopyRates в цикл do while.
А в принципе, нет никакой разницы как получить время текущего бара.
Что касается запуска в OnInit(), это зависит от стратегии. Если надо при запуске подождать нового бара, то конечно надо. Но если надо сразу выполнить весь код, то этого делать не надо.
Привет!
Всё же есть разница. Я когда то заморочился и померил время выполнения. Один вызов SeriesInfo... быстрее чем вызов CopyRates. Разница почти в 2 раза при однократном вызове. Но если надо несколько параметров бара или даже нескольких баров, с SeriesInfo... будут жуткие тормоза по сравнению с CopyRates. По этому я редко пользую SeriesInfo...
Вызов SeriesInfo... или CopyRates при инициализации, иногда применяю для пинка терминала, аналог «подёргать кота за причинное место». Что бы терминал узнал что нужны будут данные бара по символу. Часто в цикле с контролем ошибки.

P. S. Может быть новичкам рано знать о времени выполнения функций и о надобности дёргать кота, но когда то придётся и об этом задумываться.
 

AlexeyVik

Программист mql4 mql5
Да, наверное лучше было-бы объявить структуру MqlTick на глобальном уровне и использовать по необходимости.
 
  • Like
Реакции: Ugar

AlexeyVik

Программист mql4 mql5
Хотя нет. Что-то я завернул какую-то чушь. Ведь мы говорим о функции открытия бара, а в структуре MqlTick время последнего тика, а не время открытия бара.
 
  • Like
Реакции: Ugar
Верх