
Парадигмы программирования (Часть 1): Процедурный подход к разработке советника на основе ценовой динамики
Введение
В мире разработки программного обеспечения парадигмы программирования являются руководящими принципами написания и организации кода. Подобно выбору разных маршрутов для достижения пункта назначения, существуют разные подходы или парадигмы программирования для выполнения задач с использованием MQL5.
В двух статьях мы рассмотрим основные парадигмы программирования, необходимые для создания торговых инструментов с помощью MQL5. Моя цель — поделиться эффективными и передовыми методами, которые дают отличные результаты с помощью короткого и эффективного кода. Я объясню каждый стиль программирования и продемонстрирую его, создав полнофункциональный советник.Типы парадигм программирования
Существует три основные парадигмы программирования, о которых должен знать каждый MQL5-разработчик:
- Процедурное программирование: Статья будет посвящена именно этой парадигме.
- Функциональное программирование: Эта парадигма также будет рассмотрена в статье, так как она очень похожа на процедурное программирование.
- Объектно-ориентированное программирование (ООП): Эта парадигма будет рассмотрена в следующей статье.
Процедурное программирование
Процедурное программирование — это систематический, пошаговый подход к написанию кода. Он предполагает разбиение любой проблемы на последовательность точных инструкций, что очень похоже на готовку блюда по рецепту. Программисты создают четкий путь для компьютера, давая ему четки пошаговый алгоритм для достижения желаемого результата.
Независимо от того, новичок вы в программировании или просто интересуетесь организацией кода, процедурное программирование обеспечивает простую и интуитивно понятную точку входа в тему.
Основные свойства процедурного программирования
- Функции:
В основе процедурного программирования лежат функции. Это наборы инструкций, сгруппированных вместе для выполнения конкретной задачи. Функции инкапсулируют функциональность, обеспечивая модульность и повторное использование кода. - Нисходящее проектирование:
В процедурном программировании часто используется нисходящий подход к проектированию. Разработчики разбивают проблему на более мелкие и более управляемые подзадачи. Каждая подзадача решается индивидуально, внося свой вклад в общее решение. - Императивный стиль:
Императивный, или командный характер процедурного программирования подразумевает явные утверждения, которые изменяют состояние программы. Разработчики указывают, как программа должна решать задачу с помощью серии процедурных команд. - Переменные и данные:
Процедуры или функции в процедурном программировании манипулируют переменными и данными. Эти переменные могут хранить значения, которые изменяются во время выполнения программы. Изменения состояния являются фундаментальным аспектом процедурного программирования. - Последовательное выполнение:
Выполнение программы происходит последовательно. Операторы выполняются один за другим, а управляющие структуры, такие как циклы и условные выражения, направляют ход программы. - Модульность:
Процедурное программирование способствует модульности, организуя код в процедуры или функции. Каждый модуль обрабатывает определенный аспект функциональности программы, улучшая организацию кода и удобство сопровождения. - Многоразовое использование:
Возможность повторного использования кода — ключевое преимущество процедурного программирования. После того как функция написана и протестирована, ее можно использовать везде, где эта конкретная функциональность необходима в программе, что снижает избыточность и повышает эффективность. - Читабельность:
Процедурный код обычно более читабелен, особенно для тех, кто привык к пошаговому подходу. Линейный поток выполнения позволяет легко следовать логике программы.
Функциональное программирование
Функциональное программирование вращается вокруг концепции функций как первоклассных граждан и неизменяемости. Парадигма похожа на процедурное программирование, за исключением основного принципа обработки данных и выполнения задач.
В отличие от процедурного программирования, где данные могут менять свой внешний вид и роль во время выполнения программы, функциональное программирование предпочитает более стабильную среду. После создания данных они остаются неизменными. Приверженность неизменности обеспечивает определенный уровень предсказуемости и помогает предотвратить непредвиденные побочные эффекты в коде.
Основные свойства функционального программирования
- Неизменяемость:
Основной принцип функционального программирования. После создания данных они остаются неизменными. Вместо изменения существующих данных создаются новые данные с необходимыми изменениями. Это обеспечивает предсказуемость и помогает избежать непредвиденных побочных эффектов. - Функции как первоклассные граждане:
Функции рассматриваются как первоклассные элементы, то есть их можно присваивать переменным, передавать в качестве аргументов другим функциям и возвращать в качестве результатов из других функций. Эта гибкость позволяет создавать функции более высокого порядка и способствует более модульному и выразительному стилю кодирования. - Декларативный стиль:
Функциональное программирование предпочитает декларативный стиль программирования, в котором основное внимание уделяется тому, чего должна достичь программа, а не тому, как этого достичь. Это способствует созданию более лаконичного и читаемого кода. - Избежание изменчивого состояния:
Изменяемое состояние минимизируется или устраняется. Данные считаются неизменяемыми, а функции избегают изменения внешнего состояния. Эта характеристика упрощает рассуждения о поведении функций. - Рекурсия и функции высшего порядка:
В функциональном программировании обычно используются рекурсия и функции высшего порядка, когда функции принимают другие функции в качестве аргументов или возвращают их в качестве результатов. Это приводит к созданию более модульного и многоразового кода.
Процедурный подход к разработке советника на основе ценовой динамики (Price Action)
Теперь, когда мы углубились в суть парадигм процедурного и функционального программирования, давайте применим теорию на практике - создадим советника на основе ценовой динамики. Сначала я расскажу о торговой стратегии, которую мы собираемся автоматизировать. Позже мы пройдемся по различным компонентам кода, раскроем их функциональность и то, как они органично работают вместе.
Стратегия ценовой динамики с индикатором EMA
Наша торговая стратегия основана на одном индикаторе, известном как экспоненциальная скользящая средняя (Exponential Moving Average, EMA). Индикатор широко используется в техническом анализе и помогает определить направление рынка на основе выбранной торговой установки. Скользящая средняя является стандартным индикатором в MQL5, что упрощает ее включение в наш код.
Покупка:
Открываем позицию на покупку, когда последняя закрытая свеча является бычьей, и ее минимальная и максимальная цены находятся выше экспоненциальной скользящей средней (EMA).
Продажа:
Открываем позицию на продажу, когда последняя закрытая свеча является медвежьей, и ее минимальная и максимальная цены находятся ниже экспоненциальной скользящей средней (EMA).
Выход:
Автоматически закрываем все открытые позиции и получаем соответствующую прибыль или убыток, когда достигается указанный пользователем процент прибыли или убытка для счета, или используем традиционные ордера стоп-лосс или тейк-профит.
Настройка | Условия |
---|---|
Покупка | Последняя закрытая свеча является бычьей, и ее минимальная и максимальная цены находятся выше экспоненциальной скользящей средней (EMA). |
Продажа | Последняя закрытая свеча является медвежьей, и ее минимальная и максимальная цены находятся ниже экспоненциальной скользящей средней (EMA). |
Выход | закрываем все открытые позиции и получаем соответствующую прибыль или убыток, когда достигается указанный пользователем процент прибыли/убытка для счета или срабатывает стоп-лосс/тейк-профит. |
Реализация торговой стратегии в коде
Теперь, когда мы установили наши торговые правила и план, давайте воплотим нашу торговую стратегию в жизнь, написав код на MQL5 в среде MetaEditor. Выполните следующие шаги, чтобы начать с чистого шаблона советника, содержащего только необходимые обязательные функции:
Шаг 1: Откройте MetaEditor и запустите Мастера, нажав кнопку "Создать".
Шаг 2: Выберите "Советник (шаблон)" и нажмите "Далее".
Шаг 3: В "Общих параметрах" введите имя советника и нажмите "Далее".
Шаг 4: Убедитесь, что в разделе "Обработчики событий" не выбраны никакие параметры. Снимите флажки, если они есть, а затем нажмите "Далее".
Шаг 5: Убедитесь, что в разделе "Обработчики событий тестирования" не выбраны никакие параметры. Снимите флажки со всех опций, если они выбраны, а затем нажмите "Готово", чтобы создать шаблон советника 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
Анализируя результаты нашего тестирования на истории, мы видим, что наш советник не только приносил прибыль, но и поддерживал удивительно низкую просадку. Эта стратегия многообещающая и может быть дополнительно модифицирована и оптимизирована для достижения еще лучших результатов, особенно при одновременном применении к нескольким символам.
Заключение
Даже начинающим MQL5-программистам не составит труда разобраться в процедурном коде только что созданного нами советника. Эта простота возникает из-за ясного и прямого характера процедурного программирования, особенно при использовании функций для организации кода на основе конкретных задач и глобальных переменных для передачи измененных данных различным функциям.
Вы можете заметить, что недостатком процедурного кода является его тенденция к значительному расширению по мере усложнения советника, что делает его пригодным в первую очередь для менее сложных проектов. В случаях, когда проект очень сложен, лучше выбрать объектно-ориентированный подход.
В нашей следующей статье мы познакомимся с объектно-ориентированным программированием и преобразуем наш недавно созданный код процедурного советника на основе ценовой динамики в объектно-ориентированный код. Это обеспечит четкое сравнение этих парадигм и более четкое понимание различий.
Спасибо за внимание! Желаю вам успехов в MQL5-разработке и торговле.
Перевод с английского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/en/articles/13771





- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования
Очень познавательно и интересно
Отличная статья о процедурном программировании!
Хорошая статья. Я ожидал какого-то процедурного кодирования price action, вроде структуры волн ABCD или условного зигзага с шагами, как в шаге 1 найти пик, в шаге 2 найти впадину и т.д... Я не думаю, что свеча low high выше или ниже EMA является процедурным "прайс экшн", если мы оставим торговые функции в стороне.