English 中文 Deutsch 日本語
preview
Как упростить ручное тестирование стратегий с помощью MQL5: строим свой набор инструментов

Как упростить ручное тестирование стратегий с помощью MQL5: строим свой набор инструментов

MetaTrader 5Тестер |
663 6
Allan Munene Mutiiria
Allan Munene Mutiiria

Введение

Тестирование торговых стратегий на исторических данных является краеугольным камнем успешной торговли, но автоматизация каждой возникшей идеи может создавать ощущение ограниченности, тогда как ручному тестированию часто недостает структурированности и точности. Что если бы вы могли объединить контроль ручной торговли с мощью Тестера стратегий MetaTrader 5? В этой статье представляем пользовательский экспертный советник на языке MetaQuotes Language 5 (MQL5), превращающий ручное тестирование на исторических данных в интуитивно понятный и эффективный процесс, предоставляя вам набор инструментов для тестирования стратегий на ваших условиях. Рассмотрим эти шаги в следующем порядке:

  1. План: Разработка набора инструментов для ручного тестирования на исторических данных
  2. Реализация на языке MQL5: Представление набора инструментов аудитории
  3. Тестирование на исторических данных в действии: Использование набора инструментов
  4. Заключение

К концу этой работы вы получите практическое решение для быстрого и уверенного тестирования на исторических данных и совершенствования своих торговых идей в Тестере стратегий.


План: Разработка набора инструментов для ручного тестирования на исторических данных

Наша цель — создать набор инструментов, который объединит ручное управление с высокой скоростью тестирования на исторических данных Тестера стратегий в MetaTrader 5 и позволит обойти медленные тики в реальном времени при традиционном ручном тестировании. Мы разработаем программу с кнопками на графике для открытия сделок на покупку или продажу, настройки размера лотов, установки уровней стоп-лосса (SL) и тейк-профита (TP), а также специальной тревожной кнопкой для закрытия всех позиций. Программа полностью интегрируется с любой стратегией, от индикаторов и моделей японских свечей до ценового действия, и все это работает в ускоренном темпе Тестера. Такая гибкая настройка позволит нам быстро и точно протестировать любой торговый подход в интерактивном режиме, что упрощает отработку стратегии в моделируемой среде. Вкратце — вот визуализация того, к чему мы стремимся:

ПЛАН ИЗОБРАЖЕНИЯ


Реализация на языке MQL5: Представление набора инструментов аудитории

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

//+------------------------------------------------------------------+
//|                       Manual backtest toolkit in Strategy Tester |
//|                        Copyright 2025, Forex Algo-Trader, Allan. |
//|                                 "https://t.me/Forex_Algo_Trader" |
//+------------------------------------------------------------------+
#property copyright "Forex Algo-Trader, Allan"
#property link      "https://t.me/Forex_Algo_Trader"
#property version   "1.00"
#property description "This EA Enables manual backtest in the strategy tester"
#property strict //--- Enforce strict coding rules to catch errors early

#define BTN_BUY "BTN BUY" //--- Define the name for the Buy button
#define BTN_SELL "BTN SELL" //--- Define the name for the Sell button
#define BTN_P "BTN P" //--- Define the name for the button that increase lot size
#define BTN_M "BTN M" //--- Define the name for the button that decrease lot size
#define BTN_LOT "BTN LOT" //--- Define the name for the lot size display button
#define BTN_CLOSE "BTN CLOSE" //--- Define the name for the button that close all positions
#define BTN_SL "BTN SL" //--- Define the name for the Stop Loss display button
#define BTN_SL1M "BTN SL1M" //--- Define the name for the button that slightly lower Stop Loss
#define BTN_SL2M "BTN SL2M" //--- Define the name for the button that greatly lower Stop Loss
#define BTN_SL1P "BTN SL1P" //--- Define the name for the button that slightly raise Stop Loss
#define BTN_SL2P "BTN SL2P" //--- Define the name for the button that greatly raise Stop Loss
#define BTN_TP "BTN TP" //--- Define the name for the Take Profit display button
#define BTN_TP1M "BTN TP1M" //--- Define the name for the button that slightly lower Take Profit
#define BTN_TP2M "BTN TP2M" //--- Define the name for the button that greatly lower Take Profit
#define BTN_TP1P "BTN TP1P" //--- Define the name for the button that slightly raise Take Profit
#define BTN_TP2P "BTN TP2P" //--- Define the name for the button that greatly raise Take Profit
#define BTN_YES "BTN YES" //--- Define the name for the button that confirm a trade
#define BTN_NO "BTN NO" //--- Define the name for the button that cancel a trade
#define BTN_IDLE "BTN IDLE" //--- Define the name for the idle button between Yes and No
#define HL_SL "HL SL" //--- Define the name for the Stop Loss horizontal line
#define HL_TP "HL TP" //--- Define the name for the Take Profit horizontal line

#include <Trade/Trade.mqh> //--- Bring in the Trade library needed for trading functions
CTrade obj_Trade; //--- Create a trading object to handle trade operations

bool tradeInAction = false; //--- Track whether a trade setup is currently active
bool isHaveTradeLevels = false; //--- Track whether Stop Loss and Take Profit levels are shown

input double init_lot = 0.03;
input int slow_pts = 10;
input int fast_pts = 100;

