Система голосовых уведомлений торговых событий и сигналов

3 июля 2020, 11:56
Alexander Fedosov
0
2 743

Содержание

Введение

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


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

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

Система реализована в виде класса CSoundsLib во включаемом файле. Поэтому создадим в папке MQL5/Include папку SoundsLib и в ней файл SoundsLib.mqh. Перед созданием класса введем два перечисления, которые нужны в дальнейшем при работе с голосовыми уведомлениями. Первое их них LANGUAGE — для выбора языка уведомлений. В системе они будут на двух языках  — на русском и английском.

//+------------------------------------------------------------------+
//| Перечисление для переключения языка голосовых уведомлений        |
//+------------------------------------------------------------------+
enum LANGUAGE
{
   RUSSIAN,       // Russian
   ENGLISH        // English
};

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

//+------------------------------------------------------------------+
//| Список голосовых уведомлений                                     |
//+------------------------------------------------------------------+
enum MESSAGE
{
   STATUS_ON,                          // Статус включенных голосовых уведомлений
   SIGNAL_BUY,                         // Сигнал на покупку
   SIGNAL_SELL,                        // Сигнал на продажу
   BUY_ORDER_SET,                      // Ордер на покупку установлен
   SELL_ORDER_SET,                     // Ордер на продажу установлен
   BUYLIMIT_ORDER_SET,                 // Лимитный ордер на покупку установлен
   BUYSTOP_ORDER_SET,                  // Стоп ордер на покупку установлен
   SELLLIMIT_ORDER_SET,                // Лимитный ордер на продажу установлен
   SELLSTOP_ORDER_SET,                 // Стоп ордер на продажу установлен
   BUYLIMIT_ORDER_DELETE,              // Лимитный ордер на покупку удален
   BUYSTOP_ORDER_DELETE,               // Стоп ордер на покупку удален
   SELLLIMIT_ORDER_DELETE,             // Лимитный ордер на продажу удален
   SELLSTOP_ORDER_DELETE,              // Стоп ордер на продажу удален
   BUY_ORDER_CLOSE_PROFIT,             // Ордер на покупку закрыт с прибылью
   BUY_ORDER_CLOSE_LOSS,               // Ордер на покупку закрыт с убытком
   SELL_ORDER_CLOSE_PROFIT,            // Ордер на продажу закрыт с прибылью
   SELL_ORDER_CLOSE_LOSS,              // Ордер на продажу закрыт с убытком
   BUY_ORDER_CLOSE_TP,                 // Ордер на покупку закрыт по тейк-профиту
   BUY_ORDER_CLOSE_SL,                 // Ордер на покупку закрыт по стоп-лосс
   SELL_ORDER_CLOSE_TP,                // Ордер на продажу закрыт по тейк-профиту
   SELL_ORDER_CLOSE_SL,                // Ордер на продажу закрыт по стоп-лосс
   MARKET_CLOSE,                       // Рынок закрыт
   AUTO_TRADING_ON,                    // Автоматическая торговля разрешена
   AUTO_TRADING_OFF,                   // Автоматическая торговля запрещена
};

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

Теперь создадим класс CSoundsLib, пропишем в нем методы, необходимые для работы.

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
class CSoundsLib
{
private:
   LANGUAGE          m_language;
   bool              m_activity_status;
public:
                     CSoundsLib(void); 
                    ~CSoundsLib(void);
   //--- Установка языка уведомлений
   void              Language(LANGUAGE lang);
   //--- Установка/получения статуса активности голосовых уведомлений
   void              IsActive(bool flag);
   bool              IsActive(void);
   //--- Проигрывание заданного уведомления
   bool              Message(MESSAGE msg);
};

В приватной секции две переменные m_language и m_activity_status, которые нужны для созданных ниже методов Language() и IsActive(). То есть для установки языка голосовых уведомлений и получения/установки активности самой системы. Реализация их представлена в листинге ниже:

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void CSoundsLib::Language(LANGUAGE lang)
{
   m_language=lang;
}
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CSoundsLib::IsActive(void)
{
   return(m_activity_status);
}

Еще один метод — это Message(), который проигрывает само уведомление, установленное из перечисления MESSAGE.  Реализация этого метода также проста для понимания:

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool  CSoundsLib::Message(MESSAGE msg)
{
   if(!m_activity_status)
      return(false);
   string name=(m_language==RUSSIAN ? EnumToString(msg)+"_RU" : EnumToString(msg)+"_EN");
   if(PlaySound("\\Files\\SoundsLib\\"+name+".wav"))
      return(true);
   else
   {
      if(TerminalInfoString(TERMINAL_LANGUAGE)=="Russian")
         Print("Файл не найден");
      else
         Print("File not found");
      return(false);
   }
}

