English Deutsch 日本語
preview
Парадигмы программирования (Часть 1): Процедурный подход к разработке советника на основе ценовой динамики

Парадигмы программирования (Часть 1): Процедурный подход к разработке советника на основе ценовой динамики

MetaTrader 5Примеры | 8 апреля 2024, 16:02
354 6
Kelvin Muturi Muigua
Kelvin Muturi Muigua

Введение

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

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

Типы парадигм программирования

Существует три основные парадигмы программирования, о которых должен знать каждый MQL5-разработчик:

  1. Процедурное программирование: Статья будет посвящена именно этой парадигме.
  2. Функциональное программирование: Эта парадигма также будет рассмотрена в статье, так как она очень похожа на процедурное программирование.
  3. Объектно-ориентированное программирование (ООП): Эта парадигма будет рассмотрена в следующей статье.
Каждая парадигма имеет свои уникальные правила и свойства, предназначенные для решения конкретных задач и эффективной разработки различных торговых инструментов с помощью MQL5.


Процедурное программирование

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

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

Основные свойства процедурного программирования


  1. Функции:
    В основе процедурного программирования лежат функции. Это наборы инструкций, сгруппированных вместе для выполнения конкретной задачи. Функции инкапсулируют функциональность, обеспечивая модульность и повторное использование кода.
  2. Нисходящее проектирование:
    В процедурном программировании часто используется нисходящий подход к проектированию. Разработчики разбивают проблему на более мелкие и более управляемые подзадачи. Каждая подзадача решается индивидуально, внося свой вклад в общее решение.
  3. Императивный стиль:
    Императивный, или командный характер процедурного программирования подразумевает явные утверждения, которые изменяют состояние программы. Разработчики указывают, как программа должна решать задачу с помощью серии процедурных команд.
  4. Переменные и данные:
    Процедуры или функции в процедурном программировании манипулируют переменными и данными. Эти переменные могут хранить значения, которые изменяются во время выполнения программы. Изменения состояния являются фундаментальным аспектом процедурного программирования.
  5. Последовательное выполнение:
    Выполнение программы происходит последовательно. Операторы выполняются один за другим, а управляющие структуры, такие как циклы и условные выражения, направляют ход программы.
  6. Модульность:
    Процедурное программирование способствует модульности, организуя код в процедуры или функции. Каждый модуль обрабатывает определенный аспект функциональности программы, улучшая организацию кода и удобство сопровождения.
  7. Многоразовое использование:
    Возможность повторного использования кода — ключевое преимущество процедурного программирования. После того как функция написана и протестирована, ее можно использовать везде, где эта конкретная функциональность необходима в программе, что снижает избыточность и повышает эффективность.
  8. Читабельность:
    Процедурный код обычно более читабелен, особенно для тех, кто привык к пошаговому подходу. Линейный поток выполнения позволяет легко следовать логике программы.

Функциональное программирование

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

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

Основные свойства функционального программирования



  1. Неизменяемость:
    Основной принцип функционального программирования. После создания данных они остаются неизменными. Вместо изменения существующих данных создаются новые данные с необходимыми изменениями. Это обеспечивает предсказуемость и помогает избежать непредвиденных побочных эффектов.
  2. Функции как первоклассные граждане:
    Функции рассматриваются как первоклассные элементы, то есть их можно присваивать переменным, передавать в качестве аргументов другим функциям и возвращать в качестве результатов из других функций. Эта гибкость позволяет создавать функции более высокого порядка и способствует более модульному и выразительному стилю кодирования.
  3. Декларативный стиль:
    Функциональное программирование предпочитает декларативный стиль программирования, в котором основное внимание уделяется тому, чего должна достичь программа, а не тому, как этого достичь. Это способствует созданию более лаконичного и читаемого кода.
  4. Избежание изменчивого состояния:
    Изменяемое состояние минимизируется или устраняется. Данные считаются неизменяемыми, а функции избегают изменения внешнего состояния. Эта характеристика упрощает рассуждения о поведении функций.
  5. Рекурсия и функции высшего порядка:
    В функциональном программировании обычно используются рекурсия и функции высшего порядка, когда функции принимают другие функции в качестве аргументов или возвращают их в качестве результатов. Это приводит к созданию более модульного и многоразового кода.


