English 中文 Español Deutsch 日本語 Português
preview
Создание советника Daily Drawdown Limiter на языке MQL5

Создание советника Daily Drawdown Limiter на языке MQL5

MetaTrader 5Трейдинг | 20 января 2025, 08:56
568 2
Kikkih25
Kikkih25

Введение 

В этой статье мы создаем советник для торговли на рынке Форекс — Daily Drawdown Limiter — на языке MetaQoutes (MQL5) для MetaTrader 5. Цель советника — установить дневной лимит вывода средств для торговых счетов. Советник анализирует такие факторы, как общее количество баров, начальный баланс, время суток и дневной баланс, а также проверяет выполнение определенных условий при торговле. Кроме того, он собирает с платформы MetaTrader информацию о балансе, капитале и средствах. Советник специально разработан для работы с платформой MetaTrader, и для его правильного функционирования требуется торговый счет.

В этой статье будут рассматриваться следующие темы:

  1. Характеристика советника Drawdown Limiter
  2. Создание советника на MQL5
  3. Заключение


Характеристика советника Drawdown limiter

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

Трейдерам нелегко контролировать просадку при торговле финансируемыми счетами. Для них предусмотрен дневной лимит просадки.  Проп-фирмы обычно устанавливают правило Trader Daily Drawdown ("Дневная просадка трейдера") и дисквалифицируют нарушающих его трейдеров. Ограничитель просадки помогает трейдерам в:

  1. Отслеживании просадки по счету
  2. Информировании трейдера при совершении сделок с высоким риском
  3. Отслеживании дневной просадки трейдера
  4. Защите трейдера от чрезмерной торговли, ограничивая открытые позиции 


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

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

Нам нужно открывать торговые позиции. Проще всего позиции открыть, включив торговый экземпляр. Обычно для этого нужно добавить еще один файл, предназначенный для открытия позиций. Мы используем директиву include для включения торговой библиотеки, содержащей функции для торговых операций. Сначала мы используем угловые скобки, обозначая ими, что включаемый файл содержится в папке include, и указываем папку trade, затем следует обычная или обратная косая черта и имя целевого файла, в данном случае — Trade.mqh. cTrade — класс обработки торговых операций, а obj-trade — экземпляр этого класса, обычно объект-указатель, созданный из класса cTrade для предоставления доступа к переменным — членам класса.

#include <Trade/Trade.mqh>
CTrade obj-Trade;

После этого нам потребуется некая логика управления для генерации сигналов на открытие позиций. В нашем случае функция OnTick() проверяет истинность переменной isTradeAllowed. Если да, вызывается функция checkDailyProfit(), предполагая, что целью функции OnTick() является проверка дневной прибыли и потенциально — по результатам проверки — разрешение или запрет на совершение сделок. Бары отслеживают общее количество баров на графике, гарантируя, что торговая логика выполняется на каждом новом баре только один раз, предотвращая многократное выполнение в пределах одного бара. В совокупности эти переменные позволяют советнику генерировать торговые сигналы на основе полученных значений, сохраняя при этом корректное время исполнения. Так как функция не принимает параметры, перейдем к выполняемым ею действиям:

  • Она определяет переменную total_day_Profit и инициализируют ее значением 0.  
  • Кроме того, она принимает текущее время и преобразует его в строку с помощью функции TimeToString, хранящейся в переменной date.
  • Аналогично она вычисляет начальный час дня, добавляя 1 к началу дня, и сохраняет в переменной.
  • Проверяет, не меньше ли текущее время (daytime), чем time. Если меньше, она устанавливает значение dayTime и рассчитывает текущий баланс с помощью функции Acc_B, сохраненной в переменной dayBalance.
  • Выделяет исторические данные за день с помощью функции History select, устанавливая время начала и окончания на начало и конец дня.
  • Рассчитывает общее количество сделок за день с помощью функции HistoryDealsTotal и сохраняет в переменной TotalDeals.
  • Более того, она просматривает каждую транзакцию в истории и проверяет, является ли ее запись записью типа DEAL_ENTRY_OUT, означающей, что это сделка при закрытии. Если да, рассчитывает торговую прибыль сложением значений DEAL_PROFIT, DEAL_COMMISSION и DEAL_SWAP и добавляет ее к переменной total_day_profit.
  • Она вычисляет начальный баланс дня путем вычитания значения total_day_Profit из текущего баланса счета с помощью функции AccountInfoDouble с параметром ACCOUNT-BALANCE.

Функция возвращает рассчитанный начальный баланс в виде значения Double.

int totalBars = 0;
double initialBalance = 0;
datetime dayTime = 0;
double dayBalance = 0;
bool isTradeAllowed = true;

Теперь перейдем к определению функции. Функция, видимо, связана с информацией о счете и возвращает разные значения в зависимости от имени функции. Функции Acc_B(), Acc_E() и Acc_S() используются для получения информации о балансе, средствах и валюте счета соответственно. Эти функции используют для мониторинга финансового состояния счета.