Хотелось бы обратить внимание на несколько важных моментов, потому как понимание их будет напрямую влиять на правильность расширения данной системы собственными голосовыми уведомлениями. Первый момент, это верное место установки звуковых файлов, а именно — по умолчанию они находятся в папке MQL5/Files/SoundsLib. При это папку SoundsLib необходимо создать. Второй момент, это названия и формат звуковых файлов, которые будут помещаться в созданную папку. Обратим внимание на эту строки кода — в них к перечислению типа MESSAGE в конце добавляется суффикс _RU или _EN. Поэтому имя файла, соответствующее, например, голосовому уведомлению о сигнале на покупку SIGNAL_BUY, будет иметь связь с двумя звуковыми файлами: SIGNAL_BUY _RU и SIGNAL_BUY_EN для русской и английской озвучки. Помимо этого следует помнить, что системная функция PlaySound() может проигрывать файл только в формате *.WAV, и поэтому полные имена файлов в нашем примере с расширениями в папке SoundsLib  будут выглядеть так:

Рис.1 Полное имя и расширение звукового файла.

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

Рис.2 Сервис перевода текста в звуковой файл.

В данном инструменте предостаточно функционала для реализации требуемой задачи. Можно выбрать как язык голосового движка, так и типы с необходимым нам форматом. Однако, для английского голосового движка нет формата WAV, поэтому либо через любой онлайн конвертер, либо через любой другой софт для работы со звуковыми файлами можно легко перевести формат mp3 в wav. С помощью данного сервиса получим все необходимые файлы для нашей системы и положим их в папку по адресу MQL5\Files\SoundsLib с правильным форматом и названиями согласно перечислению MESSAGE и суффиксами языка. Вот какой список у меня получился:

Рис.3 Полный список звуковых файлов для голосовых уведомлений.

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

Шаг 1. Добавление в систему вашего голосового события.

Для этого перейдем в файл SoundsLib.mqh и найдем перечисление MESSAGE. В него следует добавить ваше событие с понятным названием. Пример названий приведены на рис.3 и выше в коде.

Шаг 2. Создание звукового файла будущего голосового уведомления.

Переходим в сервис(или же любой какой вам нравится) и там под себя настраиваем любые параметры (указаны на рис.2) и сохраняем файл в формате WAV в папку MQL5\Files\SoundsLib с названием "Имя вашего события в перечислении MESSAGE"+_RU(_EN) в зависимости от принадлежности к русскому или английскому языку. Если вы всё сделали верно, то новый звуковой файл будет связан с новым событием, которое вы добавили на шаге 1 и он готов к использованию.


Практическое применение в индикаторах

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

Параметр Описание
Используемый индикатор ADXCloud
Используемый индикатор ColorZerolagRVI
Таймфрейм Любой
Условия на покупку Облако ADXCloud зеленого цвета, облако ColorZerolagRVI переходит из красной в зеленцю зону.
Условия на продажу Облако ADXCloud красного цвета, облако ColorZerolagRVI переходит из зеленой в красную зону.

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

Рис.4 Условия входа по сигналам данных индикаторов.