Здесь мы начнем с определения набора интерактивных кнопок, таких как BTN_BUY и BTN_SELL, с помощью ключевого слова #define, позволяющие начинать сделки в любое время, что дает нам прямой контроль над точками входа, а BTN_P и BTN_M позволяют настраивать размер init_lot (первоначально установленный на уровне 0.03) в сторону увеличения или уменьшения в соответствии с нашей склонностью к риску. Кроме того, мы включаем BTN_CLOSE в качестве аварийного выхода — быстрого способа в мгновение ока закрыть все позиции, а также полагаемся на tradeInAction, чтобы следить за тем, находимся ли мы в процессе настройки сделки, и на isHaveTradeLevels — чтобы сигнализировать об активности визуальных индикаторов Stop Loss и Take Profit.

Затем мы подключаемся к классу CTrade из <Trade/Trade.mqh> для создания объекта obj_Trade с целью плавного и эффективного выполнения торговых операций. Чтобы обеспечить еще большую гибкость, добавляем настраиваемые входные данные, такие как slow_pts на уровне 10 и fast_pts на уровне 100, чтобы мы могли настраивать уровни Stop Loss и Take Profit «на лету», гарантируя адаптацию нашего набора инструментов к любой тестируемой нами стратегии. Теперь, поскольку нам нужно создать кнопки на панели, создадим функцию со всеми возможными входными данными для повторного использования и настройки.

//+------------------------------------------------------------------+
//| Create button function                                           |
//+------------------------------------------------------------------+
void CreateBtn(string objName,int xD,int yD,int xS,int yS,string txt,
               int fs=13,color clrTxt=clrWhite,color clrBg=clrBlack,
               color clrBd=clrBlack,string font="Calibri"){
   ObjectCreate(0,objName,OBJ_BUTTON,0,0,0); //--- Create a new button object on the chart
   ObjectSetInteger(0,objName,OBJPROP_XDISTANCE, xD); //--- Set the button's horizontal position
   ObjectSetInteger(0,objName,OBJPROP_YDISTANCE, yD); //--- Set the button's vertical position
   ObjectSetInteger(0,objName,OBJPROP_XSIZE, xS); //--- Set the button's width
   ObjectSetInteger(0,objName,OBJPROP_YSIZE, yS); //--- Set the button's height
   ObjectSetInteger(0,objName,OBJPROP_CORNER, CORNER_LEFT_UPPER); //--- Position the button from the top-left corner
   ObjectSetString(0,objName,OBJPROP_TEXT, txt); //--- Set the text displayed on the button
   ObjectSetInteger(0,objName,OBJPROP_FONTSIZE, fs); //--- Set the font size of the button text
   ObjectSetInteger(0,objName,OBJPROP_COLOR, clrTxt); //--- Set the color of the button text
   ObjectSetInteger(0,objName,OBJPROP_BGCOLOR, clrBg); //--- Set the background color of the button
   ObjectSetInteger(0,objName,OBJPROP_BORDER_COLOR,clrBd); //--- Set the border color of the button
   ObjectSetString(0,objName,OBJPROP_FONT,font); //--- Set the font style of the button text

   ChartRedraw(0); //--- Refresh the chart to show the new button
}

Здесь мы определяем функцию CreateBtn для создания на графике каждой кнопки, например BTN_BUY или BTN_SELL, принимая такие входные данные, как objName для идентификатора кнопки, xD и yD для ее горизонтального и вертикального положений, xS и yS для ее ширины и высоты, а также txt для метки, которую мы хотим отобразить, например BUY или SELL. Для этого используем функцию ObjectCreate, чтобы разместить на графике новый объект OBJ_BUTTON, установив для простоты его основание в координатах (0,0,0). Затем позиционируем его с помощью ObjectSetInteger для настройки OBJPROP_XDISTANCE на xD, а OBJPROP_YDISTANCE — на yD, обеспечивая, чтобы он был расположен именно там, где нам нужно, и изменяем его размер с помощью OBJPROP_XSIZE для xS и с помощью OBJPROP_YSIZE для yS, чтобы он соответствовал нашей конструкции.

Закрепим его в верхнем левом углу, установив OBJPROP_CORNER на CORNER_LEFT_UPPER, что обеспечивает единообразие макета, и используем ObjectSetString для назначения OBJPROP_TEXT в качестве txt, чтобы кнопка ясно демонстрировала свое назначение. Для стиля меняем OBJPROP_FONTSIZE на fs (по умолчанию 13), OBJPROP_COLOR — на clrTxt (по умолчанию белый) для текста, OBJPROP_BGCOLOR — на clrBg (по умолчанию черный) для фона и OBJPROP_BORDER_COLOR — на clrBd (по умолчанию черный) для контура, тогда как OBJPROP_FONT получает значение font (по умолчанию Calibri) для четкого внешнего вида. Наконец, используем функцию ChartRedraw для обновления графика с идентификатором окна 0, мгновенно отображающую нашу новую кнопку, с которой мы можем взаимодействовать в Тестере стратегий. Теперь мы можем вызывать эту функцию всякий раз, когда хотим создать кнопку, и начнем с вызова ее в обработчике событий OnInit.

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit(){
   CreateBtn(BTN_P,150,45,40,25,CharToString(217),15,clrBlack,clrWhite,clrBlack,"Wingdings"); //--- Make the button to increase lot size with an up arrow
   CreateBtn(BTN_LOT,190,45,60,25,string(init_lot),12,clrWhite,clrGray,clrBlack); //--- Make the button showing the current lot size
   CreateBtn(BTN_M,250,45,40,25,CharToString(218),15,clrBlack,clrWhite,clrBlack,"Wingdings"); //--- Make the button to decrease lot size with a down arrow
   CreateBtn(BTN_BUY,110,70,110,30,"BUY",15,clrWhite,clrGreen,clrBlack); //--- Make the Buy button with a green background
   CreateBtn(BTN_SELL,220,70,110,30,"SELL",15,clrWhite,clrRed,clrBlack); //--- Make the Sell button with a red background
   CreateBtn(BTN_CLOSE,110,100,220,30,"PANIC BUTTON (X)",15,clrWhite,clrBlack,clrBlack); //--- Make the emergency button to close all trades
         
   return(INIT_SUCCEEDED); //--- Tell the system the EA start up successfully
}