double Acc_B(){return AccountInfoDouble(ACCOUNT_BALANCE);}
double Acc_E(){return AccountInfoDouble(ACCOUNT_EQUITY);}
string Acc_S(){return AccountInfoString(ACCOUNT_CURRENCY);}

Полный код открытия позиций:

#include <Trade/Trade.mqh>
CTrade obj-Trade;

int totalBars = 0;
double initialBalance = 0;
datetime dayTime = 0;
double dayBalance = 0;
bool isTradeAllowed=true;

double Acc_B(){return AccountInfoDouble(ACCOUNT_BALANCE);}
double Acc_E(){return AccountInfoDouble(ACCOUNT_EQUITY);}
string Acc_S(){return AccountInfoString(ACCOUNT_CURRENCY);}

Обработчик событий Onlnit вызывают при каждой инициализации советника. Именно в этом случае нам нужно инициализировать индикатор и создать текст для отображения данных о начальном балансе счета для дальнейшего анализа. Для инициализации индикатора используем встроенную функцию, возвращающую его createText, указав правильные параметры. Текстовый объект устанавливается в определенных координатах и использует цвета с размером шрифта. Вот краткое описание достигнутого с помощью этой функции:

  1. Она ищет начальный баланс счета с помощью функции Acc_B() и сохраняет в переменной initialBalance.
  2. Благодаря этому на экране создается текст '* PROP FIRM PROGRESS DASHBOARD *' в точке с координатами (30,30), размером шрифта 13, голубого цвета (clrAqual).
  3. Это создаст несколько текстовых полей для отображения разных сообщений и информации для пользователя. Текстовые поля располагаются в разных местах на экране, используя разные размеры шрифта и цвета.

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

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit() {

   initialBalance = Acc_B();
   
   createText("0","***DAILY DRAWDOWN LIMITER ***",30,30,clrAqua,13);
   createText("00","______________________________________",30,30,clrAqua,13);
   createText("1","DrawDown Limiter is Active.",70,50,clrWhite,11);
   createText("2","Counters will be Reset on Next Day Start.",70,65,clrWhite,10);
   createText("3","From: ",70,80,clrWhite,10);
   createText("4",'Time Here',120,80,clrGray,10);
   createText("5","To: ",70,95,clrWhite,10);
   createText("6",'Time Here',120,95,clrGray,10);
   createText("7",'Current: ',70,110,clrWhite,10);
   createText("8",'Time Here',120,110,clrGray,10);

   createText("9",'ACCOUNT DRAWDOWN ============',70,130,clrPeru,11);
   createText("10",'Account Initial Balance: ',70,145,clrWhite,10);
   createText("11",DoubleToString(initialBalance,2)+" "+Acc_S(),250,145,clrWhite,10);
   createText("12",'Torelated DrawDown: ',70,160,clrWhite,10);
   createText("13","12.00 %",250,160,clrAqua,10);
   createText("14",'Current Account Equity: ',70,175,clrWhite,10);
   createText("15",DoubleToString(Acc_E(),2)+" "+Acc_S(),250,175,clrWhite,10);
   createText("16",'Current Balance Variation: ',70,190,clrWhite,10);
   createText("17",DoubleToString((Acc_E()-Acc_B())/Acc_B()*100,2)+" %",250,190,clrGray,10);

   createText("18",'DAILY DRAWDOWN ================',70,210,clrPeru,11);
   createText("19",'Starting Balance: ',70,225,clrWhite,10);
   createText("20",DoubleToString(Acc_B(),2)+" "+Acc_S(),270,225,clrWhite,10);
   createText("21",'DrawDowm Maximum Threshold: ',70,240,clrWhite,10);
   createText("22",'5.00 %"+" "+Acc_S(),270,240,clrAqua,10);
   createText("23",'DrawDown Maximum Amount: ',70,255,clrWhite,10);
   createText("24",'-"+DoubleToString(Acc_B()*5/100,2)+' "+Acc_S(),270,255,clrYellow,10);
   createText("25",'Current Closed Daily Profit: ',70,270,clrWhite,10);
   createText("26",'0.00"+" "+Acc_S(),270,270,clrGray,10);
   createText("27",'Current DrawDown Percent: ',70,285,clrWhite,10);
   createText("28",'0.00"+" %",270,285,clrGray,10);

   createText("29",'>>> Initializing The Program, Get Ready To Trade.",70,300,clrYellow,10);
   
   return(INIT_SUCCEEDED);
}

Здесь функция OnTick вызывается на каждом новом тике для символа, к которому прикреплен советник. Для функции checkDailyProfit нужно убедиться, что она реализована правильно. isTradeAllowed — логическая переменная, контролирующая, разрешена ли торговля. Если isTradeAllowed имеет значение false, оно сразу возвращается, и никакой дополнительный код не выполняется внутри функции OnTick.

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick(){   
   
   checkDailyProfit();
   
   if (!isTradeAllowed) return;
    

Просто определим случаи прорывного колебания. Это необходимо делать на каждом тике, так что сделаем это без ограничений. Сначала мы объявляем цены Ask и Bid, которые будем использовать для открытия позиций после выполнения соответствующих условий. Обратите внимание, что это необходимо делать на каждом тике, чтобы мы получали самые свежие котировки цен. Здесь объявляем переменные типа данных double для хранения последних цен и нормализуем их по цифрам валюты символа, округляя число с плавающей запятой для поддержания точности.

double ask = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK),_Digits); 
double bid = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID),_Digits); 

Определив примеры прорывного колебания, перейдем к определению функции с именем iBars, принимающей в нашем случае два параметра: _Symbol и _Period. iBars возвращает целочисленное значение, известное как 'bars'. Затем проверим, равна ли переменная totalBars значению, возвращаемому функцией iBars. В случае их равенства функция возвращается без дополнительных действий. Если они не равны, значение totalBars устанавливается равным значению bars, возвращаемому функцией iBars.

   int bars = iBars(_Symbol,_Period);
   if (totalBars == bars) return;
   totalBars = bars;

Теперь продолжим с определения функции iBars; здесь мы проверяем, превышают ли результаты вызова функции positionsTotal() значение 1. Если да, функция возвращается без дополнительных действий. Если нет, код переходит на следующую строку. Строка int number = MathRand()% представляется неполной, потому что отсутствует закрывающая скобка или точка с запятой. Если предположить, что цель — сгенерировать случайное целое число, строка завершится следующим образом: " int number = MathRand()% totalBars;' Эта строка генерирует случайное число от 0 до значения totalBars (inclusive) и присваивает его переменной 'number'.

 if (PositionsTotal() > 1) return;
   int number = MathRand()%

Полный код определения функций выглядит так:

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick(){   
   
   checkDailyProfit();
   
   if (!isTradeAllowed) return;
   
   double ask = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK),_Digits);
   double bid = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID),_Digits);

   int bars = iBars(_Symbol,_Period);
   if (totalBars == bars) return;
   totalBars = bars;
   
   if (PositionsTotal() > 1) return;
   int number = MathRand()%

