English Русский 中文 Deutsch 日本語 Português 한국어 Français Italiano Türkçe
Cómo trabajar con el módem GSM de un experto de MQL5

Cómo trabajar con el módem GSM de un experto de MQL5

MetaTrader 5Integración | 16 abril 2014, 15:03
2 207 0
Serhii Shevchuk
Serhii Shevchuk

Introducción

En la actualidad existen medios suficientes para monitorizar a distancia una cuenta comercial, con toda comodidad: con la ayuda de los terminales móviles, las notificaciones push y el trabajo con ICQ. Pero para todo ello se debe tener conexión a internet. Este artículo describe la creación un experto que les permitirá mantenerse en contacto con su terminal comercial, incluso en el caso de que el internet móvil no está disponible, más concretamente con ayuda de llamadas y mensajes SMS. Asímismo, este experto le tendrá al tanto de el corte y el restablecimiento de la conexión con el servidor comercial.

Para ello nos valdrá prácticamente cualquier módem GSM, así como la mayoría de los teléfonos que tengan la función de módem. Como ejemplo hemos elegido el módem Huawei E1550, ya que se trata de uno de los dispositivos más extendidos de su género. Además, al final del artículo probaremos a conectar un viejo teléfono Siemens M55 (del año 2003) en lugar del módem y veremos qué ocurre.

Pero antes diremos unas cuantas palabras sobre cómo enviar un byte de información del experto al módem.


1. Trabajar con un puerto-COM

Después de conectar el módem al ordenador e instalar todos los drivers necesarios, en el sistema parecerá un puerto-COM virtual. A partir de ahora, todas las operaciones que realizadas en el trabajo con el módem, se llevarán a cabo a través de dicho puerto. Por lo tanto, para intercambiar datos con el módem, hay que obtener en primer lugar acceso al puerto-COM.

Dib. 1. El módem Huawei se encuentra en el puerto COM3

Dib. 1. El módem Huawei se encuentra en el puerto COM3

Aquí nos resultará de ayuda la biblioteca DLL TrComPort.dll, que se encuentra ampliamente difundida en Internet junto con los archivos fuente . Con su ayuda podemos configurar el puerto COM y inquirir sobre su estado, además de recibir y enviar datos. Para ello usaremos las siguientes funciones:

#import "TrComPort.dll"
   int TrComPortOpen(int portnum);
   int TrComPortClose(int portid);   
   int TrComPortSetConfig(int portid, TrComPortParameters& parameters);
   int TrComPortGetConfig(int portid, TrComPortParameters& parameters);
   int TrComPortWriteArray(int portid, uchar& buffer[], uint length, int timeout);
   int TrComPortReadArray(int portid, uchar& buffer[], uint length, int timeout);   
   int TrComPortGetQueue(int portid, uint& input_queue, uint& output_queue);
#import

Hemos tenido que redactar ligeramente los datos transmitidos, para que sean compatibles con MQL5.

La estructura de TrComPortParameters tiene el siguiente aspecto:

struct TrComPortParameters
{
   uint   DesiredParams;
   int    BaudRate;           // Velocidad del intercambio
   int    DefaultTimeout;     // Timeout por defecto (en milisegundos)
   uchar  ByteSize;           // Tamaño de los datos (4-8)
   uchar  StopBits;           // Número de bits de parada
   uchar  CheckParity;        // Control de paridad (0-no,1-sí)
   uchar  Parity;             // Tipo de paridad
   uchar  RtsControl;         // Estado inicial de RTS
   uchar  DtrControl;         // Estado inicial de DTR
};

La mayoría de los dispositivos funcionan con la configuración siguiente: 8 bits de información, sin control de paridad, 1 bit de parada. Por eso, dentro de la configuración tendría sentido sólo añadir a los parámetros del experto el número y la velocidad de intercambio del puerto COM:

input ComPortList    inp_com_port_index=COM3;   // Eligiendo el puerto-COM
input BaudRateList   inp_com_baudrate=_9600bps; // Velocidad de intercambio

Entonces, la función de inicialización del puerto-COM tendrá el aspecto que sigue:

//+------------------------------------------------------------------+
//| Inicialización del puerto-COM                                    |
//+------------------------------------------------------------------+
bool InitComPort()
  {
   rx_cnt=0;
   tx_cnt=0;
   tx_err=0;
//--- Intento de abrir el puerto
   PortID=TrComPortOpen(inp_com_port_index);
   if(PortID!=inp_com_port_index)
     {
      Print("Error al abrir el puerto-COM"+DoubleToString(inp_com_port_index+1,0));
      return(false);
     }
   else
     {
      Print("El puerto COM"+DoubleToString(inp_com_port_index+1,0)+" se ha abierto con éxito");
      //--- requerimos todos los parámetros, por eso establecemos todas las banderas
      com_par.DesiredParams=tcpmpBaudRate|tcpmpDefaultTimeout|tcpmpByteSize|tcpmpStopBits|tcpmpCheckParity|tcpmpParity|tcpmpEnableRtsControl|tcpmpEnableDtrControl;
      //--- lectura de los parametros actuales 
      if(TrComPortGetConfig(PortID,com_par)==-1)
         return(false);//error en la lectura
      //
      com_par.ByteSize=8;                //8 bits
      com_par.Parity=0;                  //sin control de paridad
      com_par.StopBits=0;                //1 bit de parada
      com_par.DefaultTimeout=100;        //timeout 100 ms
      com_par.BaudRate=inp_com_baudrate; //velocidad - de los parámetros del experto
      //---
      if(TrComPortSetConfig(PortID,com_par)==-1)
         return(false);//error de escritura
     }
   return(true);
  }

En caso de que la inicialización tenga éxito, la variable PortID almacenará el identificador del puerto COM abierto.

Aquí hay que señalar que los identificadores se numeran a partir de cero, por eso el identificador del puerto COM3 será igual a 2. Ahora que el puerto está abierto, podemos intercambiar datos con el módem. Y, por cierto, no sólo con el módem. El acceso al puerto COM desde el experto otorga grandes posibilidades de ser creativo para aquellos a los que se les dé bien la soldadura blanda: se puede conectar al experto un LED, o un display de texto móvil, donde mostrar la equidad (valor de cuenta) o las cotizaciones de las parejas de divisas que resulten interesantes.

Para obtener información sobre los datos en la cola del receptor y el trasnsmisor del puerto COM, hay que usar la función TrComPortGetQueue:

int TrComPortGetQueue(
   int   portid,           // Identificador del puerto COM
   uint& input_queue,      // Cantidad de bytes en búfer de entrada
   uint& output_queue      // Cantidad de bytes en búfer de salida
   );

En caso de error, retornará un valor negativo del código de error. Se puede encontrar una descripción detallada de los códigos de errores en el archivo con las bibliotecas fuente TrComPort.dll.

Si la función devuelve una cantidad de datos que no sea cero al búfer receptor, habrá que leerlos. Para ello se usa la función TrComPortReadArray:

int TrComPortReadArray(
   int portid,             // Indicador del puerto
   uchar& buffer[],        // Indicador del búfer que hay que leer
   uint length,            // Número de bytes de información
   int timeout             // Ejecución del timeout (en milisegundos)
   );

En caso de error, retornará un valor negativo del código de error. La cantidad de bytes de los datos debe corresponderse con la magnitud retornada por la función TrComPortGetQueue.

Para utilizar el timeout por defecto (establecido al inicializar el puerto COM), hay que transmitir el valor -1.

Para enviar datos a un puerto COM se usa la función TrComPortWriteArray:

int TrComPortWriteArray(
   int portid,             // Identificador del puerto 
   uchar& buffer[],        // Indicador del búfer fuente
   uint length,            // Número de bytes de información
   int timeout             // Ejecución del timeout (en milisegundos)
   );

Ejemplo de uso. Esperamos la línea "Hello world!", como respuesta debemos enviar "Have a nice day!".

uchar rx_buf[1024];
uchar tx_buf[1024];
string rx_str;
int rxn, txn;
TrComPortGetQueue(PortID, rxn, txn);
if(rxn>0)
{  //--- información recibida en el búfer receptor
   //--- procedemos a la lectura
   TrComPortReadArray(PortID, rx_buf, rxn, -1);
   //--- transformamos en una línea
   rx_str = CharArrayToString(rx_buf,0,rxn,CP_ACP);
   //--- comprobamos el mensaje recibido (la frase esperada "Hello world!"
   if(StringFind(rx_str,"Hello world!",0)!=-1)
   {//--- si coincide, preparamos la respuesta
      string tx_str = "Have a nice day!";
      int len = StringLen(tx_str);//obtener la longitud en caracteres
      //--- convertimos al búfer uchar
      StringToCharArray(tx_str, tx_buf, 0, len, CP_ACP);
      //--- enviamos al puerto
      if(TrComPortWriteArray(PortID, tx_buf, len, -1)<0) Print("error al escribir al puerto");
   }
}

