English Русский 中文 Español Deutsch 日本語 한국어 Français Italiano Türkçe
Trabalhando com modem GSM a partir de um Expert Advisor MQL5

Trabalhando com modem GSM a partir de um Expert Advisor MQL5

MetaTrader 5Integração | 7 julho 2014, 12:33
1 407 0
Serhii Shevchuk
Serhii Shevchuk

Introdução

Atualmente há um número razoável de meios para uma monitorização remota confortável de uma conta de negociação: terminais móveis, notificações push, trabalhando com o ICQ. Mas tudo requer conexão com a Internet. Este artigo descreve o processo de criação de um Expert Advisor que lhe permitirá ficar em contato com o terminal de negociação, mesmo quando a Internet móvel não estiver disponível, através de chamadas e mensagens de texto. Este Expert Advisor também poderá notificá-lo sobre uma conexão perdida ou restabelecida com o servidor de negociação.

Para este objetivo, virtualmente qualquer modem GSM, assim como a maioria dos telefones com a função modem poderiam executar. Para ilustração, eu escolhi o Huawei E1550, já que este modem é um dos dispositivos mais utilizados dentro do gênero. Além disso, no final do artigo, vamos tentar substituir o modem por um velho celular Siemens M55 (lançado em 2003) e ver o que acontece.

Primeiramente algumas palavras sobre como enviar um byte de dados a partir de um Expert Advisor a um modem.


1. Trabalhando com Porta COM

Depois de conectar o modem ao computador e instalar todos os drivers necessários, você será capaz de ver uma porta COM virtual no sistema. Todas as operações futuras com o modem são realizadas através desta porta. Consequentemente, a fim de trocar dados com o modem, você deve primeiro obter acesso a porta COM.

O modem na forma como é exibido no gerenciador de dispositivos

Fig. 1. O modem Huawei está conectado à porta COM

Aqui, vamos precisar da biblioteca DLL TrComPort.dll que é distribuída livremente na Internet, juntamente com os arquivos de origem. Ela será usada para configurar a porta COM, consultar seu estado, bem como receber e enviar dados. Para conseguir esse feito, vamos usar as seguintes funções:

#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

Os tipos de dados transmitidos tiveram que ser ligeiramente modificados para compatibilidade com MQL5.

A estrutura TrComPortParameters é a seguinte:

struct TrComPortParameters
{
   uint   DesiredParams;
   int    BaudRate;           // Data rate
   int    DefaultTimeout;     // Default timeout (in milliseconds)
   uchar  ByteSize;           // Data size(4-8)
   uchar  StopBits;           // Number of stop bits
   uchar  CheckParity;        // Parity check(0-no,1-yes)
   uchar  Parity;             // Parity type
   uchar  RtsControl;         // Initial RTS state
   uchar  DtrControl;         // Initial DTR state
};

A maioria dos dispositivos trabalha com as seguintes configurações: 8 bits de dados, sem verificação de paridade, 1 bit de stop. Portanto, fora de todos os parâmetros da porta COM, faz sentido apenas adicionar o número da porta COM e a taxa de dados nos parâmetros do Expert Advisor:

input ComPortList    inp_com_port_index=COM3;   // Choosing the COM port
input BaudRateList   inp_com_baudrate=_9600bps; // Data rate

A função de inicialização da porta COM será dessa forma:

//+------------------------------------------------------------------+
//| COM port initialization                                          |
//+------------------------------------------------------------------+
bool InitComPort()
  {
   rx_cnt=0;
   tx_cnt=0;
   tx_err=0;
//--- attempt to open the port
   PortID=TrComPortOpen(inp_com_port_index);
   if(PortID!=inp_com_port_index)
     {
      Print("Error when opening the COM port"+DoubleToString(inp_com_port_index+1,0));
      return(false);
     }
   else
     {
      Print("The COM port"+DoubleToString(inp_com_port_index+1,0)+" opened successfully");
      //--- request all parameters, so set all flags
      com_par.DesiredParams=tcpmpBaudRate|tcpmpDefaultTimeout|tcpmpByteSize|tcpmpStopBits|tcpmpCheckParity|tcpmpParity|tcpmpEnableRtsControl|tcpmpEnableDtrControl;
      //--- read current parameters 
      if(TrComPortGetConfig(PortID,com_par)==-1)
         ,bnreturn(false);//read error
      //
      com_par.ByteSize=8;                //8 bits
      com_par.Parity=0;                  //no parity check
      com_par.StopBits=0;                //1 stop bit
      com_par.DefaultTimeout=100;        //100 ms timeout
      com_par.BaudRate=inp_com_baudrate; //rate - from the parameters of the Expert Advisor
      //---
      if(TrComPortSetConfig(PortID,com_par)==-1)
         return(false);//write error
     }
   return(true);
  }

Em caso de sucesso de inicialização da variável PortID, será armazenado o identificador da abertura da porta COM.

Aqui deve ser observado que os identificadores são numerados a partir de zero, então o identificador da porta COM3 será igual a 2. Agora que a porta está aberta, os dados podem ser trocados com o modem. E, a propósito, não apenas com um modem. O acesso a porta COM do Expert Advisor abre grandes oportunidades para a criatividade de quem é bom de solda: você pode conectar o Expert Advisor para um LED ou um display de texto em movimento para mostrar o patrimônio líquido ou preço de mercado de certos pares de moedas.

A função TrComPortGetQueue deve ser usada para obter informações sobre os dados na fila do receptor e transmissor da porta COM:

int TrComPortGetQueue(
   int   portid,           // COM port identifier
   uint& input_queue,      // Number of bytes in the input buffer
   uint& output_queue      // Number of bytes in the output buffer
   );

No caso de erro, retorna um valor negativo do código de erro. Uma descrição detalhada dos códigos de erro está disponível no arquivo com os códigos fonte da biblioteca TrComPort.dll.

Se a função retorna um número diferente de zero nos dados do buffer de recepção, então eles precisam ser lidos. Para este propósito, usamos a função TrComPortReadArray:

int TrComPortReadArray(
   int portid,             // Port identifier
   uchar& buffer[],        // Pointer to the buffer to read
   uint length,            // Number of data bytes
   int timeout             // Execution timeout (in ms)
   );