Процедурный подход к разработке советника на основе ценовой динамики (Price Action)

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


Стратегия ценовой динамики с индикатором EMA


Наша торговая стратегия основана на одном индикаторе, известном как экспоненциальная скользящая средняя (Exponential Moving Average, EMA). Индикатор широко используется в техническом анализе и помогает определить направление рынка на основе выбранной торговой установки. Скользящая средняя является стандартным индикатором в MQL5, что упрощает ее включение в наш код.


Покупка:
Открываем позицию на покупку, когда последняя закрытая свеча является бычьей, и ее минимальная и максимальная цены находятся выше экспоненциальной скользящей средней (EMA).

Сигнал на покупку в стратегии ценовой динамики EMA


Продажа:
Открываем позицию на продажу, когда последняя закрытая свеча является медвежьей, и ее минимальная и максимальная цены находятся ниже экспоненциальной скользящей средней (EMA).

Сигнал на продажу в стратегии ценовой динамики EMA


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


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


Реализация торговой стратегии в коде

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


Шаг 1: Откройте MetaEditor и запустите Мастера, нажав кнопку "Создать".

Создание нового советника в Мастере MQL5


Шаг 2: Выберите "Советник (шаблон)" и нажмите "Далее".

Создание нового советника в Мастере MQL5


Шаг 3: В "Общих параметрах" введите имя советника и нажмите "Далее".

Создание нового советника в Мастере MQL5


Шаг 4: Убедитесь, что в разделе "Обработчики событий" не выбраны никакие параметры. Снимите флажки, если они есть, а затем нажмите "Далее".

Создание нового советника в Мастере MQL5


Шаг 5: Убедитесь, что в разделе "Обработчики событий тестирования" не выбраны никакие параметры. Снимите флажки со всех опций, если они выбраны, а затем нажмите "Готово", чтобы создать шаблон советника MQL5.

Создание нового советника в Мастере MQL5


Теперь у нас есть чистый шаблон советника MQL5 с обязательными функциями (OnInit, OnDeinit и OnTick). Не забудьте сохранить новый файл, прежде чем продолжить.

Вот как выглядит наш недавно сгенерированный код советника:

//+------------------------------------------------------------------+
//|                                               PriceActionEMA.mq5 |
//|                                  Copyright 2023, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2023, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#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()
  {
//---

  }
//+------------------------------------------------------------------+


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

#property description "A price action EA to demonstrate how to "
#property description "implement the procedural programming paradigm."

//--User input variables
input long magicNumber = 101;//Magic Number (Set 0 [Zero] to disable

input group ""
input ENUM_TIMEFRAMES tradingTimeframe = PERIOD_H1;//Trading Timeframe
input int emaPeriod = 20;//Moving Average Period
input int emaShift = 0;//Moving Average Shift

input group ""
input bool enableTrading = true;//Enable Trading
input bool enableAlerts = false;//Enable Alerts

input group ""
input double accountPercentageProfitTarget = 10.0;//Account Percentage (%) Profit Target
input double accountPercentageLossTarget = 10.0;//Account Percentage (%) Loss Target

input group ""
input int maxPositions = 3;//Max Positions (Max open positions in one direction)
input int tp = 5000;//TP (Take Profit Points/Pips [Zero (0) to diasable])
input int sl = 10000;//SL (Stop Loss Points/Pips [Zero (0) to diasable])


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

Функция GetInit:

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