Здесь мы запускаем наш набор инструментов для тестирования на исторических данных с помощью обработчика событий OnInit, настраивая его интерфейс в Тестере стратегий. Используем функцию CreateBtn, чтобы разместить BTN_P на уровнях xD 150 и yD 45 со стрелкой вверх от CharToString(217) в Wingdings, BTN_LOT на уровне xD 190, демонстрирующая init_lot, и BTN_M на уровне xD 250 со стрелкой вниз от CharToString(218) — все в стиле для управления размерами лотов. Затем добавляем BTN_BUY на уровнях xD 110 и yD 70 с текстом BUY на clrGreen, BTN_SELL на уровне xD 220 с текстом SELL на clrRed и BTN_CLOSE на уровнях xD 110 и yD 100 в качестве тревожной кнопки PANIC BUTTON (X) на clrBlack, прежде чем сигнализировать об успешном завершении с помощью return и INIT_SUCCEEDED. Шрифт Wingdings, который мы используем для иконок, взят из уже заданной таблицы символов на MQL5, которая приведена ниже.

WINGDINGS

При запуске программы мы получаем следующий результат.

ИНТЕРФЕЙС КНОПОК

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

int GetState(string Name){return (int)ObjectGetInteger(0,Name,OBJPROP_STATE);} //--- Get whether a button is pressed or not
string GetValue(string Name){return ObjectGetString(0,Name,OBJPROP_TEXT);} //--- Get the text shown on an object
double GetValueHL(string Name){return ObjectGetDouble(0,Name,OBJPROP_PRICE);} //--- Get the price level of a horizontal line

Здесь мы определим функцию GetState для проверки нажатий кнопок, где мы используем функцию ObjectGetInteger с OBJPROP_STATE для возврата при нажатии кнопки Name, GetValue для извлечения текста из Name с помощью ObjectGetString с OBJPROP_TEXT и GetValueHL для получения уровней цен кнопки Name с ObjectGetDouble, используя OBJPROP_PRICE для точного управления торговле. Теперь мы можем использовать функции для получения состояний кнопок в обработчике событий OnTick, поскольку не можем напрямую использовать обработчик событий OnChartEvent в Тестере стратегий. Вот как мы этого достигаем.

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick(){
   double Ask = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK),_Digits); //--- Get the current Ask price and adjust it to the right decimal places
   double Bid = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID),_Digits); //--- Get the current Bid price and adjust it to the right decimal places

   if (GetState(BTN_BUY)==true || GetState(BTN_SELL)){ //--- Check if either the Buy or Sell button is clicked
      tradeInAction = true; //--- Set trade setup to active
   }
}

Здесь мы используем обработчик событий OnTick для управления действиями нашего набора инструментов в реальном времени в Тестере стратегий, где мы применяем функцию NormalizeDouble с SymbolInfoDouble для установки Ask на текущую цену SYMBOL_ASK и Bid на цену SYMBOL_BID, причем обе скорректированы на _Digits для точности, а если GetState показывает значение BTN_BUY или BTN_SELL как true, устанавливаем tradeInAction на true, чтобы начать настройку нашей торговли. Это тот момент, когда нам нужны дополнительные торговые уровни, чтобы иметь возможность устанавливать уровни и корректировать их динамически. Создадим для этого соответствующую функцию.

//+------------------------------------------------------------------+
//| Create high low function                                         |
//+------------------------------------------------------------------+
void createHL(string objName,datetime time1,double price1,color clr){
   if (ObjectFind(0,objName) < 0){ //--- Check if the horizontal line doesn’t already exist
      ObjectCreate(0,objName,OBJ_HLINE,0,time1,price1); //--- Create a new horizontal line at the specified price
      ObjectSetInteger(0,objName,OBJPROP_TIME,time1); //--- Set the time property (though not critical for HLINE)
      ObjectSetDouble(0,objName,OBJPROP_PRICE,price1); //--- Set the price level of the horizontal line
      ObjectSetInteger(0,objName,OBJPROP_COLOR,clr); //--- Set the color of the line (red for SL, green for TP)
      ObjectSetInteger(0,objName,OBJPROP_STYLE,STYLE_DASHDOTDOT); //--- Set the line style to dash-dot-dot
      ChartRedraw(0); //--- Refresh the chart to display the new line
   }
}

