Функции в MQL4: что они из себя представляют и как их использовать?

Для новичков в программировании функции в MQL4 могут показаться сложными, и может потребоваться некоторое время для полного понимания их потенциала.

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

Функции являются строительными блоками современных языков программирования. Функция — это блок кода, предназначенный для выполнения определенной задачи, например, для размещения ордера или расчета стоп-лосса. MQL4 имеет множество встроенных функций для самых разных задач. К примеру, для получения значений технических индикаторов или размещения ордеров.

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

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

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

Типы функций

Прежде всего, нам необходимо определить функцию. Определение функции является эквивалентом объявления переменных, вы просто информируете MetaEditor о существовании функции. Определение также предназначено для того, чтобы проинструктировать MetaTrader о том, что делает данная функция.

У нас есть два типа функций:

  • которые возвращают результат своей работы.
  • которые не возвращают никакого результата.

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

тип данных функции имя функции(аргумент 1, аргумент 2, ..., аргумент x)
{
   тело функции;
   возвращаемое значение;
}

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

void имя функции(аргумент 1, аргумент 2, ..., аргумент x)
{
   тело функции;
}

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

Аргументы функций

Функции могут иметь аргументы. Аргументы являются входными значениями для функции, так что некоторые действия внутри функции могут быть рассчитаны в зависимости от внешних параметров. Мы передаем аргументы в функцию при ее вызове.

Для скриптов функция OnStart() является стандартной функцией по умолчанию. Она определена как void, поэтому ничего не возвращает и вызывается без аргументов, когда скрипт выполняется на графике.

//Определение функции OnStart() как void (без возврата значения) и без аргументов
void OnStart()
  {
//---
   Alert("Привет, мир!");
  }
//+---

Давайте рассмотрим простую функцию для размещения ордера на покупку. Эта функция имеет аргументы, которые необходимо передать в функцию. Данная функция будет выставлять рыночный ордер на покупку с указанным размером лота, стоп-лоссом и тейк-профитом.

int OpenBuyOrder(double LotSize, double StopLoss, double TakeProfit) {
        int Ticket = OrderSend(Symbol(),OP_BUY,LotSize,Ask,StopLoss,TakeProfit);
        return(Ticket);
}

Эта функция имеет три аргумента: LotSize, StopLoss и TakeProfit.

Аргументы — это переменные, которые используются только внутри функции. Их значение присваивается вызывающей функцией.

Вот как мы будем вызывать эту функцию в коде, используя константы:

    OpenBuyOrder(2, 1.5550, 1.6050);

Это позволит нам разместить ордер на покупку 2 лотов со стоп-лоссом 1,5550 и тейк-профитом 1,6050.

Аргументы могут иметь значения по умолчанию. Это означает, что если параметр явно не передан функции, аргумент примет значение по умолчанию. Значения аргументов по умолчанию всегда будут в конце списка аргументов. Вот пример функции с несколькими значениями по умолчанию:

int DefaultValFunc(int Ticket, double Price, int Number = 0, string Comment = NULL)

Эта функция имеет два аргумента со значениями по умолчанию, Number и Comment, со значениями по умолчанию 0 и NULL соответственно. Если мы хотим использовать значения по умолчанию для Number и Comment, мы просто пропускаем эти аргументы при вызове функции:

    DefaultValFunc(TicketNum,UsePrice);

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

Примеры функций

Рассмотрим пример некоторых простых пользовательских функций, которые вы можете создать.

//Сначала мы определяем функцию с именем Sum, которая возвращает значение типа int и принимает два параметра типа int - a и b.
int Sum (int a, int b){
   //Внутри функции мы можем объявлять другие переменные. В этом случае мы объявляем переменную типа int s, которая является суммой a и b
   int s=a+b;
   //Далее функция возвращает значение s
   return s;
}
 
//MetaTrader вызывает функцию OnStart(), когда скрипт запускается на графике
//OnStart() не имеет параметров и не возвращает никакого значения
void OnStart()
{
   //Объявляется новая переменная total типа int
   //Ей присваивается значение функции Sum, которой передаются значения 1 и 2
   int total=Sum(1,2);
 
   //Выводится переменная total
   Print(total);
 
   //Sum также может быть вызвана из другой функции, в данном случае Print
   //Sum передаются значения 10 и 20
   Print(Sum(10,20));
 
}