int GetInit() //Function to initialize the robot and all the variables
  {
   int returnVal = 1;
//create the iMA indicator
   emaHandle = iMA(Symbol(), tradingTimeframe, emaPeriod, emaShift, MODE_EMA, PRICE_CLOSE);
   if(emaHandle < 0)
     {
      Print("Error creating emaHandle = ", INVALID_HANDLE);
      Print("Handle creation: Runtime error = ", GetLastError());
      //force program termination if the handle is not properly set
      return(-1);
     }
   ArraySetAsSeries(movingAverage, true);

//reset the count for positions
   totalOpenBuyPositions = 0;
   totalOpenSellPositions = 0;
   buyPositionsProfit = 0.0;
   sellPositionsProfit = 0.0;
   buyPositionsVol = 0.0;
   sellPositionsVol = 0.0;

   closedCandleTime = iTime(_Symbol, tradingTimeframe, 1);

   startingCapital = AccountInfoDouble(ACCOUNT_EQUITY);//used to calculate the account percentage profit

   if(enableAlerts)
     {
      Alert(MQLInfoString(MQL_PROGRAM_NAME), " has just been LOADED in the ", Symbol(), " ", EnumToString(Period()), " period chart.");
     }

//structure our comment string
   commentString = "\n\n" +
                   "Account No: " + IntegerToString(AccountInfoInteger(ACCOUNT_LOGIN)) +
                   "\nAccount Type: " + EnumToString((ENUM_ACCOUNT_TRADE_MODE)AccountInfoInteger(ACCOUNT_TRADE_MODE)) +
                   "\nAccount Leverage: " + IntegerToString(AccountInfoInteger(ACCOUNT_LEVERAGE)) +
                   "\n-----------------------------------------------------------------------------------------------------";
   return(returnVal);
  }


Функция GetDeinit:

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

void GetDeinit()  //De-initialize the robot on shutdown and clean everything up
  {
   IndicatorRelease(emaHandle); //delete the moving average handle and deallocate the memory spaces occupied
   ArrayFree(movingAverage); //free the dynamic arrays containing the moving average buffer data

   if(enableAlerts)
     {
      Alert(MQLInfoString(MQL_PROGRAM_NAME), " has just been REMOVED from the ", Symbol(), " ", EnumToString(Period()), " period chart.");
     }
//delete and clear all chart displayed messages
   Comment("");
  }

Функция GetEma:

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

void GetEma()
  {
//Get moving average direction
   if(CopyBuffer(emaHandle, 0, 0, 100, movingAverage) <= 0)
     {
      return;
     }
   movingAverageTrend = "FLAT";
   buyOk = false;
   sellOk = false;
   if(movingAverage[1] > iHigh(_Symbol, tradingTimeframe, 1) && movingAverage[1] > iLow(_Symbol, tradingTimeframe, 1))
     {
      movingAverageTrend = "SELL/SHORT";
      if(iClose(_Symbol, tradingTimeframe, 1) < iOpen(_Symbol, tradingTimeframe, 1))
        {
         sellOk = true;
         buyOk = false;
        }
     }
   if(movingAverage[1] < iHigh(_Symbol, tradingTimeframe, 1) && movingAverage[1] < iLow(_Symbol, tradingTimeframe, 1))
     {
      movingAverageTrend = "BUY/LONG";
      if(iClose(_Symbol, tradingTimeframe, 1) > iOpen(_Symbol, tradingTimeframe, 1))
        {
         buyOk = true;
         sellOk = false;
        }
     }
  }


Функция GetPositionsData:

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

void GetPositionsData()
  {
//get the total number of all open positions and their status
   if(PositionsTotal() > 0)
     {
      //variables for storing position properties values
      ulong positionTicket;
      long positionMagic, positionType;
      string positionSymbol;
      int totalPositions = PositionsTotal();

      //reset the count
      totalOpenBuyPositions = 0;
      totalOpenSellPositions = 0;
      buyPositionsProfit = 0.0;
      sellPositionsProfit = 0.0;
      buyPositionsVol = 0.0;
      sellPositionsVol = 0.0;

      //scan all the open positions
      for(int x = totalPositions - 1; x >= 0; x--)
        {
         positionTicket = PositionGetTicket(x);//gain access to other position properties by selecting the ticket
         positionMagic = PositionGetInteger(POSITION_MAGIC);
         positionSymbol = PositionGetString(POSITION_SYMBOL);
         positionType = PositionGetInteger(POSITION_TYPE);

         if(positionMagic == magicNumber && positionSymbol == _Symbol)
           {
            if(positionType == POSITION_TYPE_BUY)
              {
               ++totalOpenBuyPositions;
               buyPositionsProfit += PositionGetDouble(POSITION_PROFIT);
               buyPositionsVol += PositionGetDouble(POSITION_VOLUME);
              }
            if(positionType == POSITION_TYPE_SELL)
              {
               ++totalOpenSellPositions;
               sellPositionsProfit += PositionGetDouble(POSITION_PROFIT);
               sellPositionsVol += PositionGetDouble(POSITION_VOLUME);
              }
           }
        }
      //Get and save the account percentage profit
      accountPercentageProfit = ((buyPositionsProfit + sellPositionsProfit) * 100) / startingCapital;
     }
   else  //if no positions are open then the account percentage profit should be zero
     {
      startingCapital = AccountInfoDouble(ACCOUNT_EQUITY);
      accountPercentageProfit = 0.0;

      //reset position counters too
      totalOpenBuyPositions = 0;
      totalOpenSellPositions = 0;
     }
  }