Ниже — описание таких основных компонентов функций статьи, как размещение сделок, создание текстовых меток на графике и проверка дневной прибыли: 

1. Trade Execution - тип торгового исполнения 

В этом компоненте проверяем значение переменной с именем number и в зависимости от этого значения совершаем различные действия.

Если значение number равно 0, код запускает метод с именем Buy на объекте, которые идентифицируется как obj-Trade. Для этого метода требуется пять параметров: первый — 0.1, второй — переменная _Symbol, третий — переменная ask, четвертый — значение ask минус 70-кратное значение переменной _Point, а пятый — значение ask плюс 70-кратное значение той же самой переменной _Point. Это значит, что код пытается приобрести актив по цене немного ниже текущей цены ask с учетом спреда между bid и ask.

Если значение number равно 1, код исполняет метод с именем Sell для того же объекта obj-Trade. Кроме того, этот метод принимает пять параметров: первый — 0.1, второй — переменная _Symbol, третий — переменная bid, четвертый — значение bid плюс 70-кратное значение переменной _Point и пятый — значение bid минус 70-кратное значение той же переменной _Point. Это подразумевает, что код пытается продать актив по цене немного выше текущей цены bid, также учитывая спред между ценами bid и ask. Это определяет значение переменной, известной как number, и в зависимости от этого значения совершает различные действия.

if (number == 0){
      obj_Trade.Buy(0.1,_Symbol,ask,ask-70*_Point,ask+70*_Point);
   }
   else if (number == 1){
      obj_Trade.Sell(0.1,_Symbol,bid,bid+70*_Point,bid-70*_Point);
   }

2. Создание текста

В предыдущем коде существует функция с именем createText, ответственная за создание на графике объекта метки. Для выполнения этой функции требуются определенные параметры, такие как имя объекта (objName), текстовое содержание (text), координаты x и y для размещения метки (x и y), цвет текста (clrTxt) и размер шрифта (font size). Используя эти входные данные, функция создает на графике объект метки, настраивает его функции и обновляет график. Кроме того, функция возвращает логическое значение, указывающее на успешное или неудачное создание метки.