Сначала определяем функцию createHL, чтобы рисовать горизонтальные линии для нашего набора инструментов в Тестере стратегий, где мы используем функцию ObjectFind для проверки наличия objName и того факта, что его значение ниже 0, мы используем функцию ObjectCreate для создания OBJ_HLINE при time1 и price1, мы используем функцию ObjectSetInteger для установки OBJPROP_TIME на time1 и OBJPROP_COLOR на clr, а OBJPROP_STYLE — на STYLE_DASHDOTDOT, мы используем функцию ObjectSetDouble для установки OBJPROP_PRICE на price1, и мы используем функцию ChartRedraw, для обновления графика с 0 для его отображения. Затем интегрируем эту функцию в другую функцию для плавного создания торговых уровней, как показано ниже.

//+------------------------------------------------------------------+
//| Create trade levels function                                     |
//+------------------------------------------------------------------+
void CreateTradeLevels(){
   double Ask = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK),_Digits); //--- Get unnoticed the current Ask price, adjusted for digits
   double Bid = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID),_Digits); //--- Get the current Bid price, adjusted for digits
   
   string level_SL,level_TP; //--- Declare variables to hold SL and TP levels as strings
   if (GetState(BTN_BUY)==true){ //--- Check if the Buy button is active
      level_SL = string(Bid-100*_Point); //--- Set initial Stop Loss 100 points below Bid for Buy
      level_TP = string(Bid+100*_Point); //--- Set initial Take Profit 100 points above Bid for Buy
   }
   else if (GetState(BTN_SELL)==true){ //--- Check if the Sell button is active
      level_SL = string(Ask+100*_Point); //--- Set initial Stop Loss 100 points above Ask for Sell
      level_TP = string(Ask-100*_Point); //--- Set initial Take Profit 100 points below Ask for Sell
   }
   
   createHL(HL_SL,0,double(level_SL),clrRed); //--- Create a red Stop Loss line at the calculated level
   createHL(HL_TP,0,double(level_TP),clrGreen); //--- Create a green Take Profit line at the calculated level

   CreateBtn(BTN_SL,110,135,110,23,"SL: "+GetValue(HL_SL),13,clrRed,clrWhite,clrRed); //--- Make a button showing the Stop Loss level
   CreateBtn(BTN_TP,220,135,110,23,"TP: "+GetValue(HL_TP),13,clrGreen,clrWhite,clrGreen); //--- Make a button showing the Take Profit level
   
   CreateBtn(BTN_SL1M,110,158,27,20,"-",17,clrBlack,clrWhite,clrGray); //--- Make a button to slightly lower Stop Loss
   CreateBtn(BTN_SL2M,137,158,27,20,"--",17,clrBlack,clrWhite,clrGray); //--- Make a button to greatly lower Stop Loss
   CreateBtn(BTN_SL2P,164,158,27,20,"++",17,clrBlack,clrWhite,clrGray); //--- Make a button to greatly raise Stop Loss
   CreateBtn(BTN_SL1P,191,158,27,20,"+",17,clrBlack,clrWhite,clrGray); //--- Make a button to slightly raise Stop Loss
   
   CreateBtn(BTN_TP1P,222,158,27,20,"+",17,clrBlack,clrWhite,clrGray); //--- Make a button to slightly raise Take Profit
   CreateBtn(BTN_TP2P,249,158,27,20,"++",17,clrBlack,clrWhite,clrGray); //--- Make a button to greatly raise Take Profit
   CreateBtn(BTN_TP2M,276,158,27,20,"--",17,clrBlack,clrWhite,clrGray); //--- Make a button to greatly lower Take Profit
   CreateBtn(BTN_TP1M,303,158,27,20,"-",17,clrBlack,clrWhite,clrGray); //--- Make a button to slightly lower Take Profit
   
   CreateBtn(BTN_YES,110,178,70,30,CharToString(254),20,clrWhite,clrDarkGreen,clrWhite,"Wingdings"); //--- Make a green checkmark button to confirm the trade
   CreateBtn(BTN_NO,260,178,70,30,CharToString(253),20,clrWhite,clrDarkRed,clrWhite,"Wingdings"); //--- Make a red X button to cancel the trade
   CreateBtn(BTN_IDLE,180,183,80,25,CharToString(40),20,clrWhite,clrBlack,clrWhite,"Wingdings"); //--- Make a neutral button between Yes and No
}

Здесь мы определяем функцию CreateTradeLevels для настройки наших торговых уровней, где мы используем функцию NormalizeDouble с SymbolInfoDouble для установки Ask на SYMBOL_ASK и Bid на SYMBOL_BID, скорректированные по _Digits, и объявляем level_SL и level_TP как строки. Если GetState отображает BTN_BUY как true, мы устанавливаем level_SL на Bid-100_Point и level_TP на Bid+100_Point, но если BTN_SELL представляет собой true, мы устанавливаем level_SL на Ask+100_Point и level_TP на Ask-100_Point.

Мы используем функцию createHL для отрисовки HL_SL на уровне double(level_SL) в clrRed и HL_TP на уровне double(level_TP) в clrGreen, затем используем функцию CreateBtn для создания кнопок, таких как BTN_SL с текстом GetValue(HL_SL), BTN_TP с текстом GetValue(HL_TP), и кнопок регулировки BTN_SL1M, BTN_SL2M, BTN_SL2P, BTN_SL1P, BTN_TP1P, BTN_TP2P, BTN_TP2M и BTN_TP1M с такими символами, как - и +, плюс BTN_YES, BTN_NO и BTN_IDLE с помощью CharToString для опций confirm, cancel и neutral в Wingdings. Имея такую функцию, мы можем вызывать ее при нажатии кнопок покупки или продажи, чтобы инициализировать настройку уровней торговли.