No caso de erro, retorna um valor negativo do código de erro. O número de bytes de dados correspondendo ao valor devolvido pela função TrComPortGetQueue.

Para usar o tempo limite padrão (definido na inicialização da porta COM), você precisa passar o valor de -1.

Para transmitir dados para a porta COM, usamos a função TrComPortWriteArray:

int TrComPortWriteArray(
   int portid,             // Port identifier
   uchar& buffer[],        // Pointer to the initial buffer
   uint length,            // Number of data bytes
   int timeout             // Execution timeout (in ms)
   );

Exemplo de aplicação. Em resposta à mensagem dizendo "Olá, mundo!", devemos enviar "Tenha um bom dia!".

uchar rx_buf[1024];
uchar tx_buf[1024];
string rx_str;
int rxn, txn;
TrComPortGetQueue(PortID, rxn, txn);
if(rxn>0)
{  //--- data received in the receiving buffer
   //--- read
   TrComPortReadArray(PortID, rx_buf, rxn, -1);
   //--- convert to a string
   rx_str = CharArrayToString(rx_buf,0,rxn,CP_ACP);
   //--- check the received message (expected message "Hello world!"
   if(StringFind(rx_str,"Hello world!",0)!=-1)
   {//--- if we have a match, prepare the reply
      string tx_str = "Have a nice day!";
      int len = StringLen(tx_str);//get the length in characters
      //--- convert to uchar buffer
      StringToCharArray(tx_str, tx_buf, 0, len, CP_ACP);
      //--- send to the port
      if(TrComPortWriteArray(PortID, tx_buf, len, -1)<0) Print("Error when writing to the port");
   }
}

Tenha uma atenção especial para a função de fechamento da porta:

int TrComPortClose(
   int portid         // Port identifier
   );  

Esta função deve estar sempre presente no processo de desinicialização do Expert Advisor. Na maioria dos casos a porta que foi deixada aberta ficará disponível novamente apenas depois de reiniciar o sistema, mesmo religando o modem.


2. Comandos AT e Trabalhando com Modem

O trabalho com um modem é organizado através dos comandos АT. Aqueles que já usaram a Internet móvel a partir de um computador devem se lembrar da chamada "string de inicialização modem ", parecido mais ou menos com o seguinte: AT + CGDCONT = 1, "IP", "internet". Este é um dos comandos АT. Quase todos eles começam com o prefixo AT e terminam com 0x0d (carriage return).

Nós vamos usar o conjunto mínimo de comandos AT necessários para a implementação da funcionalidade desejada. Isto irá reduzir o esforço para assegurar a compatibilidade do conjunto de comandos com vários dispositivos.

Abaixo está a lista dos comandos AT utilizados pelo nosso processador para trabalhar com o modem:

 Comando Descrição
  ATE1                                    
  Habilitar eco
  AT+CGMI
  Obter o nome do fabricante
  AT+CGMM
  Obter o modelo do dispositivo
  AT^SCKS
  Obter status do cartão SIM
  AT^SYSINFO
  Obter informações sobre o sistema
  AT+CREG
  Obter o registro de inscrição na rede
  AT+COPS
  Obter o nome da operadora de telefonia móvel atual
  AT+CMGF
  Alternar entre modos de texto/PDU
  AT+CLIP
  Ativar identificação da linha
  AT+CPAS
  Obter status do modem
  AT+CSQ
  Obter qualidade do sinal
  AT+CUSD
  Enviar um pedido USSD
  AT+CALM
  Ativar o modo silencioso (aplicável aos telefones)
  AT+CBC
  Obter estatus da bateria (plicável aos telefones)
  AT+CSCA
  Obter o status da bateria (aplicável aos telefones)
  AT+CMGL
  Obter número do centro de serviço SMS
  AT+CPMS
  Obter lista de mensagens SMS
  AT+CMGD
  Apagar mensagem SMS a partir da memória
  AT+CMGR
  Ler mensagem SMS a partir da memória
  AT+CHUP
  Rejeitar chamada recebida
  AT+CMGS
  Enviar uma mensagem SMS


Eu não vou sair fora do tópico, descrevendo as sutilezas de trabalhar com os comandos AT. Há uma abundância de informações relevantes em fóruns técnicos. Além disso, tudo já foi implementado e a fim de criar um Expert Advisor capaz de trabalhar com um modem o que todos nós precisamos é incluir um arquivo de cabeçalho e começar a usar as funções e estruturas prontas. Isto é o que eu rei elaborar.


2.1. Funções

Inicalização da porta COM

bool InitComPort();

Valor de retorno: se inicializado com êxito - verdadeiro, caso contrário - falso. Ela é chamada a partir da função OnInit(), antes da inicialização do modem.

Desinicialização da porta COM:

void DeinitComPort();

Valor de retorno: nenhum. Esta função é chamada de OnDeinit().

Inicialização do modem:

void InitModem();

Valor de retorno: nenhum. Valor de retorno: nenhum. Ela é chamada a partir da função OnInit(), após uma inicialização bem-sucedida da porta COM.

Evento handler do Modem:

void ModemTimerProc();

Valor de retorno: nenhum. É chamado a partir da função OnTimer() em intervalos de 1 segundo.

Ler mensagem SMS pelo índice da memória do modem:

bool ReadSMSbyIndex(
   int index,             // SMS message index in the modem memory
   INCOMING_SMS_STR& sms  // Pointer to the structure where the message will be moved
   );

Valor de retorno: se lido com êxito - verdadeiro, caso contrário - falso.

Apagar mensagem SMS pelo índice da memória do modem:

bool DelSMSbyIndex(
   int index              // SMS message index in the modem memory
   );

Valor de retorno: se deletado com êxito - verdadeiro, caso contrário - falso.

Conversão do índice de qualidade de conexão para uma string:

string rssi_to_str(
   int rssi               // Connection quality index, values 0..31, 99
   );

Valor devolvido: uma string, por exemplo, "-55 dBm".

Enviando mensagem SMS:

bool SendSMS(
   string da,      // Recipient's phone number in international format
   string text,    // Text of the message, Latin characters and numbers, maximum length - 158 characters
   bool flash      // Flash message flag
   );

Valor de retorno: se enviado com êxito - verdadeiro, caso contrário - falso. Mensagens SMS só podem ser enviadas quando escritas com caracteres Latinos. Caracteres cirílicos são suportados apenas para mensagens SMS recebidas. Se for definido que flash=true, uma mensagem flash será enviada.