bool createText(string objName,string text,int x, int y,color clrTxt,int fontSize){
   ResetLastError();
   if (!ObjectCreate(0,objName,OBJ_LABEL,0,0,0)){
      Print(__FUNCTION__,": failed to create the Label! Error Code = ",GetLastError());
      return (false);
   }
   ObjectSetInteger(0,objName,OBJPROP_XDISTANCE,x);
   ObjectSetInteger(0,objName,OBJPROP_YDISTANCE,y);
   ObjectSetInteger(0,objName,OBJPROP_CORNER,CORNER_LEFT_UPPER);
   ObjectSetString(0,objName,OBJPROP_TEXT,text);
   ObjectSetInteger(0,objName,OBJPROP_COLOR,clrTxt);
   ObjectSetInteger(0,objName,OBJPROP_FONTSIZE,fontSize);
   
   ChartRedraw(0);
   return (true);
}

3. Проверка дневной прибыли

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

  • Определяет переменную total_day_profit и инициализирует ее значением 0.
  • Получает текущее время и преобразует его в строку с использованием функции TimeToString, хранящейся в переменной date.
  • Вычисляет начальный час дня, добавляя 1 к началу дня и сохраняя в переменной.
  • Проверяет, меньше ли суточное время текущего времени. Если да, устанавливает dayTime и рассчитывает текущий баланс с помощью функции Acc_B, которая хранится в переменной dayBalance.
  • Выделяет исторические данные за день с помощью функции HistorySelect, устанавливая время начала и окончания на начало и конец дня.
  • Рассчитывает общее количество сделок за день с помощью функции HistoryDealsTotal и сохраняет в переменной TotalDeals.
  • Просматривает свою транзакцию на истории и проверяет, является ли запись транзакции записью типа DEAL_ENTRY_OUT, означающей, что это сделка при закрытии. Если да, она рассчитывает торговую прибыль сложением значений DEAL_PROFIT, DEAL_COMMISSION и DEAL_SWAP и прибавляет их к переменной total_day_profit.
  • Вычисляет начальный баланс дня путем вычитания значения total_day_Profit из текущего баланса счета с помощью функции AccountInfoDouble с параметром ACCOUNT-BALANCE.

Функция возвращает рассчитанный начальный баланс в виде значения Double.

