Lavorare con il modem GSM da un Expert Advisor MQL5

Serhii Shevchuk | 12 gennaio, 2022

Introduzione

Attualmente esistono un discreto numero di mezzi per un comodo monitoraggio remoto di un conto di trading: terminali mobili, notifiche push, lavoro con ICQ. Ma tutte richiedono una connessione a Internet. Questo articolo descrive il processo di creazione di un Expert Advisor che ti consentirà di rimanere in contatto con il tuo terminale di trading anche quando Internet mobile non è disponibile, tramite chiamate e messaggi di testo. Inoltre, questo Expert Advisor sarà in grado di avvertirti se la connessione viene persa o ristabilita con il server di trading.

A questo scopo, andrebbe bene praticamente qualsiasi modem GSM, così come la maggior parte dei telefoni con la funzione modem. Per illustrare, ho scelto Huawei E1550, poiché questo modem è uno dei dispositivi più utilizzati nel suo genere. Inoltre, alla fine dell'articolo, proveremo a sostituire il modem con un vecchio cellulare Siemens M55 (rilasciato nel 2003) per vedere cosa succede.

Ma prima, qualche parola su come inviare un byte di dati da un Expert Advisor a un modem.


1. Lavorare con la porta COM

Dopo aver collegato il modem al computer e aver installato tutti i driver necessari, sarai in grado di vedere una porta COM virtuale nel sistema. Tutte le operazioni future con il modem vengono eseguite tramite questa porta. Di conseguenza, per scambiare dati con il modem, è necessario prima accedere alla porta COM.

Il modem come visualizzato in gestione dispositivi

Fig. 1. Il modem Huawei è collegato alla porta COM3

Qui avremo bisogno di una libreria DLL TrComPort.dll che è distribuita liberamente in Internet insieme ai file sorgente. Verrà utilizzato per configurare la porta COM, interrogare il suo stato, nonché per ricevere e inviare dati. Per farlo utilizzeremo le seguenti funzioni:

#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

I tipi di dati trasmessi dovevano essere leggermente modificati per la compatibilità con MQL5.

La struttura di TrComPortParameters è la seguente:

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

La maggior parte dei dispositivi funziona con le seguenti impostazioni: 8 bit di dati, nessun controllo di parità, 1 bit di stop. Pertanto, tra tutti i parametri della porta COM ha senso aggiungere ai parametri dell'Expert Advisor solo il numero della porta COM e la velocità dei dati:

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

La funzione di inizializzazione della porta COM sarà quindi la seguente:

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

In caso di inizializzazione riuscita, la variabile PortID memorizzerà l'identificatore della porta COM aperta.

Va notato qui che gli identificatori sono numerati da zero, quindi l'identificatore della porta COM3 sarà uguale a 2. Ora che la porta è aperta possiamo scambiare dati con il modem. E, tra l'altro, non solo con un modem. L'accesso alla porta COM dall'Expert Advisor apre grandi opportunità di creatività a chi è bravo a saldare: puoi collegare l'Expert Advisor a un LED o a un display di testo mobile per mostrare i prezzi di borsa o di mercato di determinate coppie di valute.

La funzione TrComPortGetQueue deve essere utilizzata per ottenere i dettagli dei dati nella coda del ricevitore e trasmettitore della 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
   );

In caso di errore restituisce un valore negativo del codice di errore. Una descrizione dettagliata dei codici di errore è disponibile nell'archivio con i codici sorgente della libreria TrComPort.dll.

Se la funzione restituisce un numero di dati diverso da zero nel buffer di ricezione, è necessario leggerli. A tale scopo utilizziamo la funzione 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)
   );

In caso di errore restituisce un valore negativo del codice di errore. Il numero di byte di dati deve corrispondere al valore restituito dalla funzione TrComPortGetQueue.

Per utilizzare il timeout predefinito (impostato all'inizializzazione della porta COM), è necessario passare il valore di -1.

Per trasmettere i dati alla porta COM, utilizziamo la funzione 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)
   );