Функция TradingIsAllowed:

Эта функция проверяет, имеет ли советник разрешение на торговлю от пользователя, терминала и брокера.

bool TradingIsAllowed()
  {
//check if trading is enabled
   if(enableTrading &&
      MQLInfoInteger(MQL_TRADE_ALLOWED) && TerminalInfoInteger(TERMINAL_TRADE_ALLOWED) &&
      AccountInfoInteger(ACCOUNT_TRADE_ALLOWED) && AccountInfoInteger(ACCOUNT_TRADE_EXPERT)
     )
     {
      tradingStatus = "\n-----------------------------------------------------------------------------------------" +
                      "\nTRADING IS FULLY ENABLED! *** SCANNING FOR ENTRY ***";
      return(true);
     }
   else  //trading is disabled
     {
      tradingStatus = "\n-----------------------------------------------------------------------------------------" +
                      "\nTRADING IS NOT FULLY ENABLED! *** GIVE EA PERMISSION TO TRADE ***";
      return(false);
     }
  }

Функция TradeNow:

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

void TradeNow()
  {
//Detect new candle formation and open a new position
   if(closedCandleTime != iTime(_Symbol, tradingTimeframe, 1))  //-- New candle found
     {
      //use the candle time as the position comment to prevent opening dublicate trades on one candle
      string positionComment = IntegerToString(iTime(_Symbol, tradingTimeframe, 1));

      //open a buy position
      if(buyOk && totalOpenBuyPositions < maxPositions)
        {
         //Use the positionComment string to check if we had already have a position open on this candle
         if(!PositionFound(_Symbol, POSITION_TYPE_BUY, positionComment)) //no position has been openend on this candle, open a buy position now
           {
            BuySellPosition(POSITION_TYPE_BUY, positionComment);
           }
        }

      //open a sell position
      if(sellOk && totalOpenSellPositions < maxPositions)
        {
         //Use the positionComment string to check if we had already have a position open on this candle
         if(!PositionFound(_Symbol, POSITION_TYPE_SELL, positionComment)) //no position has been openend on this candle, open a sell position now
           {
            BuySellPosition(POSITION_TYPE_SELL, positionComment);
           }
        }

      //reset closedCandleTime value to prevent new entry orders from opening before a new candle is formed
      closedCandleTime = iTime(_Symbol, tradingTimeframe, 1);
     }
  }

Функция ManageProfitAndLoss:

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

