Sistema de notificaciones de voz para evetos comerciales y señales

Alexander Fedosov | 13 noviembre, 2020

Contenido

Introducción

El terminal comercial MetaTrader 5 tiene un sistema para ajustar notificaciones de sonido que incluye 11 eventos a los que se puede asignar una u otra señal de sonido. No obstante, al utilizar el terminal, existen muchas más situaciones en las que resultaría útil recibir información en forma de sonido, ya sea la aparición de una señal de un sistema comercial o información sobre el hecho de que un experto comercial activo ha abierto, cerrado o modificado una posición colocada. En nuestros tiempos, los asistentes de voz juegan hace mucho un papel considerable en la vida del hombre, ya sea como navegador, buscador de voz o traductor. Por eso, esta idea se puede utilizar al comerciar en el terminal MetaTrader 5. En el presente artículo trataremos de desarrollar un sistema sencillo y comprensible de notificaciones de voz para los diferentes eventos comerciales, los estados del mercado o las señales de los sistemas comerciales.


Desarrollando los sistemas de notificaciones de voz

Antes de proceder a crear el propio sistema, querríamos destacar que los eventos elegidos para implementar las notificaciones de voz son solo un conjunto estándar que hemos seleccionado para mostrar el sistema en sí. Por eso, para aquellos a quienes no les parezca suficiente, el sistema permitirá añadir eventos propios con sus correspondientes notificaciones de voz. La expansión del sistema según las propias necesidades será lo bastante sencilla para que la entiendan incluso aquellos que no tienen muchos conocimientos de MQL5.

El sistema ha sido implementado en forma de clase CSoundsLib en un archivo de inclusión. Por eso, vamos a crear en la carpeta MQL5/Include la carpeta SoundsLib, y en ella, el archivo SoundsLib.mqh. Antes de crear la clase, introduciremos dos enumeraciones que se usarán más adelante para trabajar con las notificaciones de voz. La primera de ellas es LANGUAGE, que se usará para seleccionar el idioma de las notificaciones. En el sistema tendremos dos idiomas: inglés y ruso.

//+------------------------------------------------------------------+
//| Enumeration for switching the notification language              |
//+------------------------------------------------------------------+
enum LANGUAGE
{
   RUSSIAN,       // Russian
   ENGLISH        // English
};

La segunda enumeración contendrá el conjunto de eventos que hemos elegido con fines ilustrativos. Más tarde, mostraremos cómo integrarlos en varios sistemas listos para utilizar, incluyendo indicadores, asesores expertos y herramientas de negociación rápida. Bien, la enumeración se llamará MESSAGE:

//+------------------------------------------------------------------+
//| List of voice alerts                                             |
//+------------------------------------------------------------------+
enum MESSAGE
{
   STATUS_ON,                          // Status of enabled voice alerts
   SIGNAL_BUY,                         // A Buy signal
   SIGNAL_SELL,                        // A Sell signal
   BUY_ORDER_SET,                      // A Buy order has been placed
   SELL_ORDER_SET,                     // A Sell order has been placed
   BUYLIMIT_ORDER_SET,                 // A Limit Buy order has been placed
   BUYSTOP_ORDER_SET,                  // A Stop Buy order has been placed
   SELLLIMIT_ORDER_SET,                // A Limit Sell order has been placed
   SELLSTOP_ORDER_SET,                 // A Stop Sell order has been placed
   BUYLIMIT_ORDER_DELETE,              // A Limit Buy order has been deleted
   BUYSTOP_ORDER_DELETE,               // A Stop Buy order has been deleted
   SELLLIMIT_ORDER_DELETE,             // A Limit Sell order has been deleted
   SELLSTOP_ORDER_DELETE,              // A Stop Sell order has been deleted
   BUY_ORDER_CLOSE_PROFIT,             // A Buy order has closed with a profit
   BUY_ORDER_CLOSE_LOSS,               // A Buy order has closed with a loss
   SELL_ORDER_CLOSE_PROFIT,            // A Sell order has closed with a profit
   SELL_ORDER_CLOSE_LOSS,              // A Sell order has closed with a loss
   BUY_ORDER_CLOSE_TP,                 // A Buy order has been closed by Take Profit
   BUY_ORDER_CLOSE_SL,                 // A Buy order has been closed by Stop Loss
   SELL_ORDER_CLOSE_TP,                // A Sell order has been closed by Take Profit
   SELL_ORDER_CLOSE_SL,                // A Sell order has been closed by Stop Loss
   MARKET_CLOSE,                       // Market is closed
   AUTO_TRADING_ON,                    // Automated trading is allowed
   AUTO_TRADING_OFF,                   // Automated trading is prohibited
};