//+------------------------------------------------------------------+
//|                                                      Example.mq5 |
//|                                                         Alex2356 |
//|                           https://www.mql5.com/ru/users/alex2356 |
//+------------------------------------------------------------------+
#property copyright "Alex2356"
#property link      "https://www.mql5.com/ru/users/alex2356"
#property version   "1.00"
#property indicator_chart_window
//--- для расчета и отрисовки индикатора использовано два буфера
#property indicator_buffers 2
//--- использовано графических построения
#property indicator_plots   2
#property indicator_label1  "Buy Signal"
#property indicator_type1   DRAW_ARROW
//---
#property indicator_label2  "Sell Signal"
#property indicator_type2   DRAW_ARROW
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
input group "ADX Cloud Parameters"
input int                  ADXPeriod         =  8;
input double               Alpha1            =  0.25;
input double               Alpha2            =  0.25;
input group "RVI Color Parameters"
input uint                 Smoothing         =  15;
//----
input double               Weight1           =  0.05;
input int                  RVI_period1       =  8;
//----
input double               Weight2           = 0.10;
input int                  RVI_period2       =   21;
//----
input double               Weight3           = 0.16;
input int                  RVI_period3       =   34;
//----
input double               Weight4           = 0.26;
input int                  RVI_period4       =   55;
//----
input double               Weight5           = 0.43;
input int                  RVI_period5       =   89;
//---
double BuySignal[],SellSignal[],ADXCloud[],FastRVI[],SlowRVI[];
int ADX_Handle,RVI_Hadnle,min_rates_total;
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
{
//---
   SetIndexBuffer(0,BuySignal,INDICATOR_DATA);
   SetIndexBuffer(1,SellSignal,INDICATOR_DATA);
//---
   PlotIndexSetInteger(0,PLOT_ARROW,233);
   PlotIndexSetInteger(1,PLOT_ARROW,234);
//---
   PlotIndexSetInteger(0,PLOT_LINE_COLOR,clrDodgerBlue);
   PlotIndexSetInteger(1,PLOT_LINE_COLOR,clrCrimson);
//---
   ArraySetAsSeries(SellSignal,true);
   ArraySetAsSeries(BuySignal,true);
   PlotIndexSetDouble(0,PLOT_EMPTY_VALUE,EMPTY_VALUE);
   PlotIndexSetDouble(1,PLOT_EMPTY_VALUE,EMPTY_VALUE);
   PlotIndexSetInteger(0,PLOT_ARROW_SHIFT,20);
   PlotIndexSetInteger(1,PLOT_ARROW_SHIFT,-20);
//---
   ADX_Handle=iCustom(Symbol(),PERIOD_CURRENT,"adxcloud",ADXPeriod,Alpha1,Alpha2);
   if(ADX_Handle==INVALID_HANDLE)
   {
      Print(" Не удалось получить хендл индикатора");
      return(INIT_FAILED);
   }
//---
   RVI_Hadnle=iCustom(Symbol(),PERIOD_CURRENT,"colorzerolagrvi",
                      Smoothing,
                      Weight1,RVI_period1,
                      Weight2,RVI_period2,
                      Weight3,RVI_period3,
                      Weight4,RVI_period4,
                      Weight5,RVI_period5
                     );
   if(RVI_Hadnle==INVALID_HANDLE)
   {
      Print(" Не удалось получить хендл индикатора");
      return(INIT_FAILED);
   }
//---
   return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const datetime &time[],
                const double &open[],
                const double &high[],
                const double &low[],
                const double &close[],
                const long &tick_volume[],
                const long &volume[],
                const int &spread[])
{
//--- c
   if(BarsCalculated(ADX_Handle)<rates_total || BarsCalculated(RVI_Hadnle)<rates_total || rates_total<min_rates_total)
      return(0);
//--- 
   int limit,to_copy,i;
//--- 
   ArraySetAsSeries(ADXCloud,true);
   ArraySetAsSeries(FastRVI,true);
   ArraySetAsSeries(SlowRVI,true);
   ArraySetAsSeries(high,true);
   ArraySetAsSeries(low,true);
//--- 
   if(prev_calculated>rates_total || prev_calculated<=0) 
      limit=rates_total-2;
   else
      limit=rates_total-prev_calculated; 
   to_copy=limit+2;
//---
   if(CopyBuffer(ADX_Handle,0,0,to_copy,ADXCloud)<=0)
      return(0);
//---
   if(CopyBuffer(RVI_Hadnle,0,0,to_copy,FastRVI)<=0)
      return(0);
   if(CopyBuffer(RVI_Hadnle,1,0,to_copy,SlowRVI)<=0)
      return(0);
//--- 
   for(i=limit-1; i>=0 && !IsStopped(); i--)
   {
      if(ADXCloud[i+1]>0 && FastRVI[i+1]>SlowRVI[i+1] && FastRVI[i+2]<SlowRVI[i+2])
      {
         BuySignal[i]=low[i];
         SellSignal[i]=EMPTY_VALUE;
      }
      else if(ADXCloud[i+1]<0 && FastRVI[i+1]<SlowRVI[i+1] && FastRVI[i+2]>SlowRVI[i+2])
      {
         SellSignal[i]=high[i];
         BuySignal[i]=EMPTY_VALUE;
      }
      else
      {
         BuySignal[i]=EMPTY_VALUE;
         SellSignal[i]=EMPTY_VALUE;
      }
   }
//--- return value of prev_calculated for next call
   return(rates_total);
}
//+------------------------------------------------------------------+

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

Рис.5 Стрелочный индикатор на основе двух индикаторов.

Для начала подключим к индикатору файл SoundsLib.mqh:

#include <SoundsLib/SoundsLib.mqh>

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

CSoundsLib Notify;

В функции инициализации OnInit() установим язык голосовых уведомлений, я поставлю английский. Хотя, если вы используете английский язык уведомлений, то язык можно не устанавливать, так как он задан по умолчанию, но для полного понимая установим.

Notify.Language(ENGLISH);

Так как стрелочный индикатор показывает лишь точки входа в рынок или сигналы на покупку/продажу, то из перечисления MESSAGE мы будет использовать два голосовых уведомления это: 

   SIGNAL_BUY,                         // Сигнал на покупку
   SIGNAL_SELL,                        // Сигнал на продажу

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

//--- 
   for(i=limit-1; i>=0 && !IsStopped(); i--)
   {
      if(ADXCloud[i+1]>0 && FastRVI[i+1]>SlowRVI[i+1] && FastRVI[i+2]<SlowRVI[i+2])
      {
         BuySignal[i]=low[i];
         SellSignal[i]=EMPTY_VALUE;
         if(i==0)
            Notify.Message(SIGNAL_BUY);
      }
      else if(ADXCloud[i+1]<0 && FastRVI[i+1]<SlowRVI[i+1] && FastRVI[i+2]>SlowRVI[i+2])
      {
         SellSignal[i]=high[i];
         BuySignal[i]=EMPTY_VALUE;
         if(i==0)
            Notify.Message(SIGNAL_SELL);
      }
      else
      {
         BuySignal[i]=EMPTY_VALUE;
         SellSignal[i]=EMPTY_VALUE;
      }
   }

Здесь мы проверяем, есть ли сигнал на нулевом баре, и при его наличии уведомляем об этом пользователя терминала


Практическое применение в торговом эксперте

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

Параметр Описание
Используемый индикатор ColorStDev
Используемый индикатор Три уровня Тироне
Таймфрейм Любой
Условия на покупку Гистограмма ColorStdDev красного цвета (сильный тренд), при этом текущая цена должна быть выше верхнего уровня Тироне.
Условия на продажу Гистограмма ColorStdDev красного цвета (сильный тренд), при этом текущая цена должна быть ниже нижнего уровня Тироне.
Условия выхода   Тейк-профит/Стоп-лосс

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