Esempio di applicazione. In risposta al messaggio che dice "Ciao mondo!", dovremmo inviare "Buona giornata!".

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

Particolare attenzione dovrebbe essere prestata alla funzione di chiusura del porto:

int TrComPortClose(
   int portid         // Port identifier
   );  

Questa funzione deve essere sempre presente durante il processo di deinizializzazione dell'Expert Advisor. Nella maggior parte dei casi la porta lasciata aperta tornerà disponibile solo dopo aver riavviato il sistema. In effetti, anche spegnere e riaccendere il modem potrebbe non essere d'aiuto.


2. Comandi AT e utilizzo del modem

Il funzionamento con un modem è organizzato utilizzando i comandi AT. Chi di voi ha mai utilizzato Internet mobile da un computer deve ricordare la cosiddetta "stringa di inizializzazione del modem", che ha approssimativamente il seguente aspetto: AT+CGDCONT=1,"IP","internet". Questo è uno dei comandi AT. Quasi tutti iniziano con il prefisso AT e terminano con 0x0d (ritorno a capo).

Utilizzeremo il set minimo di comandi AT richiesti per l'implementazione della funzionalità desiderata. Ciò ridurrà lo sforzo per garantire la compatibilità del set di comandi con vari dispositivi.

Di seguito l'elenco dei comandi AT utilizzati dal nostro gestore per lavorare con il modem:

 Comando Descrizione
  ATE1                                    
  Abilita eco
  AT+CGMI
  Ottieni il nome del produttore
  AT+CGMM
  Ottieni il modello del dispositivo
  AT^SCKS
  Ottieni lo stato della carta SIM
  AT^SYSINFO
  Informazioni di sistema
  AT+CREG
  Ottieni lo stato di registrazione della rete
  AT+COPS
  Ottieni il nome dell'attuale operatore di telefonia mobile
  AT+CMGF
  Passa tra le modalità testo/PDU
  AT+CLIP
  Abilita l'identificazione della linea chiamante
  AT+CPAS
  Ottieni lo stato del modem
  AT+CSQ
  Ottieni la qualità del segnale
  AT+CUSD
  Invia una richiesta USSD
  AT+CALM
  Abilita modalità silenziosa (applicabile ai telefoni)
  AT+CBC
  Ottieni lo stato della batteria (applicabile ai telefoni)
  AT+CSCA
  Ottieni il numero del centro servizi SMS
  AT+CMGL
  Ottieni l'elenco dei messaggi SMS
  AT+CPMS
  Seleziona la memoria per i messaggi SMS
  AT+CMGD
  Elimina messaggio SMS dalla memoria
  AT+CMGR
  Leggi il messaggio SMS dalla memoria
  AT+CHUP
  Rifiuta chiamata in arrivo
  AT+CMGS
  Invia un messaggio SMS


Non andrò fuori tema, descrivendo le sottigliezze del lavoro con i comandi AT. Ci sono molte informazioni rilevanti sui forum tecnici. Inoltre, tutto è già stato implementato e per creare un Expert Advisor in grado di lavorare con un modem, è sufficiente includere un file di intestazione e iniziare a utilizzare funzioni e strutture già pronte. Questo è ciò su cui andrò ad approfondire.


2.1. Funzioni

Inizializzazione della porta COM:

bool InitComPort();

Valore restituito: se inizializzato con successo - vero, altrimenti - falso. Viene chiamato dalla funzione OnInit() prima dell'inizializzazione del modem.

Deinizializzazione della porta COM:

void DeinitComPort();

Valore restituito: nessuno. Viene chiamato dalla funzione OnDeinit().

Inizializzazione del modem:

void InitModem();

Valore restituito: nessuno. Viene chiamato dalla funzione OnInit() dopo un'inizializzazione riuscita della porta COM.

Gestore di eventi del modem:

void ModemTimerProc();

Valore restituito: nessuno. Viene chiamato dalla funzione OnTimer() a intervalli di 1 secondo.