En el conjunto básico hay 24 notificaciones. La mayoría de ellas se relacionan con el funcionamiento y el estado de las posiciones abiertas y las órdenes pendientes. Algunas notificaciones se usan para alertar sobre el entorno comercial. Las tres últimas se refieren a eventos comunes. La notificación que indica que el estado del sistema de alerta por voz está activado, así como la notificación sobre la aparición de señales de compra o venta, resultan adecuadas para trabajar con asesores expertos en operaciones manuales o semiautomatizadas, o bien al usar las señales de los indicadores tanto simples como complejos disponibles como parte de una estrategia comercial.

Ahora, crearemos la clase CSoundsLib, y escribiremos en ella los métodos necesarios para su funcionamiento.

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
class CSoundsLib
{
private:
   LANGUAGE          m_language;
   bool              m_activity_status;
public:
                     CSoundsLib(void); 
                    ~CSoundsLib(void);
   //--- Set the notification language
   void              Language(LANGUAGE lang);
   //--- Set/get the status of the voice alerts system
   void              IsActive(bool flag);
   bool              IsActive(void);
   //--- Play the specified notification
   bool              Message(MESSAGE msg);
};

En la sección privada, se encuentran las dos variables m_language y m_activity_status, necesarias para los métodos Language() y IsActive() creados más abajo, que se encargan de definir el idioma de las notificaciones de voz y obtener/establecer la actividad del propio sistema. Su implementación se encuentra en el listado a continuación:

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

Hay otro método, Message(), que reproduce la propia notificación establecida desde la enumeración MESSAGE. La implementación de este método también resulta muy sencilla:

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
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);
   }
}

Preste atención a los siguientes puntos importantes: solo si comprende estos, podrá expandir correctamente el sistema con sus propias notificaciones de voz. El primer punto implica que guardemos los archivos de audio en el lugar correcto: por defecto se encuentran en la carpeta MQL5/Files/SoundsLib. En este caso, además, no debemos olvidarnos de crear la carpeta SoundsLib. El segundo punto implica que nos aseguremos de que los nombres y el formato del archivo de audio en la carpeta creada sean los adecuados. Debe prestar atención a estas líneas de código: en ellas, a la enumeración del tipo MESSAGE, se añade el sufijo _RU o _EN. Por consiguiente, el nombre del archivo correspondiente, por ejemplo, a una notificación de señal de compra SIGNAL_BUY se vinculará con dos archivos de audio, SIGNAL_BUY _RU y SIGNAL_BUY_EN, para la alerta de voz en ruso e inglés. Además, no tenemos que olvidar que la función de sistema PlaySound() solo puede reproducir archivos en formato *.WAV y, por consiguiente, los nombres completos de los archivos con extensiones en la carpeta SoundsLib  se verán así:

Fig.1 Nombre completo y extensión del archivo de sonido.

Por eso, a los eventos establecidos en las enumeración MESSAGE 24 les corresponderán 48 archivos de sonido, 2 para cada evento en los dos idiomas. A continuación, mostraremos un método propio para crear notificaciones de voz, pero cada cual es libre de utilizar el que más le convenga. Para el artículo, hemos utilizado un servicio gratuito de conversión de mensajes de texto en archivos de voz.

Fig.2 Servicio de conversión de texto en archivos de voz.

En este instrumento, la funcionalidad resulta más que suficiente para implementar la tarea requerida. Podemos seleccionar tanto el idioma como los tipos con el formato que necesitemos. No obstante, el formato WAV no es compatible con el inglés, por consiguiente, podemos usar aquí cualquier conversor en línea o cualquier otro software para convertir mp3 a wav. Hemos preparado todos los archivos necesarios para el sistema y los hemos almacenado en la carpeta MQL5\Files\SoundsLib con el formato y los nombres correctos según la enumeración MENSAJE y los sufijos de idioma. Aquí está la lista resultante:

Fig.3 Lista completa de archivos de sonido para las notificaciones de voz.

Para que resulte lo más comprensible posible, abajo mostramos unas instrucciones paso a paso para crear notificaciones de voz propias.

Paso 1. Añada un evento de voz al sistema.

Para ello, tenemos que entrar en el archivo SoundsLib.mqh y encontrar la enumeración MESSAGE. Agréguele su evento con un nombre significativo. En la fig.3 y en el código anterior se muestran varios ejemplos de nombres.

Paso 2. Cree el archivo de sonido de la futura notificación de voz.

Entre en el servicio (u otro cualquiera que le guste), ajuste a su gusto los parámetros requeridos (estos se muestran en la fig.2) y guarde el archivo en formato WAV en la carpeta MQL5\Files\SoundsLib con el nombre "Nombre de su evento en la enumeración MESSAGE"+_RU(_EN) según el idioma. Si usted completa todos los pasos correctamente, el nuevo archivo de sonido se vinculará al nuevo evento que añadió en el paso 1 y, por consiguiente, estará listo para su uso.


Aplicación práctica en los indicadores

Ahora, vamos a ver cómo funciona esto con diferentes ejemplos. Para ello, crearemos un indicador basado en las señales de los dos indicadores descritos en el recuadro de abajo:

Parámetro Descripción
Indicador utilizado ADXCloud
Indicador utilizado ColorZerolagRVI
Marco temporal Cualquiera
Condiciones de compra La nube ADXCloud es de color verde, la nube ColorZerolagRVI pasa de la zona roja a la verde.
Condiciones de venta La nube ADXCloud es de color roja, la nube ColorZerolagRVI pasa de la zona verde a la roja.

En la fig.4 mostramos varios ejemplos de entrada basados ​​en las dos señales de estos indicadores. Aunque son bastante simples, los usaremos para crear un indicador de señal compuesto que muestre los puntos de entrada en el mercado como flechas en el gráfico. 

Fig.4 Condiciones de entrada según las señales de los indicadores.

//+------------------------------------------------------------------+
//|                                                      Example.mq5 |
//|                                                         Alex2356 |
//|                           https://www.mql5.com/en/users/alex2356 |
//+------------------------------------------------------------------+
#property copyright "Alex2356"
#property link      "https://www.mql5.com/en/users/alex2356"
#property version   "1.00"
#property indicator_chart_window
//--- two buffers are used for calculating and drawing the indicator
#property indicator_buffers 2
//--- used graphic constructions
#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(" Failed to create indicator handle");
      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(" Failed to create indicator handle");
      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 the next call
   return(rates_total);
}
//+------------------------------------------------------------------+

Como resultado, obtenemos la implementación mostrada en la fig.5. Ahora, tenemos que implementar el sistema de notificaciones de voz.

Fig.5 Indicador de flechas basado en dos indicadores.

Para comenzar, incluimos en el indicador el archivo SoundsLib.mqh:

#include <SoundsLib/SoundsLib.mqh>

Luego, creamos una instancia de la clase del sistema de notificaciones de voz:

CSoundsLib Notify;

En la función de inicialización OnInit(), establecemos el idioma de las notificaciones de voz; nosotros pondremos el inglés. En realidad, el inglés está configurado por defecto, por lo que no será necesario ajustarlo de forma adicional si usted quiere usar este idioma. Nosotros lo haremos con fines ilustrativos.

Notify.Language(ENGLISH);

Como el indicador de flechas muestra solo los puntos de entrada en el mercado o las señales de compra/venta, utilizaremos dos notificaciones de voz de la enumeración MESSAGE: 

   SIGNAL_BUY,                         // A Buy signal
   SIGNAL_SELL,                        // A Sell signal

Sin embargo, al implementar las notificaciones de voz en el indicador creado, será necesario que no lleguen según todas las señales en la historia, sino solo de la barra actual, por eso, modificaremos el ciclo de búsqueda de señales de la forma siguiente:

//--- 
   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;
      }
   }

Aquí, comprobamos si hay una señal en la barra cero, y de haberla, notificamos esto al usuario del terminal