Рис.6 Примеры входа в рынок по данной стратегии.

Теперь реализуем эту стратегию для MetaTrader 5, при этом будем использовать голосовые уведомления при некоторых событиях. 

//+------------------------------------------------------------------+
//|                                                  VoiceNotify.mq5 |
//|                                                         Alex2356 |
//|                           https://www.mql5.com/ru/users/alex2356 |
//+------------------------------------------------------------------+
#property copyright "Alex2356"
#property link      "https://www.mql5.com/ru/users/alex2356"
#property version   "1.00"
#include <SoundsLib/SoundsLib.mqh>
#include <DoEasy25/Engine.mqh>
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
input uint                 InpStopLoss          =  150;              // Stop Loss, in pips
input uint                 InpTakeProfit        =  250;              // Take Profit, in pips
input double               InpLot               =  0.1;              // Take Profit, in pips
input ulong                InpDeviation         =  10;               // Deviation
input int                  InpMagic             =  2356;             // Magic number
input LANGUAGE             NotifyLanguage       =  ENGLISH;          // Notification Language
//--- Параметры индикатора ColorStDev
input int                  StDevPeriod          =  12;               // Smoothing period StDev
input ENUM_MA_METHOD       MA_Method            =  MODE_EMA;         // Histogram smoothing method
input ENUM_APPLIED_PRICE   applied_price        =  PRICE_CLOSE;      // Applied price
input int                  MaxTrendLevel        =  90;               // Maximum trend level
input int                  MiddLeTrendLevel     =  50;               // Middle trend level
input int                  FlatLevel            =  20;               // Flat level
//--- Параметры индикатора Tirone Levels
input int                  TironePeriod         =  13;               // Tirone Period
//---
CEngine trade;
CSoundsLib notify;
int Handle1,Handle2;
double stdev[],tirone_b[],tirone_s[];
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()


{
//---
   if(!TerminalInfoInteger(TERMINAL_TRADE_ALLOWED))
      notify.Message(AUTO_TRADING_OFF);
//---
   OnInitTrading();
//--- Получение хэндла индикатора ColorStDev
   Handle1=iCustom(Symbol(),PERIOD_CURRENT,"ArticleVoiceNotify\\colorstddev",
                   StDevPeriod,
                   MA_Method,
                   applied_price,
                   MaxTrendLevel,
                   MiddLeTrendLevel,
                   FlatLevel
                  );
   if(Handle1==INVALID_HANDLE)
   {
      Print("Failed to get colorstddev handle");
      Print("Handle = ",Handle1,"  error = ",GetLastError());
      return(INIT_FAILED);
   }
//--- Получение хэндла индикатора Tirone Levels
   Handle2=iCustom(Symbol(),PERIOD_CURRENT,"ArticleVoiceNotify\\tirone_levels_x3",TironePeriod,0);
   if(Handle2==INVALID_HANDLE)
   {
      Print("Failed to get Tirone Levels handle");
      Print("Handle = ",Handle2,"  error = ",GetLastError());
      return(INIT_FAILED);
   }
//---
   return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
//---
}
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
{
//--- Если нет рыночных позиций
   if(ExistPositions(Symbol(),-1,InpMagic)<1)
   {
      //--- Получение данных для расчета
      if(!GetIndValue())
         return;
      //--- Открытие ордера при начилии сигнала на покупку
      if(BuySignal())
      {
         notify.Message(SIGNAL_BUY);
         if(trade.OpenBuy(InpLot,Symbol(),InpMagic,InpStopLoss,InpTakeProfit))
         {
            Sleep(1400);
            notify.Message(BUY_ORDER_SET);
         }
      }
      //--- Открытие ордера при начилии сигнала на продажу
      if(SellSignal())
      {
         notify.Message(SIGNAL_SELL);
         if(trade.OpenSell(InpLot,Symbol(),InpMagic,InpStopLoss,InpTakeProfit))
         {
            Sleep(1400);
            notify.Message(SELL_ORDER_SET);
         }
      }
   }
}
//+------------------------------------------------------------------+
//| Условия на покупку                                               |
//+------------------------------------------------------------------+
bool BuySignal()
{
   return(tirone_b[1]>iClose(Symbol(),PERIOD_CURRENT,1) && stdev[0]>FlatLevel)?true:false;
}
//+------------------------------------------------------------------+
//| Условия на продажу                                               |
//+------------------------------------------------------------------+
bool SellSignal()
{
   return(tirone_b[1]<iClose(Symbol(),PERIOD_CURRENT,1) && stdev[0]>FlatLevel)?true:false;
}
//+------------------------------------------------------------------+
//| Получение текущих значений индикаторов                           |
//+------------------------------------------------------------------+
bool GetIndValue()
{
   return(CopyBuffer(Handle1,0,0,2,stdev)<=0    ||
          CopyBuffer(Handle2,0,0,2,tirone_b)<=0 ||
          CopyBuffer(Handle2,2,0,2,tirone_s)<=0
         )?false:true;
}
//+----------------------------------------------------------------------------+
//|  Возвращает количество открытых ордеров                                    |
//+----------------------------------------------------------------------------+
//|  Параметры:                                                                |
//|    op - операция                   (-1   - любая позиция)                  |
//|    mn - MagicNumber                (-1   - любой магик)                    |
//+----------------------------------------------------------------------------+
int ExistPositions(string sy,int op=-1,int mn=-1)
{
   int pos=0;
   uint total=PositionsTotal();
//---
   for(uint i=0; i<total; i++)
   {
      if(SelectByIndex(i))
         if(PositionGetString(POSITION_SYMBOL)==sy)
            if(op<0 || PositionGetInteger(POSITION_TYPE)==op)
               if(mn<0 || PositionGetInteger(POSITION_MAGIC)==mn)
                  pos++;
   }
   return(pos);
}
//+------------------------------------------------------------------+
//| Select a position on the index                                   |
//+------------------------------------------------------------------+
bool SelectByIndex(const int index)
{
   ENUM_ACCOUNT_MARGIN_MODE margin_mode=(ENUM_ACCOUNT_MARGIN_MODE)AccountInfoInteger(ACCOUNT_MARGIN_MODE);
//---
   if(margin_mode==ACCOUNT_MARGIN_MODE_RETAIL_HEDGING)
   {
      ulong ticket=PositionGetTicket(index);
      if(ticket==0)
         return(false);
   }
   else
   {
      string name=PositionGetSymbol(index);
      if(name=="")
         return(false);
   }
//---
   return(true);
}
//+------------------------------------------------------------------+
//| Инициализация торгового окружения                                |
//+------------------------------------------------------------------+
void OnInitTrading()
{
   string array_used_symbols[];
//--- Заполнение массива используемых символов
   CreateUsedSymbolsArray(SYMBOLS_MODE_CURRENT,"",array_used_symbols);
//--- Установка типа используемого списка символов в коллекции символов и заполнение списка таймсерий символов
   trade.SetUsedSymbols(array_used_symbols);
//--- Передача в торговый класс всех имеющихся коллекций
   trade.TradingOnInit();
   trade.TradingSetMagic(InpMagic);
   trade.TradingSetLogLevel(LOG_LEVEL_ERROR_MSG);
//--- Установка синхронной передачи приказов для всех используемых символов
   trade.TradingSetAsyncMode(false);
//--- Установка корректных типов истечения и заливки ордера всем торговым объектам
   trade.TradingSetCorrectTypeExpiration();
   trade.TradingSetCorrectTypeFilling();
}
//+------------------------------------------------------------------+

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

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