Lettura del messaggio SMS per indice dalla memoria del 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
   );

Valore restituito: se letto con successo - vero, altrimenti - falso.

Cancellazione SMS per indice dalla memoria del modem:

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

Valore restituito: se cancellato con successo - vero, altrimenti - falso.

Conversione dell'indice di qualità della connessione in una stringa:

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

Valore restituito: una stringa, ad es. "-55 dBm".

Invio messaggio 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
   );

Valore restituito: se inviato con successo - vero, altrimenti - falso. I messaggi SMS possono essere inviati solo se scritti utilizzando caratteri latini. I caratteri cirillici sono supportati solo per i messaggi SMS in arrivo. Se è impostato flash=true, verrà inviato un messaggio flash.


2.2. Eventi (funzioni chiamate dal gestore del modem)

Aggiornamento dei dati nella struttura di stato del modem:

void ModemChState();

Parametri passati: nessuno. Quando questa funzione viene chiamata dall'handler del modem, suggerisce che i dati sono stati aggiornati nella struttura del modem (la descrizione della struttura verrà fornita di seguito).

Chiamata in arrivo:

void IncomingCall(
   string number          // Caller number
   );

Parametri passati: numero chiamante. Quando questa funzione viene chiamata dal gestore del modem, suggerisce che la chiamata in arrivo dal numero 'numero' è stata accettata e rifiutata.

Nuovo messaggio SMS in arrivo:

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

Parametri passati: Struttura del messaggio SMS (di seguito verrà fornita la descrizione della struttura). Quando questa funzione viene chiamata dal gestore del modem, suggerisce che nella memoria del modem sono presenti uno o più nuovi messaggi SMS non letti. Se il numero di messaggi non letti è maggiore di uno, il messaggio più recente verrà passato a questa funzione.

Memoria SMS piena:

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

Parametri passati: numero di messaggi nella memoria del modem. Quando questa funzione viene chiamata dal gestore del modem, suggerisce che la memoria SMS è piena e il modem non accetterà nuovi messaggi fino a quando la memoria non verrà liberata.


2.3. Struttura dello stato dei parametri del 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;

Questa struttura è compilata esclusivamente dal gestore di eventi del modem e dovrebbe essere utilizzata da altre funzioni solo per la lettura.