void checkDailyProfit(){

   double total_day_Profit = 0;
   datetime end = TimeCurrent();
   string sdate = TimeToString(TimeCurrent(),TIME_DATE);
   datetime start = StringToTime(sdate);
   datetime to = start + (1*24*60*60);
   
   if (dayTime < to){
      dayTime = to;
      dayBalance = Acc_B();
   }
   
   HistorySelect(start,end);
   int TotalDeals = HistoryDealsTotal();
   for (int i=0; i<TotalDeals; i++){
      ulong Ticket = HistoryDealGetTicket(i);
      if (HistoryDealGetInteger(Ticket,DEAL_ENTRY)==DEAL_ENTRY_OUT){
         double Latest_Day_Profit = (HistoryDealGetDouble(Ticket,DEAL_PROFIT)
                               +HistoryDealGetDouble(Ticket,DEAL_COMMISSION)
                               +HistoryDealGetDouble(Ticket,DEAL_SWAP));
         total_day_Profit += Latest_Day_Profit;
      }
   }
   double startingBalance = 0;
   startingBalance = AccountInfoDouble(ACCOUNT_BALANCE) - total_day_Profit;
   double daily_profit_or_drawdown = NormalizeDouble((total_day_Profit*100/startingBalance),2);
   string daily_profit_in_Text_Format = "";
   daily_profit_in_Text_Format = DoubleToString(daily_profit_or_drawdown,2)+" %";
   
   //Print(total_day_Profit, " >>> ",daily_profit_in_Text_Format);
   
   createText("4",TimeToString(start),120,80,clrYellow,10);
   createText("6",TimeToString(to),120,95,clrYellow,10);
   createText("8",TimeToString(end),120,110,clrWhite,10);

   if (Acc_E() > initialBalance){
      createText("15",DoubleToString(Acc_E(),2)+" "+Acc_S(),250,175,clrLime,10);
      createText("17",DoubleToString((Acc_E()-initialBalance)/initialBalance*100,2)+" %",250,190,clrLime,10);
   }
   else if (Acc_E() < initialBalance){
      createText("15",DoubleToString(Acc_E(),2)+" "+Acc_S(),250,175,clrRed,10);
      createText("17",DoubleToString((Acc_E()-initialBalance)/initialBalance*100,2)+" %",250,190,clrRed,10);
   }
   if (Acc_E() == initialBalance){
      createText("15",DoubleToString(Acc_E(),2)+" "+Acc_S(),250,175,clrWhite,10);
      createText("17",DoubleToString((Acc_E()-initialBalance)/initialBalance*100,2)+" %",250,190,clrWhite,10);
   }
   
   createText("20",DoubleToString(dayBalance,2)+" "+Acc_S(),270,225,clrWhite,10);
   createText("24","-"+DoubleToString(dayBalance*5/100,2)+" "+Acc_S(),270,255,clrYellow,10);

   if (Acc_B() > dayBalance){
      createText("26",DoubleToString(total_day_Profit,2)+" "+Acc_S(),270,270,clrLime,10);
      createText("28",daily_profit_in_Text_Format,270,285,clrLime,10);
   }
   else if (Acc_B() < dayBalance){
      createText("26",DoubleToString(total_day_Profit,2)+" "+Acc_S(),270,270,clrRed,10);
      createText("28",daily_profit_in_Text_Format,270,285,clrRed,10);
   }
   else if (Acc_B() == dayBalance){
      createText("26",DoubleToString(total_day_Profit,2)+" "+Acc_S(),270,270,clrWhite,10);
      createText("28",daily_profit_in_Text_Format,270,285,clrWhite,10);
   }
   
   if (daily_profit_or_drawdown <= -5.00 || ((Acc_E()-initialBalance)/initialBalance*100) < -12.00){
      createText("29",">>> Maximum Threshold Hit, Can't Trade.",70,300,clrRed,10);
      isTradeAllowed = false;
   }
   else {
      createText("29",">>> Maximum Threshold Not Hit, Can Trade.",70,300,clrL…

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

2;
   if (number == 0){
      obj_Trade.Buy(0.1,_Symbol,ask,ask-70*_Point,ask+70*_Point);
   }
   else if (number == 1){
      obj_Trade.Sell(0.1,_Symbol,bid,bid+70*_Point,bid-70*_Point);
   }
}
//+------------------------------------------------------------------+
bool createText(string objName,string text,int x, int y,color clrTxt,int fontSize){
   ResetLastError();
   if (!ObjectCreate(0,objName,OBJ-LABEL,0,0,0)){
      Print(__FUNCTION__,": failed to create the Label! Error Code = ",GetLastError());
      return (false);
   }
   ObjectSetInteger(0,objName,OBJPROP-XDISTANCE,x);
   ObjectSetInteger(0,objName,OBJPROP-YDISTANCE,y);
   ObjectSetInteger(0,objName,OBJPROP-CORNER,CORNER_LEFT_UPPER);
   ObjectSetString(0,objName,OBJPROP-TEXT,text);
   ObjectSetInteger(0,objName,OBJPROP-COLOR,clrTxt);
   ObjectSetInteger(0,objName,OBJPROP-FONTSIZE,fontSize);
   
   ChartRedraw(0);
   return (true);
}

void checkDailyProfit(){

   double total_day_Profit = 0;
   datetime end = TimeCurrent();
   string sdate = TimeToString(TimeCurrent(),TIME_DATE);
   datetime start = StringToTime(sdate);
   datetime to = start + (1*24*60*60);
   
   if (dayTime < to){
      dayTime = to;
      dayBalance = Acc_B();
   }
   
   HistorySelect(start,end);
   int TotalDeals = HistoryDealsTotal();
   for (int i=0; i<TotalDeals; i++){
      ulong Ticket = HistoryDealGetTicket(i);
      if (HistoryDealGetInteger(Ticket,DEAL_ENTRY)==DEAL_ENTRY_OUT){
         double Latest_Day_Profit = (HistoryDealGetDouble(Ticket,DEAL_PROFIT)
                               +HistoryDealGetDouble(Ticket,DEAL_COMMISSION)
                               +HistoryDealGetDouble(Ticket,DEAL_SWAP));
         total_day_Profit += Latest_Day_Profit;
      }
   }
   double startingBalance = 0;
   startingBalance = AccountInfoDouble(ACCOUNT-BALANCE) - total_day_Profit;
   double daily_profit_or_drawdown = NormalizeDouble((total_day_Profit*100/startingBalance),2);
   string daily_profit_in_Text_Format = "";
   daily_profit_in_Text_Format = DoubleToString(daily_profit_or_drawdown,2)+" %";
   
   //Print(total_day_Profit, " >>> ",daily_profit_in_Text_Format);
   
   createText("4",TimeToString(start),120,80,clrYellow,10);
   createText("6",TimeToString(to),120,95,clrYellow,10);
   createText("8",TimeToString(end),120,110,clrWhite,10);

   if (Acc_E() > initialBalance){
      createText("15",DoubleToString(Acc_E(),2)+" "+Acc_S(),250,175,clrLime,10);
      createText("17",DoubleToString((Acc_E()-initialBalance)/initialBalance*100,2)+" %",250,190,clrLime,10);
   }
   else if (Acc_E() < initialBalance){
      createText("15",DoubleToString(Acc_E(),2)+" "+Acc_S(),250,175,clrRed,10);
      createText("17",DoubleToString((Acc_E()-initialBalance)/initialBalance*100,2)+" %",250,190,clrRed,10);
   }
   if (Acc_E() == initialBalance){
      createText("15",DoubleToString(Acc_E(),2)+" "+Acc_S(),250,175,clrWhite,10);
      createText("17",DoubleToString((Acc_E()-initialBalance)/initialBalance*100,2)+" %",250,190,clrWhite,10);
   }
   
   createText("20",DoubleToString(dayBalance,2)+" "+Acc_S(),270,225,clrWhite,10);
   createText("24","-"+DoubleToString(dayBalance*5/100,2)+" "+Acc_S(),270,255,clrYellow,10);

   if (Acc_B() > dayBalance){
      createText("26",DoubleToString(total_day_Profit,2)+" "+Acc_S(),270,270,clrLime,10);
      createText("28",daily_profit_in_Text_Format,270,285,clrLime,10);
   }
   else if (Acc_B() < dayBalance){
      createText("26",DoubleToString(total_day_Profit,2)+" "+Acc_S(),270,270,clrRed,10);
      createText("28",daily_profit_in_Text_Format,270,285,clrRed,10);
   }
   else if (Acc_B() == dayBalance){
      createText("26",DoubleToString(total_day_Profit,2)+" "+Acc_S(),270,270,clrWhite,10);
      createText("28",daily_profit_in_Text_Format,270,285,clrWhite,10);
   }
   
   if (daily_profit_or_drawdown <= -5.00 || ((Acc_E()-initialBalance)/initialBalance*100) < -12.00){
      createText("29",">>> Maximum Threshold Hit, Can't Trade.",70,300,clrRed,10);
      isTradeAllowed = false;
   }
   else {
      createText("29",">>> Maximum Threshold Not Hit, Can Trade.",70,300,clrRed);

Вот что мы получим.

Пример высокопороговой логики.

FAR THRESHHOLD

Пример околопороговой логики.

NEAR THRESHOLD

Пример логики с удержанием порогового значения:

THRESHOLD HIT

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

//+------------------------------------------------------------------+
//|                                       Daily Drawdown Limiter.mq5 |
//|                                  Copyright 2024, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2024, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#property version   "1.00"

#include <Trade/Trade.mqh>
CTrade obj_Trade;

int totalBars = 0;
double initialBalance = 0;
double dayBalance = 0;
datetime dayTime = 0;
bool isTradeAllowed = true;

// Functions to get account balance, equity, and currency
double Acc_B() {return AccountInfoDouble(ACCOUNT_BALANCE);}
double Acc_E() {return AccountInfoDouble(ACCOUNT_EQUITY);}
string Acc_S() {return AccountInfoString(ACCOUNT_CURRENCY);}

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
   // Initialize initial balance
   initialBalance = Acc_B();
   
   // Create dashboard texts
   createText("0","*** Daily Drawdown Limiter ***",30,30,clrBlack,13);
   createText("00","______________________________________",30,30,clrBlack,13);
   createText("1","DrawDown Limiter is Active.",70,50,clrBlack,11);
   createText("2","Counters will be reset on Next Day Start.",70,65,clrBlack,10);
   createText("3","From: ",70,80,clrBlack,10);
   createText("4","Time Here",120,80,clrGray,10);
   createText("5","To: ",70,95,clrBlack,10);
   createText("6","Time Here",120,95,clrGray,10);
   createText("7","Current: ",70,110,clrBlack,10);
   createText("8","Time Here",120,110,clrGray,10);

   createText("9","ACCOUNT DRAWDOWN ============",70,130,clrPeru,11);
   createText("10","Account Initial Balance: ",70,145,clrBlack,10);
   createText("11",DoubleToString(initialBalance,2)+" "+Acc_S(),250,145,clrBlack,10);
   createText("12","Tolerated DrawDown: ",70,160,clrBlack,10);
   createText("13","12.00 %",250,160,clrBlack,10);
   createText("14","Current Account Equity: ",70,175,clrBlack,10);
   createText("15",DoubleToString(Acc_E(),2)+" "+Acc_S(),250,175,clrBlack,10);
   createText("16","Current Balance Variation: ",70,190,clrBlack,10);
   createText("17",DoubleToString((Acc_E()-Acc_B())/Acc_B()*100,2)+" %",250,190,clrGray,10);

   createText("18","DAILY DRAWDOWN ================",70,210,clrPeru,11);
   createText("19","Starting Balance: ",70,225,clrBlack,10);
   createText("20",DoubleToString(Acc_B(),2)+" "+Acc_S(),270,225,clrBlack,10);
   createText("21","DrawDown Maximum Threshold: ",70,240,clrBlack,10);
   createText("22","5.00 %",270,240,clrBlack,10);
   createText("23","DrawDown Maximum Amount: ",70,255,clrBlack,10);
   createText("24","-"+DoubleToString((Acc_B()*5/100),2)+" "+Acc_S(),270,255,clrBlue,10);
   createText("25","Current Closed Daily Profit: ",70,270,clrBlack,10);
   createText("26","0.00"+" "+Acc_S(),270,270,clrGray,10);
   createText("27","Current DrawDown Percent: ",70,285,clrBlack,10);
   createText("28","0.00 %",270,285,clrGray,10);
   createText("29",">>> Initializing The Program, Get Ready To Trade.",70,300,clrBlue,10);

   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   // Deinitialization code here (if needed)
  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
   // Check daily profit and drawdown
   checkDailyProfit();
   
   // If trading is not allowed, exit function
   if (!isTradeAllowed) return;
   
   // Get current ask and bid prices
   double ask = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK),_Digits);
   double bid = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID),_Digits);
   
   // Check for new bar
   int bars = iBars(_Symbol,_Period);
   if (totalBars == bars) return;
   totalBars = bars;
   
   // If more than one position, exit function
   if (PositionsTotal() > 1) return;
   
   // Random trade decision
   int number = MathRand()%2;
   Print(number);
   
   if (number == 0){
      obj_Trade.Buy(1,_Symbol,ask,ask-70*_Point,ask+70*_Point);
   }
   else if (number == 1){
      obj_Trade.Sell(1,_Symbol,bid,bid+70*_Point,bid-70*_Point);
   }
  }