if (!isHaveTradeLevels){ //--- Check if trade levels aren't already on the chart
   CreateTradeLevels(); //--- Add Stop Loss and Take Profit levels and controls to the chart
   isHaveTradeLevels = true; //--- Mark that trade levels are now present
}

Здесь мы настраиваем проверку, при которой тестируем, имеет ли isHaveTradeLevels значение false с !isHaveTradeLevels, и если это так, используем функцию CreateTradeLevels для размещения на графике элементов управления Stop Loss и Take Profit, а затем обновляем значение isHaveTradeLevels до true, чтобы показать, что они активны. После компиляции у нас будет следующий результат.

ТОРГОВЫЕ УРОВНИ

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

if (tradeInAction){ //--- Continue if a trade setup is active

   // SL SLOW/FAST BUTTONS
   if (GetState(BTN_SL1M)){ //--- Check if the small Stop Loss decrease button is clicked
      ObjectSetDouble(0,HL_SL,OBJPROP_PRICE,GetValueHL(HL_SL)-slow_pts*_Point); //--- Move the Stop Loss down by a small amount
      ObjectSetInteger(0,BTN_SL1M,OBJPROP_STATE,false); //--- Turn off the button press state
      ChartRedraw(0); //--- Refresh the chart to show the change
   }
   if (GetState(BTN_SL2M)){ //--- Check if the large Stop Loss decrease button is clicked
      ObjectSetDouble(0,HL_SL,OBJPROP_PRICE,GetValueHL(HL_SL)-fast_pts*_Point); //--- Move the Stop Loss down by a large amount
      ObjectSetInteger(0,BTN_SL2M,OBJPROP_STATE,false); //--- Turn off the button press state
      ChartRedraw(0); //--- Refresh the chart to show the change
   }
   if (GetState(BTN_SL1P)){ //--- Check if the small Stop Loss increase button is clicked
      ObjectSetDouble(0,HL_SL,OBJPROP_PRICE,GetValueHL(HL_SL)+slow_pts*_Point); //--- Move the Stop Loss up by a small amount
      ObjectSetInteger(0,BTN_SL1P,OBJPROP_STATE,false); //--- Turn off the button press state
      ChartRedraw(0); //--- Refresh the chart to show the change
   }
   if (GetState(BTN_SL2P)){ //--- Check if the large Stop Loss increase button is clicked
      ObjectSetDouble(0,HL_SL,OBJPROP_PRICE,GetValueHL(HL_SL)+fast_pts*_Point); //--- Move the Stop Loss up by a large amount
      ObjectSetInteger(0,BTN_SL2P,OBJPROP_STATE,false); //--- Turn off the button press state
      ChartRedraw(0); //--- Refresh the chart to show the change
   }
   
   // TP SLOW/FAST BUTTONS
   if (GetState(BTN_TP1M)){ //--- Check if the small Take Profit decrease button is clicked
      ObjectSetDouble(0,HL_TP,OBJPROP_PRICE,GetValueHL(HL_TP)-slow_pts*_Point); //--- Move the Take Profit down by a small amount
      ObjectSetInteger(0,BTN_TP1M,OBJPROP_STATE,false); //--- Turn off the button press state
      ChartRedraw(0); //--- Refresh the chart to show the change
   }
   if (GetState(BTN_TP2M)){ //--- Check if the large Take Profit decrease button is clicked
      ObjectSetDouble(0,HL_TP,OBJPROP_PRICE,GetValueHL(HL_TP)-fast_pts*_Point); //--- Move the Take Profit down by a large amount
      ObjectSetInteger(0,BTN_TP2M,OBJPROP_STATE,false); //--- Turn off the button press state
      ChartRedraw(0); //--- Refresh the chart to show the change
   }
   if (GetState(BTN_TP1P)){ //--- Check if the small Take Profit increase button is clicked
      ObjectSetDouble(0,HL_TP,OBJPROP_PRICE,GetValueHL(HL_TP)+slow_pts*_Point); //--- Move the Take Profit up by a small amount
      ObjectSetInteger(0,BTN_TP1P,OBJPROP_STATE,false); //--- Turn off the button press state
      ChartRedraw(0); //--- Refresh the chart to show the change
   }
   if (GetState(BTN_TP2P)){ //--- Check if the large Take Profit increase button is clicked
      ObjectSetDouble(0,HL_TP,OBJPROP_PRICE,GetValueHL(HL_TP)+fast_pts*_Point); //--- Move the Take Profit up by a large amount
      ObjectSetInteger(0,BTN_TP2P,OBJPROP_STATE,false); //--- Turn off the button press state
      ChartRedraw(0); //--- Refresh the chart to show the change
   }

}