Aplicación práctica en un experto comecial

Por lo general, dos tipos de notificaciones de voz son suficientes para los indicadores. Asimismo, podemos implementar alertas para notificar el ingreso de un valor en la zona de sobrecompra para los osciladores, sobre las rupturas de canal para las bandas de Bollinger, etcétera. Podemos usar en los asesores expertos muchas más notificaciones diferentes del conjunto compuesto. Por consiguiente, vamos a crear un robot comercial de prueba no solo para notificar sobre una señal de entrada en el mercado, sino también para comentar acciones adicionales, por ejemplo, qué tipo de posición se abre. En primer lugar, definimos la estrategia de entrada en el mercado del asesor experto. 

Parámetro Descripción
Indicador utilizado ColorStDev
Indicador utilizado Tres niveles de Tirone
Marco temporal Cualquiera
Condiciones de compra El histograma ColorStdDev es de color rojo (tendencia fuerte), en este caso, además, el precio debe encontrarse por encima del nivel superior de Tirone.
Condiciones de venta El histograma ColorStdDev es de color rojo (tendencia fuerte), en este caso, además, el precio debe encontrarse por debajo del nivel inferior de Tirone.
Condiciones de salida   Take-profit/Stop-loss

A nivel visual, los puntos de entrada tendrán un aspecto semejante a la fig.6, que mostramos como ejemplo.

Fig.6 Ejemplos de entrada en el mercado según esta estrategia.

Ahora, vamos a implementar esta estrategia para MetaTrader 5; en este caso, además, utilizaremos las notificaciones de voz con algunos eventos. 

//+------------------------------------------------------------------+
//|                                                  VoiceNotify.mq5 |
//|                                                         Alex2356 |
//|                           https://www.mql5.com/en/users/alex2356 |
//+------------------------------------------------------------------+
#property copyright "Alex2356"
#property link      "https://www.mql5.com/en/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 indicator parameters
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 indicator parameters
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();
//--- Get the handle of the ColorStDev indicator
   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);
   }
//--- Getting the handle of the Tirone Levels indicator
   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 there are no market positions
   if(ExistPositions(Symbol(),-1,InpMagic)<1)
   {
      //--- Getting data for calculations
      if(!GetIndValue())
         return;
      //--- Open an order if there is a buy signal
      if(BuySignal())
      {
         notify.Message(SIGNAL_BUY);
         if(trade.OpenBuy(InpLot,Symbol(),InpMagic,InpStopLoss,InpTakeProfit))
         {
            Sleep(1400);
            notify.Message(BUY_ORDER_SET);
         }
      }
      //--- Opening an order if there is a sell signal
      if(SellSignal())
      {
         notify.Message(SIGNAL_SELL);
         if(trade.OpenSell(InpLot,Symbol(),InpMagic,InpStopLoss,InpTakeProfit))
         {
            Sleep(1400);
            notify.Message(SELL_ORDER_SET);
         }
      }
   }
}
//+------------------------------------------------------------------+
//| Buy conditions                                                   |
//+------------------------------------------------------------------+
bool BuySignal()
{
   return(tirone_b[1]>iClose(Symbol(),PERIOD_CURRENT,1) && stdev[0]>FlatLevel)?true:false;
}
//+------------------------------------------------------------------+
//| Sell conditions                                                  |
//+------------------------------------------------------------------+
bool SellSignal()
{
   return(tirone_b[1]<iClose(Symbol(),PERIOD_CURRENT,1) && stdev[0]>FlatLevel)?true:false;
}
//+------------------------------------------------------------------+
//| Getting the current values of indicators                         |
//+------------------------------------------------------------------+
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;
}
//+----------------------------------------------------------------------------+
//|  Returns the number of open orders                                         |
//+----------------------------------------------------------------------------+
//|  Parameters:                                                               |
//|    op - operation                  (-1   - any position)                   |
//|    mn - MagicNumber                (-1   - any magic number)               |
//+----------------------------------------------------------------------------+
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);
}
//+------------------------------------------------------------------+
//| Trading Environment Initialization                               |
//+------------------------------------------------------------------+
void OnInitTrading()
{
   string array_used_symbols[];
//--- Fill in the array of used symbols
   CreateUsedSymbolsArray(SYMBOLS_MODE_CURRENT,"",array_used_symbols);
//--- Set the type of the used symbol list in the symbol collection and fill in the list of symbol timeseries
   trade.SetUsedSymbols(array_used_symbols);
//--- Pass all existing collections to the trading class
   trade.TradingOnInit();
   trade.TradingSetMagic(InpMagic);
   trade.TradingSetLogLevel(LOG_LEVEL_ERROR_MSG);
//--- Set synchronous passing of orders for all used symbols
   trade.TradingSetAsyncMode(false);
//--- Set correct order expiration and filling types to all trading objects
   trade.TradingSetCorrectTypeExpiration();
   trade.TradingSetCorrectTypeFilling();
}
//+------------------------------------------------------------------+