Di seguito la descrizione degli elementi della struttura:

 ElementoDescrizione
  modem.init_ok
  Un'indicazione che il modem è stato inizializzato con successo.
  Il valore iniziale di false diventa vero al termine dell'inizializzazione.
  modem.produttore
  Produttore di modem, ad es. "huawei".
  Il valore iniziale è "n/a".
  modem.device
  Modello di modem, ad es "E1550"
  Il valore iniziale è "n/a".
  modem.sim_stat
  Stato della carta SIM. Può assumere i seguenti valori:
  -1 - Nessun dato
   0 - la carta è mancante, bloccata o fuori servizio
   1 - la carta è disponibile
  modem.net_reg
  Stato di registrazione della rete. Può assumere i seguenti valori:
  -1 - Nessun dato
   0 - non registrato
   1 - Registrato
   2 - ricerca
   3 - Non consentito
   4 - stato non definito
   5 - registrato in roaming
  modem.status
  Stato del modem. Può assumere i seguenti valori:
  -1- Inizializzazione
   0 - pronto
   1 - Errore
   2 - Errore
   3 - chiamata in arrivo
   4 - chiamata attiva
  modem.op
  Attuale operatore di telefonia mobile.
  Può essere uguale al nome dell'operatore (es "MTS UKR"),
  o il codice operatore internazionale (es. "25501").
 Il valore iniziale è "n/a".
  modem.rssi
  Indice di qualità del segnale. Può assumere i seguenti valori:
  -1 - Nessun dato
   0 - segnale -113 dBm o inferiore
   1 - segnale -111 dBm
   2...30 - segnale -109...-53 dBm
  31 - segnale -51 dBm o superiore
  99 - Nessun dato
  Per la conversione in una stringa, usa la funzione rssi_to_str().
  modem.sms_sca
  Numero del centro servizi SMS. È contenuto nella memoria della carta SIM.
  È necessario per generare un messaggio SMS in uscita.
  In rari casi, se il numero non viene salvato nella memoria della carta SIM, sarà
  sostituito con il numero specificato nei parametri di input dell'Expert Advisor.
  modem.bat_stat
  Stato della batteria del modem (applicabile solo ai telefoni).
  Può assumere i seguenti valori:
  -1 - Nessun dato
   0 - il dispositivo funziona a batteria
   1 - la batteria è disponibile ma il dispositivo non è alimentato a batteria
   2 - nessuna batteria
   3 - Errore
  modem.bat_charge
  Carica della batteria in percentuale.
  Può assumere valori da 0 a 100.
  modem.bal
  Saldo del conto mobile. Il valore si ottiene
  dalla risposta dell'operatore alla richiesta USSD pertinente.
  Il valore iniziale (prima dell'inizializzazione): -10000.
  modem.exp_date
  Data di scadenza del numero di cellulare. Il valore si ottiene
  dalla risposta dell'operatore alla richiesta USSD pertinente.
  Il valore iniziale è "n/a".
  modem.sms_free
  Numero di pacchetti SMS disponibili. Si calcola come la differenza tra
  il numero iniziale e il contatore del pacchetto SMS utilizzato.
  modem.sms_free_cnt
  Contatore di pacchetti SMS utilizzati. Il valore si ottiene
  dalla risposta dell'operatore alla richiesta USSD pertinente. Il valore iniziale è -1.
  modem.sms_mem_size
  Dimensione della memoria SMS del modem.
  modem.sms_mem_used
  Memoria SMS utilizzata.
  modem.incoming
  Numero dell'ultimo chiamante.
  Il valore iniziale è "n/a".


2.4. Struttura dei messaggi 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
};

L'etichetta dell'ora del centro SMS è l'ora in cui un determinato messaggio dal mittente è stato ricevuto nel centro SMS. La struttura dell'etichetta temporale è la seguente:

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

Il fuso orario è espresso in intervalli di 15 minuti. Quindi, il valore di 8 corrisponde a GMT+02:00.

Il testo del messaggio SMS ricevuto può essere scritto utilizzando caratteri latini e cirillici. Le codifiche a 7 bit e UCS2 sono supportate per i messaggi ricevuti. L'unione di messaggi lunghi non è implementata (in considerazione del fatto che questa operazione è progettata per comandi brevi).

I messaggi SMS possono essere inviati solo se scritti utilizzando caratteri latini. La lunghezza massima del messaggio è di 158 caratteri. In caso di messaggio più lungo, verrà inviato senza i caratteri in eccesso rispetto al numero specificato.


3. Sviluppare un Expert Advisor

Per cominciare, è necessario copiare il file TrComPort.dll nella cartella Libraries e posizionare ComPort.mqh, modem.mqh e sms.mqh nella cartella Include.

Quindi, utilizzando la procedura guidata, creiamo un nuovo Expert Advisor e aggiungiamo il minimo richiesto per lavorare con il modem. Questo è:

Includi modem.mqh::

#include <modem.mqh>

Parametri di Input

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

Funzioni chiamate dal gestore del 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;
   }
}

La porta COM e l'inizializzazione del modem insieme a un timer impostato a intervalli di 1 secondo dovrebbero essere aggiunti alla funzione 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);
}

Nella funzione OnTimer(), dobbiamo chiamare il gestore del modem:

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

È necessario chiamare la deinizializzazione della porta COM nella funzione OnDeinit():

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

Compiliamo il codice e vediamo: 0 Errore(i)

Ora esegui l'Expert Advisor, ma ricorda di consentire l'importazione della DLL e seleziona la porta COM associata al modem. Dovresti essere in grado di vedere i seguenti messaggi nella scheda "Expert Advisor":