//+------------------------------------------------------------------+
//| Check daily profit and drawdown                                  |
//+------------------------------------------------------------------+
void checkDailyProfit() {
   
   double total_day_Profit = 0.0;
   datetime end = TimeCurrent();
   string sdate = TimeToString (TimeCurrent(), TIME_DATE);
   datetime start = StringToTime(sdate);
   datetime to = start + (1*24*60*60);
   
   // Reset daily balance and time at start of new day
   if (dayTime < to){
      dayTime = to;
      dayBalance = Acc_B();
   }

   // Calculate total daily profit
   HistorySelect(start,end);
   int TotalDeals = HistoryDealsTotal();

   for(int i = 0; i < TotalDeals; i++){
      ulong Ticket = HistoryDealGetTicket(i);
      if(HistoryDealGetInteger(Ticket,DEAL_ENTRY) == DEAL_ENTRY_OUT){
         double Latest_Day_Profit = (HistoryDealGetDouble(Ticket,DEAL_PROFIT)
                                    + HistoryDealGetDouble(Ticket,DEAL_COMMISSION)
                                    + HistoryDealGetDouble(Ticket,DEAL_SWAP));
         total_day_Profit += Latest_Day_Profit;
      }
   }
   
   double startingBalance = 0.0;
   startingBalance = AccountInfoDouble(ACCOUNT_BALANCE) - total_day_Profit;
   string day_profit_in_TextFormat = "";
   double daily_Profit_or_Drawdown = NormalizeDouble(((total_day_Profit) * 100/startingBalance),2);
   day_profit_in_TextFormat = DoubleToString(daily_Profit_or_Drawdown,2) + " %";
      
   // Update dashboard texts with new data
   createText("4",TimeToString(start),120,80,clrBlue,10);
   createText("6",TimeToString(to),120,95,clrBlue,10);
   createText("8",TimeToString(end),120,110,clrBlack,10);

   createText("11",DoubleToString(initialBalance,2)+" "+Acc_S(),250,145,clrBlack,10);
   if (Acc_E() > initialBalance){
      createText("15",DoubleToString(Acc_E(),2)+" "+Acc_S(),250,175,clrMediumBlue,10);
      createText("17",DoubleToString(((Acc_E()-initialBalance)/initialBalance)*100,2)+" %",250,190,clrMediumBlue,10);
   }
   else if (Acc_E() < initialBalance){
      createText("15",DoubleToString(Acc_E(),2)+" "+Acc_S(),250,175,clrRed,10);
      createText("17",DoubleToString(((Acc_E()-initialBalance)/initialBalance)*100,2)+" %",250,190,clrRed,10);
   }
   else if (Acc_E() == initialBalance){
      createText("15",DoubleToString(Acc_E(),2)+" "+Acc_S(),250,175,clrBlack,10);
      createText("17",DoubleToString(((Acc_E()-initialBalance)/initialBalance)*100,2)+" %",250,190,clrBlack,10);
   }

   createText("20",DoubleToString(dayBalance,2)+" "+Acc_S(),270,225,clrBlack,10);
   createText("24","-"+DoubleToString((dayBalance*5/100),2)+" "+Acc_S(),270,255,clrBlue,10);
   if (Acc_B() > dayBalance){
      createText("26",DoubleToString(total_day_Profit,2)+" "+Acc_S(),270,270,clrMediumBlue,10);
      createText("28",day_profit_in_TextFormat,270,285,clrMediumBlue,10);
   }
   else if (Acc_B() < dayBalance){
      createText("26",DoubleToString(total_day_Profit,2)+" "+Acc_S(),270,270,clrRed,10);
      createText("28",day_profit_in_TextFormat,270,285,clrRed,10);
   }
   else if (Acc_B() == dayBalance){
      createText("26",DoubleToString(total_day_Profit,2)+" "+Acc_S(),270,270,clrBlack,10);
      createText("28",day_profit_in_TextFormat,270,285,clrBlack,10);
   }
   
   // Check if drawdown limits are hit and update trading permission
   if (daily_Profit_or_Drawdown <= -5.00 ||((Acc_E()-initialBalance)/initialBalance)*100 < -12.00){
      createText("29",">>> Max ThreshHold Hit, Can't Trade.",70,300,clrRed,10);
      isTradeAllowed = false;
   }
   else {
      createText("29",">>> Max ThresHold Not Hit, Can Trade.",70,300,clrMediumBlue,10);
      isTradeAllowed = true;
   }
}