Vamos a analizar este código con más detalle desde el punto de vista del uso de notificaciones de voz. En la función de inicialización del robot comercial se ha verificado si los sistemas comerciales automáticos pueden operar en el terminal. Si esta opción está desactivada, se le comunicará al usuario mediante la reproducción de la notificación de voz correspondiente. A continuación, en la función OnTick(), al encontrar la señal comercial deseada, el asesor notificará que se ha localizado la señal de compra o venta requerida. Se intentará abrir una posición de acuerdo con la señal, y si hay éxito, se reproducirá otra alerta de voz para notificar al usuario que se ha colocado una posición.

Este tipo de notificaciones son mucho más eficientes a la hora de informar al usuario del terminal, que no siempre puede ver a tiempo los mensajes de texto necesarios del asesor en la pestaña Expertos o en otra notificación de texto. En cuanto a las alertas, aquí, la información del paquete estándar se entrega solo mediante diferentes tipos de sonidos, cuyos valores en diferentes indicadores y asesores pueden ser distintos. Con las notificaciones de voz todo resulta claro y comprensible, porque el evento necesario se comunica con el habla.


Aplicación práctica en el instrumental de comercio rápido

En artículos anteriores, desarrollamos un conjunto de herramientas para los tráders que trabajan manualmente, es decir, que buscan entradas de mercado, colocan órdenes, administran posiciones y cierran estas por sí mismos. Con fines ilustrativos, querríamos añadir a este instrumental un sistema de notificación de voz. De esta manera, también demostraríamos que la funcionalidad de las notificaciones de voz se puede añadir fácilmente a cualquier herramienta. Para ello, utilizaremos como base el archivo adjunto al final de este artículo. Primero, vamos a definir la lista de acciones y eventos para los que añadiremos notificaciones de voz.

Antes de comenzar a integrar las notificaciones de voz, vamos a incluir la biblioteca en nuestro proyecto. Para ello, abrimos el archivo Program.mqh; luego, al inicio del mismo, hacemos lo siguiente:

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

Ahora, vamos a pasar a la sección privada de la clase CFastTrading y a crear la variable de instancia de clase CSoundsLib.

   //---
   CSoundsLib        m_notify;

Asimismo, debemos indicar los dos nuevos parámetros en el propio instrumental para poder activar o desactivar las notificaciones de voz y seleccionar su idioma. Entramos en SimpleTrading.mq5 y escribimos los nuevos parámetros en el apartado de parámetros de entrada:

//+------------------------------------------------------------------+
//| Expert Advisor input parameters                                  |
//+------------------------------------------------------------------+
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

Para transmitirlos a la instancia de la clase CSoundsLib, m_notify en la sección pública de la clase básica CFastTrading, creamos dos métodos y los implementamos:

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

Ahora, los aplicamos en la función OnInit() del archivo SimpleTrading.mq5 y transmitimos los parámetros de entrada a los métodos que acabamos de crear.

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
{
//---
   tick_counter=GetTickCount();
//--- Initialize class variables
   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);
//--- Set up the trading panel
   if(!program.CreateGUI())
   {
      Print(__FUNCTION__," > Failed to create graphical interface!");
      return(INIT_FAILED);
   }
   program.OnInitEvent();
//---
   return(INIT_SUCCEEDED);
}