функции в mql4

Рассмотрим функцию, которая не возвращает никакого значения.

//Сначала мы определяем функцию с именем Sum, которая не возвращает никакого значения, а только выполняет действие.
//Функция принимает два параметра int - a и b
void Sum(int a, int b){
   //Внутри функции мы объявляем переменную типа int s, которая является суммой a и b
   int s=a+b;
   //Sum выводит значение s
   Print(s);
}
 
//MetaTrader вызывает функцию OnStart(), когда скрипт запускается на графике
//OnStart() не имеет параметров и не возвращает никакого значения
void OnStart()
{
 
   //Вызывается функция Sum, которая выполняет свою задачу
   Sum(10,10);
 
}

функции в mql4, которые не возвращают значения

Функция, которая возвращает строковое значение.

//Определяем функцию CreateHello, которая возвращает строку с аргументом типа string
string CreateHello(string name)
{
   //Прикрепляем к функции Привет
   string s="Привет "+name;
   //Sum выводит значение s
   return s;
}
 
//MetaTrader вызывает функцию OnStart(), когда скрипт запускается на графике
//OnStart() не имеет параметров и не возвращает никакого значения
void OnStart()
{
 
   //Мы вызываем функцию CreateHello и печатаем Привет, а затем добавляем строку с именем
   Print(CreateHello("Александр"));
 
}

функция в mql4, которая выводит строковое значение

Давайте создадим функцию PipPoint(), которая вычисляет количество десятичных знаков в текущей валютной паре и автоматически настраивается для 3-х или 5-ти значных брокеров. Для пар с йеной функция возвращает 0,01. Для всех остальных пар (4 и 5 цифр) функция возвращает 0,0001.

Вот как мы будем вызывать функцию из кода:

    double UsePoint;
    UsePoint = PipPoint();

Мы объявляем переменную типа double с именем UsePoint. Затем мы вызываем функцию PipPoint() и присваиваем ей результат UsePoint.

Вот код для функции PipPoint():

    double PipPoint()
      {
        if(Digits == 2 || Digits == 3) double UsePoint = 0.01;
        else if(Digits == 4 || Digits == 5) UsePoint = 0.0001;
        return(UsePoint);
}

Первая строка — это объявление нашей функции. Как и переменные, функции имеют тип данных и используют те же типы данных, что и переменные. Тип данных зависит от данных, которые возвращает функция. Поскольку эта функция возвращает дробное число, мы используем тип данных double.

Тело функции содержится в скобках {}. У нас есть оператор if-else, который оценивает количество цифр после запятой и присваивает соответствующее значение переменной UsePoint. Далее у нас идет оператор return, который возвращает значение UsePoint для нашей функции.

Функция для вычисления размера лота

Начнем с расчета размера лота:

double CalcLotSize(bool argDynamicLotSize, double argEquityPercent, double argStopLoss, double argFixedLotSize) {
if(argDynamicLotSize == true) {
double RiskAmount = AccountEquity() * (argEquityPercent / 100); 
double TickValue = MarketInfo(Symbol(),MODE_TICKVALUE); 
if(Point == 0.001 || Point == 0.00001) TickValue *= 10; double LotSize = (RiskAmount / argStopLoss) / TickValue;
}
else LotSize = argFixedLotSize;
        return(LotSize);
      }

Первая строка — это объявление нашей функции. Мы называем нашу функцию CalcLotSize(). Обратите внимание, что DynamicLotSize, EquityPercent, StopLoss и FixedLotSize теперь являются аргументами функции. Внешние переменные с этими именами все еще существуют в нашей программе, теперь мы просто передадим их функции в качестве параметров.

Мы добавили оператор return в конце функции, который вернет значение LotSize нашей функции.

Сама функция будет размещена где-нибудь в нашем программном файле, вне функций start() и init(), или будет расположена во внешнем включаемом файле. В последнем случае оператор #include в верхней части программы будет включать файл для использования в нашей программе.