2.2. Eventos (funções chamadas pelo handler do modem)

Atualizando dados na estrutura do estado do modem:

void ModemChState();

Parâmetros passados: nenhum. Quando esta função é chamada pelo handler do modem, ela sugere que os dados foram atualizados na estrutura do modem (a descrição da estrutura será fornecida abaixo).

Chamada de entrada:

void IncomingCall(
   string number          // Caller number
   );

Parâmetros passados: número de chamada. Quando esta função é chamada pelo handler do modem, ela sugere que a chamada do "número" foi aceita e rejeitada.

Nova entrada de mensagem SMS:

void IncomingSMS(
   INCOMING_SMS_STR& sms  // SMS message structure
   );

Parâmetros passados: estrutura de mensagem SMS (a descrição da estrutura será fornecida abaixo). Quando esta função é chamada pelo handler do modem, ela sugere que há uma ou mais novas mensagens SMS não lidas na memória do modem. Se o número de mensagens é maior do que um, a mensagem mais recente será passada para esta função.

Memória SMS cheia:

void SMSMemoryFull(
   int n                  // Number of SMS messages in the modem memory
   );

Parâmetros passados: número de mensagens na memória do modem. Quando esta função é chamada pelo handler do modem, ela sugere que a memória SMS está cheia e o modem não aceitará novas mensagens até que a memória seja liberada.


2.3. Estrutura do estado dos parâmetros do modem

struct MODEM_STR
{
   bool     init_ok;          // The required minimum initialized
   //
   string   manufacturer;     // Manufacturer
   string   device;           // Model
   int      sim_stat;         // SIM status
   int      net_reg;          // Network registration state
   int      status;           // Modem status
   string   op;               // Operator
   int      rssi;             // Signal quality
   string   sms_sca;          // SMS center number
   int      bat_stat;         // Battery state
   int      bat_charge;       // Battery charge in percent (applicability depends on bat_stat)
   //
   double   bal;              // Mobile account balance
   string   exp_date;         // Mobile number expiration date
   int      sms_free;         // Package SMS available
   int      sms_free_cnt;     // Counter of package SMS used
   //
   int      sms_mem_size;     // SMS memory size
   int      sms_mem_used;     // Used SMS memory size
   //
   string   incoming;         // Caller number
};

MODEM_STR modem;

Esta estrutura é preenchida exclusivamente pelo processador de eventos do modem e deve ser utilizada por outras funções somente para leitura.

Abaixo está a descrição dos elementos de estrutura:

 ElementoDescrição
  modem.init_ok
  Uma indicação de que o modem foi inicializado com êxito.
  O valor inicial falso se torna verdadeiro após a inicialização completa.
  modem.manufacturer
  Fabricante do modem, por exemplo, "huawei".
  O valor inicial é "n/a".
  modem.device
  Modelo do modem, por exemplo, "E1550"
  O valor inicial é "n/a".
  modem.sim_stat
  Status do cartão SIM. Pode assumir os seguintes valores:
  -1 - sem dados
   0 - o cartão está em falta, bloqueado ou fora de ordem
   1 - o cartão está disponível
  modem.net_reg
  Registro do estado da rede. Pode assumir os seguintes valores:
  -1 - sem dados
   0 - não registrado
   1 - registrado
   2 - procurando
   3 - proibido
   4 - estado indefinido
   5 - registrado em roaming
  modem.status
  Status do Modem. Pode assumir os seguintes valores:
  -1 - inicialização
   0 - pronto
   1 - erro
   2 - erro
   3 - chamada recebida
   4 - chamada ativa
  modem.op
  Operadora de telefonia móvel atual.
Pode ser igual ao nome da operadora (por exemplo, "MTS UKR"),
  ou ao código de operadora internacional (por exemplo, "25501").
 O valor inicial é "n/a".
  modem.rssi
  Índice de qualidade do sinal. Pode assumir os seguintes valores:
  -1 - sem dados
   0 - sinal -113 dBm ou baixo
   1 - sinal -111 dBm
   2...30 - sinal -109...-53 dBm
  31 - sinal -51 dBm ou alto
  99 - sem dados
  Use a função rssi_to_str() para converter a uma string.
  modem.sms_sca
  Número do centro de serviço SMS. Está contida na memória do cartão SIM.
  É necessário para a geração de uma mensagem SMS de saída.
  Em casos raros, se o número não é salvo na memória do cartão SIM, então será
substituído pelo número nos parâmetros de entrada do Expert Advisor.
  modem.bat_stat
  Estado da bateria do Modem (aplicável aos telefones apenas).
  Pode assumir os seguintes valores:
  1 - sem dados
   0 - o dispositivo funciona com bateria
   1 - a bateria está disponível, mas o dispositivo não é alimentado por bateria
   2 - sem bateria
   3 - erro
  modem.bat_charge
  Carga da bateria em porcentagem.
  Ela pode assumir valores de 0 a 100.
  modem.bal
  Saldo da conta do celular. O valor obtido
  a partir da resposta da operadora para o pedido USSD correspondente.
  O valor inicial (antes da inicialização): -1000
  modem.exp_date
  Data de validade do número de celular. O valor obtido
  a partir da resposta da operadora para o pedido USSD correspondente.
  O valor inicial é "n/a".
  modem.sms_free
  Número de pacote SMS disponível. É calculado com a diferença entre
o número inicial e o contador do pacote SMS utilizado.
  modem.sms_free_cnt
  Contador do pacote SMS utilizado. O valor obtido
  a partir da resposta da operadora para o pedido USSD correspondente. O valor inicial é -1.
  modem.sms_mem_size
  Tamanho da memória do SMS Modem .
  modem.sms_mem_used
  Memória SMS Modem usada.
  modem.incoming
  Número da última chamada.
  O valor inicial é "n/a".


2.4. Estrutura da mensagem SMS

//+------------------------------------------------------------------+
//| SMS message structure                                            |
//+------------------------------------------------------------------+
struct INCOMING_SMS_STR
{
   int index;                //index in the modem memory
   string sca;               //sender's SMS center number
   string sender;            //sender's number
   INCOMING_CTST_STR scts;   //SMS center time label
   string text;              //text of the message
};

