Lavorare con il modem GSM da un Expert Advisor MQL5
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.
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:
Elemento | Descrizione |
---|---|
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":
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:
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:
Fig. 4. Parametri della richiesta USSD per il saldo disponibile
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:
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".
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:
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:
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:
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:
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:
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:
Fig. 13. Collegamento Siemens M55
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.
Tradotto dal russo da MetaQuotes Ltd.
Articolo originale: https://www.mql5.com/ru/articles/797
- App di trading gratuite
- Oltre 8.000 segnali per il copy trading
- Notizie economiche per esplorare i mercati finanziari
Accetti la politica del sito e le condizioni d’uso