Практическое применение в инструментарии быстрой торговли

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

  • Успешное открытие позиции на покупку или продажу.
  • Успешная установка отложенных ордеров и их удаление.
  • Проверка на возможность установки ордеров с помощью советника.

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

//+------------------------------------------------------------------+
//|                                                      Program.mqh |
//|                                                         Alex2356 |
//|                    https://www.mql5.com/ru/users/alex2356/       |
//+------------------------------------------------------------------+
#include <EasyAndFastGUI\WndEvents.mqh>
#include <DoEasy25\Engine.mqh>
#include "Defines.mqh"
#include <SoundsLib/SoundsLib.mqh>

Теперь перейдем в приватную секцию класса CFastTrading и создадим переменную-экземпляр класс CSoundsLib.

   //---
   CSoundsLib        m_notify;

Необходимо также задать два новых параметра в самом инструментарии для возможности включать или отключать голосовые уведомления и выбирать их язык. Заходим в SimpleTrading.mq5 и в разделе Входные параметры эксперта пропишем новые:

//+------------------------------------------------------------------+
//| Входные параметры эксперта                                       |
//+------------------------------------------------------------------+
input int                  Inp_BaseFont      =  10;                  // Base FontSize
input color                Caption           =  C'0,130,225';        // Caption Color
input color                Background        =  clrWhite;            // Back color
input LANG                 Language          =  ENGLISH;             // Interface language
input ulong                MagicNumber       =  1111;                // Magic Number
//---
input bool                 UseVoiceNotify    =  true;                // Use Voice Notify
input LANGUAGE             NotifyLanguage    =  ENGLISH;             // Notification Language

И чтобы их передать в экземпляр класса CSoundsLib m_notify в публичной секции базового класса CFastTrading, создадим два метода и реализуем их:

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void CFastTrading::SetNotifyLanguage(LANGUAGE lang)
{
   m_notify.Language(lang);
}
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void CFastTrading::UseVoiceNotify(bool state)
{
   m_notify.IsActive(state);
}
//+------------------------------------------------------------------+

Теперь применим их в функции OnInit() файла SimpleTrading.mq5 и передадим входные параметры в только что созданные методы.

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
{
//---
   tick_counter=GetTickCount();
//--- Инициализация переменных класса
   program.FontName("Trebuchet MS");
   program.FontSize(Inp_BaseFont);
   program.BackgroundColor(Background);
   program.CaptionColor(Caption);
   program.SetLanguage(Language);
   program.SetMagicNumber(MagicNumber);
   program.UseVoiceNotify(UseVoiceNotify);
   program.SetNotifyLanguage(NotifyLanguage);
//--- Установим торговую панель
   if(!program.CreateGUI())
   {
      Print(__FUNCTION__," > Не удалось создать графический интерфейс!");
      return(INIT_FAILED);
   }
   program.OnInitEvent();
//---
   return(INIT_SUCCEEDED);
}

