Trabalhando com modem GSM a partir de um Expert Advisor MQL5
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.
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:
Elemento | Descriçã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":
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:
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:
Fig. 4. Parâmetros do pedido USSD para o saldo disponível
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:
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".
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:
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:
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:
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:
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:
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:
Fig. 13. Conectando o celular Siemens M55
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
- Aplicativos de negociação gratuitos
- 8 000+ sinais para cópia
- Notícias econômicas para análise dos mercados financeiros
Você concorda com a política do site e com os termos de uso