Il primo avvio

Fig. 2. Messaggi dell'Expert Advisor dopo una corsa di successo

Se hai gli stessi messaggi, significa che il tuo modem (telefono) è adatto per lavorare con questo Expert Advisor. In questo caso andiamo oltre.

Disegniamo una tabella per la visualizzazione dei parametri del modem. Verrà posizionato nell'angolo in alto a sinistra della finestra del terminale, sotto la riga OHLC. Il carattere del testo da utilizzare nella tabella sarà a spaziatura fissa, ad es "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);
  }

Per aggiornare i dati nella tabella, utilizzeremo la funzione 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);
  }

La funzione DelTab() elimina la tabella:

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

Aggiungiamo le funzioni per lavorare con la tabella ai gestori di eventi OnInit() e OnDeinit(), nonché alla funzione 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();
  }

Inoltre, aggiungiamo l'opportunità di aggiornare il numero dell'ultima chiamata in arrivo nella tabella alla funzione 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);   
} 

Ora compila il codice ed esegui l'Expert Advisor. Dovresti essere in grado di vedere il seguente rapporto nella finestra del terminale:

Parametri di stato del modem

Fig. 3. Parametri del modem

Prova a chiamare il modem. La chiamata verrà rifiutata e il tuo numero apparirà nella riga "In arrivo".


4. Lavorare con le richieste USSD

Un conto mobile non ricaricato in tempo utile può interrompere l'operatività di un Expert Advisor nel momento meno opportuno. Quindi, la funzione che controlla il saldo del conto è una delle più importanti. Per controllare il saldo dell'account mobile, di solito utilizziamo le richieste USSD. Inoltre, utilizzeremo le richieste USSD per ottenere informazioni sul numero di pacchetti SMS disponibili.

I dati per la generazione delle richieste e l'elaborazione delle risposte ricevute si trovano nei parametri di input:

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 non viene specificato il numero della richiesta, la richiesta non verrà evasa. In alternativa, la richiesta verrà inviata subito dopo l'inizializzazione del modem e verrà inviata ripetutamente dopo il periodo di tempo specificato. Inoltre, la richiesta non verrà evasa se il tuo modem (telefono) non supporta il relativo comando IT (si tratta di vecchi modelli di cellulare). 

Supponiamo che in seguito alla richiesta di saldo si riceva la seguente risposta dal proprio operatore:

7.13 UAH, scade il 22.05.2014. Piano telefonico - Super MTS 3D Null 25.

Affinché il gestore identifichi correttamente la risposta, il suffisso del saldo deve essere impostato su "UAH" e il prefisso della data di scadenza del numero deve essere "scadenza il".

Poiché si prevede che il nostro Expert Advisor invii messaggi SMS abbastanza spesso, sarebbe opportuno acquistare un pacchetto SMS dal tuo operatore, ovvero un servizio in base al quale ricevi un certo numero di messaggi SMS con un piccolo supplemento. In questo caso può essere molto utile sapere quanti pacchetti SMS sono ancora disponibili. Questo può essere fatto anche utilizzando una richiesta USSD. Solitamente l'operatore risponde con il numero di SMS utilizzati invece di quelli disponibili.

Supponiamo di aver ricevuto la seguente risposta dal tuo operatore:

Saldo: 69 minuti di chiamate locali per oggi. Usato oggi: 0 SMS e 0 MB.

In questo caso, il suffisso del contatore SMSdeve essere impostato su "SMS" e il limite giornaliero deve essere impostato in conformità con i termini e le condizioni del pacchetto SMS. Ad esempio, se ti vengono dati 30 SMS al giorno e la richiesta ha restituito il valore 10, significa che hai a disposizione 30-10=20 SMS. Questo numero verrà inserito dal gestore nell'elemento appropriato della struttura dello stato del modem.