Hay que prestar especial atención a la función de cierre del puerto:

int TrComPortClose(
   int portid         // Identificador del puerto
   );  

Esta función debe estar presente de manera obligatoria en el proceso de desinicialización del experto. Un puerto que haya sido dejado abierto, en la mayoría de los casos, sólo volverá a estar disponible después de reiniciar el sistema. Es posible que ni siquiera apagar y encender el módem resulte de ayuda.


2. Comandos AT y trabajo con el módem

El trabajo con el módem se lleva a cabo mediante los comandos AT. Aquellos que hayan usado internet móvil desde su computadora deben de recordar la llamada "línea de inicialización del módem" que tiene aproximadamente el aspecto siguiente: AT+CGDCONT=1,"IP","internet". Este es uno de los comandos AT. Prácticamente todos ellos comienzan con el prefijo AT y terminan con el símbolo 0x0d (retorno de carro).

Utilizaremos el grupo mínimo de comandos AT imprescindible para la realización de la funcional que necesitamos. Esto disminuirá el trabajo para posibilitar la compatibilidad del grupo de comandos con diversos dispositivos.

Más abajo tenemos la lista de comandos AT que usa nuestro operador informático para trabajar con el módem:

 Comando  Descripción
  ATE1                                    
  Activar eco
  AT+CGMI
  Obtener el nombre del fabricante
  AT+CGMM
  Obtener el modelo del dispositivo
  AT^SCKS
  Conocer el estado de la tarjeta SIM
  AT^SYSINFO
  Obtener información sobre el sistema
  AT+CREG
  Conocer el estado del registro en la red
  AT+COPS
  Conocer el nombre del operador móvil al que estamos conectados en el momento actual
  AT+CMGF
  Cambiar entre los modos texto/PDU
  AT+CLIP
  Activar la identificación de la línea de llamada
  AT+CPAS
  Conocer el estado del módem
  AT+CSQ
  Conocer la calidad de la señal
  AT+CUSD
  Enviar una petición USSD
  AT+CALM
  Conectar el modo silencioso (relevante para los teléfonos)
  AT+CBC
  Conocer el estado de la batería (relevante para los teléfonos)
  AT+CSCA
  Obtener el número del centro de servicio de mensajes SMS
  AT+CMGL
  Obtener la lista de mensajes SMS
  AT+CPMS
  Sleccionar memoria para los mensajes SMS
  AT+CMGD
  Borrar un mensaje SMS de la memoria
  AT+CMGR
  Leer un mensaje SMS de la memoria
  AT+CHUP
  Rechazar una llamada entrante
  AT+CMGS
  Enviar un mensaje SMS


No voy alejarme mucho del tema, describiendo todos los detalles del trabajo de los comandos AT. Se puede encontrar información suficiente en los foros técnicos. Además, todo ha sido implementado, y para crear un experto que pueda funcionar con el módem, basta con incluir el archivo de cabecera correpondiente y, en lo sucesivo, continuar utilizando funciones y estructuras ya preparadas. Sobre este tema sí hablaré más en profundidad.


2.1. Funciones

Inicialización de un puerto COM:

bool InitComPort();

Valor retornado: en caso de que se inicialice con éxito - true, si no - false. Llamar desde la función OnInit() antes de inicializar el módem.

Desinicialización del puerto COM:

void DeinitComPort();

Valor retornado: no. Llamar desde la función OnDeinit().

Inicialización del módem:

void InitModem();

Valor retornado: no. Llamar desde la función OnInit() después de inicializar con éxito el puerto COM.

Operador informático de eventos del módem:

void ModemTimerProc();

Valor retornado: no. Llamar desde la función OnTimer() con un periodo de 1 segundo.

Leyendo mensajes SMS según el índice en la memoria del módem:

bool ReadSMSbyIndex(
   int index,             // índice del mensaje SMS en la memoria del módem
   INCOMING_SMS_STR& sms  // Indicador de la estructura donde se ubicará el mensaje
   );

Valor retornado: en caso de lectura correcta - true, en caso contrario - false.

Eliminar un mensaje SMS de la memoria del módem por su índice:

bool DelSMSbyIndex(
   int index              // Índice del mensaje SMS en la memoria del módem
   );

Valor retornado: en caso de eliminarlo con éxito - true, en caso contrario - false.

Transformación del índice de calidad de la conexión en una línea:

string rssi_to_str(
   int rssi               // Índice de calidad de la conexión, valores 0..31, 99
   );

Valor retornado: una línea, por ejemplo "-55 dBm".

Envío de mensajes SMS:

bool SendSMS(
   string da,      // Número de teléfono del receptor en formato internacional
   string text,    // Texto del mensaje, cifras y letras latinas, máximo 158 símbolos
   bool flash      // Bandera de mensaje flash
   );

Valor retornado: si se ha enviado con éxito - true, si no - false. El envío de mensajes SMS sólo está permitido en letras del alfabeto latino. El alfabeto cirílico se muestra sólo en los mensajes entrantes. Si se establece flash=true, se enviará un mensaje flash.


2.2. Eventos (funciones llamadas por el operario informático del módem)

Actualización de datos en la estructura del estado del módem:

void ModemChState();

Parámetros transmitidos: no. La llamada de esta función por parte del operador informático del módem significa que en la estructura modem (la descripción de la estructura se mostrará más abajo) se han actualizado los datos.

Llamada entrante:

void IncomingCall(
   string number          // Número del abonado que llama
   );

Parámetros transmitidos: número del abonado que llama. La llamada de esta función por parte del operador informático del módem significa que la llamada del número number ha sido aceptada y luego rechazada.

Nuevo mensaje SMS recibido:

void IncomingSMS(
   INCOMING_SMS_STR& sms  // Estructura del mensaje SMS
   );

Parámetros transmitidos: estructura del mensaje SMS (la descripción de la estructura se mostrará más abajo). La llamada de esta función por parte del operador informático del módem significa que en la memoria del módem han aparecido uno o varios mensajes SMS no leídos. Si la cantidad de mensajes no leídos es mayor a uno, entonces el mensaje más reciente será transmitido a esta función.

Memoria de mensajes SMS llena:

void SMSMemoryFull(
   int n                  // Cantidad de mensajes SMS en la memoria del módem
   );

Parámetros transmitidos: cantidad de mensajes SMS en la memoria del módem. La llamada de esta función por parte del operador informático del módem significa que la memoria de mensajes SMS está llena por completo, el módem no recibirá más mensajes hasta que no se libere espacio en la memoria.


2.3. Estructura del estado de los parámetros del módem

struct MODEM_STR
{
   bool     init_ok;          // Mínimo necesario inicializado
   //
   string   manufacturer;     // Fabricante
   string   device;           // Modelo
   int      sim_stat;         // Estatus de la SIM
   int      net_reg;          // Estado del registro en la red
   int      status;           // Estado del módem
   string   op;               // Operador
   int      rssi;             // Calidad de la señal
   string   sms_sca;          // Número del centro SMS
   int      bat_stat;         // Estado de la batería
   int      bat_charge;       // Carga de la batería en tanto por ciento (la relevancia depende de bat_stat)
   //
   double   bal;              //Saldo de la cuenta móvil
   string   exp_date;         // Fecha de expiración del número del móvil
   int      sms_free;         // Paquete SMS disponible
   int      sms_free_cnt;     // Contador del paquete SMS utilizado
   //
   int      sms_mem_size;     // Volumen de memoria SMS
   int      sms_mem_used;     // Volumen de memoria SMS utilizada
   //
   string   incoming;         // Número del abonado que llama
};

MODEM_STR modem;

Esta estructura debe ser rellenada exclusivamente por el operador informático de los eventos del módem, y deberá ser utilizada por parte de otras funciones sólo para la lectura.