void ManageProfitAndLoss()
  {
//if the account percentage profit or loss target is hit, delete all positions
   double lossLevel = -accountPercentageLossTarget;
   if(
      (accountPercentageProfit >= accountPercentageProfitTarget || accountPercentageProfit <= lossLevel) ||
      ((totalOpenBuyPositions >= maxPositions || totalOpenSellPositions >= maxPositions) && accountPercentageProfit > 0)
   )
     {
      //delete all open positions
      if(PositionsTotal() > 0)
        {
         //variables for storing position properties values
         ulong positionTicket;
         long positionMagic, positionType;
         string positionSymbol;
         int totalPositions = PositionsTotal();

         //scan all the open positions
         for(int x = totalPositions - 1; x >= 0; x--)
           {
            positionTicket = PositionGetTicket(x);//gain access to other position properties by selecting the ticket
            positionMagic = PositionGetInteger(POSITION_MAGIC);
            positionSymbol = PositionGetString(POSITION_SYMBOL);
            positionType = PositionGetInteger(POSITION_TYPE);
            int positionDigits= (int)SymbolInfoInteger(positionSymbol, SYMBOL_DIGITS);
            double positionVolume = PositionGetDouble(POSITION_VOLUME);
            ENUM_POSITION_TYPE positionType = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);

            if(positionMagic == magicNumber && positionSymbol == _Symbol)
              {
               //print the position details
               Print("*********************************************************************");
               PrintFormat(
                  "#%I64u %s  %s  %.2f  %s [%I64d]",
                  positionTicket, positionSymbol, EnumToString(positionType), positionVolume,
                  DoubleToString(PositionGetDouble(POSITION_PRICE_OPEN), positionDigits), positionMagic
               );

               //reset the the tradeRequest and tradeResult values by zeroing them
               ZeroMemory(tradeRequest);
               ZeroMemory(tradeResult);
               //set the operation parameters
               tradeRequest.action = TRADE_ACTION_DEAL;//type of trade operation
               tradeRequest.position = positionTicket;//ticket of the position
               tradeRequest.symbol = positionSymbol;//symbol
               tradeRequest.volume = positionVolume;//volume of the position
               tradeRequest.deviation = SymbolInfoInteger(_Symbol, SYMBOL_SPREAD);//allowed deviation from the price
               tradeRequest.magic = magicNumber;//MagicNumber of the position

               //set the price and order type depending on the position type
               if(positionType == POSITION_TYPE_BUY)
                 {
                  tradeRequest.price = SymbolInfoDouble(positionSymbol, SYMBOL_BID);
                  tradeRequest.type = ORDER_TYPE_SELL;
                 }
               else
                 {
                  tradeRequest.price = SymbolInfoDouble(positionSymbol, SYMBOL_ASK);
                  tradeRequest.type = ORDER_TYPE_BUY;
                 }

               //print the position close details
               PrintFormat("Close #%I64d %s %s", positionTicket, positionSymbol, EnumToString(positionType));
               //send the tradeRequest
               if(OrderSend(tradeRequest, tradeResult)) //trade tradeRequest success, position has been closed
                 {
                  if(enableAlerts)
                    {
                     Alert(
                        _Symbol + " PROFIT LIQUIDATION: Just successfully closed POSITION (#" +
                        IntegerToString(positionTicket) + "). Check the EA journal for more details."
                     );
                    }
                  PrintFormat("Just successfully closed position: #%I64d %s %s", positionTicket, positionSymbol, EnumToString(positionType));
                  PrintFormat("retcode=%u  deal=%I64u  order=%I64u", tradeResult.retcode, tradeResult.deal, tradeResult.order);
                 }
               else  //trade tradeRequest failed
                 {
                  //print the information about the operation
                  if(enableAlerts)
                    {
                     Alert(
                        _Symbol + " ERROR ** PROFIT LIQUIDATION: closing POSITION (#" +
                        IntegerToString(positionTicket) + "). Check the EA journal for more details."
                     );
                    }
                  PrintFormat("Position clossing failed: #%I64d %s %s", positionTicket, positionSymbol, EnumToString(positionType));
                  PrintFormat("OrderSend error %d", GetLastError());//print the error code
                 }
              }
           }
        }
     }
  }

Функция PrintOnChart:

Форматирует и отображает статус советника на графике, предоставляя пользователю визуальное текстовое представление статуса счета и советника.