//+------------------------------------------------------------------+
//| Create text label on the chart                                   |
//+------------------------------------------------------------------+
bool createText(string objName, string text, int x, int y, color clrTxt,int fontSize) {
 ResetLastError();
     if (!ObjectCreate(0,objName,OBJ_LABEL,0,0,0)){
        Print(__FUNCTION__,": failed to create the Label! Error code = ", GetLastError());
        return(false);
     }

   ObjectSetInteger(0,objName,OBJPROP_XDISTANCE, x);
   ObjectSetInteger(0,objName,OBJPROP_YDISTANCE, y);
   ObjectSetInteger(0,objName,OBJPROP_CORNER, CORNER_LEFT_UPPER);
   ObjectSetString(0,objName,OBJPROP_TEXT, text);
   ObjectSetInteger(0,objName,OBJPROP_FONTSIZE, fontSize);
   //ObjectSetString(0,objName,OBJPROP_FONT, "Calibri");
   ObjectSetInteger(0,objName,OBJPROP_COLOR, clrTxt);
   
   ChartRedraw(0);
   
   return(true); 
}

Мы — молодцы! Нам удалось создать дневной лимит просадки для торгующего на рынке Форекс советника на основе установления дневного лимита вывода средств торговых счетов.


Заключение

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

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

Прикрепленные файлы |
Последние комментарии | Перейти к обсуждению на форуме трейдеров (2)
atesz5870
atesz5870 | 10 июл. 2024 в 13:29
здорово!
clueboard
clueboard | 11 июл. 2024 в 09:40