Здесь мы управляем настройками уровней Stop Loss и Take Profit в нашем наборе инструментов, когда tradeInAction имеет значение true, где мы используем функцию GetState для проверки, нажаты ли такие кнопки, как BTN_SL1M, BTN_SL2M, BTN_SL1P или BTN_SL2P, и корректируем HL_SL по slow_pts_Point или fast_pts_Point с помощью функции ObjectSetDouble с OBJPROP_PRICE и GetValueHL, а затем используем функцию ObjectSetInteger для сброса OBJPROP_STATE до false, а функцию ChartRedraw для обновления графика и аналогичной обработки BTN_TP1M, BTN_TP2M, BTN_TP1P или BTN_TP2P для корректировок HL_TP. Наконец, как только уровни установлены, мы можем подтвердить размещение и открыть соответствующие позиции, а затем очистить настройку торговых уровней, но сначала нам нужно будет получить функцию для удаления панели настройки уровней.

//+------------------------------------------------------------------+
//| Delete objects function                                          |
//+------------------------------------------------------------------+
void DeleteObjects_SLTP(){
   ObjectDelete(0,HL_SL); //--- Remove the Stop Loss line from the chart
   ObjectDelete(0,HL_TP); //--- Remove the Take Profit line from the chart
   ObjectDelete(0,BTN_SL); //--- Remove the Stop Loss display button
   ObjectDelete(0,BTN_TP); //--- Remove the Take Profit display button
   ObjectDelete(0,BTN_SL1M); //--- Remove the small Stop Loss decrease button
   ObjectDelete(0,BTN_SL2M); //--- Remove the large Stop Loss decrease button
   ObjectDelete(0,BTN_SL1P); //--- Remove the small Stop Loss increase button
   ObjectDelete(0,BTN_SL2P); //--- Remove the large Stop Loss increase button
   ObjectDelete(0,BTN_TP1P); //--- Remove the small Take Profit increase button
   ObjectDelete(0,BTN_TP2P); //--- Remove the large Take Profit increase button
   ObjectDelete(0,BTN_TP2M); //--- Remove the large Take Profit decrease button
   ObjectDelete(0,BTN_TP1M); //--- Remove the small Take Profit decrease button
   ObjectDelete(0,BTN_YES); //--- Remove the confirm trade button
   ObjectDelete(0,BTN_NO); //--- Remove the cancel trade button
   ObjectDelete(0,BTN_IDLE); //--- Remove the idle button
   
   ChartRedraw(0); //--- Refresh the chart to show all objects removed
}

Здесь мы выполняем очистку в нашем наборе инструментов с помощью функции DeleteObjects_SLTP, где мы используем функцию ObjectDelete для удаления с графика HL_SL, HL_TP, BTN_SL, BTN_TP, BTN_SL1M, BTN_SL2M, BTN_SL1P, BTN_SL2P, BTN_TP1P, BTN_TP2P, BTN_TP2M, BTN_TP1M, BTN_YES, BTN_NO и BTN_IDLE, а затем используем функцию ChartRedraw со значением 0 для обновления и отображения всех очищенных данных. Теперь мы можем использовать эту функцию в своей логике размещения заказов.

// BUY ORDER PLACEMENT
if (GetState(BTN_BUY) && GetState(BTN_YES)){ //--- Check if both Buy and Yes buttons are clicked
   obj_Trade.Buy(double(GetValue(BTN_LOT)),_Symbol,Ask,GetValueHL(HL_SL),GetValueHL(HL_TP)); //--- Place a Buy order with set lot size, SL, and TP
   DeleteObjects_SLTP(); //--- Remove all trade level objects from the chart
   isHaveTradeLevels = false; //--- Mark that trade levels are no longer present
   ObjectSetInteger(0,BTN_YES,OBJPROP_STATE,false); //--- Turn off the Yes button press state
   ObjectSetInteger(0,BTN_BUY,OBJPROP_STATE,false); //--- Turn off the Buy button press state
   tradeInAction = false; //--- Mark the trade setup as complete
   ChartRedraw(0); //--- Refresh the chart to reflect changes
}
// SELL ORDER PLACEMENT
else if (GetState(BTN_SELL) && GetState(BTN_YES)){ //--- Check if both Sell and Yes buttons are clicked
   obj_Trade.Sell(double(GetValue(BTN_LOT)),_Symbol,Bid,GetValueHL(HL_SL),GetValueHL(HL_TP)); //--- Place a Sell order with set lot size, SL, and TP
   DeleteObjects_SLTP(); //--- Remove all trade level objects from the chart
   isHaveTradeLevels = false; //--- Mark that trade levels are no longer present
   ObjectSetInteger(0,BTN_YES,OBJPROP_STATE,false); //--- Turn off the Yes button press state
   ObjectSetInteger(0,BTN_SELL,OBJPROP_STATE,false); //--- Turn off the Sell button press state
   tradeInAction = false; //--- Mark the trade setup as complete
   ChartRedraw(0); //--- Refresh the chart to reflect changes
}
else if (GetState(BTN_NO)){ //--- Check if the No button is clicked to cancel
   DeleteObjects_SLTP(); //--- Remove all trade level objects from the chart
   isHaveTradeLevels = false; //--- Mark that trade levels are no longer present
   ObjectSetInteger(0,BTN_NO,OBJPROP_STATE,false); //--- Turn off the No button press state
   ObjectSetInteger(0,BTN_BUY,OBJPROP_STATE,false); //--- Turn off the Buy button press state
   ObjectSetInteger(0,BTN_SELL,OBJPROP_STATE,false); //--- Turn off the Sell button press state
   tradeInAction = false; //--- Mark the trade setup as canceled
   ChartRedraw(0); //--- Refresh the chart to reflect changes
}