void PrintOnChart()
  {
//update account status strings and display them on the chart
   accountStatus = "\nAccount Balance: " + DoubleToString(AccountInfoDouble(ACCOUNT_BALANCE), 2) + accountCurrency +
                   "\nAccount Equity: " + DoubleToString(AccountInfoDouble(ACCOUNT_EQUITY), 2) + accountCurrency +
                   "\nAccount Profit: " + DoubleToString(AccountInfoDouble(ACCOUNT_PROFIT), 2) + accountCurrency +
                   "\nAccount Percentage Profit: " + DoubleToString(accountPercentageProfit, 2) + "%" +
                   "\n-----------------------------------------------------------------------------------------" +
                   "\nTotal Buy Positions Open: " + IntegerToString(totalOpenBuyPositions) +
                   "        Total Vol/Lots: " + DoubleToString(buyPositionsVol, 2) +
                   "        Profit: " + DoubleToString(buyPositionsProfit, 2) + accountCurrency +
                   "\nTotal Sell Positions Open: " + IntegerToString(totalOpenSellPositions) +
                   "        Total Vol/Lots: " + DoubleToString(sellPositionsVol, 2) +
                   "        Profit: " + DoubleToString(sellPositionsProfit, 2) + accountCurrency +
                   "\nPositionsTotal(): " + IntegerToString(PositionsTotal()) +
                   "\n-----------------------------------------------------------------------------------------" +
                   "\nJust Closed Candle:     Open: " + DoubleToString(iOpen(_Symbol, tradingTimeframe, 1), _Digits) +
                   "     Close: " + DoubleToString(iClose(_Symbol, tradingTimeframe, 1), _Digits) +
                   "     High: " + DoubleToString(iHigh(_Symbol, tradingTimeframe, 1), _Digits) +
                   "     Low: " + DoubleToString(iLow(_Symbol, tradingTimeframe, 1), _Digits) +
                   "\n-----------------------------------------------------------------------------------------" +
                   "\nMovingAverage (EMA): " + DoubleToString(movingAverage[1], _Digits) +
                   "     movingAverageTrend = " + movingAverageTrend +
                   "\nsellOk: " + IntegerToString(sellOk) +
                   "\nbuyOk: " + IntegerToString(buyOk);

//show comments on the chart
   Comment(commentString + accountStatus + tradingStatus);
  }


Функция BuySellPosition:

Эта функция открывает новые позиции на покупку и продажу.

bool BuySellPosition(int positionType, string positionComment)
  {
//reset the the tradeRequest and tradeResult values by zeroing them
   ZeroMemory(tradeRequest);
   ZeroMemory(tradeResult);
//initialize the parameters to open a position
   tradeRequest.action = TRADE_ACTION_DEAL;
   tradeRequest.symbol = Symbol();
   tradeRequest.deviation = SymbolInfoInteger(_Symbol, SYMBOL_SPREAD);
   tradeRequest.magic = magicNumber;
   tradeRequest.comment = positionComment;
   double volumeLot = NormalizeDouble(((SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN) * AccountInfoDouble(ACCOUNT_EQUITY)) / 10000), 2);

   if(positionType == POSITION_TYPE_BUY)
     {
      if(sellPositionsVol > volumeLot && AccountInfoDouble(ACCOUNT_MARGIN_LEVEL) > 200)
        {
         volumeLot = NormalizeDouble((sellPositionsVol + volumeLot), 2);
        }
      if(volumeLot < 0.01)
        {
         volumeLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
        }
      if(volumeLot > SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX))
        {
         volumeLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX);
        }
      tradeRequest.volume = NormalizeDouble(volumeLot, 2);
      tradeRequest.type = ORDER_TYPE_BUY;
      tradeRequest.price = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
      if(tp > 0)
        {
         tradeRequest.tp = NormalizeDouble(tradeRequest.price + (tp * _Point), _Digits);
        }
      if(sl > 0)
        {
         tradeRequest.sl = NormalizeDouble(tradeRequest.price - (sl * _Point), _Digits);
        }
      if(OrderSend(tradeRequest, tradeResult)) //successfully openend the position
        {
         if(enableAlerts)
           {
            Alert(_Symbol, " Successfully openend BUY POSITION #", tradeResult.order, ", Price: ", tradeResult.price);
           }
         PrintFormat("retcode=%u  deal=%I64u  order=%I64u", tradeResult.retcode, tradeResult.deal, tradeResult.order);
         return(true);
        }
      else
        {
         if(enableAlerts)
           {
            Alert(_Symbol, " ERROR opening a BUY POSITION at: ", SymbolInfoDouble(_Symbol, SYMBOL_ASK));
           }
         PrintFormat("ERROR: Opening a BUY POSITION: ErrorCode = %d",GetLastError());//OrderSend failed, output the error code
         return(false);
        }
     }

   if(positionType == POSITION_TYPE_SELL)
     {
      if(buyPositionsVol > volumeLot && AccountInfoDouble(ACCOUNT_MARGIN_LEVEL) > 200)
        {
         volumeLot = NormalizeDouble((buyPositionsVol + volumeLot), 2);
        }
      if(volumeLot < 0.01)
        {
         volumeLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
        }
      if(volumeLot > SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX))
        {
         volumeLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX);
        }
      tradeRequest.volume = NormalizeDouble(volumeLot, 2);
      tradeRequest.type = ORDER_TYPE_SELL;
      tradeRequest.price = SymbolInfoDouble(_Symbol, SYMBOL_BID);
      if(tp > 0)
        {
         tradeRequest.tp = NormalizeDouble(tradeRequest.price - (tp * _Point), _Digits);
        }
      if(sl > 0)
        {
         tradeRequest.sl = NormalizeDouble(tradeRequest.price + (sl * _Point), _Digits);
        }
      if(OrderSend(tradeRequest, tradeResult)) //successfully openend the position
        {
         if(enableAlerts)
           {
            Alert(_Symbol, " Successfully openend SELL POSITION #", tradeResult.order, ", Price: ", tradeResult.price);
           }
         PrintFormat("retcode=%u  deal=%I64u  order=%I64u", tradeResult.retcode, tradeResult.deal, tradeResult.order);
         return(true);
        }
      else
        {
         if(enableAlerts)
           {
            Alert(_Symbol, " ERROR opening a SELL POSITION at: ", SymbolInfoDouble(_Symbol, SYMBOL_ASK));
           }
         PrintFormat("ERROR: Opening a SELL POSITION: ErrorCode = %d",GetLastError());//OrderSend failed, output the error code
         return(false);
        }
     }
   return(false);
  }