Más abajo se muestra la descripción de los elementos de la estructura:

 Elemento Descripción
  modem.init_ok
  Indica que el módem ha sido inicializado con éxito.
  El valor inicial false, tras finalizar la inicialización, es igual a true.
  modem.manufacturer
  Fabricante del módem, por ejemplo: "huawei".
  Valor inicial "n/a".
  modem.device
  Modelo del módem, por ejmplo: "E1550"
  Valor inicial "n/a".
  modem.sim_stat
  Estado de la tarjeta SIM. Puede adoptar los siguientes valores:
  -1 - desconocido
   0 - no hay tarjeta, está bloqueada o fuera de servicio
   1 - la tarjeta está disponible    
  modem.net_reg
  Estado del registro en la red. Puede adoptar los siguientes valores:
  -1 - desconocido
   0 - no está registrado
   1 - registrado
   2 - buscando
   3 - prohibido
   4 - estado indeterminado
   5 - resgistrado en roming
  modem.status
  Estado del módem. Puede adoptar los siguientes valores:
  -1 - inicialización
   0 - listo
   1 - error
   2 - error
   3 - llamada entrante
   4 - llamada activa
  modem.op
  Es el operador al que nos encontramos conectados en este momento.
  Puede ser igual, o bien al nombre del operador (por ejemplo, "MTS UKR"),
  o bien al código internacional del operador (por ejemplo, "25501").
  Valor inicial "n/a".
  modem.rssi
  Índice de calidad de la señal. Puede adoptar los siguientes valores:
  -1 - desconocido
   0 - señal -113 dBm o menor
   1 - señal -111 dBm
   2...30 - señal -109...-53 dBm
  31 - señal -51 dBm o mayor
  99 - desconocido
  Para convertirlo en una línea, se utiliza la función rssi_to_str().
  modem.sms_sca
  Nombre del centro de servicio de los mensajes SMS. Está guardado en la memoria de la tarjeta SIM.
  Es imprescindible para general el mensaje SMS saliente.
  En casos poco frecuentes, si el número no está guardado en la memoria de la tarjeta SIM, en su lugar
  se utilizará el número indicado en los parámetros de entrada del experto.
  modem.bat_stat
  Estado de la batería del módem (relevante sólo  para los teléfonos).
  Puede adoptar los siguientes valores:
  -1 - desconocido
   0 - el dispositivo está funcionando con la batería
   1 - batería disponible, pero el dispositivo no está funcionando con ella
   2 - batería no disponible
   3 - error
  modem.bat_charge
  Carga de la batería en tanto por ciento.
  Puede adoptar valores de 0 hasta 100.
  modem.bal
  Saldo en la cuenta del móvil. El valor se obtiene
  de la respuesta del operador a una solicitud USSD.
  Valor inicial (antes de la inicialización): -10000.
  modem.exp_date
  Fecha de expiración del número de teléfono móvil. El valor se obtiene
  de la respuesta del operador a una solicitud USSD.
  Valor inicial "n/a".
  modem.sms_free
  Cantidad de paquetes SMS disponibles. Se calcula como la diferencia entre
  la cantidad inicial y el contador de paquetes SMS utilizados.
  modem.sms_free_cnt
  Contador de paquetes SMS utilizados. El valor se obtiene
  de la respuesta del operador a una solicitud USSD. Valor inicial -1.
  modem.sms_mem_size
  Volumen de la memoria de mensajes SMS del módem.
  modem.sms_mem_used
  Volumen utilizado de la memoria de mensajes SMS del módem.
  modem.incoming
  Número del último abonado que ha llamado.
  Valor inicial "n/a".


2.4. Estructura del mensaje SMS

//+------------------------------------------------------------------+
//| Estructura del mensaje SMS                                       |
//+------------------------------------------------------------------+
struct INCOMING_SMS_STR
{
   int index;                //índice en la memoria del módem
   string sca;               //número del centro SMS del remitente
   string sender;            //número del remitente
   INCOMING_CTST_STR scts;   //etiqueta de la hora del centro SMS
   string text;              //texto del mensaje
};

Etiqueta de la hora del centro SMS - se trata de la hora a la que el centro SMS ha recibido dicho mensaje del remitente. La estructura de la etiqueta de la hora tiene el siguiente aspecto:

//+------------------------------------------------------------------+
//| estructura de la etiqueta de la hora                             |
//+------------------------------------------------------------------+
struct INCOMING_CTST_STR
{
   datetime time;            // hora
   int gmt;                  // huso horario
};

El huso horario se muestra en intervalos de 15 minutos. Es decir, que el valor 8 corresponde a GMT+02:00.

El texto de los mensajes SMS recibidos puede estar tanto en carácteres latinos, como cirílicos. Los mensajes se pueden recibir tanto en codificación de 7 bits, como en UCS2. La fusión de mensajes largos no se encuentra implementada (en vista de que la recepción está pensada para comandos cortos).

El envío de mensajes SMS sólo es posible con letras latinas. La longitud máxima del mensaje es de 158 símbolos. Si se intenta enviar una línea más larga, los símbolos sobrantes serán obviados.


3. Procedamos a la creación del experto

Para empezar, se debe copiar el archivo TrComPort.dll en la carpeta Libraries, y los archivos ComPort.mqh, modem.mqh y sms.mqh en la carpeta Include.

Después, con la ayuda del Asistente, creamos un nuevo experto y añadimos el mínimo necesario para trabajar con el módem. Que sería, precisamente:

Incluimos el archivo modem.mqh:

#include <modem.mqh>

Añadimos los parametros de entrada:

input string         str00="Configuración del puerto COM";
input ComPortList    inp_com_port_index=COM3;   // Elección del puerto COM
input BaudRateList   inp_com_baudrate=_9600bps; // Velocidad de intercambio
//
input string         str01="Módem";
input int            inp_refr_period=3;         // Periodo de consulta del módem, en segundos
input int            inp_ussd_request_tout=20;  // Timeout de espera de la respuesta a una solicitud USSD, en seg
input string         inp_sms_service_center=""; // Número del centro de servicio de SMS
//
input string         str02="Estado del saldo";
input int            inp_refr_bal_period=12;    // Periodo de consulta, en horas
input string         inp_ussd_get_balance="";   // Consulta de saldo por USSD
input string         inp_ussd_bal_suffix="";    // Sufijo del saldo
input string         inp_ussd_exp_prefix="";    // Prefijo de la fecha de expiración del número
//
input string         str03="Cantidad de paquetes SMS";
input int            inp_refr_smscnt_period=6;  // Periodo de consulta, en horas
input string         inp_ussd_get_sms_cnt="";   // Consulta del estado del paquete de servicios por USSD
input string         inp_ussd_sms_suffix="";    // Sufijo del contador de SMS
input int            inp_free_sms_daily=0;      // Límite diario de SMS

Funciones llamadas por el operador informático del módem:

//+------------------------------------------------------------------+
//| Se llama al recibir un nuevo mensaje SMS                         |
//+------------------------------------------------------------------+
void IncomingSMS(INCOMING_SMS_STR& sms)
{  
} 

//+------------------------------------------------------------------+
//| La memoria de SMS está llena                                     |
//+------------------------------------------------------------------+
void SMSMemoryFull(int n)
{
}

//+------------------------------------------------------------------+
//| Se llama al recibir una nueva llamada entrante                   |
//+------------------------------------------------------------------+
void IncomingCall(string number)
{ 
}  

//+------------------------------------------------------------------+
//| se llama después de actualizar los datos en la estructura        |
//| del estado del módem                                             |
//+------------------------------------------------------------------+
void ModemChState()
{
   static bool init_ok = false;
   if(modem.init_ok==true && init_ok==false)
   {
      Print("La inicialización del módem ha tenido éxito");      
      init_ok = true;
   }
}

A la función OnInit() se debe añadir la inicialización del puerto COM y del módem, además se deberá iniciar el temporizador con intervalos de 1 segundo:

int OnInit()
{  //---inicialización del puerto COM
   if(InitComPort()==false)
   {
      Print("Error de inicialización del puerto COM"+DoubleToString(inp_com_port_index+1,0));
      return(INIT_FAILED);
   }      
   //--- inicialización del módem
   InitModem();
   //--- puesta en marcha del temporizador 
   EventSetTimer(1); //intervalo de 1 segundo
   //      
   return(INIT_SUCCEEDED);
}

En la función OnTimer() hay que llamar al operador informático del módem:

void OnTimer()
{
//---
   ModemTimerProc();
}

En la función OnDeinit() hay que llamar necesariamente la desinicialización del puerto COM:

void OnDeinit(const int reason)
{
//--- destroy timer
   EventKillTimer();
   DeinitComPort();   
}

Compilamos el código y echamos un vistazo: 0 error(s).