A etiqueta do tempo da central SMS é o momento quando uma determinada mensagem do remetente foi recebida na central SMS. A estrutura da etiqueta de tempo é a seguinte:

//+------------------------------------------------------------------+
//| Time label structure                                             |
//+------------------------------------------------------------------+
struct INCOMING_CTST_STR
{
   datetime time;            // time
   int gmt;                  // time zone
};

A zona de tempo é expressa em intervalos de 15 minutos. Assim, o valor 8 corresponde a GMT +02:00.

O texto da mensagem SMS recebida pode ser escrito usando caracteres Latino, bem como caracteres cirílicos. Codificações de 7-bit e UCS2 são suportados pelas mensagens recebidas. Mensagens longas não são implementada (tendo em conta o fato de que esta operação foi criada para comandos curtos).

Mensagens SMS só podem ser enviadas quando escritas com caracteres latinos. Comprimento máximo da mensagem é de 158 caracteres. No caso de uma mensagem mais longa, poderá ser enviada sem os caracteres em excesso do número especificado.


3. Desenvolvendo um Expert Advisor

Para iniciar, você precisa copiar o arquivo TrComPort.dll para a pasta Libraries e colocar os arquivos ComPort.mqh, modem.mqh e sms.mqh na pasta Include.

Em seguida, usando o Assistente criamos um novo Expert Advisor e adicionamos o mínimo necessário para trabalhar com o modem. É isso:

Incluir modem.mqh:

#include <modem.mqh>

Adicionar os parâmetros de entrada:

input string         str00="COM port settings";
input ComPortList    inp_com_port_index=COM3;   // Selecting the COM port
input BaudRateList   inp_com_baudrate=_9600bps; // Data rate
//
input string         str01="Modem";
input int            inp_refr_period=3;         // Modem query period, sec
input int            inp_ussd_request_tout=20;  // Timeout for response to a USSD request, sec
input string         inp_sms_service_center=""; // SMS service center number
//
input string         str02="Balance";
input int            inp_refr_bal_period=12;    // Query period, hr
input string         inp_ussd_get_balance="";   // Balance USSD request
input string         inp_ussd_bal_suffix="";    // Balance suffix
input string         inp_ussd_exp_prefix="";    // Prefix of the number expiration date
//
input string         str03="Number of package SMS";
input int            inp_refr_smscnt_period=6;  // Query period, hr
input string         inp_ussd_get_sms_cnt="";   // USSD request for the package service status
input string         inp_ussd_sms_suffix="";    // SMS counter suffix
input int            inp_free_sms_daily=0;      // Daily SMS limit

Funções chamadas pelo handler do modem:

//+------------------------------------------------------------------+
//| Called when a new SMS message is received                        |
//+------------------------------------------------------------------+
void IncomingSMS(INCOMING_SMS_STR& sms)
{  
} 

//+------------------------------------------------------------------+
//| SMS memory is full                                               |
//+------------------------------------------------------------------+
void SMSMemoryFull(int n)
{
}

//+------------------------------------------------------------------+
//| Called upon receiving an incoming call                           |
//+------------------------------------------------------------------+
void IncomingCall(string number)
{ 
}  

//+------------------------------------------------------------------+
//| Called after updating data in the modem status structure         |
//+------------------------------------------------------------------+
void ModemChState()
{
   static bool init_ok = false;
   if(modem.init_ok==true && init_ok==false)
   {
      Print("Modem initialized successfully");      
      init_ok = true;
   }
}

A inicialização da porta COM e do modem, juntamente com um temporizador definido em intervalos de 1 segundo devem ser adicionados a função OnInit():

int OnInit()
{  //---COM port initialization
   if(InitComPort()==false)
   {
      Print("Error when initializing the COM"+DoubleToString(inp_com_port_index+1,0)+" port");
      return(INIT_FAILED);
   }      
   //--- modem initialization
   InitModem();
   //--- setting the timer
   EventSetTimer(1); //1 second interval
   //      
   return(INIT_SUCCEEDED);
}

Na função OnTimer(), precisamos chamar o handler do modem:

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

É necessário chamar a desinicialização da porta COM na função OnDeinit():

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

Nós compilamos o código e veja: 0 erro(s).

Agora, executar o Expert Advisor, mas lembre-se de permitir a importação de DLL e selecionar a porta COM associada ao modem. Você deve ser capaz de ver as seguintes mensagens na aba "Expert Advisors":

A primeira execução

Fig. 2. Mensagens do Consultor Especialista após uma execução bem sucedida

Se você tem as mesmas mensagens, isso significa que o seu modem (telefone) é adequado para trabalhar com este Expert Advisor. Neste caso, nós avançamos.

Vamos desenhar uma tabela para a visualização dos parâmetros do modem. Ela será colocada no canto superior esquerdo da janela do terminal, sob a linha OHLC. A fonte do texto usada na tabela será monoespaçada, por exemplo, "Courier New".

//+------------------------------------------------------------------+
//| TextXY                                                           |
//+------------------------------------------------------------------+
void TextXY(string ObjName,string Text,int x,int y,color TextColor)
  {
//--- display the text string
   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);
  }
//+------------------------------------------------------------------+
//| Drawing the table of modem parameters                            |
//+------------------------------------------------------------------+
void DrawTab()
  {
   int   x=20, //horizontal indent
   y = 20,     //vertical indent
   dy = 15;    //step along the Y-axis
//--- draw the background
   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);
//--- port parameters
   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;
//--- modem parameters
   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;
//--- mobile account balance
   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;
//--- number of the last incoming call
   TextXY("str16","Incoming:        ",x,y,clrWhite); y+=(dy*3)/2;
//--- parameters of the last received SMS message
   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;
//--- text of the last received SMS message
   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 atualizar os dados na tabela, vamos usar a função RefreshTab():