Функция PositionFound:

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

bool PositionFound(string symbol, int positionType, string positionComment)
  {
   if(PositionsTotal() > 0)
     {
      ulong positionTicket;
      int totalPositions = PositionsTotal();
      //scan all the open positions
      for(int x = totalPositions - 1; x >= 0; x--)
        {
         positionTicket = PositionGetTicket(x);//gain access to other position properties by selecting the ticket
         if(
            PositionGetInteger(POSITION_MAGIC) == magicNumber && PositionGetString(POSITION_SYMBOL) == symbol &&
            PositionGetInteger(POSITION_TYPE) == positionType && PositionGetString(POSITION_COMMENT) == positionComment
         )
           {
            return(true);//a similar position exists, don't open another position on this candle
            break;
           }
        }
     }
   return(false);
  }


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

  • Поместим и вызовем функцию GetInit в функции OnInit. 

int OnInit()
  {
//---
   if(GetInit() <= 0)
     {
      return(INIT_FAILED);
     }
//---
   return(INIT_SUCCEEDED);
  }

  • Поместим и вызовем функцию GetDeinit в функции OnDeinit. 

void OnDeinit(const int reason)
  {
//---
   GetDeinit();
  }

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

void OnTick()
  {
//---
   GetEma();
   GetPositionsData();
   if(TradingIsAllowed())
     {
      TradeNow();
      ManageProfitAndLoss();
     }
   PrintOnChart();
  }

Когда код советника будет готов, сохраните и скомпилируйте его. Это позволит вам получить доступ к своему советнику прямо из торгового терминала. Полный код прикреплен внизу.


Тестируем наш советник в тестере стратегий

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

Вот настройки, которые мы применим в тестере стратегий:

  • Брокер: MetaQuotes-Demo (автоматически создается при установке MetaTrader 5)

  • Символ: EURUSD

  • Период тестирования (Интервал): Последний год (ноябрь 2022 - ноябрь 2023)

  • Моделирование: Каждый тик на основе реальных тиков

  • Начальный депозит: 10 000 USD

  • Плечо: 1:100

Настройки тестера стратегий PriceActionEA


Настройки тестера стратегий PriceActionEA


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

Результаты тестирования PriceActionEMA

Результаты тестирования PriceActionEMA

Результаты тестирования PriceActionEMA


Заключение

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

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

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

Спасибо за внимание! Желаю вам успехов в MQL5-разработке и торговле.

Перевод с английского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/en/articles/13771