Похоже, что вы просто использовали ForexAlgo-Trader(YouTube), не отдавая ему никаких почестей и не выдавая его работу за свою. Из его видео здесь, вы можете сказать, что это его код строка за строкой, переменная за переменной.

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

От начального до среднего уровня: Переменные (II) От начального до среднего уровня: Переменные (II)
Сегодня мы рассмотрим, как работать со статическими переменными. Этот вопрос часто ставит в тупик многих программистов, как начинающих, так и имеющих некоторый опыт, и это связано с тем, что существует несколько советов и рекомендаций, которые необходимо соблюдать при использовании данного механизма. Представленные здесь материалы предназначены исключительно для дидактических целей. Ни в коем случае нельзя рассматривать это как приложение, чьей целью будет что-то иное помимо изучения и освоения представленных концепций.
Торговый инструментарий MQL5 (Часть 2): Расширение и применение EX5-библиотеки для управления позициями Торговый инструментарий MQL5 (Часть 2): Расширение и применение EX5-библиотеки для управления позициями
Узнайте, как импортировать и использовать EX5-библиотеки в вашем коде или проектах MQL5. В этой статье мы расширим ранее созданную EX5-библиотеку, добавив больше функций управления позициями и создав два советника. В первом примере будет использоваться технический индикатор Variable Index Dynamic Average для разработки советника по стратегии трейлинг-стопа, а во втором - торговая панель для мониторинга, открытия, закрытия и изменения позиций. Эти два примера продемонстрируют, как использовать обновленную EX5-библиотеку для управления позициями.
Пользовательский индикатор: Отображение сделок входа, выхода и разворота позиции на неттинговых счетах Пользовательский индикатор: Отображение сделок входа, выхода и разворота позиции на неттинговых счетах
В данной статье мы рассмотрим нестандартный способ создания индикатора в MQL5. Вместо того, чтобы фокусироваться на тренде или графическом паттерне, нашей целью будет управление собственными позициями, включая частичные входы и выходы. Мы будем активно использовать динамические матрицы и некоторые торговые функции, связанные с историей сделок и открытыми позициями, чтобы указать на графике, где осуществились данные сделки.
Квантовые вычисления и трейдинг: Новый взгляд на прогнозы цен Квантовые вычисления и трейдинг: Новый взгляд на прогнозы цен
В статье рассматривается инновационный подход к прогнозированию движения цен на финансовых рынках с использованием квантовых вычислений. Основное внимание уделяется применению алгоритма квантовой оценки фазы (QPE) для поиска продобразов ценовых паттернов, что позволяет значительно ускорить процесс анализа рыночных данных.