Тем самым мы настроили основные входные параметры системы голосовых уведомлений. Теперь найдем методы установки рыночных позиций на покупку и продажу. В базовом классе CFastTrading это методы SetBuyOrder() и SetSellOrder(). Зайдем в тело метода выставления позиции на покупку и в местах, где происходит проверка на успешное открытие позиции пропишем соответствующее голосовое уведомление BUY_ORDER_SET:

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CFastTrading::SetBuyOrder(int id,long lparam)
{
   if((id==CHARTEVENT_CUSTOM+ON_CLICK_BUTTON && lparam==m_buy_execute.Id()) ||
         (id==CHARTEVENT_KEYDOWN && lparam==KEY_B))
   {
      //---
      double lot;
      if(m_switch_button[0].IsPressed())
         lot=LotPercent(Symbol(),ORDER_TYPE_BUY,SymbolInfoDouble(Symbol(),SYMBOL_ASK),StringToDouble(m_lot_edit[0].GetValue()));
      else
         lot=NormalizeLot(Symbol(),StringToDouble(m_lot_edit[0].GetValue()));
      if(m_switch_button[1].IsPressed() && m_switch_button[2].IsPressed())
      {
         double tp=double(m_tp_edit[0].GetValue());
         double sl=double(m_sl_edit[0].GetValue());
         if(m_trade.OpenBuy(lot,Symbol(),m_magic_number,sl,tp))
         {
            m_notify.Message(BUY_ORDER_SET);
            return(true);
         }
      }
      else if(!m_switch_button[1].IsPressed() && !m_switch_button[2].IsPressed())
      {
         int tp=int(m_tp_edit[0].GetValue());
         int sl=int(m_sl_edit[0].GetValue());
         if(m_trade.OpenBuy(lot,Symbol(),m_magic_number,sl,tp))
         {
            m_notify.Message(BUY_ORDER_SET);
            return(true);
         }
      }
      else if(m_switch_button[1].IsPressed() && !m_switch_button[2].IsPressed())
      {
         double tp=double(m_tp_edit[0].GetValue());
         int sl=int(m_sl_edit[0].GetValue());
         if(m_trade.OpenBuy(lot,Symbol(),m_magic_number,sl,tp))
         {
            m_notify.Message(BUY_ORDER_SET);
            return(true);
         }
      }
      else if(!m_switch_button[1].IsPressed() && m_switch_button[2].IsPressed())
      {
         int tp=int(m_tp_edit[0].GetValue());
         double sl=double(m_sl_edit[0].GetValue());
         if(m_trade.OpenBuy(lot,Symbol(),m_magic_number,sl,tp))
         {
            m_notify.Message(BUY_ORDER_SET);
            return(true);
         }
      }
   }
   return(false);
}

Аналогичное изменение проделаем и с методом открытия позиций на продажу, но только с вызовом уведомления голосом SELL_ORDER_SET:

bool CFastTrading::SetSellOrder(int id,long lparam)
{
   if((id==CHARTEVENT_CUSTOM+ON_CLICK_BUTTON && lparam==m_sell_execute.Id()) ||
         (id==CHARTEVENT_KEYDOWN && lparam==KEY_S))
   {
      //---
      double lot;
      if(m_switch_button[3].IsPressed())
         lot=LotPercent(Symbol(),ORDER_TYPE_SELL,SymbolInfoDouble(Symbol(),SYMBOL_BID),StringToDouble(m_lot_edit[1].GetValue()));
      else
         lot=NormalizeLot(Symbol(),StringToDouble(m_lot_edit[1].GetValue()));
      //---
      if(m_switch_button[4].IsPressed() && m_switch_button[5].IsPressed())
      {
         double tp=double(m_tp_edit[1].GetValue());
         double sl=double(m_sl_edit[1].GetValue());
         if(m_trade.OpenSell(lot,Symbol(),m_magic_number,sl,tp))
         {
            m_notify.Message(SELL_ORDER_SET);
            return(true);
         }
      }
      else if(!m_switch_button[4].IsPressed() && !m_switch_button[5].IsPressed())
      {
         int tp=int(m_tp_edit[1].GetValue());
         int sl=int(m_sl_edit[1].GetValue());
         if(m_trade.OpenSell(lot,Symbol(),m_magic_number,sl,tp))
         {
            m_notify.Message(SELL_ORDER_SET);
            return(true);
         }
      }
      else if(!m_switch_button[4].IsPressed() && m_switch_button[5].IsPressed())
      {
         int tp=int(m_tp_edit[1].GetValue());
         double sl=double(m_sl_edit[1].GetValue());
         if(m_trade.OpenSell(lot,Symbol(),m_magic_number,sl,tp))
         {
            m_notify.Message(SELL_ORDER_SET);
            return(true);
         }
      }
      else if(m_switch_button[4].IsPressed() && !m_switch_button[5].IsPressed())
      {
         double tp=double(m_tp_edit[1].GetValue());
         int sl=int(m_sl_edit[1].GetValue());
         if(m_trade.OpenSell(lot,Symbol(),m_magic_number,sl,tp))
         {
            m_notify.Message(SELL_ORDER_SET);
            return(true);
         }
      }
   }
   return(false);
}