Inicie el experto. Además, no se olvide de permitir la importación de DLL y elija el puerto COM en el que se encuentre el módem. En la pestaña "Expertos" deberán aparecer los siguientes mensajes:

Dib. 2. Mensajes del experto al iniciarse con éxito

Dib. 2. Mensajes del experto al iniciarse con éxito

Si usted ha obtenidos los mismos, significa que su módem (teléfono) es adecuado para trabajar con este experto. En ese caso, sigamos.

Vamos a dibujar un recuadro para visualizar los parámetros del módem. Se situará en la esquina superior izquierda del terminal, bajo la línea OHLC. Eligiremos una letra monoespaciada para el texto del recuadro, por ejemplo, "Courier New".

//+------------------------------------------------------------------+
//| TextXY                                                           |
//+------------------------------------------------------------------+
void TextXY(string ObjName,string Text,int x,int y,color TextColor)
  {
//--- mostramos en el display la línea de texto
   ObjectDelete(0,ObjName);
   ObjectCreate(0,ObjName,OBJ_LABEL,0,0,0,0,0);
   ObjectSetInteger(0,ObjName,OBJPROP_XDISTANCE,x);
   ObjectSetInteger(0,ObjName,OBJPROP_YDISTANCE,y);
   ObjectSetInteger(0,ObjName,OBJPROP_COLOR,TextColor);
   ObjectSetInteger(0,ObjName,OBJPROP_FONTSIZE,9);
   ObjectSetString(0,ObjName,OBJPROP_FONT,"Courier New");
   ObjectSetString(0,ObjName,OBJPROP_TEXT,Text);
  }
//+------------------------------------------------------------------+
//| Dibujando el recuadro de parámetros del módem                    |
//+------------------------------------------------------------------+
void DrawTab()
  {
   int   x=20, //sangría horizontal 
   y = 20,     //sangría vertical
   dy = 15;    //escalón del eje vertical
//--- dibujamos el fondo
   ObjectDelete(0,"bgnd000");
   ObjectCreate(0,"bgnd000",OBJ_RECTANGLE_LABEL,0,0,0,0,0);
   ObjectSetInteger(0,"bgnd000",OBJPROP_XDISTANCE,x-10);
   ObjectSetInteger(0,"bgnd000",OBJPROP_YDISTANCE,y-5);
   ObjectSetInteger(0,"bgnd000",OBJPROP_XSIZE,270);
   ObjectSetInteger(0,"bgnd000",OBJPROP_YSIZE,420);
   ObjectSetInteger(0,"bgnd000",OBJPROP_BGCOLOR,clrBlack);
//--- parámetros del puerto
   TextXY("str0",  "Port:            ", x, y, clrWhite); y+=dy;
   TextXY("str1",  "Speed:           ", x, y, clrWhite); y+=dy;
   TextXY("str2",  "Rx:              ", x, y, clrWhite); y+=dy;
   TextXY("str3",  "Tx:              ", x, y, clrWhite); y+=dy;
   TextXY("str4",  "Err:             ", x, y, clrWhite); y+=(dy*3)/2;
//--- parámetros del módem
   TextXY("str5",  "Modem:           ", x, y, clrWhite); y+=dy;
   TextXY("str6",  "SIM:             ", x, y, clrWhite); y+=dy;
   TextXY("str7",  "NET:             ", x, y, clrWhite); y+=dy;
   TextXY("str8",  "Operator:        ", x, y, clrWhite); y+=dy;
   TextXY("str9",  "SMSC:            ", x, y, clrWhite); y+=dy;
   TextXY("str10", "RSSI:            ", x, y, clrWhite); y+=dy;
   TextXY("str11", "Bat:             ", x, y, clrWhite); y+=dy;
   TextXY("str12", "Modem status:    ", x, y, clrWhite); y+=(dy*3)/2;
//--- estado de la cuenta móvil
   TextXY("str13", "Balance:         ", x, y, clrWhite); y+=dy;
   TextXY("str14", "Expiration date: ", x, y, clrWhite); y+=dy;
   TextXY("str15", "Free SMS:        ", x, y, clrWhite); y+=(dy*3)/2;
//--- número de la última llamada entrante
   TextXY("str16","Incoming:        ",x,y,clrWhite); y+=(dy*3)/2;
//--- parámetros del último mensaje SMS recibido
   TextXY("str17", "SMS mem full:    ", x, y, clrWhite); y+=dy;
   TextXY("str18", "SMS number:      ", x, y, clrWhite); y+=dy;
   TextXY("str19", "SMS date/time:   ", x, y, clrWhite); y+=dy;
//--- texto del último mensaje SMS recibido
   TextXY("str20", "                 ", x, y, clrGray); y+=dy;
   TextXY("str21", "                 ", x, y, clrGray); y+=dy;
   TextXY("str22", "                 ", x, y, clrGray); y+=dy;
   TextXY("str23", "                 ", x, y, clrGray); y+=dy;
   TextXY("str24", "                 ", x, y, clrGray); y+=dy;
//---
   ChartRedraw(0);
  }

Para actualizar los datos en el recuadro se utilizará la función RefreshTab():

//+------------------------------------------------------------------+
//| actualizando los valores del recuadro                            |
//+------------------------------------------------------------------+
void RefreshTab()
  {
   string str;
//--- índice del puerto COM:
   str="COM"+DoubleToString(PortID+1,0);
   ObjectSetString(0,"str0",OBJPROP_TEXT,"Port:            "+str);
//--- velocidad de intercambio:
   str=DoubleToString(inp_com_baudrate,0)+" bps";
   ObjectSetString(0,"str1",OBJPROP_TEXT,"Speed:           "+str);
//--- cantidad de bytes recibidos:
   str=DoubleToString(rx_cnt,0)+" bytes";
   ObjectSetString(0,"str2",OBJPROP_TEXT,"Rx:              "+str);
//--- cantidad de bytes transmitidos:
   str=DoubleToString(tx_cnt,0)+" bytes";
   ObjectSetString(0,"str3",OBJPROP_TEXT,"Tx:              "+str);
//--- cantidad de errores de puerto:
   str=DoubleToString(tx_err,0);
   ObjectSetString(0,"str4",OBJPROP_TEXT,"Err:             "+str);
//--- frabricante y modelo del módem:
   str=modem.manufacturer+" "+modem.device;
   ObjectSetString(0,"str5",OBJPROP_TEXT,"Modem:           "+str);
//--- estado de la tarjeta SIM:
   string sim_stat_str[2]={"Error","Ok"};
   if(modem.sim_stat==-1)
      str="n/a";
   else
      str=sim_stat_str[modem.sim_stat];
   ObjectSetString(0,"str6",OBJPROP_TEXT,"SIM:             "+str);
//--- registro en la red:
   string net_reg_str[6]={"No","Ok","Search...","Restricted","Unknown","Roaming"};
   if(modem.net_reg==-1)
      str="n/a";
   else
      str=net_reg_str[modem.net_reg];
   ObjectSetString(0,"str7",OBJPROP_TEXT,"NET:             "+str);
//--- nombre del operador móvil:
   ObjectSetString(0,"str8",OBJPROP_TEXT,"Operator:        "+modem.op);
//--- nombre del centro de servicio de SMS
   ObjectSetString(0,"str9",OBJPROP_TEXT,"SMSC:            "+modem.sms_sca);
//--- nivel de la señal:
   if(modem.rssi==-1)
      str="n/a";
   else
      str=rssi_to_str(modem.rssi);
   ObjectSetString(0,"str10",OBJPROP_TEXT,"RSSI:            "+str);
//--- estado de la batería (relevante para los teléfonos):
   string bat_stats_str[4]={"Ok, ","Ok, ","No","Err"};
   if(modem.bat_stat==-1)
      str="n/a";
   else
      str=bat_stats_str[modem.bat_stat];
   if(modem.bat_stat==0 || modem.bat_stat==1)
      str+=DoubleToString(modem.bat_charge,0)+"%";
   ObjectSetString(0,"str11",OBJPROP_TEXT,"Bat:             "+str);
//--- estado del módem:
   string modem_stat_str[5]={"Ready","Err","Err","Incoming call","Active call"};
   if(modem.status==-1)
      str="init...";
   else
     {
      if(modem.status>4 || modem.status<0)
         Print("Unknown modem status: "+DoubleToString(modem.status,0));
      else
         str=modem_stat_str[modem.status];
     }
   ObjectSetString(0,"str12",OBJPROP_TEXT,"Modem status:    "+str);
//--- estado de la cuenta móvil:
   if(modem.bal==-10000)
      str="n/a";
   else
      str=DoubleToString(modem.bal,2)+" "+inp_ussd_bal_suffix;
   ObjectSetString(0,"str13",OBJPROP_TEXT,"Balance:         "+str);
//--- fecha de expiración del número del móvil:
   ObjectSetString(0,"str14",OBJPROP_TEXT,"Expiration date: "+modem.exp_date);
//--- paquetes SMS disponibles:
   if(modem.sms_free<0)
      str="n/a";
   else
      str=DoubleToString(modem.sms_free,0);
   ObjectSetString(0,"str15",OBJPROP_TEXT,"Free SMS:        "+str);
//--- memoria SMS llena:
   if(sms_mem_full==true)
      str="Yes";
   else
      str="No";
   ObjectSetString(0,"str17",OBJPROP_TEXT,"SMS mem full:    "+str);
//---
   ChartRedraw(0);
  }