//+------------------------------------------------------------------+
//| Refreshing values in the table                                   |
//+------------------------------------------------------------------+
void RefreshTab()
  {
   string str;
//--- COM port index:
   str="COM"+DoubleToString(PortID+1,0);
   ObjectSetString(0,"str0",OBJPROP_TEXT,"Port:            "+str);
//--- data rate:
   str=DoubleToString(inp_com_baudrate,0)+" bps";
   ObjectSetString(0,"str1",OBJPROP_TEXT,"Speed:           "+str);
//--- number of bytes received:
   str=DoubleToString(rx_cnt,0)+" bytes";
   ObjectSetString(0,"str2",OBJPROP_TEXT,"Rx:              "+str);
//--- number of bytes transmitted:
   str=DoubleToString(tx_cnt,0)+" bytes";
   ObjectSetString(0,"str3",OBJPROP_TEXT,"Tx:              "+str);
//--- number of port errors:
   str=DoubleToString(tx_err,0);
   ObjectSetString(0,"str4",OBJPROP_TEXT,"Err:             "+str);
//--- modem manufacturer and model:
   str=modem.manufacturer+" "+modem.device;
   ObjectSetString(0,"str5",OBJPROP_TEXT,"Modem:           "+str);
//--- SIM card status:
   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);
//--- network registration:
   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);
//--- name of mobile operator:
   ObjectSetString(0,"str8",OBJPROP_TEXT,"Operator:        "+modem.op);
//--- SMS service center number
   ObjectSetString(0,"str9",OBJPROP_TEXT,"SMSC:            "+modem.sms_sca);
//--- signal level:
   if(modem.rssi==-1)
      str="n/a";
   else
      str=rssi_to_str(modem.rssi);
   ObjectSetString(0,"str10",OBJPROP_TEXT,"RSSI:            "+str);
//--- battery status (applicable to phones):
   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);
//--- modem status:
   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);
//--- mobile account balance:
   if(modem.bal==-10000)
      str="n/a";
   else
      str=DoubleToString(modem.bal,2)+" "+inp_ussd_bal_suffix;
   ObjectSetString(0,"str13",OBJPROP_TEXT,"Balance:         "+str);
//--- mobile number expiration date:
   ObjectSetString(0,"str14",OBJPROP_TEXT,"Expiration date: "+modem.exp_date);
//--- package SMS available:
   if(modem.sms_free<0)
      str="n/a";
   else
      str=DoubleToString(modem.sms_free,0);
   ObjectSetString(0,"str15",OBJPROP_TEXT,"Free SMS:        "+str);
//--- SMS memory full:
   if(sms_mem_full==true)
      str="Yes";
   else
      str="No";
   ObjectSetString(0,"str17",OBJPROP_TEXT,"SMS mem full:    "+str);
//---
   ChartRedraw(0);
  }

A função DelTab() exclui a tabela:

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

Vamos adicionar funções para trabalhar com a tabela para handlers de eventos OnInit() e OnDeinit(), assim como a função ModemChState():

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- COM port initialization
   if(InitComPort()==false)
     {
      Print("Error when initializing the COM port"+DoubleToString(inp_com_port_index+1,0));
      return(INIT_FAILED);
     }
//---
   DrawTab();
//--- modem initialization
   InitModem();
//--- setting the timer 
   EventSetTimer(1);//1 second interval
//---
   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("Modem status changed");
   if(modem.init_ok==true && init_ok==false)
     {
      Print("Modem initialized successfully");
      init_ok=true;
     }
//---
   RefreshTab();
  }

Além disso, nós adicionamos a oportunidade para atualizar o número da última chamada recebida na tabela para a função IncomingCall():

//+------------------------------------------------------------------+
//| Called upon receiving an incoming call                           |
//+------------------------------------------------------------------+
void IncomingCall(string number)
{    
   //--- update the number of the last incoming call:
   ObjectSetString(0, "str16",OBJPROP_TEXT, "Incoming:        "+number);   
} 

Agora, compile o código e execute o Expert Advisor. Você poderá ver o seguinte relatório na janela do terminal:

Parâmetros de status do modem

Fig. 3. Parâmetros do modem

Tentando chamar o modem e a chamada sendo rejeitada, o seu número será exibido na linha "Incoming".


4. Trabalhando com Pedidos USSD

A conta do celular não coberta no tempo adequado pode alterar o funcionamento do Expert Advisor no momento menos apropriado. Assim, a função que verifica o saldo da conta é um dos mais importantes. Para verificar o saldo da conta do celular geralmente usamos os pedidos USSD. Além disso, vamos usar pedidos USSD para obter informações sobre o número de pacote SMS disponível.

Os dados para a geração de pedidos e respostas de processamento recebidos estão localizados nos parâmetros de entrada:

input string         str02="=== Balance ======";
input int            inp_refr_bal_period=12;  //query period, hr
input string         inp_ussd_get_balance=""; //balance USSD request
input string         inp_ussd_bal_suffix="";  //balance suffix
input string         inp_ussd_exp_prefix="";  //prefix of the number expiration date
//
input string         str03="= Number of package SMS ==";
input int            inp_refr_smscnt_period=6;//query period, hr
input string         inp_ussd_get_sms_cnt=""; //USSD request for the package service status
input string         inp_ussd_sms_suffix="";  //SMS counter suffix
input int            inp_free_sms_daily=0;    //daily SMS limit

Se o número do pedido não for determinado, a solicitação não será processada. Como alternativa, o pedido será enviado logo após a inicialização do modem e será enviado repetidamente após o período de tempo especificado. Além disso, o pedido não será processado se o seu modem (telefone) não suporta o comando de IT relevante (tratando-se de modelos antigos de celulares). 

Suponha que na sequência do pedido solicitado você recebe a seguinte resposta da sua operadora:

7.13 UAH, expira em 22.05.2014. Plano do Telefone - Super MTS 3D Null 25.

Para garantir que o handler identifique a resposta correta, o sufixo do saldo deve ser definido como "UAH" e o prefixo da data do número de vencimento deve ser "expires on".

Desde que esperamos o nosso Expert Advisor envie muitas mensagens SMS, seria interessante comprar um pacote de SMS da sua operadora, ou seja, um serviço pelo qual você receba um certo número de mensagens SMS por uma pequena taxa. Neste caso, pode ser muito útil sabermos quantos créditos SMS ainda estão disponíveis. Isto também pode ser feito usando um pedido USSD. A operadora normalmente responde com o número SMS usado em vez dos disponíveis.

Suponhamos que você recebeu a seguinte resposta da sua operadora:

Saldo: 69 minutos de chamadas locais para hoje. Usado hoje: 0 SMS e 0 MB.