ATTENZIONE! Stai molto attento con i numeri di richiesta USSD! L'invio di una richiesta errata può avere conseguenze indesiderate, ad esempio l'abilitazione di alcuni servizi a pagamento indesiderati!

Affinché il nostro Expert Advisor inizi a lavorare con le richieste USSD, dobbiamo solo specificare i parametri di input pertinenti.

Ad esempio, i parametri per l'operatore mobile ucraino, MTS Ukraine, saranno i seguenti:

Parametri della richiesta del saldo disponibile

Fig. 4. Parametri della richiesta USSD per il saldo disponibile

Parametri della richiesta per il numero di pacchetti SMS disponibili

Fig. 5. Parametri della richiesta USSD per il numero di pacchetti SMS disponibili

Imposta i valori relativi al tuo operatore di telefonia mobile. Successivamente, il saldo disponibile nel tuo account mobile e il numero di SMS disponibili verranno visualizzati nella tabella dello stato del modem:

Saldo disponibile

Fig. 6. Parametri ottenuti dalle risposte USSD

Al momento della stesura di questo articolo, il mio operatore di telefonia mobile stava inviando un annuncio di Natale invece della data di scadenza del numero. Di conseguenza, il gestore non è riuscito a ottenere il valore della data, motivo per cui possiamo vedere "n/a" nella riga "Data di scadenza". Si prega di notare che tutte le risposte dell'operatore sono visualizzate nella scheda "Expert Advisor".

Risposte dell'operatore

Fig. 7. Risposte dell'operatore visualizzate nella scheda "Expert Advisor"


5. Invio di messaggi SMS

Inizieremo ad aggiungere funzioni utili, ad esempio, l'invio di messaggi SMS che indicano l'attuale profitto, l'equità e il numero di posizioni aperte. L'invio verrà avviato da una chiamata in arrivo.

Tale risposta è sicuramente attesa solo nel caso del numero di amministratore, quindi avremo un altro parametro di input:

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

Il numero deve essere specificato in formato internazionale, includendo "+" prima del numero.

Il controllo del numero, così come la generazione e l'invio del testo SMS devono essere aggiunti al gestore delle chiamate in entrata:

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

Ora, se c'è una chiamata al modem dal numero dell'amministratore inp_admin_number, verrà inviato un messaggio SMS in risposta:

SMS in risposta a una chiamata in arrivo

Fig. 8. Il messaggio SMS inviato dall'Expert Advisor in risposta alla chiamata ricevuta dal numero di telefono dell'amministratore

Qui possiamo vedere i valori attuali di profitto e patrimonio, nonché il numero di posizioni aperte e il saldo del conto mobile.


6. Nessuna connessione con il trade server

Aggiungiamo le notifiche in caso di perdita e ripristino della connessione con il server di trading. A questo scopo, controlleremo la connettività del server di trading una volta ogni 10 secondi utilizzando TerminalInfoInteger() con l'identificatore di proprietà TERMINAL_CONNECTED.

Per filtrare le perdite di connessione a breve termine, utilizzeremo l'isteresi che dovrebbe essere aggiunta all'elenco dei parametri di input:

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

Il valore 6 significa che la connessione sarà considerata persa se non c'è connessione per più di 6*10=60 secondi. Allo stesso modo, la connessione sarà considerata ristabilita se è disponibile per più di 60 secondi. L'ora locale della prima mancanza di connettività registrata sarà considerata l'ora della perdita della connessione, mentre l'ora locale della prima disponibilità della connessione sarà considerata l'ora del ripristino.

Per implementare ciò, aggiungiamo il seguente codice alla funzione 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;
   }

La struttura cm è la seguente:

//+------------------------------------------------------------------+
//| 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

Nel testo del messaggio SMS indicheremo l'ora in cui la connessione con il server di trading è stata persa (o ristabilita), nonché l'ora in cui la connessione era disponibile (o non disponibile) calcolata come differenza tra l'ora di stabilita connessione e l'ora della perdita della connessione. Per convertire la differenza di tempo da secondi a dd hh:mm:ss, aggiungeremo la funzione 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);
}