Мы совершаем сделки в нашем наборе инструментов в Тестере стратегий, где используем функцию GetState для проверки, имеют ли BTN_BUY и BTN_YES значения true, а затем используем метод obj_Trade.Buy с double(GetValue(BTN_LOT)), _Symbol, Ask, GetValueHL(HL_SL) и GetValueHL(HL_TP) для размещения ордера Buy или, если BTN_SELL и BTN_YES имеют значения true, используем obj_Trade.Sell с Bid вместо этого, и в любом случае мы используем функцию DeleteObjects_SLTP для очистки объектов, установим значения isHaveTradeLevels и tradeInAction на false, используем функцию ObjectSetInteger для сброса OBJPROP_STATE на BTN_YES, BTN_BUY или BTN_SELL на false и используем функцию ChartRedraw для обновления графика, но если значение BTN_NO равно true, мы отменяем операцию, очищая объекты и сбрасывая состояния аналогичным образом. Аналогичным образом мы обрабатываем кнопки увеличения или уменьшения объема торговли следующим образом.

if (GetState(BTN_P)==true){ //--- Check if the lot size increase button is clicked
   double newLot = (double)GetValue(BTN_LOT); //--- Get the current lot size as a number
   double lotStep = SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_STEP); //--- Get the minimum lot size change allowed
   newLot += lotStep; //--- Increase the lot size by one step
   newLot = NormalizeDouble(newLot,2); //--- Round the new lot size to 2 decimal places
   newLot = newLot > 0.1 ? lotStep : newLot; //--- Ensure lot size doesn't exceed 0.1, otherwise reset to step
   ObjectSetString(0,BTN_LOT,OBJPROP_TEXT,string(newLot)); //--- Update the lot size display with the new value
   ObjectSetInteger(0,BTN_P,OBJPROP_STATE,false); //--- Turn off the increase button press state
   ChartRedraw(0); //--- Refresh the chart to show the new lot size
}
if (GetState(BTN_M)==true){ //--- Check if the lot size decrease button is clicked
   double newLot = (double)GetValue(BTN_LOT); //--- Get the current lot size as a number
   double lotStep = SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_STEP); //--- Get the minimum lot size change allowed
   newLot -= lotStep; //--- Decrease the lot size by one step
   newLot = NormalizeDouble(newLot,2); //--- Round the new lot size to 2 decimal places
   newLot = newLot < lotStep ? lotStep : newLot; //--- Ensure lot size doesn't go below minimum, otherwise set to step
   ObjectSetString(0,BTN_LOT,OBJPROP_TEXT,string(newLot)); //--- Update the lot size display with the new value
   ObjectSetInteger(0,BTN_M,OBJPROP_STATE,false); //--- Turn off the decrease button press state
   ChartRedraw(0); //--- Refresh the chart to show the new lot size
}

Здесь мы корректируем размеры лотов, начиная с увеличения, где мы используем функцию GetState, чтобы проверить, имеет ли BTN_P значение true, а затем используем GetValue для установки newLot из BTN_LOT, используем SymbolInfoDouble для получения lotStep из SYMBOL_VOLUME_STEP, добавим lotStep к newLot и используем NormalizeDouble для округления до 2 знаков после запятой, ограничив значением lotStep, если оно больше 0.1, перед использованием ObjectSetString для обновления OBJPROP_TEXT и ObjectSetInteger в BTN_LOT, чтобы сбросить OBJPROP_STATE в BTN_P до значения false, а затем ChartRedraw для обновления.

Для уменьшения мы используем GetState для проверки BTN_M, вычитаем lotStep из newLot после извлечения тем же способом, сохраняем его на уровне не менее lotStep и выполняем те же шаги с функциями ObjectSetString, ObjectSetInteger и ChartRedraw для обновления значения BTN_LOT и сброса значения BTN_M. Что касается тревожной кнопки, нам потребуется определить функцию, которая будет закрывать все открытые позиции при ее нажатии.

//+------------------------------------------------------------------+
//| Close all positions function                                     |
//+------------------------------------------------------------------+
void closeAllPositions(){
   for (int i=PositionsTotal()-1; i>=0; i--){ //--- Loop through all open positions, starting from the last one
      ulong ticket = PositionGetTicket(i); //--- Get the ticket number of the current position
      if (ticket > 0){ //--- Check if the ticket is valid
         if (PositionSelectByTicket(ticket)){ //--- Select the position by its ticket number
            if (PositionGetString(POSITION_SYMBOL)==_Symbol){ //--- Check if the position is for the current chart symbol
               obj_Trade.PositionClose(ticket); //--- Close the selected position
            }
         }
      }
   }
}

Мы обрабатываем закрытие всех позиций с помощью функции closeAllPositions, где мы используем функцию PositionsTotal для создания цикла от i как последней позиции минус 1 до 0, используем функцию PositionGetTicket для получения значения ticket для каждого индекса i, и если ticket действителен, мы используем PositionSelectByTicket для его выбора, а затем используем PositionGetString для проверки, соответствует ли POSITION_SYMBOL значению _Symbol, перед использованием метода obj_Trade.PositionClose для закрытия позиции с помощью тикета. Затем мы можем вызвать эту функцию при нажатии тревожной кнопки, чтобы закрыть все позиции.