Теперь перейдем к отложенным ордерам. В инструментарии их четыре вида, и для установки каждого существует свой метод:

   bool              SetBuyStopOrder(int id,long lparam);
   bool              SetSellStopOrder(int id,long lparam);
   bool              SetBuyLimitOrder(int id,long lparam);
   bool              SetSellLimitOrder(int id,long lparam);

Для каждого из них необходимо установить соответствующее голосовое уведомление. Сделаем это на примере BuyStop-ордера, потому как с остальными необходимо проделать похожие действия. Как видно из листинга ниже, используется уведомление BUYSTOP_ORDER_SET.

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CFastTrading::SetBuyStopOrder(int id,long lparam)
{
   if(!m_orders_windows[1].IsVisible())
      return(false);
   if((id==CHARTEVENT_CUSTOM+ON_CLICK_BUTTON && lparam==m_buystop_execute.Id()) ||
         (id==CHARTEVENT_KEYDOWN && lparam==KEY_1))
   {
      //---
      double lot;
      if(m_p_switch_button[0].IsPressed())
         lot=LotPercent(Symbol(),ORDER_TYPE_BUY,SymbolInfoDouble(Symbol(),SYMBOL_ASK),StringToDouble(m_lot_edit[2].GetValue()));
      else
         lot=NormalizeLot(Symbol(),StringToDouble(m_lot_edit[2].GetValue()));
      //---
      double pr=double(m_pr_edit[0].GetValue());
      //---
      if(m_p_switch_button[1].IsPressed() && m_p_switch_button[2].IsPressed())
      {
         double tp=double(m_tp_edit[2].GetValue());
         double sl=double(m_sl_edit[2].GetValue());
         if(m_trade.PlaceBuyStop(lot,Symbol(),pr,sl,tp,m_magic_number))
         {
            m_notify.Message(BUYSTOP_ORDER_SET);
            return(true);
         }
      }
      else if(!m_p_switch_button[1].IsPressed() && !m_p_switch_button[2].IsPressed())
      {
         int tp=int(m_tp_edit[2].GetValue());
         int sl=int(m_sl_edit[2].GetValue());
         if(m_trade.PlaceBuyStop(lot,Symbol(),pr,sl,tp,m_magic_number))
         {
            m_notify.Message(BUYSTOP_ORDER_SET);
            return(true);
         }
      }
      else if(m_p_switch_button[1].IsPressed() && !m_p_switch_button[2].IsPressed())
      {
         double tp=double(m_tp_edit[2].GetValue());
         int sl=int(m_sl_edit[2].GetValue());
         if(m_trade.PlaceBuyStop(lot,Symbol(),pr,sl,tp,m_magic_number))
         {
            m_notify.Message(BUYSTOP_ORDER_SET);
            return(true);
         }
      }
      else if(!m_p_switch_button[1].IsPressed() && m_p_switch_button[2].IsPressed())
      {
         int tp=int(m_tp_edit[2].GetValue());
         double sl=double(m_sl_edit[2].GetValue());
         if(m_trade.PlaceBuyStop(lot,Symbol(),pr,sl,tp,m_magic_number))
         {
            m_notify.Message(BUYSTOP_ORDER_SET);
            return(true);
         }
      }
   }
   return(false);
}

Если с установкой уведомлений голосом для выставления отложенных ордеров мы закончили, то теперь осталось внедрить уведомления для удаления ранее выставленных. Для этого найдем метод RemoveOrder() и в нем происходит определение какой из отложенных ордеров в таблице выбран и далее происходит возможность работы с ним — редактирование или удаление. Но в данном методе происходит удаление отложенного ордера по нажатию на кнопку удалить. 

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CFastTrading::RemoveOrder(long lparam)
{
//--- Проверка идентификатора элемента
   if(lparam==m_small_button[3].Id())
   {
      //--- Получим индекс и символ
      if(m_table_orders.SelectedItem()==WRONG_VALUE)
         return(false);
      int row=m_table_orders.SelectedItem();
      ulong ticket=(ulong)m_table_orders.GetValue(0,row);
      //---
      if(OrderSelect(ticket))
      {
         string position_symbol=OrderGetString(ORDER_SYMBOL);                          // символ
         ulong  magic=OrderGetInteger(ORDER_MAGIC);                                    // MagicNumber позиции
         ENUM_ORDER_TYPE type=(ENUM_ORDER_TYPE)OrderGetInteger(ORDER_TYPE);            // тип позиции
         if(type==ORDER_TYPE_BUY_STOP)
            m_notify.Message(BUYSTOP_ORDER_DELETE);
         else if(type==ORDER_TYPE_SELL_STOP)
            m_notify.Message(SELLSTOP_ORDER_DELETE);
         else if(type==ORDER_TYPE_BUY_LIMIT)
            m_notify.Message(BUYLIMIT_ORDER_DELETE);
         else if(type==ORDER_TYPE_SELL_LIMIT)
            m_notify.Message(SELLLIMIT_ORDER_DELETE);
         //--- объявление запроса и результата
         MqlTradeRequest request;
         MqlTradeResult  result;
         //--- обнуление значений запроса и результата
         ZeroMemory(request);
         ZeroMemory(result);
         //--- установка параметров операции
         request.action=TRADE_ACTION_REMOVE;             // тип торговой операции
         request.order = ticket;                         // тикет ордера
         //--- отправка запроса
         bool res=true;
         for(int j=0; j<5; j++)
         {
            res=OrderSend(request,result);
            if(res && result.retcode==TRADE_RETCODE_DONE)
               return(true);
            else
               PrintFormat("OrderSend error %d",GetLastError());  // если отправить запрос не удалось, вывести код ошибки
         }
      }
   }
//---
   return(false);
}