Per garantire che l'Expert Advisor non invii un messaggio di testo sulla connessione stabilita ogni volta che viene eseguito l'Expert Advisor, aggiungiamo una funzione conn_mon_init() che imposta i valori sugli elementi della struttura cm in modo tale, come se la connessione fosse già stabilito. In questo caso, la connessione si riterrà stabilita all'ora locale di esecuzione dell'Expert Advisor. Questa funzione deve essere chiamata dalla funzione 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;
} 

Ora compila ed esegui Expert Advisor. Quindi prova a disconnettere il computer da Internet. In 60 (più o meno 10) secondi, riceverai un messaggio che ti informa che la connessione con il server è stata persa. Connettiti nuovamente a Internet. In 60 secondi, riceverai un messaggio sulla connessione ristabilita, indicando il tempo totale di disconnessione:

Il messaggio sulla connessione persa Il messaggio sulla connessione ristabilita

Fig. 9. Messaggi di testo che notificano la perdita e il ripristino della connessione con il server


7. Invio di rapporti sull'apertura e la chiusura della posizione

Per monitorare l'apertura e la chiusura delle posizioni, aggiungiamo il seguente codice alla funzione 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;
         }
      }
   }    
}

dove ps è il buffer delle strutture 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];

Il buffer è necessario nel caso in cui più di una posizione sia stata chiusa (o aperta) in un breve periodo di tempo. Quando una posizione viene aperta o chiusa, dopo che l'operazione è stata aggiunta alla cronologia, otteniamo tutti i parametri necessari e impostiamo il flag new_event.

Di seguito è riportato il codice che verrà aggiunto alla funzione OnTimer() per controllare i flag new_event e generare report 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);
   }

Ora compila ed esegui Expert Advisor. Proviamo ad acquistare AUDCAD, con la dimensione del lotto di 0,14. L'Expert Advisor invierà il seguente messaggio SMS: "Compra 0,14 AUDCAD, prezzo=0,96538, entrata: in". Dopo un po' chiudiamo la posizione e riceviamo il seguente messaggio di testo relativo alla chiusura della posizione:

Il messaggio sull'apertura della posizione  Il messaggio sulla chiusura della posizione

Fig. 10. Messaggi di testo sull'apertura (entrata: dentro) e la chiusura (entrata: fuori) della posizione


8. Elaborazione dei messaggi SMS in arrivo per la gestione delle posizioni aperte

Fino ad ora, il nostro Expert Advisor ha inviato messaggi solo al numero di telefono dell'amministratore. Ora insegniamogli a ricevere ed eseguire comandi SMS. Questo può essere utile, ad esempio, chiudere tutte o alcune posizioni aperte. Come sappiamo, non c'è niente come avere la tua posizione chiusa in tempo.

Ma dobbiamo prima assicurarci che i messaggi SMS vengano ricevuti correttamente. Per fare ciò, aggiungiamo alla funzione IncomingSMS() la visualizzazione dell'ultimo messaggio ricevuto:

//+------------------------------------------------------------------+
//| 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 ora inviamo un messaggio SMS al modem, verrà visualizzato nella tabella:

Nuovo messaggio SMS

Fig. 11. Il messaggio SMS in arrivo come visualizzato nella finestra del terminale

Tieni presente che tutti i messaggi SMS in arrivo vengono visualizzati nella scheda "Expert Advisor" nella seguente forma: <index_in_modem_memory>text_of_the_message:

Il messaggio SMS visualizzato nella scheda "Expert Advisor"

Fig. 12. Il messaggio SMS in arrivo visualizzato nella scheda "Expert Advisor"

La parola "close" verrà utilizzata come comando per chiudere le trattative. Dovrebbe essere seguito dallo spazio e dal parametro - il simbolo della posizione che deve essere chiusa, o "tutto" nel caso in cui sia necessario chiudere tutte le posizioni. Il caso non ha importanza in quanto prima di elaborare il testo del messaggio, utilizziamo la funzione StringToUpper(). Quando si analizza il messaggio, assicurarsi di controllare che il numero di telefono del mittente corrisponda al numero dell'amministratore impostato.