Вот как мы будем использовать эту функцию в коде. Сначала давайте перечислим внешние переменные, которые мы будем использовать для наших настроек размера лота:

extern bool DynamicLotSize = true;
extern double EquityPercent = 2;
extern double FixedLotSize = 0.1;
extern double StopLoss = 50;

А вот как мы называем функцию. Эта строка кода будет расположена внутри функции start():

double LotSize = CalcLotSize (DynamicStopLoss, EquityPercent, StopLoss, FixedLotSize);

Внешние переменные передаются в функцию в качестве аргументов. Функция рассчитает размер нашего лота, а значение будет сохранено в переменной LotSize. Обратите внимание, что эта переменная отличается от переменной LotSize, которая находится внутри функции CalcLotSize(). Обе переменные являются локальными для своих функций, поэтому даже если они имеют одинаковое имя, они не являются одной и той же переменной.

Функция проверки размера лота

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

double VerifyLotSize(double argLotSize) {
if(argLotSize < MarketInfo(Symbol(),MODE_MINLOT)) { argLotSize = MarketInfo(Symbol(),MODE_MINLOT); } else if(argLotSize > MarketInfo(Symbol(),MODE_MAXLOT)) {
argLotSize = MarketInfo(Symbol(),MODE_MAXLOT); 
}
if(MarketInfo(Symbol(),MODE_LOTSTEP) == 0.1) {
argLotSize = NormalizeDouble(argLotSize,1); 
} else argLotSize = NormalizeDouble(argLotSize,2);
return(argLotSize);
}

Для этой функции мы передадим переменную с размером лота, который мы рассчитали, используя CalcLotSize() в качестве аргумента. Переменная аргумента argLotSize затем обрабатывается и возвращается обратно в вызывающую функцию.

Функция размещения ордеров

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

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

Мы разместим наш ордер на покупку по текущей рыночной цене, используя OrderSend(), и если ордер не был размещен, мы запустим код обработки ошибок.

Мы указываем символ ордера, используя аргумент argSymbol. Вместо использования предопределенных переменных Bid и Ask нам нужно будет использовать функцию MarketInfo() с параметрами MODE_ASK и MODE_BID, чтобы получить цену Bid и Ask для конкретного символа.

Мы также указали значение по умолчанию для комментария к заказу. Аргумент argComment имеет значение по умолчанию, «Ордер на покупку». Если для этого аргумента не указано значение, используется значение по умолчанию. Предположим, что размер лота и проскальзывание были рассчитаны и проверены до вызова функции:

int OpenBuyOrder(string argSymbol, double argLotSize, double argSlippage, double argMagicNumber, string argComment = "Buy Order")
{
        while(IsTradeContextBusy()) Sleep(10);
 
// Размещаем ордер на покупку
int Ticket = OrderSend(argSymbol,OP_BUY,argLotSize,MarketInfo(argSymbol,MODE_ASK), argSlippage,0,0,argComment,argMagicNumber,0,Green);
 
// Обработка ошибок
if(Ticket == -1) {
int ErrorCode = GetLastError();
string ErrDesc = ErrorDescription(ErrorCode);
string ErrAlert = StringConcatenate("Ордер на покупку – ошибка ", ErrorCode,": ",ErrDesc);
             Alert(ErrAlert);
string ErrLog = StringConcatenate("Bid: ",MarketInfo(argSymbol,MODE_BID), " Ask: ",MarketInfo(argSymbol,MODE_ASK)," Lots: ",argLotSize);
             Print(ErrLog);
          }
        return(Ticket);
      }

Обратите внимание, что в функции OrderSend() мы использовали функцию MarketInfo() с параметром MODE_ASK вместо предопределенной переменной Ask. Это позволит получить текущую цену Ask для символа валюты, обозначенного argSymbol.

Если сделка не была успешно размещена, будет запущена процедура обработки ошибок. В противном случае тикет ордера будет возвращен вызывающей функции, или будет возвращено -1, если ордер не был размещен.

Отложенное размещение ордеров

Чтобы разместить отложенные ордера, нам нужно будет передать параметры цены отложенного ордера, а также время истечения ордера. Добавим аргументы argPendingPrice и argExpiration в нашу функцию.

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