Рассмотрим изменение тела метода более подробно. После определения тикета выбранного ордера мы получаем необходимые данные для того, чтобы установить запрос на его удаление посредством заполнения структуры MqlTradeRequest и вызовом метода OrderSend(). Для того чтобы понимать какой тип у отложенного ордера, который был выбран в таблице, мы используем значение переменной type. Исходя из ее значения мы назначаем методом Message() соответствующий тип голосового уведомления.

И последнее, что было поставлено задачей добавить в инструментарий, это сообщать голосом, если в терминале MetaTrader 5 запрещен автоматический режим торговли. Потому как инструментарий, по сути, является торговым экспертом, и хоть ордера выставляются им только действиями пользователя, то есть вручную, но терминал и брокер распознает их как автоторговлю. Для добавления проверки на разрешение нужно в базовом классе приложения перейти в обработчик событий OnEvent() в секцию Завершение создания интерфейса ON_END_CREATE_GUI и внизу прописать проверку с назначением голосового уведомления:

//--- Завершение создания интерфейса
   if(id==CHARTEVENT_CUSTOM+ON_END_CREATE_GUI)
   {
      //---
      SetButtonParam(m_switch_button[0],LOT);
      SetButtonParam(m_switch_button[1],POINTS);
      SetButtonParam(m_switch_button[2],POINTS);
      SetButtonParam(m_switch_button[3],LOT);
      SetButtonParam(m_switch_button[4],POINTS);
      SetButtonParam(m_switch_button[5],POINTS);
      //---
      SetButtonParam(m_p_switch_button[0],LOT);
      SetButtonParam(m_p_switch_button[1],POINTS);
      SetButtonParam(m_p_switch_button[2],POINTS);
      SetButtonParam(m_p_switch_button[3],LOT);
      SetButtonParam(m_p_switch_button[4],POINTS);
      SetButtonParam(m_p_switch_button[5],POINTS);
      SetButtonParam(m_p_switch_button[6],LOT);
      SetButtonParam(m_p_switch_button[7],POINTS);
      SetButtonParam(m_p_switch_button[8],POINTS);
      SetButtonParam(m_p_switch_button[9],LOT);
      SetButtonParam(m_p_switch_button[10],POINTS);
      SetButtonParam(m_p_switch_button[11],POINTS);
      //---
      if(!TerminalInfoInteger(TERMINAL_TRADE_ALLOWED))
         m_notify.Message(AUTO_TRADING_OFF);
   }

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


Заключение

В конце статьи приложен архив со всеми перечисленными файлами, отсортированными по папкам. Поэтому для корректной работы достаточно положить папку MQL5 в корень терминала. Для того чтобы найти корень терминала, в котором находится папка MQL5, нужно в MetaTrader 5 нажать комбинацию клавиш   Ctrl+Shift+D или воспользоваться контекстным меню, как показано на рис.7 ниже.


Рис.7 Поиск папки MQL5 в корне терминала MetaTrader 5

Прикрепленные файлы |
MQL5.zip (8998.17 KB)
Теория вероятностей и математическая статистика с примерами (Часть I): Основы и элементарная теория Теория вероятностей и математическая статистика с примерами (Часть I): Основы и элементарная теория

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

Работа с таймсериями в библиотеке DoEasy (Часть 46): Мультипериодные, мультисимвольные индикаторные буферы Работа с таймсериями в библиотеке DoEasy (Часть 46): Мультипериодные, мультисимвольные индикаторные буферы

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

Использование криптографии совместно с внешними приложениями Использование криптографии совместно с внешними приложениями

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

Дискретизация ценового ряда, случайная составляющая и "шумы" Дискретизация ценового ряда, случайная составляющая и "шумы"

Мы привыкли анализировать рынок при помощи свечей или баров, которые "нарезают" ценовой ряд через равные промежутки времени. Но насколько сильно такой способ дискретизации искажает реальную структуру рыночных движений? Дискретизировать звуковой сигнал через равные промежутки времени — это приемлемое решение, потому что звуковой сигнал — это функция, меняющаяся от времени. Сам по себе сигнал — это амплитуда, зависящая от времени и это свойство в нем, является фундаментальным.