if (GetState(BTN_CLOSE)==true){ //--- Check if the close all positions button is clicked
   closeAllPositions(); //--- Close all open trades
   ObjectSetInteger(0,BTN_CLOSE,OBJPROP_STATE,false); //--- Turn off the close button press state
   ChartRedraw(0); //--- Refresh the chart to reflect closed positions
}

Для управления закрытием всех сделок, мы используем функцию GetState, чтобы проверить, является ли значение BTN_CLOSE значением true, и если это так, используем функцию closeAllPositions для закрытия всех открытых позиций, а затем используем функцию ObjectSetInteger, чтобы установить OBJPROP_STATE в BTN_CLOSE как false, и функцию ChartRedraw с 0, чтобы обновить график. После компиляции и запуска программы мы получили следующий результат.

ОКОНЧАТЕЛЬНЫЙ РЕЗУЛЬТАТ

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


Тестирование на исторических данных в действии: Использование набора инструментов

Мы тестируем наш набор инструментов в Тестере стратегий MetaTrader 5, загрузив программу, выбрав наши настройки и запустив ее — посмотрите на элемент Формат для обмена изображениями, Graphics Interchange Format (GIF) ниже, чтобы увидеть кнопки Buy (покупка), Sell (продажа) и Adjustment (корректировка) в действии с молниеносной скоростью. Нажмите на Buy или Sell, настройте уровни Stop Loss, Take Profit и размер лота, а затем подтвердите, нажав Yes или отмените с помощью No, а тревожная кнопка (Panic) будет готова быстро закрыть все сделки. Вот она.

GIF ПРОВЕРКИ ТЕСТЕРА НА ИСТОРИЧЕСКИХ ДАННЫХ


Заключение

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

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

Прикрепленные файлы |
Последние комментарии | Перейти к обсуждению на форуме трейдеров (6)
Allan Munene Mutiiria
Allan Munene Mutiiria | 16 апр. 2025 в 14:31
Mogulh Chilyalya Kiti #:
Что делать с разными таймфреймами

Здравствуйте. На данный момент только один таймфрейм. Возможно, попробуем это в ближайшем будущем.

Blessing Dumbura
Blessing Dumbura | 16 апр. 2025 в 15:47
Спасибо, это полезный инструмент
Allan Munene Mutiiria
Allan Munene Mutiiria | 16 апр. 2025 в 18:26
Blessing Dumbura #:
Спасибо, это полезный инструмент

Конечно. Добро пожаловать и спасибо за отзыв.

Dontrace
Dontrace | 7 мая 2025 в 11:08

хотел узнать, как конвертировать индикатор mq4 в mq5

zigooo
zigooo | 17 июн. 2025 в 17:16
Я попытался модифицировать его так, чтобы он мог быстро открывать ордера с лотами и tp/sl в соответствии с входными параметрами советника. Это работает на реальном рынке, но не работает в режиме бэктеста. Каково решение?
Нейросети в трейдинге: Устойчивые торговые сигналы в любых режимах рынка (Окончание) Нейросети в трейдинге: Устойчивые торговые сигналы в любых режимах рынка (Окончание)
В статье подробно рассмотрена интеграция подходов фреймворка ST-Expert в архитектуру Extralonger, позволяющая одновременно анализировать временные и пространственные представления данных. Представлены результаты тестирования на реальных исторических данных, демонстрирующие эффективность модели и её устойчивость к рыночным аномалиям. Описана модульная структура фреймворка, обеспечивающая воспроизводимость, гибкость для исследований и возможность поэтапной оптимизации компонентов.
Нейросети в трейдинге: Устойчивые торговые сигналы в любых режимах рынка (Модули внимания) Нейросети в трейдинге: Устойчивые торговые сигналы в любых режимах рынка (Модули внимания)
В данной статье мы продолжаем реализацию подходов фреймворка ST-Expert, сосредотачиваясь на практических аспектах его применения средствами MQL5. Ранее мы рассмотрели теоретические основы и ключевые компоненты модели, а теперь переходим к непосредственной работе с алгоритмами графового внимания, локального и глобального распределения внимания. Основная цель текущей работы — показать, как концептуальные идеи ST-Expert превращаются в работоспособные решения для анализа и прогнозирования финансовых рядов.
Автоматизация торговых стратегий на MQL5 (Часть 3): система Zone Recovery RSI для динамического управления торговлей Автоматизация торговых стратегий на MQL5 (Часть 3): система Zone Recovery RSI для динамического управления торговлей
В этой статье мы создадим систему Zone Recovery RSI EA на языке MQL5, используя сигналы RSI для запуска сделок и стратегию восстановления для управления убытками. Мы реализуем класс ZoneRecovery для автоматизации входа в сделку, логики восстановления и управления позициями. В заключение статьи приводятся результаты бэктестинга для оптимизации производительности и повышения эффективности советника.
Переходим на MQL5 Algo Forge (Часть 3): Использование чужих репозиториев в собственном проекте Переходим на MQL5 Algo Forge (Часть 3): Использование чужих репозиториев в собственном проекте
Рассмотрим, как можно уже сейчас подключить чужой код из любого репозитория в хранилище MQL5 Algo Forge к своему проекту. В этой статье мы наконец обратимся к этой многообещающей, но и более сложной задаче: как на практике подключить и использовать в своём проекте библиотеки из чужих репозиториев хранилища MQL5 Algo Forge.