Вот код для размещения отложенного ордера на покупку:

int OpenBuyStopOrder(string argSymbol, double argLotSize, double argPendingPrice, double argStopLoss, double argTakeProfit, double argSlippage, double argMagicNumber, datetime argExpiration = 0, string argComment = "Buy Stop Order") {
        while(IsTradeContextBusy()) Sleep(10);
 
// Размещаем Buy Stop ордер
int Ticket = OrderSend(argSymbol,OP_BUYSTOP,argLotSize,argPendingPrice,
argSlippage,argStopLoss,argTakeProfit,argComment,argMagicNumber, argExpiration,Green);
 
// Обработка ошибок
if(Ticket == -1) {
int ErrorCode = GetLastError();
string ErrDesc = ErrorDescription(ErrorCode);
string ErrAlert = StringConcatenate("Open Buy Stop Order - Error ",ErrorCode, ": ",ErrDesc);
             Alert(ErrAlert);
string ErrLog = StringConcatenate("Ask: ",MarketInfo(argSymbol,MODE_ASK), " Lots: ",argLotSize," Price: ",argPendingPrice," Stop: ",argStopLoss, " Profit: ",argTakeProfit," Expiration: ",TimeToStr(argExpiration));
             Print(ErrLog);
          }
        return(Ticket);
      }

Обратите внимание, что мы указали значение по умолчанию 0 для argExpiration. Если вы не используете время истечения отложенного ордера и хотите использовать комментарий ордера по умолчанию, вы можете просто опустить аргументы для argExpiration и argComment при вызове функции.

В следующем примере будет выставлен стоп-ордер на покупку без истечения срока действия и комментарий к ордеру по умолчанию «Buy Stop Order»:

int Ticket = OpenBuyStopOrder (Symbol (), LotSize, PendingPrice, StopLoss, TakeProfit, UseSlippage, MagicNumber);

Функции открытия ордеров Stop Stop, Buy Limit и Limit Sell идентичны вышеприведенному коду. Единственное отличие состоит в том, что параметр типа заказа для функции OrderSend() изменяется соответствующим образом.

Функция закрытия ордеров

Давайте создадим функцию для закрытия ордера:

bool CloseBuyOrder(string argSymbol, int argCloseTicket, double argSlippage) {
OrderSelect(argCloseTicket,SELECT_BY_TICKET);
if(OrderCloseTime() == 0) {
double CloseLots = OrderLots();
while(IsTradeContextBusy()) Sleep(10);
double ClosePrice = MarketInfo(argSymbol,MODE_ASK);
bool Closed = OrderClose(argCloseTicket,CloseLots,ClosePrice,argSlippage,Red);
 
if(Closed == false) {
int ErrorCode = GetLastError();
string ErrDesc = ErrorDescription(ErrorCode);
string ErrAlert = StringConcatenate("Close Buy Order - Error: ",ErrorCode, ": ",ErrDesc);
                 Alert(ErrAlert);
string ErrLog = StringConcatenate("Ticket: ",argCloseTicket," Ask: ", MarketInfo(argSymbol,MODE_ASK));
                 Print(ErrLog);
               }
}
        return(Closed);
      }

Для переменной ClosePrice мы используем MarketInfo(), чтобы получить текущую цену Ask для валюты, указанной в argSymbol. Мы используем аргументы функции argCloseTicket и argSlippage для заявки на закрытие ордера и проскальзывания, соответственно. Если заказ не был успешно закрыт, мы запускаем блок обработки ошибок, который печатает номер заявки и текущую цену Ask в журнал.

Функция закрытия отложенного ордера

Вот функция для закрытия одного отложенного ордера. Она будет работать для всех типов отложенных ордеров на покупку или продажу:

bool ClosePendingOrder(string argSymbol, int argCloseTicket, double argSlippage) {
OrderSelect(argCloseTicket,SELECT_BY_TICKET);
if(OrderCloseTime() == 0) {
while(IsTradeContextBusy()) Sleep(10);
bool Deleted = OrderDelete(argCloseTicket,Red);
if(Deleted == false) {
int ErrorCode = GetLastError();
string ErrDesc = ErrorDescription(ErrorCode);
string ErrAlert = StringConcatenate("Close Pending Order - Error: ", ErrorCode,": ",ErrDesc);
                 Alert(ErrAlert);
string ErrLog = StringConcatenate("Ticket: ",argCloseTicket, " Bid: ",MarketInfo(argSymbol,MODE_BID),
" Ask: ",MarketInfo(argSymbol,MODE_ASK));
                 Print(ErrLog);
               }
          }
        return(Deleted);
}