Para eliminar el recuadro, tenemos la función DelTab():

//+------------------------------------------------------------------+
//| Eliminando el recuadro                                           |
//+------------------------------------------------------------------+
void DelTab()
  {
   for(int i=0; i<25; i++)
      ObjectDelete(0,"str"+DoubleToString(i,0));
   ObjectDelete(0,"bgnd000");
  }

Vamos a añadir llamadas de las funciones, para trabajar con el recuadro, a los operadores informáticos de eventos OnInit(), OnDeinit() y a la función ModemChState():

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- inicialización del puerto COM
   if(InitComPort()==false)
     {
      Print("Error de inicialización del puerto COM"+DoubleToString(inp_com_port_index+1,0));
      return(INIT_FAILED);
     }
//---
   DrawTab();
//--- inicialización del módem
   InitModem();
//--- inicio del temporizador 
   EventSetTimer(1);//intervalos de 1 segundo
//---
   RefreshTab();
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//--- destroy timer
   EventKillTimer();
   DeinitComPort();
   DelTab();
  }
//+------------------------------------------------------------------+
//| ModemChState                                                     |
//+------------------------------------------------------------------+
void ModemChState()
  {
   static bool init_ok=false;
//Print("el estado del módem ha cambiado");
   if(modem.init_ok==true && init_ok==false)
     {
      Print("La inicialización del módem ha tenido éxito");
      init_ok=true;
     }
//---
   RefreshTab();
  }

Añadimos a la función IncomingCall() la actualización de la línea del recuadro "número de la última llamada":

//+------------------------------------------------------------------+
//| se llama al recibir una llamada entrante                         |
//+------------------------------------------------------------------+
void IncomingCall(string number)
{    
   //--- actualizamos el número de la última llamada entrante:
   ObjectSetString(0, "str16",OBJPROP_TEXT, "Incoming:        "+number);   
} 

Compilamos el código y ponemos en marcha el experto. En la ventana del terminal deberá aparecer lo siguiente:

Parámetros del estado del módem

Dib. 3. Parámetros del módem

Intente llamar al módem. La llamada será rechazada, y en la línea "Incoming" aparecerá su número.


4. Trabajo con solicitudes USSD

Una cuenta móvil cuyo saldo no haya sido recargado a tiempo puede detener el funcionamiento del experto en el momento menos adecuado. Por eso, la función encargada de comprobar el saldo en la cuenta es una de las más importantes. Para comprobar el estado de la cuenta móvil, normalmente se usan solicitudes USSD. Asímismo, usaremos las solicitudes USSD para obtener información sobre los paquetes SMS restantes.

Los datos para la formación de solicitudes y la elaboración de respuestas se encuentran en los parámetros de entrada:

input string         str02="=== Состояние баланса ======";
input int            inp_refr_bal_period=12;  //periodo de la solicitud, en horas
input string         inp_ussd_get_balance=""; //solicitud USSD sobre el saldo
input string         inp_ussd_bal_suffix="";  //sufijo del saldo
input string         inp_ussd_exp_prefix="";  //prefijo de la fecha de expiración del número
//
input string         str03="= Количество пакетных смс ==";
input int            inp_refr_smscnt_period=6;//periodo de la solicitud, en horas
input string         inp_ussd_get_sms_cnt=""; //solicitud USSD del estado de los paquetes de servicios
input string         inp_ussd_sms_suffix="";  //sufijo del contador de SMS
input int            inp_free_sms_daily=0;    //límite diario de SMS

Si el número de solicitud no se ha especificado, entonces la solicitud no será procesada. En caso contrario, la solicitud será enviada justo después de la inicialización del módem, y después tendrá lugar de nuevo el envío, cuando transcurra el intervalo de tiempo establecido. Asímismo, la solicitud no se procesará si su módem (teléfono) no es capaz de gestionar el comando AT correspondiente (esto concierne a los modelos teléfonicos antiguos). 

Supongamos que a su solicitud de saldo restante el operador envía una respuesta con el aspecto que sigue:

7.13 UAH, dijsnyj do 22.05.2014. Taryf - Super MTS 3D Nul 25.

Entonces, para que el operador informático pueda reconocer correctamente la respuesta, el sufijo del saldo debe ser establecido como "UAH", y el prefijo de expiración del número debe ser "dijsnyj do".

Dado que nuestro experto enviará con bastante frecuencia mensajes SMS, tiene sentido encargarle al operador un paquete SMS que incluya una cantidad determinada de SMS por un precio reducido. En este caso, es muy útil saber cuántos paquetes SMS disponibles quedan. Y esto se puede hacer mediante una solicitud USSD. El operador normalmente responde con el número de SMS que se han utilizado, y no con los que quedan.

Supongamos que el operador le envía el mensaje siguiente:

Saldo: 69 minutos de llamadas locales para hoy. Usados hoy 0 SMS y 0 MB.

En este caso, el sufijo del contador de SMS debe ser establecido como "SMS", y el límite diario, de acuerdo con las condiciones del servicio del paquete SMS. Por ejemplo, si le proporcionan 30 mensajes al día, y a la solicitud le responden 10, significa que aún le quedan 30-10=20. Esta cantidad precisamente será la que el operador informático coloque en el elemento correspondiente del estado del módem.

¡IMPORTANTE! ¡Al rellenar las solicitudes USSD preste mucha atención! ¡El envío de una solicitud errónea podría tener consecuencias desagradables, por ejemplo, la activación de algún servicio de pago indeseado!

Para que nuestro experto comience a trabajar con solicitudes USSD, simplemente hay que rellenar los parámetros de entrada correspondientes.

Por ejemplo, para el operador "МТС Ukrania" tendrá el aspecto siguiente:

Dib. 4. Parámetros de solicitud USSD de saldo disponible

Dib. 4. Parámetros de solicitud USSD de saldo disponible

Dib. 5. Parámetros de solicitud USSD de SMS en el paquete

Dib. 5. Parámetros de solicitud USSD de SMS en el paquete

Establezca los valores correspondientes a su operador móvil. Después, en el recuadro de estado del módem empezarán a mostrarse los valores de saldo disponible en la cuenta móvil, y los SMS restantes en el paquete:

Saldo

Dib. 6. Parámetros recibidos de las respuestas a las solicitudes USSD

Mientras escribo este artículo, mi operador móvil, en lugar de la fecha de expiración del número, me ha enviado un anuncio pre-navideño. Propiamente, el operador no ha encontrado la fecha, y en la línea "Expiration date" se muestra "n/a". Preste atención al hecho de que todas las respuestas del operador se muestran en la pestaña "Expertos".

Dib. 7. Respuestas del operador en la pestaña "Expertos"

Dib. 7. Respuestas del operador en la pestaña "Expertos"


5. Envío de mensajes SMS

Comenzamos a añadir funciones útiles. Por ejemplo, el envío de mensajes SMS con el valor actual de beneficio, equidad y la cantidad de posiciones abiertas. Una llamada entrante iniciará el envío.

Por supuesto que sólo debe reaccionar al número del administrador, por eso tendremos un parámetro de entrada adicional:

input string         inp_admin_number="+XXXXXXXXXXXX";//número de teléfono del administrador

El número debe introducirse en formato internacional, incluyendo el signo "+" delante del número.

La comprobación del número, así como la formación del texto del mensaje SMS y su envío deben ser añadidos al operario informático de las llamadas entrantes:

//+------------------------------------------------------------------+
//| se llama al recibir la llamada entrante                          |
//+------------------------------------------------------------------+
void IncomingCall(string number)
{
   bool result;
   if(number==inp_admin_number)
   {
      Print("Número del administrador. Enviando SMS.");
      //
      string mob_bal="";
      if(modem.bal!=-10000)//saldo disponible en la cuenta móvil
         mob_bal = "\n(m.bal="+DoubleToString(modem.bal,2)+")";
      result = SendSMS(inp_admin_number, "Account: "+DoubleToString(AccountInfoInteger(ACCOUNT_LOGIN),0)   
               +"\nProfit: "+DoubleToString(AccountInfoDouble(ACCOUNT_PROFIT),2)
               +"\nEquity: "+DoubleToString(AccountInfoDouble(ACCOUNT_EQUITY),2)
               +"\nPositions: "+DoubleToString(PositionsTotal(),0)
               +mob_bal
               , false);
      if(result==true)
         Print("SMS enviado con éxito");
      else
         Print("Error en el envío del SMS");               
   }   
   else
      Print("Número no autorizado ("+number+")"); 
   //--- actualizar el número de la última llamada entrante:
   ObjectSetString(0, "str16",OBJPROP_TEXT, "Incoming:        "+number);   
}  

Ahora, al llamar al módem desde el número inp_admin_number, un mensaje SMS será enviado como respuesta:

Dib. 8. SMS informativo enviado por el experto, como respuesta a una llamada entrante desde el número del administrador

Dib. 8. SMS informativo enviado por el experto, como respuesta a una llamada entrante desde el número del administrador

Aquí podemos ver los valores actuales de beneficio, equidad y la cantidad de posiciones abiertas, así como el saldo de la cuenta móvil.


6. La monitorización se une al servidor comercial

Vamos a añadir el envío de informes en caso de que la comunicación con el servidor comercial se interrumpa y restablezca. Para ello, comprobaremos cada 10 segundos si hay conexión con el servidor comercial, con ayuda de TerminalInfoInteger() con el identificador de propiedad TERMINAL_CONNECTED.

Para filtrar las interrupciones breves de la comunicación, utilizamos la histéresis, que deberá ser añadida a la lista de parámetros de entrada:

input int            inp_conn_hyst=6; //Histéresis, х10 sec

El valor 6 significa que la conexión se considerará perdida en caso de que no haya conexión por más de 6*10=60 segundos. De manera análoga, la conexión se considerará restablecida si se encuentra disponible durante más de 60 segundos. Se considerá la hora de la pérdida de la conexión la hora local de la primera ausencia registrada de la misma, de igual manera, será considerada hora del restablecimiento la hora local de la recuperación de la misma.

Para ello añadimos a la función OnTimer() el siguiente código:

   static int s10 = 0;//precontador en 10 segundos
   static datetime conn_time;
   static datetime disconn_time;
   if(++s10>=10)
   {//--- una vez cada 10 segundos
      s10 = 0;
      //
      if((bool)TerminalInfoInteger(TERMINAL_CONNECTED)==true)
      {
         if(cm.conn_cnt==0)             //primer requerimiento con éxito en la secuencia
            conn_time = TimeLocal();    //guardamos la hora
         if(cm.conn_cnt<inp_conn_hyst)
         {
            if(++cm.conn_cnt>=inp_conn_hyst)
            {//--- la conexión se ha estabilizado
               if(cm.connected == false)
               {//--- si antes de esto teníamos una pérdida persistente de comunicación
                  cm.connected = true;
                  cm.new_state = true;
                  cm.conn_time = conn_time;
               }
            }
         }
         cm.disconn_cnt = 0;
      }
      else
      {
         if(cm.disconn_cnt==0)          //primer requerimiento sin éxito en la secuencia
            disconn_time = TimeLocal(); //guardamos la hora
         if(cm.disconn_cnt<inp_conn_hyst)
         {
            if(++cm.disconn_cnt>=inp_conn_hyst)
            {//--- interrupción persistente de la comunicación
               if(cm.connected == true)
               {//--- si antes de esto la conexión era estable 
                  cm.connected = false;
                  cm.new_state = true;
                  cm.disconn_time = disconn_time;
               }
            }
         }
         cm.conn_cnt = 0;
      }
   }
   //
   if(cm.new_state == true)
   {//--- ha cambiado el estado de la conexión
      if(cm.connected == true)
      {//--- conexión disponible
         string str = "Connected "+TimeToString(cm.conn_time,TIME_DATE|TIME_SECONDS);
         if(cm.disconn_time!=0)
            str+= ", offline: "+dTimeToString((ulong)(cm.conn_time-cm.disconn_time));
         Print(str);
         SendSMS(inp_admin_number, str, false);//enviando mensaje
      }
      else
      {//--- conexión no disponible
         string str = "Disconnected "+TimeToString(cm.disconn_time,TIME_DATE|TIME_SECONDS);
         if(cm.conn_time!=0)
            str+= ", online: "+dTimeToString((ulong)(cm.disconn_time-cm.conn_time));
         Print(str);
         SendSMS(inp_admin_number, str, false);//enviando mensaje
      }
      cm.new_state = false;
   }

La estructura cm tiene el aspecto siguiente:

//+------------------------------------------------------------------+
//| Estructura de la monitorización de la comunicación con           |
//| el terminal                                                      |
//+------------------------------------------------------------------+
struct CONN_MON_STR
  {
   bool              new_state;    //bandera del cambio del estado de la conexión
   bool              connected;    //estado de la conexión
   int               conn_cnt;     //contador de requerimientos de conexión con éxito
   int               disconn_cnt;  //contador de requerimientos de conexión sin éxito
   datetime          conn_time;    //hora de establecimiento de la conexión
   datetime          disconn_time; //hora de interrupción de la conexión
  };

CONN_MON_STR cm;//estructura de la monitorización de la conexión

En el texto del mensaje SMS mostraremos la hora de la pérdida (o restablecimiento) de la comunicación con el servidor comercial, así como la duración de la ausencia (o presencia) de la conexión, que se calculará como la diferencia entre la hora del establecimiento y la hora de la pérdida de la misma. Para pasar la diferencia de tiempo de segundos al formato dd hh:mm:ss añadimos la función dTimeToString():

string dTimeToString(ulong sec)
{
   string str;
   uint days = (uint)(sec/86400);
   if(days>0)
   {
      str+= DoubleToString(days,0)+" days, ";
      sec-= days*86400;
   }
   uint hour = (uint)(sec/3600);
   if(hour<10) str+= "0";
   str+= DoubleToString(hour,0)+":";
   sec-= hour*3600;
   uint min = (uint)(sec/60);
   if(min<10) str+= "0";
   str+= DoubleToString(min,0)+":";
   sec-= min*60;
   if(sec<10) str+= "0";
   str+= DoubleToString(sec,0);
   //
   return(str);
}

Para asegurarnos de que el experto no envíe un mensaje sobre el establecimiento de la conexión cada vez que se ponga en marcha, añadimos la función conn_mon_init(), que establecerá los valores de los elementos de la estructura cm de igual manera que si no se hubiera establecido la conexión. Además, la hora de establecimiento de la conexión será la hora local del inicio del experto. Dicha función se llamará desde la función OnInit().

void conn_mon_init()
{
   cm.connected = true;   
   cm.conn_cnt = inp_conn_hyst;
   cm.disconn_cnt = 0;
   cm.conn_time = TimeLocal();
   cm.new_state = false;
} 

Compile el código e inicie el experto. Después pruebe a desconectar la computadora de Internet. Transcurridos 60 (más-menos 10) segundos, recibirá un mensaje notificándole la pérdida de la conexión con el servidor. Conecte internet. Después de 60 segundos recibirá un mensaje informándole sobre el restablecimiento de la conexión, así como indicaciones sobre la duración de la ausencia de comunicación:

Dib. 9. Notificaciones por SMS sobre el reestablecimiento de la conexión con el servidor comercial Dib. 9. Notificaciones por SMS sobre el reestablecimiento de la conexión con el servidor comercial

Dib. 9. Notificaciones por SMS sobre el restablecimiento de la conexión con el servidor comercial


7. Envío de informes sobre la apertura y cierre de posiciones

Para llevar a cabo la monitorización de la apertura y cierre de posiciones, añadimos a la función OnTradeTransaction() el código siguiente:

void OnTradeTransaction(const MqlTradeTransaction& trans,
                        const MqlTradeRequest& request,
                        const MqlTradeResult& result)
{
//---
   if(trans.type==TRADE_TRANSACTION_DEAL_ADD)
   {
      if(trans.deal_type==DEAL_TYPE_BUY ||
         trans.deal_type==DEAL_TYPE_SELL)
      {
         int i;
         for(i=0;i<POS_BUF_LEN;i++)
         {
            if(ps[i].new_event==false)
               break;
         }   
         if(i<POS_BUF_LEN)
         {
            ps[i].new_event = true;
            ps[i].deal_type = trans.deal_type;
            ps[i].symbol = trans.symbol;
            ps[i].volume = trans.volume;
            ps[i].price = trans.price;
            ps[i].deal = trans.deal;
         }
      }
   }    
}

donde ps - es el búfer de estructuras del tipo POS_STR:

struct POS_STR
{
   bool new_event;
   string symbol;
   ulong deal;
   ENUM_DEAL_TYPE deal_type; 
   double volume;
   double price;
};  

#define POS_BUF_LEN  3

POS_STR ps[POS_BUF_LEN];

El búfer es necesario en caso de que se abra (o cierre) más de una posición en un periodo corto de tiempo. Al abrirse o cerrarse una posición, después de añadir la operación a la historia, obtemos todos los parámetros imprescindibles y establecemos la bandera new_event.

Añadimos a la función OnTimer() el código siguiente, que realizará un seguimiento de las banderas new_event y generará los informes por SMS:

   //--- procesando la apertura-cierre de posiciones
   string posstr="";
   for(int i=0;i<POS_BUF_LEN;i++)
   {
      if(ps[i].new_event==true)
      {
         string str;
         if(ps[i].deal_type==DEAL_TYPE_BUY)
            str+= "Buy ";
         else if(ps[i].deal_type==DEAL_TYPE_SELL)
            str+= "Sell ";
         str+= DoubleToString(ps[i].volume,2)+" "+ps[i].symbol;     
         int digits = (int)SymbolInfoInteger(ps[i].symbol,SYMBOL_DIGITS);
         str+= ", price="+DoubleToString(ps[i].price,digits);
         //
         long deal_entry;
         HistorySelect(TimeCurrent()-3600,TimeCurrent());//recuperamos la historia de la última hora
         if(HistoryDealGetInteger(ps[i].deal,DEAL_ENTRY,deal_entry)==true)
         {
            if(((ENUM_DEAL_ENTRY)deal_entry)==DEAL_ENTRY_IN)
               str+= ", entry: in";
            else if(((ENUM_DEAL_ENTRY)deal_entry)==DEAL_ENTRY_OUT)
            {
               str+= ", entry: out";
               double profit;
               if(HistoryDealGetDouble(ps[i].deal,DEAL_PROFIT,profit)==true)
               {
                  str+= ", profit = "+DoubleToString(profit,2);
               }
            }
         }           
         posstr+= str+"\r\n";
         ps[i].new_event=false;
      }
   }    
   if(posstr!="")
   {
      Print(posstr+"pos: "+DoubleToString(PositionsTotal(),0));
      SendSMS(inp_admin_number, posstr+"pos: "+DoubleToString(PositionsTotal(),0), false);
   }

Compilamos el código e iniciamos el experto. Probemos a comprar AUDCAD, tamaño del lote 0.14. El experto enviará el mensaje SMS "Buy 0.14 AUDCAD, price=0.96538, entry: in". Después, transcurrido cierto tiempo, cerraremos la posición y recibiremos un mensaje SMS sobre el cierre:

Dib. 10. Notificaciones SMS sobre la apertura (entry: in) y el cierre de posición (entry: out) Dib. 10. Notificaciones SMS sobre la apertura (entry: in) y el cierre de posición (entry: out)

Dib. 10. Notificaciones SMS sobre la apertura (entry: in) y el cierre de posición (entry: out)


8. Cómo procesar los SMS entrantes para controlar las posiciones abiertas

Hasta el momento actual, nuestro experto sólo ha enviado mensajes informativos al número del administrador. Ahora le enseñaremos a aceptar y ejecutar comandos por SMS. Esto puede resultar útil, por ejemplo, para cerrar la totalidad de las posiciones abiertas o una parte de ellas. Ya sabemos que no hay nada mejor que una posición cerrada a tiempo.

Pero, en primer lugar, hay que asegurarse de que la recepción de mensajes SMS funciona correctamente. Para ello añadimos a la función IncomingSMS() la representación en la pantalla del último mensaje recibido:

//+------------------------------------------------------------------+
//| se llama la función al recibir un nuevo SMS                      |
//+------------------------------------------------------------------+
void IncomingSMS(INCOMING_SMS_STR& sms)
{
   string str, strtmp;
   //Número del remitente del último mensaje SMS recibido:
   ObjectSetString(0, "str18", OBJPROP_TEXT, "SMS number:      "+sms.sender);
   //Fecha y hora de envío del último mensaje SMS recibido:
   str = TimeToString(sms.scts.time,TIME_DATE|TIME_SECONDS);
   ObjectSetString(0, "str19", OBJPROP_TEXT, "SMS date/time:   "+str);
   //Texto del último mensaje SMS recibido:
   strtmp = StringSubstr(sms.text, 0, 32); str = " ";
   if(strtmp!="") str = strtmp;    
   ObjectSetString(0, "str20", OBJPROP_TEXT, str);
   strtmp = StringSubstr(sms.text,32, 32); str = " ";
   if(strtmp!="") str = strtmp;    
   ObjectSetString(0, "str21", OBJPROP_TEXT, str);
   strtmp = StringSubstr(sms.text,64, 32); str = " ";
   if(strtmp!="") str = strtmp;    
   ObjectSetString(0, "str22", OBJPROP_TEXT, str);
   strtmp = StringSubstr(sms.text,96, 32); str = " ";
   if(strtmp!="") str = strtmp;    
   ObjectSetString(0, "str23", OBJPROP_TEXT, str);
   strtmp = StringSubstr(sms.text,128,32); str = " ";
   if(strtmp!="") str = strtmp;    
   ObjectSetString(0, "str24", OBJPROP_TEXT, str);  
}

Si ahora enviamos al módem un mensaje SMS, aparecerá representado en el recuadro:

Nuevo mensaje SMS

Dib. 11. Representación de un mensaje SMS entrante en la ventana del terminal

Preste atención a que en la pestaña "Expertos" se representan todos los mensajes SMS entrantes en la forma <índice_en_la_memoria_del_módem>texto_del_mensaje:

Dib. 10. Notificaciones SMS sobre la apertura (entry: in) y el cierre de posición (entry: out)

Dib. 12. Representación del mensaje SMS en la pestaña "Expertos"

Como comando para el cierre de las operaciones actuará la palabra "close". Después de ella, tras un espacio, hay que introducir el parámetro - el símbolo de la posición que es necesario cerrar, o "all", si hay que cerrar todas las posiciones. El registro del valor no tiene importancia, dado que antes de procesar el texto del mensaje, utilizamos la función StringToUpper(). Mientras analizamos el mensaje, hay que comprobar que el número del remitente conincida con el número introducido del administrador.

También hay ser consciente de que son posibles los casos en que se puede recibir un mensaje SMS con un retraso significativo (bloqueo técnico del operador, etc). En este caso, es mejor no tomar en cuenta el comando recibido en el mensaje, ya que la situación del mercado puede haber sufrido cambios. En relación con esto, introduciremos un parámetro más:

input int            inp_sms_max_old=600; //Duración del comando SMS, en seg

El valor 600 indica que los comandos cuyo envío haya tardado más de 600 segundos (10 minutos) serán ignorados. Preste atención al hecho de que, en el ejemplo, el método de comprobación de la hora de envío del mensaje presupone que el centro de servicio de SMS y el dispositivo en el que se ha iniciado el experto se encuentran en el mismo huso horario.

Para procesar los comandos en los mensajes SMS, añadiremos a la función IncomingSMS() el código siguiente:

   if(sms.sender==inp_admin_number)
   {
      Print("SMS del administrador");
      datetime t = TimeLocal();
      //--- comprobamos que el mensaje no ha caducado
      if(t-sms.scts.time<=inp_sms_max_old)
      {//--- comprobamos que el mensaje sea un comando
         string cmdstr = sms.text;
         StringToUpper(cmdstr);//lo convertimos todo al registro superior
         int pos = StringFind(cmdstr, "CLOSE", 0);
         cmdstr = StringSubstr(cmdstr, pos+6, 6);
         if(pos>=0)
         {//--- comando. lo mandamos a procesar
            ClosePositions(cmdstr);            
         } 
      }
      else
         Print("el comando SMS ha expirado");
   }   