Neste caso, o contador sufixo SMS deve ser definido como "SMS" e o do limite diário deve ser fixado de acordo com os termos do pacote SMS e condições. Por exemplo, se você recebe 30 mensagens de texto por dia e o pedido devolveu o valor de 10, isso significa que você tem 30-10=20 SMS disponíveis. Este número irá ser colocado pelo handler para o elemento apropriado da estrutura de estado do modem.

ATENÇÃO! Tenha muito cuidado com os números de pedido USSD! Enviar um pedido errado pode ter conseqüências indesejáveis, por exemplo: permitir algum serviço pago indesejado!

Para que o nosso Expert Advisor comece a trabalhar com os pedidos USSD, somente precisamos especificar os parâmetros de entrada relevantes.

Por exemplo, os parâmetros para a operadora de telefonia móvel da Ucrânia, MTS Ucrânia, serão os seguintes:

Parâmetros do pedido para o saldo disponível

Fig. 4. Parâmetros do pedido USSD para o saldo disponível

Parâmetros do pedido para o número de pacotes SMS disponíveis

Fig. 5. Parâmetros do pedido USSD para o número de pacotes de SMS disponíveis

Defina os valores relevantes para a sua operadora de celular. Depois disso, o saldo disponível na sua conta de celular e o número de SMS disponível será exibido na tabela de status do modem:

Saldo disponível

Fig. 6. Parâmetros obtidos a partir das respostas USSD

Enquanto escrevia este artigo, a minha operadora de celular estava enviando uma propaganda de Natal em vez do número data de validade. Consequentemente, não foi possível ao handler obter o valor de data, razão pela qual podemos ver "n/a" na linha "Data de validade". Por favor, note que todas as respostas da operadora serão exibidas na aba "Expert Advisors".

Respostas da operadora

Fig. 7. Respostas da Operadora exibidas na aba "Expert Advisors"


5. Envio de mensagens SMS

Nós vamos começar a adicionar funções úteis, por exemplo, o envio de mensagens SMS informando o atual lucro, patrimônio líquido e o número de posições abertas. O envio será iniciado por uma chamada recebida.

Tal resposta é certamente esperada somente no caso do número de administrador, por isso vamos ter um outro parâmetro de entrada:

input string         inp_admin_number="+XXXXXXXXXXXX";//administrator's phone number

O número deve ser colocado no formato internacional, incluindo o "+" antes do número.

O número de verificação, assim como a geração do texto SMS e o envio deve ser adicionado ao handler de chamada de entrada:

//+------------------------------------------------------------------+
//| Called upon receiving an incoming call                           |
//+------------------------------------------------------------------+
void IncomingCall(string number)
{
   bool result;
   if(number==inp_admin_number)
   {
      Print("Administrator's phone number. Sending SMS.");
      //
      string mob_bal="";
      if(modem.bal!=-10000)//mobile account balance
         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 sent successfully");
      else
         Print("Error when sending SMS");               
   }   
   else
      Print("Unauthorized number ("+number+")"); 
   //--- update the number of the last incoming call:
   ObjectSetString(0, "str16",OBJPROP_TEXT, "Incoming:        "+number);   
}  

Agora, se há uma chamada para o modem a partir do número do administrador inp_admin_number, uma mensagem SMS será enviada em resposta:

SMS em resposta a chamada de entrada:

Fig. 8. A mensagem SMS enviada pelo Expert Advisor em resposta a chamada recebida do número do telefone do administrador

Aqui, podemos ver os valores atuais de lucro e patrimônio líquido, bem como o número de posições abertas e o saldo da conta de celular.


6. Monitoramento da Conexão com o Servidor de Negócio

Vamos adicionar notificações em caso de perda de conexão e o restabelecimento com o servidor de negócio. Para isso, vamos verificar a conectividade do negócio uma vez a cada 10 segundos usando TerminalInfoInteger() com o identificador de propriedade TERMINAL_CONNECTED.

Para filtrar as perdas de conexão de curta duração, usaremos um sistema histerese, que deve ser adicionado à lista dos parâmetros de entrada:

input int            inp_conn_hyst=6; //Hysteresis, х10 sec

O valor 6 significa que a conexão é considerada perdida se não houver nenhuma conexão por mais de 6 * 10 = 60 segundos. Da mesma forma, a conexão será considerada restabelecida, se estiver disponível para mais do que 60 segundos O tempo local da primeira falta registrada da conectividade será considerada o tempo da perda de conexão, enquanto que o primeiro tempo local quando a conexão se tornou disponível será considerado o tempo de recuperação.

Para implementar isso, adicionamos o seguinte código para a função OnTimer():

   static int s10 = 0;//pre-divider by 10 seconds
   static datetime conn_time;
   static datetime disconn_time;
   if(++s10>=10)
   {//--- once every 10 seconds
      s10 = 0;
      //
      if((bool)TerminalInfoInteger(TERMINAL_CONNECTED)==true)
      {
         if(cm.conn_cnt==0)             //first successful query in the sequence
            conn_time = TimeLocal();    //save the time
         if(cm.conn_cnt<inp_conn_hyst)
         {
            if(++cm.conn_cnt>=inp_conn_hyst)
            {//--- connection has been stabilized
               if(cm.connected == false)
               {//--- if there was a long-standing connection loss prior to that
                  cm.connected = true;
                  cm.new_state = true;
                  cm.conn_time = conn_time;
               }
            }
         }
         cm.disconn_cnt = 0;
      }
      else
      {
         if(cm.disconn_cnt==0)          //first unsuccessful query in the sequence
            disconn_time = TimeLocal(); //save the time
         if(cm.disconn_cnt<inp_conn_hyst)
         {
            if(++cm.disconn_cnt>=inp_conn_hyst)
            {//--- long-standing connection loss
               if(cm.connected == true)
               {//--- if the connection was stable prior to that
                  cm.connected = false;
                  cm.new_state = true;
                  cm.disconn_time = disconn_time;
               }
            }
         }
         cm.conn_cnt = 0;
      }
   }
   //
   if(cm.new_state == true)
   {//--- connection status changed
      if(cm.connected == true)
      {//--- connection is available
         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);//sending message
      }
      else
      {//--- no connection
         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);//sending message
      }
      cm.new_state = false;
   }

A estrutura cm é a seguinte:

//+------------------------------------------------------------------+
//| Structure of monitoring connection with the terminal             |
//+------------------------------------------------------------------+
struct CONN_MON_STR
  {
   bool              new_state;    //flag of change in the connection status
   bool              connected;    //connection status
   int               conn_cnt;     //counter of successful connection queries
   int               disconn_cnt;  //counter of unsuccessful connection queries
   datetime          conn_time;    //time of established connection
   datetime          disconn_time; //time of lost connection
  };

CONN_MON_STR cm;//structure of connection monitoring

O texto da mensagem SMS irá indicar o tempo da conexão perdida (ou restabelecida) com o servidor de negócio, bem como o tempo durante o qual a conexão ficou disponível (ou não disponível), calculado com a diferença entre o tempo da conexão estabelecida e o tempo da conexão perdida. Para converter a diferença de tempo a partir dos segundos para hh: mm: ss, vamos adicionar a função 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 garantir que o Expert Advisor não envie uma mensagem de texto sobre a conexão estabelecida cada vez que o Expert Advisor é executado, nós adicionamos a função conn_mon_init() definindo os valores para o elementos de estrutura cm como se a conexão já fora estabelecida. Neste caso, a conexão será considerado estabelecida no horário local de funcionamento do Expert Advisor. Esta funcionalidade deve ser chamada a partir da função 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;
} 

Agora, compile e execute o Expert Advisor. Em seguida, desconecte o seu computador da Internet. Em até 60 segundos, você receberá uma mensagem dizendo que a conexão com o servidor foi perdida. Ligue novamente a Internet. Em até 60 segundos, você receberá uma mensagem sobre a conexão restabelecida indicando o tempo total desconectado:

A mensagem sobre a perda da conexão A mensagem sobre o restabelecimento da conexão

Fig. 9. As mensagens de texto notificando a conexão perdida e restabelecida com o servidor


7. Envio de Relatórios sobre a Abertura e Fechamento de Posições

Para controlar a abertura e fechamento de posições vamos adicionar o seguinte código na função OnTradeTransaction():

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

onde ps é o buffer das estruturas 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];

O buffer é necessário quando mais do uma posição for fechada (ou aberta) durante um curto período de tempo. Quando uma posição é aberta ou fechada, depois que o negócio é adicionado ao histórico, temos todos os parâmetros necessários e definimos a flag new_event.

Abaixo está o código que será adicionado à função OnTimer() para verificar as flags new_event e gerar os relatórios SMS:

   //--- processing of the opening/closing of positions
   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());//retrieve the history for the last hour
         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);
   }

Agora, compile e execute o Expert Advisor. Vamos tentar comprar AUDCAD, com o tamanho do lote a 0,14. O Expert Advisor irá enviar a seguinte mensagem SMS: "Buy 0.14 AUDCAD, price=0.96538, entry: in". Depois de algum tempo vamos fechar a posição e recebo a seguinte mensagem de texto em relação ao fechamento da posição:

A mensagem sobre a abertura da posição A mensagem sobre o fechamento da posição

Fig. 10. mensagens de texto sobre a abertura de posição (entry: in) e fechamento (entry: out)


8. Processamento de Entrada de Mensagens SMS para Gerenciamento de Posição Aberta

Até agora o nosso Expert Advisor tem apenas mensagens enviadas para o número de telefone do administrador. Vamos agora ensiná-lo a receber e executar os comandos SMS. Por exemplo, isto pode ser útil no fechamento de todas ou de algumas posições abertas. Como sabemos, não há nada melhor do que ter sua posição fechada na hora certa.

Mas primeiro devemos ter certeza de que as mensagens SMS são recebidas corretamente. Para fazer isso, nós adicionamos na exibição da última mensagem recebida a função IncomingSMS():

//+------------------------------------------------------------------+
//| Called when a new SMS message is received                        |
//+------------------------------------------------------------------+
void IncomingSMS(INCOMING_SMS_STR& sms)
{
   string str, strtmp;
   //Number from which the last received SMS message was sent:
   ObjectSetString(0, "str18", OBJPROP_TEXT, "SMS number:      "+sms.sender);
   //Date and time of sending the last received SMS message:
   str = TimeToString(sms.scts.time,TIME_DATE|TIME_SECONDS);
   ObjectSetString(0, "str19", OBJPROP_TEXT, "SMS date/time:   "+str);
   //Text of the last received SMS message:
   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);  
}

Se agora enviar uma mensagem SMS para o modem, ela será exibida na tabela:

Nova mensagem SMS

Fig. 11. Como é exibida a mensagem SMS recebida na janela do terminal

Por favor observe que todas as mensagens SMS recebidas são exibidas na aba "Expert Advisors" da seguinte forma: <index_in_modem_memory>text_of_the_message:

Mensagem SMS exibida na aba "Expert Advisors"

Fig. 12. Como é exibida a mensagem SMS recebida na aba "Expert Advisors"

A palavra "close" será usado como um comando para o fechamento das operações de negócios. Deve ser seguido pelo espaço e pelo parâmetro - o símbolo da posição que precisa ser fechada, ou "todas" em caso da necessidade de fechar todas as posições. Caso não tenha importância o processamento do texto da mensagem, usamos a função StringToUpper(). Ao analisar a mensagem, certifique-se de verificar se o número de telefone do remetente corresponde o número do administrador configurado.

Além disso, deve-se notar que pode haver casos de uma mensagem SMS ser recebida com um atraso considerável (devido a problemas técnicos no lado da operadora, etc.) Nesses casos, você não pode levar em conta o comando recebido na mensagem, pois a situação do mercado poderia ter mudado. Em vista disso, apresentamos um outro parâmetro de entrada:

input int            inp_sms_max_old=600; //SMS command expiration, sec

O valor 600 indica que os comandos levando mais de 600 segundos (10 minutos) para serem entregues serão ignorados. Por favor observe que o método de verificar o tempo de entrega utilizado no exemplo implica que o centro de serviço SMS e o dispositivo no qual o Expert Advisor está em execução estão situados no mesmo fuso horário.