Функции рассчета стоп-лосса и тейк-профита

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

Вот функция для расчета стоп-лосса в пунктах:

double CalcBuyStopLoss(string argSymbol, int argStopLoss, double argOpenPrice) {
if(argStopLoss == 0) return(0);
double BuyStopLoss = argOpenPrice - (argStopLoss * PipPoint(argSymbol)); 
return(BuyStopLoss);
}

Сначала мы проверим, допустимый ли уровень стоп-лосса. Если аргумент argStopLoss равен 0, мы возвращаем значение 0 вызывающей функции, указывая, что стоп-лосс не был указан.

Далее мы рассчитываем стоп-лосс, вычитая стоп-лосс в пунктах из цены открытия ордера. Мы умножаем argStopLoss на PipPoint() для вычисления дробного значения и вычитаем его из argOpenPrice. Мы будем использовать цену Bid или Ask (для рыночных ордеров) или предполагаемую цену отложенного ордера.

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

Вот функция для расчета тейк-профита на покупку в пунктах:

double CalcBuyTakeProfit(string argSymbol, int argTakeProfit, double argOpenPrice) {
if(argTakeProfit == 0) return(0);
double BuyTakeProfit = OpenPrice + (argTakeProfit * PipPoint(argSymbol)); return(BuyTakeProfit);
}

Проверка уровня стопа

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

Следующая функция проверяет, находится ли цена выше верхнего уровня стопа (цена открытия ордера плюс уровень стопа). Если это так, функция возвращает true, иначе false:

bool VerifyUpperStopLevel(string argSymbol, double argVerifyPrice, double argOpenPrice = 0) {
double StopLevel = MarketInfo(argSymbol,MODE_STOPLEVEL) * Point;
if(argOpenPrice == 0) double OpenPrice = MarketInfo(argSymbol,MODE_ASK); else OpenPrice = argOpenPrice;
double UpperStopLevel = OpenPrice + StopLevel;
if(argVerifyPrice > UpperStopLevel) bool StopVerify = true; else StopVerify = false;
        return(StopVerify);
      }

Мы передаем символ валюты, цену для проверки и цену открытия ордера в качестве аргументов. По умолчанию уровень стопа рассчитывается относительно цены Ask. Если указано значение argOpenPrice, уровень стопа будет рассчитываться относительно этой цены.

Функция проверит, является ли argVerifyPrice больше, чем UpperStopLevel. Если это так, возвращаемое значение будет истинным. В противном случае, мы получим false. Вы можете использовать эту функцию, чтобы проверить действительный стоп-лосс, тейк-профит или цену отложенного ордера, не изменяя первоначальную цену. Вот пример, где мы проверяем цену стоп-лосса и показываем сообщение об ошибке, если цена недействительна:

bool Verified = VerifyUpperStopLevel(Symbol(),SellStopLoss); 
if(Verified == false) Alert("Sell stop loss is invalid!");

Следующая функция аналогична, за исключением того, что она автоматически корректирует недопустимую цену стоп-лосса, тейк-профита или отложенного ордера:

double AdjustAboveStopLevel(string argSymbol, double argAdjustPrice, int argAddPips = 0, double argOpenPrice = 0) {
double StopLevel = MarketInfo(argSymbol,MODE_STOPLEVEL) * Point;
if(argOpenPrice == 0) double OpenPrice = MarketInfo(argSymbol,MODE_ASK); else OpenPrice = argOpenPrice;
double UpperStopLevel = OpenPrice + StopLevel;
if(argAdjustPrice <= UpperStopLevel) {
double AdjustedPrice = UpperStopLevel + (argAddPips * PipPoint(argSymbol)); 
} else AdjustedPrice = argAdjustPrice;
        return(AdjustedPrice);
      }