Si el SMS ha llegado desde el administrador, no ha caducado y se trata de un comando (contiene la palabra clave "Close"), enviamos su parámetro para ser procesado por la función ClosePositions():

uint ClosePositions(string sstr)
{//--- cerrar las posiciones indicadas
   bool all = false;
   if(StringFind(sstr, "ALL", 0)>=0)
      all = true;
   uint res = 0;
   for(int i=0;i<PositionsTotal();i++)
   {
      string symbol = PositionGetSymbol(i);
      if(all==true || sstr==symbol)
      {
         if(PositionSelect(symbol)==true)
         {
            long pos_type;
            double pos_vol;
            if(PositionGetInteger(POSITION_TYPE,pos_type)==true)
            {
               if(PositionGetDouble(POSITION_VOLUME,pos_vol)==true)
               {
                  if(OrderClose(symbol, (ENUM_POSITION_TYPE)pos_type, pos_vol)==true)
                     res|=0x01;
                  else
                     res|=0x02;   
               }
            }
         }
      }
   }
   return(res);
}

Dicha función comprueba si hay, entre las posiciones abiertas, alguna que corresponda al parámetro recibido en el comando (símbolo). Las posiciones que hayan superado esta condición, serán cerradas con la ayuda de la función OrderClose():

bool OrderClose(string symbol, ENUM_POSITION_TYPE pos_type, double vol)
{
   MqlTick last_tick;
   MqlTradeRequest request;
   MqlTradeResult result;
   double price = 0;
   //
   ZeroMemory(request);
   ZeroMemory(result);
   //
   if(SymbolInfoTick(Symbol(),last_tick))
   {
      price = last_tick.bid;
   }
   else
   {
      Print("Error al recibir los precios actuales");
      return(false);
   }
   //   
   if(pos_type==POSITION_TYPE_BUY)
   {//--- cierre de la posición BUY - VENDER(SELL)
      request.type = ORDER_TYPE_SELL;
   }
   else if(pos_type==POSITION_TYPE_SELL)
   {//--- cierre de la posición SELL - COMPRAR(BUY)
      request.type = ORDER_TYPE_BUY;
   }
   else
      return(false);
   //
   request.price = NormalizeDouble(price, _Digits);
   request.deviation = 20;
   request.action = TRADE_ACTION_DEAL;
   request.symbol = symbol;
   request.volume = NormalizeDouble(vol, 2);
   if(request.volume==0)
      return(false);
   request.type_filling = ORDER_FILLING_FOK;
   //
   if(OrderSend(request, result)==true)
   {
      if(result.retcode==TRADE_RETCODE_DONE || result.retcode==TRADE_RETCODE_DONE_PARTIAL)
      {
         Print("La orden se ha ejecutado con éxito");
         return(true);
      }
   }
   else
   {
      Print("Error en los parametros de la orden: ", GetLastError(),", Código de retorno del servidor comercial: ", result.retcode);     
      return(false);
   }      
   //
   return(false);
}

Si se han procesado correctamente las órdenes, la función de monitorización del cambio de posiciones generará y enviará una notificación SMS.


9. Borrar los mensajes de la memoria del módem

Preste atención a que el operador informático del módem no elimina por sí mismo los mensajes SMS entrantes. Por eso, transcurrido un tiempo, cuando la memoria ya esté llena de SMS, el operador informático llamará la función SMSMemoryFull(). La cantidad de mensajes que hay en la memoria del módem será transmitida a la función. Puede borrar todos los mensajes, o sólo aquellos que elija. El módem no recibirá mensajes nuevos hasta que no se libere espacio en la memoria.

//+------------------------------------------------------------------+
//| Memoria de SMS llena                                             |
//+------------------------------------------------------------------+
void SMSMemoryFull(int n)
{
   sms_mem_full = true;
   for(int i=0; i<n; i++)
   {//borrar todos los SMS
      if(DelSMSbyIndex(i)==false)
         break;
      else
         sms_mem_full = false;   
   }
}

Asímismo, se pueden borrar mensajes SMS inmediatamente después de procesarlos. Cuando el operador informático llama la función IncomingSMS(), la estructura de INCOMING_SMS_STR transmite el índice del mensaje en la memoria del módem, lo que permite eliminar el mensaje inmediatamente después de procesarlo, con la ayuda de la función DelSMSbyIndex():

//+------------------------------------------------------------------+
//| se llama la función al recibir un nuevo mensaje SMS              |
//+------------------------------------------------------------------+
void IncomingSMS(INCOMING_SMS_STR& sms)
{//
   /*
   ... procesando el mensaje ...
   */
   DelSMSbyIndex(sms.index);//eliminamos el mensaje
}


Conclusión

En este artículo hemos tratado la creación de un experto, utilizando un módem GSM para vigilar a distancia el terminal comercial. Hemos hablado con detalle de los métodos de recepción de información sobre posiciones abiertas, beneficios actuales y otra información, con ayuda de las notificacioes por SMS. Asímismo, hemos implementado las funciones elementales de gestión de posiciones abiertas, con la ayuda de los comandos por SMS. En el ejemplo se han utilizado comandos en lengua inglesa, pero se pueden utilizar comandos en lengua rusa con la misma eficacia (para no perder el tiempo cambiando de teclado en el teléfono).

Como cierre del tema, comprobemos de qué manera se comportará nuestro experto con un teléfono de 10 años de antiguedad. Dispositivo - Siemens M55. Conectamos:

Dib. 13. Conexión del teléfono Siemens M55 Siemens M55

Dib. 13. Conexión del teléfono Siemens M55

Dib. 14. El teléfono Siemens M55 se ha inicializado con éxito, pestaña "Expertos"

Dib. 14. El teléfono Siemens M55 se ha inicializado con éxito, pestaña "Expertos"

Como se puede ver, todos los parámetros necesarios han sido obtenidos, el único problema son los datos, que hemos recibido de la solicitud por USSD. El asunto es que el Siemens M55 no es compatible con el comando AT pensado para trabajar con las solicitudes por USSD. En todo lo demás, su funcionalidad no se queda atrás con respecto a un módem moderno, y puede incluso usarse para trabajar con nuestro experto.


Traducción del ruso hecha por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/ru/articles/797

Archivos adjuntos |
trcomport.zip (30.99 KB)
modem.mqh (44.74 KB)
sms.mqh (9.46 KB)
gsminformer.mq5 (25.28 KB)
comport.mqh (5.18 KB)
Pegado de contrato de futuros en MetaTrader 5 Pegado de contrato de futuros en MetaTrader 5
El análisis técnico de los contratos de futuros (futuros, en lo sucesivo) se ve dificultado por la breve duración de su circulación. En gráficos relativamente cortos resulta difícil llevar a cabo el análisis técnico, por ejemplo, la cantidad de barras en el gráfico diurno de futuros en el índice de la bolsa ucraniana UX-9.13 es de algo más de 100. Por eso al trader le surge la cuestión sobre la construcción de instrumentos sintéticos sobre los futuros. En el artículo veremos el tema del pegado de la historia de los contratos de futuros con diferentes fechas de duración en el terminal MetaTrader 5.
Indicador para la representación del gráfico "Renko" Indicador para la representación del gráfico "Renko"
En el artículo vamos a hablar del gráfico "Renko" y mostraremos una de las variantes de su realización en el lenguaje MQL5, en forma de indicador. El indicador tiene multitud de modificaciones que lo distinguen del gráfico clásico. La representación se realiza no sólo en la ventana del indicador, sino también el gráfico principal. Además, se ve realizada la representación del indicador en forma de línea en "ZigZag". Les mostraremos, igualmente, varios ejemplos de estrategias de trabajo con el gráfico.
Creación de filtros digitales sin retardo temporal Creación de filtros digitales sin retardo temporal
En el presente artículo se estudia una de las aproximaciones para determinar la utilidad de una señal (tendencia) de flujo de datos. Algunos tests para el filtrado (suavización) de las cotizaciones de la bolsa, bastante útiles, demuestran la posibilidad potencial de crear filtros digitales (indicadores) que no sufran retrasos temporales y no se redibujen en las últimas barras.
Fundamentos de programación en MQL5 - Arrays Fundamentos de programación en MQL5 - Arrays
Junto con las variables y las funciones, los arrays forman prácticamente una parte integrante de cualquier lenguaje de programación. Este artículo puede ser interesante en primer lugar para los principiantes que se han puesto a estudiar la programación en MQL5. Mientras que los programadores experimentados tienen una buena oportunidad de generalizar y sistematizar sus conocimientos.