Si segnala inoltre che possono verificarsi casi in cui un messaggio SMS viene ricevuto con notevole ritardo (a causa di disguidi tecnici da parte dell'operatore, ecc.). In tali casi, non è possibile tenere conto del comando ricevuto nel messaggio in quanto la situazione del mercato potrebbe essere cambiata. In considerazione di ciò, introduciamo un altro parametro di input:

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

Il valore di 600 suggerisce che i comandi che hanno richiesto più di 600 secondi (10 minuti) per essere consegnati verranno ignorati. Si noti che il metodo di controllo dell'ora di consegna utilizzato nell'esempio implica che il centro servizi SMS e il dispositivo su cui è in esecuzione Expert Advisor si trovino nello stesso fuso orario.

Per elaborare i comandi SMS, aggiungiamo il seguente codice alla funzione 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 il messaggio SMS è stato consegnato dall'amministratore, non è scaduto e rappresenta un comando (contiene la parola chiave "Chiudi"), inviamo il suo parametro per l'elaborazione da parte della funzione 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);
}

Questa funzione verifica se una qualsiasi posizione aperta è una corrispondenza in termini di parametro (simbolo) ricevuto nel comando. Le posizioni che soddisfano questa condizione vengono chiuse utilizzando la funzione 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);
}

Dopo la corretta elaborazione degli ordini, la funzione per il monitoraggio dei cambiamenti di posizione genererà e invierà una notifica SMS.


9. Eliminazione dei messaggi dalla memoria del modem

Si noti che il gestore del modem non elimina da solo i messaggi SMS in arrivo. Pertanto, quando la memoria SMS si riempie nel corso del tempo, il gestore chiamerà la funzione SMSMemoryFull() e le passerà il numero corrente di messaggi nella memoria del modem. Puoi eliminarli tutti o farlo su base selettiva. Il modem non accetterà nuovi messaggi finché la memoria non verrà liberata.

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

Puoi anche eliminare i messaggi SMS subito dopo che sono stati elaborati. Quando la funzione IncomingSMS() viene chiamata dal gestore del modem, la struttura INCOMING_SMS_STR passa l'indice del messaggio nella memoria del modem, che consente di eliminare il messaggio subito dopo l'elaborazione, utilizzando la funzione DelSMSbyIndex():


Conclusione

Questo articolo ha trattato lo sviluppo dell'Expert Advisor che utilizza un modem GSM per monitorare da remoto il terminale di trading. Abbiamo considerato i metodi per ottenere informazioni su posizioni aperte, profitto attuale e altri dati utilizzando le notifiche SMS. Abbiamo inoltre implementato le funzioni base per la gestione delle posizioni aperte tramite comandi SMS. L'esempio fornito presenta comandi in inglese, ma puoi usare ugualmente bene i comandi russi (per non perdere tempo a passare da un layout di tastiera all'altro nel tuo telefono).

Infine, controlliamo il comportamento del nostro Expert Advisor quando ha a che fare con un vecchio telefono cellulare lanciato sul mercato oltre 10 anni fa. Dispositivo - Siemens M55. Colleghiamolo:

I parametri di Siemens M55Siemens M55

Fig. 13. Collegamento Siemens M55

Siemens M55, scheda "Expert Advisor"

Fig. 14. Inizializzazione riuscita di Siemens M55, scheda "Expert Advisor"

Puoi vedere che tutti i parametri necessari sono stati ottenuti. L'unico problema sono i dati che otteniamo dalle richieste USSD. Il fatto è che Siemens M55 non supporta il comando AT per lavorare con le richieste USSD. A parte questo, la sua funzionalità è buona come quella di qualsiasi modem attuale, quindi può essere utilizzata per lavorare con il nostro Expert Advisor.