Аргумент argAdjustPrice — это цена, которую мы будем проверять и корректировать, если она недействительна. Мы добавили новый необязательный параметр argAddPips. Это добавит указанное количество пунктов к цене стоп-уровня при корректировке недействительной цены.

Как и прежде, мы рассчитываем уровень стопа относительно цены Ask или параметра argOpenPrice. Если параметр argAdjustPrice находится внутри уровня стопа, цена будет скорректирована таким образом, чтобы она находилась вне уровня стопа на количество пунктов, указанное в переменной argAddPips.

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

Добавление стоп-лосса и тейк-профита

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

bool AddStopProfit(int argTicket, double argStopLoss, double argTakeProfit) {
if(argStopLoss == 0 && argTakeProfit == 0) return(false); 
OrderSelect(argTicket,SELECT_BY_TICKET);
double OpenPrice = OrderOpenPrice();
        while(IsTradeContextBusy()) Sleep(10);
 
// Модификация ордера
bool TicketMod = OrderModify(argTicket,OrderOpenPrice(),argStopLoss,argTakeProfit,0);
 
// Обработка ошибок
if(TicketMod == false) {
int ErrorCode = GetLastError();
string ErrDesc = ErrorDescription(ErrorCode);
string ErrAlert = StringConcatenate("Добавление Stop/Profit - Ошибка ",ErrorCode,": ", ErrDesc);
             Alert(ErrAlert);
string ErrLog = StringConcatenate("Bid: ",MarketInfo(OrderSymbol(),MODE_BID), " Ask: ",MarketInfo(OrderSymbol(),MODE_ASK)," Ticket: ",argTicket,
" Stop: ",argStopLoss," Profit: ",argTakeProfit);
             Print(ErrLog);
          }
        return(TicketMod);
      }

Сначала мы проверяем, был ли предоставлен стоп-лосс или тейк-профит. Если нет, мы выходим из функции. В противном случае мы изменим ордер, используя стоп-лосс и тейк-профит, которые были переданы в функцию. Функция обработки ошибок будет запущена, если изменение ордера не было успешным. Эта функция будет работать на всех типах ордеров.

Использование включаемых файлов

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

Включаемые файлы не требуют специального синтаксиса. Вы объявляете функции и переменные во включаемом файле так же, как в любом файле исходного кода. У включаемых файлов не должно быть функций init(), start() или deinit(). Файл должен иметь расширение .mqh и находиться в папке \experts\include.

Использование библиотек

Библиотека — это скомпилированная коллекция функций. В то время как включаемый файл — это файл исходного кода, содержимое которого «включено» в исполняемый файл, библиотека — это отдельный исполняемый файл, который содержит импортированные функции. Поэтому для работы советника у вас должен быть как исполняемый файл советника, так и исполняемый файл библиотеки.

Библиотеки хранятся в папке \experts\library. Файлы исходного кода имеют расширение .mq4, а исполняемые файлы имеют расширение .ex4. В библиотеках нет функций start(), init() или deinit(). Чтобы объявить файл как библиотеку, вы должны поместить директиву препроцессора #property library в начало файла.

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

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

Вам нужно будет использовать директиву #import для импорта библиотечных функций в ваш эксперт. Если библиотека содержит множество функций, может быть лучше создать включаемый файл с операторами #import. Это увеличивает количество файлов, с которыми вам нужно работать. Если у вас нет очень веских причин для использования библиотек, рекомендуется использовать отдельные файлы для хранения своих функций.

Вы также можете импортировать функции из Windows DLL, используя директивы #import. Включаемый файл WinUser32.mqh в \experts\include содержит множество примеров, которые используются для функции MessageBox().

Пример кода простого советника с функциями

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

// Препроцессор
#include
// Внешние переменные
extern bool DynamicLotSize = true;
extern double EquityPercent = 2;
extern double FixedLotSize = 0.1;
extern double StopLoss = 50;
extern double TakeProfit = 100;
extern int Slippage = 5;
extern int MagicNumber = 123;
extern int FastMAPeriod = 10;
extern int SlowMAPeriod = 20;
 
// Глобальные переменные
int BuyTicket;
int SellTicket;
double UsePoint;
int UseSlippage;
 