Haciendo esto, hemos ajustado los principales parámetros de entrada del sistema de notificaciones de voz. Ahora, vamos a encontrar los métodos de colocación de posiciones de mercado de compra y venta. En la clase básica CFastTrading, se trata de los métodos SetBuyOrder() y SetSellOrder(). Entramos en el cuerpo del método para colocar las posiciones de compra y, en los lugares donde se comprueba si la posición se ha abierto con éxito, escribimos la notificación de voz correspondiente 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);
}

Asimismo, realizamos un cambio similar con el método de apertura de las posiciones de venta, pero llamando a la notificación de voz 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);
}

Ahora, vamos a pasar a las órdenes pendientes. Hay cuatro tipos dentro del instrumental, y para establecer cada uno de ellos, existe un método propio:

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

Para cada una de ellas, hay que establecer la notificación de voz correspondiente. Vamos a utilizar como ejemplo la orden BuyStop, dado que con el resto tendremos que realizar acciones similares. Como podemos ver en el listado de abajo, se usa la notificación 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);
}

Si bien hemos finalizado con el establecimiento de las notificaciones de voz para colocar órdenes pendientes, ahora tenemos que implementar las notificaciones para eliminar las órdenes anteriormente colocadas. Para ello, encontramos el método RemoveOrder(). En este se determina cuál de las órdenes pendientes en el recuadro ha sido seleccionada, y después se da la posibilidad de trabajar con las mismas, es decir, de editarlas o eliminarlas. En este método, vemos cómo tiene lugar la eliminación de una orden pendiente pulsando sobre el botón eliminar. 

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CFastTrading::RemoveOrder(long lparam)
{
//--- Check the element ID
   if(lparam==m_small_button[3].Id())
   {
      //--- Get index and symbol
      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);                          // symbol
         ulong  magic=OrderGetInteger(ORDER_MAGIC);                                    // order MagicNumber
         ENUM_ORDER_TYPE type=(ENUM_ORDER_TYPE)OrderGetInteger(ORDER_TYPE);            // 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);
         //--- declare the request and the result
         MqlTradeRequest request;
         MqlTradeResult  result;
         //--- zeroing the request and result values
         ZeroMemory(request);
         ZeroMemory(result);
         //--- set the operation parameters
         request.action=TRADE_ACTION_REMOVE;             // trading operation type
         request.order = ticket;                         // order ticket
         //--- sending a request
         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());  // if unable to send the request, output the error code
         }
      }
   }
//---
   return(false);
}

Vamos a analizar la modificación del cuerpo del método con más detalle. Después de determinar el ticket de la orden elegida, obtenemos los datos necesarios para establecer una solicitud de eliminación de esta rellenando la estructura MqlTradeRequest y llamando a OrderSend(). Para entender qué tipo tiene la orden pendiente que se ha seleccionado en el recuadro, utilizamos el valor de la variable type. Partiendo de su valor, asignamos con el método Message() el tipo correspondiente de notificación de voz.

La última tarea a implementar consiste en añadir una notificación de voz que comunique si el comercio automatizado está prohibido en el terminal MetaTrader 5. El instrumental es en realidad un asesor: aunque los usuarios colocan las órdenes manualmente, el terminal y el bróker las reconocen como comercio automatizado. Para añadir la comprobación del permiso, tenemos que entrar en el manejador de eventos OnEvent() en la clase básica de la aplicación, y después en la sección Finalizar creación de la interfaz ON_END_CREATE_GUI y añadir abajo la comprobación con la notificación de voz correspondiente:

// --- GUI creation completion
   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);
   }

A modo de cierre, el siguiente vídeo muestra cómo funcionan las notificaciones de voz en el instrumental de negociación rápida, en el que se usan notificaciones para las posiciones de mercado y las órdenes pendientes.


Conclusión

Al final del artículo se adjunta un fichero con todos los archivos enumerados, clasificados por carpetas. Por eso, para que funcione correctamente, basta con colocar la carpeta MQL5 en la carpeta raíz del terminal. Para encontrar la carpeta raíz del terminal en la que se encuentra la carpeta MQL5, debemos pulsar en MetaTarder 5 la combinación de   Ctrl+Shift+D o utilizar el menú contextual como se muestra en la fig.7, más abajo.


Fig.7 Abriendo la carpeta MQL5 en la carpeta raíz del terminal MetaTrader 5.