Para processar comandos SMS, vamos adicionar o seguinte código para a função IncomingSMS():

   if(sms.sender==inp_admin_number)
   {
      Print("SMS from the administrator");
      datetime t = TimeLocal();
      //--- message expiration check
      if(t-sms.scts.time<=inp_sms_max_old)
      {//--- check if the message is a command
         string cmdstr = sms.text;
         StringToUpper(cmdstr);//convert everything to upper case
         int pos = StringFind(cmdstr, "CLOSE", 0);
         cmdstr = StringSubstr(cmdstr, pos+6, 6);
         if(pos>=0)
         {//--- command. send it for processing
            ClosePositions(cmdstr);            
         } 
      }
      else
         Print("The SMS command has expired");
   }   

Se a mensagem SMS foi entregue pelo administrador, não expirou e representa um comando (contém a palavra-chave "Close"), enviamos seu parâmetro para processamento pela função ClosePositions():

uint ClosePositions(string sstr)
{//--- close the specified positions
   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);
}

Esta função verifica se alguma posição aberta é correspondente em termos do parâmetro (símbolo) recebido no comando. Posições que satisfazem esta condição são fechados usando a função 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 when getting current prices");
      return(false);
   }
   //   
   if(pos_type==POSITION_TYPE_BUY)
   {//--- closing a BUY position - SELL
      request.type = ORDER_TYPE_SELL;
   }
   else if(pos_type==POSITION_TYPE_SELL)
   {//--- closing a SELL position - 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("Order executed successfully");
         return(true);
      }
   }
   else
   {
      Print("Order parameter error: ", GetLastError(),", Trade server return code: ", result.retcode);     
      return(false);
   }      
   //
   return(false);
}

Após o processamento de ordens com sucesso, a função para monitoramento das mudanças de posição vai gerar e enviar uma notificação via SMS.


9. Apagando Mensagens a partir da Memória do Modem

Por favor, note que o handler do modem não apaga mensagens SMS recebidas por conta própria. Quando a memória SMS fica cheia ao longo do tempo, o handler irá chamar a função SMSMemoryFull() e passar para ela o número atual de mensagens na memória do modem. Você pode excluí-las todas ou fazer de uma forma seletiva. O modem não aceitará novas mensagens até que a memória seja liberada.

//+------------------------------------------------------------------+
//| SMS memory is full                                               |
//+------------------------------------------------------------------+
void SMSMemoryFull(int n)
{
   sms_mem_full = true;
   for(int i=0; i<n; i++)
   {//delete all SMS messages
      if(DelSMSbyIndex(i)==false)
         break;
      else
         sms_mem_full = false;   
   }
}

Você também pode excluir mensagens SMS logo após elas terem sido processadas. Quando a função IncomingSMS() é chamada pelo handler do modem, a estrutura INCOMING_SMS_STR passa o índice da mensagem na memória do modem, o que permite apagar a mensagem logo após o processamento usando a função DelSMSbyIndex():


Conclusão

Este artigo colocou o desenvolvimento do Expert Advisor que usa um modem GSM para monitorar remotamente o terminal de negociação. Nós consideramos os métodos para obter informações sobre as aberturas de posições, o lucro em andamento e outros dados usando notificações SMS. Também implementamos as funções básicas para o gerenciamento de abertura de posição usando comandos SMS. O exemplo fornecido apresenta comandos em Inglês, mas você pode usar os comandos russos igualmente bem (para não perder tempo com a alternância entre diferentes layouts de teclado no seu telefone).

Finalmente, vamos verificar o comportamento do nosso Expert Advisor quando se lida com um velho telefone celular lançado no mercado a mais de 10 anos atrás. Dispositivo - Siemens M55. Vamos ligá-lo:

Parâmetros do celular Siemens M55Siemens M55

Fig. 13. Conectando o celular Siemens M55

Siemens M55, "Expert Advisors" tab

Fig. 14. Inicialização bem-sucedida da Siemens M55, aba "Expert Advisors"

Você pode ver que todos os parâmetros necessários foram obtidos. O único problema são os dados recebidos de pedidos USSD. O Siemens M55 não suporta o comando AT para trabalhar com pedidos USSD, fora isto a sua funcionalidade é tão boa quanto o de qualquer modem atual, por isso pode ser usado para trabalhar com o nosso Expert Advisor.

Traduzido do russo pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/ru/articles/797

Arquivos anexados |
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)
Contratos futuros contínuos em MetaTrader 5 Contratos futuros contínuos em MetaTrader 5
O curto período dos contratos futuros complica sua análise técnica, é tecnicamente difícil de analisar este tipo de ativo. Por exemplo, o número de barras no gráfico diário do contrato futuro do índice de Ações Ucraniana UX-9.13 é maior do que 100, portanto o trader cria longos contratos futuros sintéticos. Este artigo explica como emendar contratos futuros com datas diferentes no terminal MetaTrader 5.
Indicador para gráfico Renko Indicador para gráfico Renko
O artigo descreve um exemplo do gráfico Renko e implementação no MQL5 como um indicador. As modificações deste indicador o distingue de um gráfico clássico. Pode ser construído tanto na janela do indicador como no gráfico principal. Além disso, existe o indicador ZigZag, onde pode-se encontrar alguns exemplos de implementação no gráfico.
SQL e MQL5: Trabalhando com Banco de Dados SQLite SQL e MQL5: Trabalhando com Banco de Dados SQLite
Este artigo é destinado aos desenvolvedores interessados ​​em usar SQL em seus projetos. Ele explica as funcionalidades e vantagens do SQLite. O artigo não exige conhecimentos especiais de funções SQLite, mas é interessante um conhecimento mínimo de SQL.
Guia prático MQL5: Desenvolvimento de um Indicador de Símbolos Múltiplos para Análise de Divergência de Preço Guia prático MQL5: Desenvolvimento de um Indicador de Símbolos Múltiplos para Análise de Divergência de Preço
Neste artigo, vamos considerar o desenvolvimento de um indicador de símbolos múltiplos para análise de divergência de preço dentro de um período de tempo determinado. Os temas centrais já foram discutidas no artigo anterior sobre programação de indicadores de múltiplas moedas: "Guia prático do MQL5: Desenvolvimento de um Indicador de Símbolos Múltiplos em MQL5". Então, desta vez vamos focar apenas nas novas características e funções que foram alteradas drasticamente. Se você é novo em programação de indicadores de múltiplas moedas, primeiro eu recomendo a leitura do artigo anterior.