// Init функция
int init()
{
UsePoint = PipPoint(Symbol());
UseSlippage = GetSlippage(Symbol(),Slippage);
}
// Start функция
int start()
{
// Скользящая средняя
double FastMA = iMA(NULL,0,FastMAPeriod,0,0,0,0); double SlowMA = iMA(NULL,0,SlowMAPeriod,0,0,0,0);
 
// Размер лота
double LotSize = CalcLotSize(DynamicLotSize,EquityPercent,StopLoss,FixedLotSize); LotSize = VerifyLotSize(LotSize);
 
// Ордер на покупку
if(FastMA > SlowMA && BuyTicket == 0) {
if(SellTicket > 0) int Closed = CloseSellOrder(Symbol(),SellTicket,UseSlippage); SellTicket = 0;
BuyTicket = OpenBuyOrder(Symbol(),LotSize,UseSlippage,MagicNumber);
if(BuyTicket > 0 && (StopLoss > 0 || TakeProfit > 0)) {
OrderSelect(BuyTicket,SELECT_BY_TICKET); double OpenPrice = OrderOpenPrice();
 
double BuyStopLoss = CalcBuyStopLoss(Symbol(),StopLoss,OpenPrice); if(BuyStopLoss > 0)
{
BuyStopLoss = AdjustBelowStopLevel(Symbol(),BuyStopLoss,5);
}
double BuyTakeProfit = CalcBuyTakeProfit(Symbol(),TakeProfit,OpenPrice); if(BuyTakeProfit > 0)
{
BuyTakeProfit = AdjustAboveStopLevel(Symbol(),BuyTakeProfit,5);
}
AddStopProfit(BuyTicket,BuyStopLoss,BuyTakeProfit); }
}
 
// Ордер на продажу
if(FastMA < SlowMA && SellTicket == 0) { if(BuyTicket > 0) Closed = CloseBuyOrder(Symbol(),BuyTicket,Slippage); BuyTicket = 0;
SellTicket = OpenSellOrder(Symbol(),LotSize,UseSlippage,MagicNumber);
if(SellTicket > 0 && (StopLoss > 0 || TakeProfit > 0))
{
OrderSelect(SellTicket,SELECT_BY_TICKET); OpenPrice = OrderOpenPrice();
double SellStopLoss = CalcSellStopLoss(Symbol(),StopLoss,OpenPrice); if(SellStopLoss > 0)
{
SellStopLoss = AdjustAboveStopLevel(Symbol(),SellStopLoss,5);
}
double SellTakeProfit = CalcSellTakeProfit(Symbol(),TakeProfit,OpenPrice); if(SellTakeProfit > 0)
{
SellTakeProfit = AdjustBelowStopLevel(Symbol(),SellTakeProfit,5);
}
AddStopProfit(SellTicket,SellStopLoss,SellTakeProfit); }
} return(0);
}

Мы начинаем с включения файла, в котором есть наши функции. В начале функции start() мы используем CalcLotSize() и VerifyLotSize() для вычисления и проверки размера нашего лота.

В наших блоках ордеров на покупку и продажу мы используем CloseBuyOrder() и CloseSellOrder(), чтобы закрыть противоположный ордер. Наши новые ордера открываются с использованием OpenBuyOrder() или OpenSellOrder(). Перед тем, как рассчитать стоп-лосс и тейк-профит, мы проверяем, что ордер был открыт и был указан StopLoss или TakeProfit.

Мы извлекаем цену открытия ордера, используя OrderSelect() и OrderOpenPrice(). Затем мы рассчитываем наш стоп-лосс, используя CalcBuyStopLoss() или CalcSellStopLoss(), а наш тейк-профит — CalcBuyTakeProfit() или CalcSellTakeProfit().

Мы проверяем, является ли стоп-лосс или тейк-профит больше 0, и используем функции AdjustAboveStopLevel() и AdjustBelowStopLevel(), чтобы проверить наши стоп-лосс и цены тейк-профита. Наконец, мы передаем эти цены в функцию AddOrderProfit(), которая добавляет стоп-лосс и тейк-профит в ордер.

Комментарии (2)