Прикрепленные файлы |
PriceActionEMA.mq5 (23.33 KB)
Последние комментарии | Перейти к обсуждению на форуме трейдеров (6)
Jay Allen
Jay Allen | 5 янв. 2024 в 19:49
Отличная статья о процедурном программировании!
[Удален] | 5 янв. 2024 в 20:13
Хорошая статья. Я ожидал какого-то процедурного кодирования price action, вроде структуры волн ABCD или условного зигзага с шагами, как в шаге 1 найти пик, в шаге 2 найти впадину и т.д... Я не думаю, что свеча low high выше или ниже EMA является процедурным "прайс экшн", если мы оставим торговые функции в стороне.
Kelvin Muturi Muigua
Kelvin Muturi Muigua | 11 янв. 2024 в 18:25
Altan Karakaya #:

Очень познавательно и интересно

Спасибо. Я рад, что вам понравилось! Я ценю ваши отзывы.
Kelvin Muturi Muigua
Kelvin Muturi Muigua | 11 янв. 2024 в 18:25
Jay Allen #:
Отличная статья о процедурном программировании!
Спасибо. Я ценю ваши добрые слова и отзывы!
Kelvin Muturi Muigua
Kelvin Muturi Muigua | 11 янв. 2024 в 18:28
Arpit T #:
Хорошая статья. Я ожидал какого-то процедурного кодирования price action, вроде структуры волн ABCD или условного зигзага с шагами, как в шаге 1 найти пик, в шаге 2 найти впадину и т.д... Я не думаю, что свеча low high выше или ниже EMA является процедурным "прайс экшн", если мы оставим торговые функции в стороне.
Спасибо за ваш отзыв! В статье в первую очередь рассматривалась парадигма процедурного программирования как стиль написания и организации кода, а торговая стратегия использовалась в качестве практического примера для реализации на MQL5. В одной из следующих статей я покажу, как создать на MQL5 волновую стратегию ABCD или стратегию зигзагообразных шагов, как вы предложили. Не стесняйтесь рекомендовать другие области, которые вы хотели бы, чтобы я осветил в следующих статьях!
Популяционные алгоритмы оптимизации: Алгоритм боидов, или алгоритм стайного поведения (Boids Algorithm, Boids) Популяционные алгоритмы оптимизации: Алгоритм боидов, или алгоритм стайного поведения (Boids Algorithm, Boids)
В данной статье мы проводим исследование алгоритма Boids, в основе которого лежат уникальные примеры стайного поведения животных. Алгоритм Boids, в свою очередь, послужил основой для создания целого класса алгоритмов, объединенных под названием "Роевый интеллект".
Разрабатываем мультивалютный советник (Часть 7): Подбор группы с учётом форвард-периода Разрабатываем мультивалютный советник (Часть 7): Подбор группы с учётом форвард-периода
Подбор группы экземпляров торговых стратегий с целью улучшения результатов при их совместной работы мы прежде оценивали только на том же временном периоде, на котором проводилась оптимизация отдельных экземпляров. Давайте посмотрим, что получится на форвард-периоде.
Машинное обучение и Data Science (Часть 16): Свежий взгляд на деревья решений Машинное обучение и Data Science (Часть 16): Свежий взгляд на деревья решений
В последней части нашей серии о машинном обучении и работе с большими данными мы снова возвращаемся к деревьям решений. Эта статья предназначена для трейдеров, которые хотят понять роль деревьев решений в анализе рыночных тенденций. В ней собрана вся основная информация о структуре, предназначении и использовании таких деревьев. Мы рассмотри корни и ветви алгоритмических деревьев и узнаем, в чем же заключается их потенциал применительно к принятию торговых решений. Давайте вместе по-новому взглянем на деревья решений и посмотри, как они могут помочь преодолевать сложности на финансовых рынках.
Нейросети — это просто (Часть 84): Обратимая нормализация (RevIN) Нейросети — это просто (Часть 84): Обратимая нормализация (RevIN)
Мы давно уже усвоили, что большую роль в стабильности обучения модели играет предварительная обработка исходных данных. И для online обработки "сырых" исходных данных мы часто используем слой пакетной нормализации. Но порой возникает необходимость обратной процедуры. Об одном из возможных подходов к решению подобных задач мы говорим в данной статье.