English Русский 中文 Español Deutsch 日本語 Português 한국어 Italiano Türkçe
Travailler avec le modem GSM d'un Expert Advisors de MQL5

Travailler avec le modem GSM d'un Expert Advisors de MQL5

MetaTrader 5Intégration | 13 janvier 2022, 13:45
80 0
Serhii Shevchuk
Serhii Shevchuk

Introduction

Il existe actuellement de nombreux moyens pour un bon suivi à distance d'un compte de trading : terminaux mobiles, notifications push, fonctionnement avec ICQ. Mais tout cela nécessite une connexion Internet. Cet article décrit le processus de création d'un Expert Advisor qui vous permettra de rester en contact avec votre terminal de trading même lorsque l'Internet mobile n'est pas disponible, par le biais d'appels et de SMS. De plus, cet Expert Advisor pourra vous informer de la perte ou du rétablissement de la connexion avec le serveur de trading.

À cette fin, pratiquement n'importe quel modem GSM, ainsi que la plupart des téléphones dotés de la fonction modem, feraient l'affaire. À titre d'illustration, j'ai choisi Huawei E1550, car ce modem est l'un des appareils les plus utilisés de sa catégorie. De plus, à la fin de l'article, nous allons essayer de remplacer le modem par un ancien téléphone portable Siemens M55 (sorti en 2003) et voir ce qui se passe.

Mais d'abord, quelques mots sur la façon d'envoyer un octet de données d'un Expert Advisor à un modem.


1. Travailler avec le port COM

Après avoir connecté le modem à votre ordinateur et installé tous les pilotes nécessaires, vous pourrez voir un port COM virtuel dans le système. Toutes les opérations futures avec le modem sont effectuées via ce port. Par conséquent, pour échanger des données avec le modem, vous devez d'abord accéder au port COM.

Le modem tel qu'affiché dans le gestionnaire de périphériques

Fig. 1. Le modem Huawei est connecté au port COM3

Ici, nous aurons besoin d'une bibliothèque TrComPort.dll qui est distribuée gratuitement sur Internet avec les fichiers source. Il sera utilisé pour configurer le port COM, interroger son état, ainsi que pour recevoir et envoyer des données. Pour ce faire, nous utiliserons les fonctions suivantes :

#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

Les types de données transmis ont dû être légèrement modifiés pour la compatibilité avec MQL5.

La structure de TrComPortParameters est la suivante :

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 plupart des appareils fonctionnent avec les paramètres suivants : 8 bits de données, pas de contrôle de parité, 1 bit d'arrêt. Par conséquent, parmi tous les paramètres de port COM, il est logique de n'ajouter aux paramètres de l'Expert Advisor que le numéro de port COM et le débit de données :

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

La fonction d'initialisation du port COM sera alors la suivante :

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

En cas d'initialisation réussie, la variable PortID stockera l'identifiant du port COM ouvert.

Il est à noter ici que les identifiants sont numérotés à partir de zéro, donc l'identifiant du port COM3 sera égal à 2. Maintenant que le port est ouvert, nous pouvons échanger des données avec le modem. Et, d'ailleurs, pas seulement avec un modem. L'accès au port COM de l'Expert Advisor ouvre de grandes opportunités de créativité à ceux qui savent souder : vous pouvez connecter l'Expert Advisor à une LED ou à un affichage de texte mobile pour afficher les prix des actions ou du marché de certaines paires de devises.

La fonction TrComPortGetQueue doit être utilisée pour obtenir les détails des données dans la file d'attente du récepteur et de l'émetteur du port 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
   );

En cas d'erreur, il renvoie une valeur négative du code d'erreur. Une description détaillée des codes d'erreur est disponible dans l'archive avec les codes sources de la bibliothèque TrComPort.dll.

Si la fonction renvoie un nombre non nul de données dans le tampon de réception, elles doivent être lues. Pour cela, nous utilisons la fonction 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)
   );

En cas d'erreur, il renvoie une valeur négative du code d'erreur. Le nombre d'octets de données doit correspondre à la valeur renvoyée par la fonction TrComPortGetQueue.

Pour utiliser le délai d'attente par défaut (défini à l'initialisation du port COM), vous devez passer la valeur -1.

Pour transmettre des données au port COM, nous utilisons la fonction 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)
   );

Exemple d'application. En réponse au message disant « Bonjour tout le monde ! », nous devrions envoyer « Passez une bonne journée ! ».

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

Une attention particulière doit être portée à la fonction de fermeture du port :

int TrComPortClose(
   int portid         // Port identifier
   );  

Cette fonction doit toujours être présente dans le processus de dés-initialisation de l'Expert Advisor. Dans la plupart des cas, le port qui était resté ouvert ne redeviendra disponible qu'après le redémarrage du système. En fait, même éteindre et rallumer le modem peut ne pas aider.


2. Commandes AT et utilisation du modem

Le travail avec un modem est organisé à l'aide des AT Ceux d'entre vous qui ont déjà utilisé l'Internet mobile à partir d'un ordinateur doivent se souvenir de la «chaîne d'initialisation du modem», qui ressemble à peu près à ce qui suit : AT+CGDCONT=1,"IP","internet". C'est l'une des commandes AT. Presque tous commencent par le préfixe AT et se terminent par 0x0d (retour chariot).

Nous utiliserons l'ensemble minimum de commandes AT requis pour la mise en œuvre de la fonctionnalité souhaitée. Cela réduira l'effort pour assurer la compatibilité du jeu de commandes avec divers appareils.

Vous trouverez ci-dessous la liste des commandes AT utilisées par notre gestionnaire pour travailler avec le modem :

 Commande Description
  ATE1                                    
  Activer l'écho
  AT+CGMI
  Obtenir le nom du fabricant
  AT+CGMM
  Obtenir le modèle d'appareil
  À^SCKS
  Obtenir l'état de la carte SIM
  AT^INFOSYS
  Informations système
  AT+CREG
  Obtenir l'état d'enregistrement du réseau
  AT+COPS
  Obtenez le nom de l'opérateur mobile actuel
  AT+CMGF
  Basculer entre les modes texte/PDU
  AT+CLIP
  Activer l'identification de la ligne d’appel
  AT+CPAS
  Obtenir l'état du modem
  AT+CSQ
  Obtenez la qualité du signal
  AT+CUSD
  Envoyer une demande USSD
  AT+CALM
  Activer le mode silencieux (applicable aux téléphones)
  AT+CBC
  Obtenir l'état de la batterie (applicable aux téléphones)
  AT+CSCA
  Obtenir le numéro du centre de service SMS
  AT+CMGL
  Obtenir la liste des messages SMS
  AT+CPMS
  Sélectionnez la mémoire pour les messages SMS
  AT+CMGD
  Supprimer le message SMS de la mémoire
  AT+CMGR
  Lire le message SMS de la mémoire
  AT+CHUP
  Rejeter l'appel entrant
  AT+CMGS
  Envoyer un SMS


Je ne vais pas m'éloigner du sujet en décrivant les subtilités du travail avec les commandes AT. Il y a beaucoup d'informations pertinentes sur les forums techniques. De plus, tout a déjà été implémenté et pour créer un Expert Advisor capable de fonctionner avec un modem, il suffit d'inclure un fichier d'en-tête et de commencer à utiliser des fonctions et des structures prêtes à l'emploi. C'est ce que je vais développer.


2.1. Fonctions

Initialisation du port COM :

bool InitComPort();

Valeur renvoyée : si initialisé avec succès - vrai, sinon - faux. Il est appelé depuis la fonction OnInit() avant l'initialisation du modem.

Dés-initialisation du port COM :

void DeinitComPort();

Valeur renvoyée : aucune. Il est appelé à partir de la fonction OnDeinit().

Initialisation

void InitModem();

Valeur renvoyée : aucune. Il est appelé depuis la fonction OnInit() suite à une initialisation réussie du port COM.

Gestionnaire d'événements de modem :

void ModemTimerProc();

Valeur renvoyée : aucune. Il est appelé à partir de la fonction OnTimer() à intervalles de 1 seconde.

Lecture de SMS par index depuis la mémoire du 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
   );

Valeur renvoyée : si lu avec succès - vrai, sinon - faux.

Suppression d'un SMS par index de la mémoire du modem :

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

Valeur renvoyée : si la suppression a réussi - vrai, sinon - faux.

Conversion de l'indice de qualité de connexion en chaîne :

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

Valeur renvoyée : une chaîne, par exemple "-55 dBm".

Envoi de 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
   );

Valeur renvoyée : si envoyé avec succès - vrai, sinon - faux. Les SMS ne peuvent être envoyés que lorsqu'ils sont écrits en caractères latins. Les caractères cyrilliques ne sont pris en charge que pour les messages SMS entrants. Si flash=true est défini, un message flash sera envoyé.


2.2. Evénements (fonctions appelées par le gestionnaire de modem)

Mise à jour des données dans la structure d'état du modem :

void ModemChState();

Paramètres passés : aucun. Lorsque cette fonction est appelée par le gestionnaire de modem, elle suggère que des données ont été mises à jour dans la structure du modem (la description de la structure sera fournie ci-dessous).

Appel entrant :

void IncomingCall(
   string number          // Caller number
   );

Paramètres passés : numéro de l'appelant. Lorsque cette fonction est appelée par le gestionnaire de modem, elle suggère que l'appel entrant du numéro 'numéro' a été accepté et rejeté.

Nouveau SMS entrant :

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

Paramètres passés : Structure du message SMS (la description de la structure sera fournie ci-dessous). Lorsque cette fonction est appelée par le gestionnaire de modem, elle suggère qu'il y a un ou plusieurs nouveaux SMS non lus dans la mémoire du modem. Si le nombre de messages non lus est supérieur à un, le message le plus récent sera transmis à cette fonction.

Mémoire SMS pleine :

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

Paramètres passés : nombre de messages dans la mémoire du modem. Lorsque cette fonction est appelée par le gestionnaire de modem, cela suggère que la mémoire SMS est pleine et que le modem n'acceptera aucun nouveau message tant que la mémoire n'est pas libérée.


2.3. Structure d'état des paramètres du 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;

Cette structure est remplie exclusivement par le gestionnaire d'événements du modem et doit être utilisée par d'autres fonctions uniquement pour la lecture.

Vous trouverez ci-dessous la description des éléments de structure :

 ÉlémentDescription
  modem.init_ok
  Une indication que le modem a été initialisé avec succès.
  La valeur initiale de faux devient vrai une fois l'initialisation terminée.
  modem.manufacturer
  Fabricant du modem, par exemple « huawei ».
  La valeur initiale est «n/a».
  modem.device
  Modèle de modem, par exemple "E1550"
  La valeur initiale est «n/a».
  modem.sim_stat
  Statut de la carte SIM. Il peut prendre les valeurs suivantes :
  -1 - pas de données
   0 - la carte est manquante, bloquée ou en panne
   1 - la carte est disponible
  modem.net_reg
  État d'enregistrement du réseau. Il peut prendre les valeurs suivantes :
  -1 - pas de données
   0 - non enregistré
   1- Enregistré
   2 - recherche
   3- Interdit
   4 - état indéfini
   5 - enregistré en itinérance
  modem.status
  État du modem. Il peut prendre les valeurs suivantes :
  -1 - initialisation
   0 - Prêt
   1 - erreur
   2 - erreur
   3 - appel entrant
   4 - appel actif
  modem.op
  Opérateur mobile actuel.
  Il peut être égal au nom de l'opérateur (par ex. «MTS UKR»),
  ou le code opérateur international (par ex. «25501»).
 La valeur initiale est «n/a».
  modem.rssi
  Indice de qualité du signal. Il peut prendre les valeurs suivantes :
  -1 - pas de données
   0 - signal -113 dBm ou moins
   1 - signal -111 dBm
   2...30 - signal -109...-53 dBm
  31 - signal -51 dBm ou plus
  99 - pas de données
  Pour la conversion en chaîne, utilisez la fonction rssi_to_str().
  modem.sms_sca
  Numéro du centre de service SMS. Il est contenu dans la mémoire de la carte SIM.
  Il est nécessaire pour générer un message SMS sortant.
  Dans de rares cas, si le numéro n'est pas enregistré dans la mémoire de la carte SIM, il sera
  remplacé par le nombre spécifié dans les paramètres d'entrée de l'Expert Advisor.
  modem.bat_stat
  État de la batterie du modem (applicable aux téléphones uniquement).
  Il peut prendre les valeurs suivantes :
  -1 - pas de données
   0 - l'appareil fonctionne sur batterie
   1 - la batterie est disponible mais l'appareil n'est pas alimenté par batterie
   2 - pas de batterie
   3 - erreur
  modem.bat_charge
  Charge de la batterie en pourcentage.
  Il peut prendre des valeurs de 0 à 100.
  modem.bal
  Solde du compte mobile. La valeur est obtenue
  de la réponse de l'opérateur à la demande USSD correspondante.
  La valeur initiale (avant l'initialisation) : -10000.
  modem.exp_date
  Date d'expiration du numéro de portable. La valeur est obtenue
  de la réponse de l'opérateur à la demande USSD correspondante.
  La valeur initiale est «n/a».
  modem.sms_free
  Nombre de forfait SMS disponibles. Il est calculé comme la différence entre
  le numéro initial et le compteur du forfait SMS utilisé.
  modem.sms_free_cnt
  Compteur du forfait SMS utilisé. La valeur est obtenue
  de la réponse de l'opérateur à la demande USSD correspondante. La valeur initiale est -1.
  modem.sms_mem_size
  Taille de la mémoire SMS du modem.
  modem.sms_mem_used
  Mémoire SMS du modem utilisée.
  modem.incoming
  Numéro du dernier appelant.
  La valeur initiale est «n/a».


2.4. Structure des 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'étiquette horaire du centre SMS est l'heure à laquelle un message donné de l'expéditeur a été reçu dans le centre SMS. La structure de l'étiquette de temps est la suivante :

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

Le fuseau horaire est exprimé par intervalles de 15 minutes. Ainsi, la valeur de 8 correspond à GMT 02:00.

Le texte du message SMS reçu peut être écrit en latin, ainsi qu'en caractères cyrilliques. Les encodages 7 bits et UCS2 sont pris en charge pour les messages reçus. La fusion de messages longs n'est pas implémentée (compte tenu du fait que cette opération est conçue pour des commandes courtes).

Les messages SMS ne peuvent être envoyés que s'ils sont écrits en caractères latins. La longueur maximale des messages est de 158 caractères. Dans le cas d'un message plus long, il sera envoyé sans les caractères dépassant le nombre spécifié.


3. Développer un Expert Advisor

Pour commencer, vous devez copier le fichier TrComPort.dll dans le dossier Bibliothèques et placer ComPort.mqh, modem.mqh et sms.mqh dans le dossier Inclure.

Ensuite, à l'aide de l'assistant, nous créons un nouvel Expert Advisor et ajoutons le minimum requis pour travailler avec le modem. C'est-à-dire:

Inclure modem.mqh :

#include <modem.mqh>

Ajouter les paramètres d’entrée :

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

Fonctions appelées par le gestionnaire de 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;
   }
}

Le port COM et l'initialisation du modem ainsi qu'un temporisateur réglé à 1 seconde d'intervalle doivent être ajoutés à la fonction 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);
}

Dans la fonction OnTimer(), nous devons appeler le gestionnaire de modem :

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

Il est nécessaire d'appeler la dés-initialisation du port COM dans la fonction OnDeinit() :

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

Nous compilons le code et voyons : 0 erreur(s).

Exécutez maintenant l'Expert Advisor, mais n'oubliez pas d'autoriser l'importation de DLL et de sélectionner le port COM associé au modem. Vous devriez pouvoir voir les messages suivants dans l'onglet « Expert Advisors » :

La première manche

Fig. 2. Messages de l'Expert Advisor suite à une exécution réussie

Si vous avez les mêmes messages, cela signifie que votre modem (téléphone) est adapté pour travailler avec cet Expert Advisor. Dans ce cas, nous allons plus loin.

Dessinons un tableau pour la visualisation des paramètres du modem. Il sera placé dans le coin supérieur gauche de la fenêtre du terminal, sous la ligne OHLC. La police de texte à utiliser dans le tableau sera à espacement fixe, par exemple «Nouveau Courrier».

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

Pour rafraîchir les données du tableau, nous utiliserons la fonction 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 fonction DelTab()supprime le tableau :

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

Ajoutons des fonctions pour travailler avec la table aux gestionnaires d'événements OnInit() et OnDeinit(), ainsi qu'à la fonction 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();
  }

De plus, nous ajoutons la possibilité d'actualiser le numéro du dernier appel entrant dans le tableau à la fonction 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);   
} 

Maintenant, compilez le code et exécutez l'Expert Advisor. Vous devriez pouvoir voir le rapport suivant dans la fenêtre du terminal :

Paramètres d'état du modem

Fig. 3. Paramètres du modem

Essayez d'appeler le modem. L'appel sera rejeté et votre numéro apparaîtra dans la ligne «Entrant».


4. Travailler avec les demandes USSD

Un compte mobile non rechargé en temps voulu peut perturber le fonctionnement d'un Expert Advisor au moment le moins opportun. Ainsi, la fonction qui vérifie le solde du compte est l'une des plus importantes. Pour vérifier le solde du compte mobile, nous utilisons généralement les requêtes USSD De plus, nous utiliserons les requêtes USSD pour obtenir des informations sur le nombre de forfaits SMS disponibles.

Les données de génération des requêtes et de traitement des réponses reçues se trouvent dans les paramètres d'entrée :

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

Si le numéro de demande n'est pas précisé, la demande ne sera pas traitée. Alternativement, la demande sera envoyée juste après l'initialisation du modem et sera envoyée à plusieurs reprises après la période de temps spécifiée. De plus, la demande ne sera pas traitée si votre modem (téléphone) ne supporte pas la commande informatique concernée (il s'agit d'anciens modèles de téléphones portables). 

Supposons qu'à la suite de la demande de solde vous recevez la réponse suivante de votre opérateur :

7.13 UAH, expire le 22.05.2014. Forfait téléphonique - Super MTS 3D Null 25.

Pour garantir que le gestionnaire identifie correctement la réponse, le suffixe du solde doit être défini sur « UAH » et le préfixe de la date d'expiration du numéro doit être « expire le ».

Étant donné que notre Expert Advisor est censé envoyer des messages SMS assez souvent, il serait bon d'acheter un forfait SMS auprès de votre opérateur, c'est-à-dire un service par lequel vous recevez un certain nombre de messages SMS pour une somme modique. Dans ce cas, il peut être très utile de savoir combien de forfait SMS sont encore disponibles. Cela peut également être fait à l'aide d'une requête USSD. L'opérateur répond généralement avec le nombre de SMS utilisés au lieu de ceux disponibles.

Supposons que vous ayez reçu la réponse suivante de votre opérateur :

Solde : 69 minutes d'appels locaux pour aujourd'hui. Utilisé aujourd'hui : 0 SMS et 0 Mo.

Dans ce cas, le suffixe du compteur SMS sera fixé à « SMS» et la limite journalière sera fixée conformément aux conditions générales du forfait SMS. Par exemple, si vous recevez 30 SMS par jour et que la demande renvoie la valeur 10, cela signifie que vous avez 30-10 = 20 SMS disponibles. Ce numéro sera placé par le gestionnaire dans l'élément approprié de la structure d'état du modem.

ATTENTION! Soyez très prudent avec les numéros de demande USSD ! L'envoi d'une mauvaise demande peut avoir des conséquences indésirables, par exemple l'activation de certains services payants non souhaités!

Pour que notre Expert Advisor commence à travailler avec les requêtes USSD, il nous suffit de spécifier les paramètres d'entrée pertinents.

Par exemple, les paramètres de l'opérateur mobile ukrainien, MTS Ukraine, seront les suivants :

Paramètres de la demande de solde disponible

Fig. 4. Paramètres de la demande USSD de solde disponible

Paramètres de la demande de nombre de SMS de forfait disponibles

Fig. 5. Paramètres de la demande USSD pour le nombre de forfait SMS disponible

Définissez les valeurs pertinentes pour votre opérateur mobile. Après cela, le solde disponible dans votre compte mobile et le nombre de SMS disponibles seront affichés dans le tableau de l'état du modem :

Solde disponible

Fig. 6. Paramètres obtenus à partir des réponses USSD

Au moment de la rédaction de cet article, mon opérateur mobile envoyait une annonce de Noël au lieu de la date d'expiration du numéro. Par conséquent, le gestionnaire n'a pas pu obtenir la valeur de la date, c'est pourquoi nous pouvons voir «n/a» dans la ligne «Date d'expiration». Veuillez noter que toutes les réponses des opérateurs sont affichées dans l'onglet « Expert Advisors ».

Réponses de l'opérateur

Fig. 7. Réponses des opérateurs affichées dans l'onglet «Expert Advisors»


5. Envoi de SMS

Nous allons commencer à ajouter des fonctions utiles, par exemple l'envoi de messages SMS indiquant le bénéfice actuel, les capitaux propres et le nombre de positions ouvertes. L'envoi sera initié par un appel entrant.

Une telle réponse n'est certainement attendue que dans le cas du numéro d'administrateur, nous aurons donc un autre paramètre d'entrée :

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

Le numéro doit être spécifié au format international, y compris "+" avant le numéro.

La vérification du numéro, ainsi que la génération et l'envoi de SMS doivent être ajoutés au gestionnaire d'appels entrants :

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

Désormais, s'il y a un appel vers le modem depuis le numéro de l'administrateur inp_admin_number, un SMS sera envoyé en réponse :

SMS en réponse à un appel entrant

Fig. 8. Le SMS envoyé par l'Expert Advisor en réponse à l'appel reçu du numéro de téléphone de l'administrateur

Ici, nous pouvons voir les valeurs actuelles des bénéfices et des capitaux propres, ainsi que le nombre de positions ouvertes et le solde du compte mobile.


6. Aucune connexion au serveur de Trade

Ajoutons des notifications en cas de connexion perdue et rétablie avec le serveur de trade. À cette fin, nous vérifierons la connectivité du serveur de trade une fois toutes les 10 secondes à l'aide de TerminalInfoInteger() avec l'identifiant de propriété TERMINAL_CONNECTED.

Pour filtrer les pertes de connexion de courte durée, nous utiliserons l'hystérésis qu'il convient d'ajouter à la liste des paramètres d'entrée :

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

La valeur 6 signifie que la connexion sera considérée comme perdue s'il n'y a pas de connexion pendant plus de 6*10=60 secondes. De même, la connexion sera considérée comme rétablie si elle est disponible pendant plus de 60 secondes. L'heure locale du premier défaut de connectivité enregistré sera considérée comme l'heure de la perte de connexion, tandis que la première heure locale à laquelle la connexion est devenue disponible sera considérée comme l'heure de récupération.

Pour implémenter cela, nous ajoutons le code suivant à la fonction 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 structure en cm est la suivante :

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

Dans le texte du message SMS, nous indiquerons l'heure à laquelle la connexion avec le serveur de trade a été perdue (ou rétablie), ainsi que le temps pendant lequel la connexion était disponible (ou non disponible) calculée comme la différence entre l'heure d'établissement de connexion et l'heure de la perte. Pour convertir la différence de temps de secondes en jj hh:mm:ss, nous allons ajouter la fonction 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);
}

Pour garantir que l'Expert Advisor n'envoie pas de message texte sur la connexion établie à chaque exécution, nous ajoutons une fonction conn_mon_init() qui définit les valeurs sur les éléments de structure cm de telle manière, comme si la connexion était déjà établi. Dans ce cas, la connexion sera réputée établie à l'heure locale d'exécution de l'Expert Advisor. Cette fonction doit être appelée à partir de la fonction 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;
} 

Maintenant, compilez et exécutez l'Expert Advisor. Essayez ensuite de déconnecter votre ordinateur d'Internet. Dans 60 (donnez ou prenez 10) secondes, vous recevrez un message indiquant que la connexion avec le serveur a été perdue. Reconnectez-vous à Internet. Dans 60 secondes, vous recevrez un message sur la connexion rétablie, indiquant le temps total de déconnexion :

Le message sur la connexion perdue Le message sur la connexion rétablie

Fig. 9. SMS informant de la perte et du rétablissement de la connexion avec le serveur


7. Envoi de rapports sur l'ouverture et la fermeture d'un poste

Pour surveiller l'ouverture et la fermeture des positions, ajoutons le code suivant à la fonction 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;
         }
      }
   }    
}

ps est le tampon des structures 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];

Le tampon est nécessaire au cas où plus d'une position a été fermée (ou ouverte) dans un court laps de temps. Lorsqu'une position est ouverte ou fermée, une fois la transaction ajoutée à l'historique, nous obtenons tous les paramètres nécessaires et définissons le drapeau new_event.

Vous trouverez ci-dessous le code qui sera ajouté à la fonction OnTimer() pour surveiller les indicateurs new_event et générer des rapports 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);
   }

Maintenant, compilez et exécutez l'Expert Advisor. Essayons d'acheter AUDCAD, avec une taille de lot de 0,14. L'Expert Advisor enverra le SMS suivant : «Achetez 0,14 AUDCAD, prix=0,96538, entrée : dans» Après un petit moment, nous fermons la position et obtenons le message texte suivant concernant la fermeture de la position :

Le message sur l'ouverture de position   Le message sur la fermeture de position

Fig. 10. SMS sur l'ouverture (entrée : entrée) et la fermeture (entrée : sortie) de la position


8. Traitement des messages SMS entrants pour la gestion des positions ouvertes

Jusqu'à présent, notre Expert Advisor n'envoyait des messages qu'au numéro de téléphone de l'administrateur. Apprenons-lui maintenant à recevoir et à exécuter des commandes SMS. Cela peut être utile, par exemple pour fermer toutes ou certaines des positions ouvertes. Comme nous le savons, il n'y a rien de tel que de fermer votre position à temps.

Mais nous devons d'abord nous assurer que les messages SMS sont reçus correctement. Pour cela, nous ajoutons l'affichage du dernier message reçu à la fonction IncomingSMS() :

//+------------------------------------------------------------------+
//| Called when a new SMS message is received                        |
//+------------------------------------------------------------------+
void IncomingSMS(INCOMING_SMS_STR& sms)
{
   string str, strtmp;
   //Number from which the last received SMS message was sent:
   ObjectSetString(0, "str18", OBJPROP_TEXT, "SMS number:      "+sms.sender);
   //Date and time of sending the last received SMS message:
   str = TimeToString(sms.scts.time,TIME_DATE|TIME_SECONDS);
   ObjectSetString(0, "str19", OBJPROP_TEXT, "SMS date/time:   "+str);
   //Text of the last received SMS message:
   strtmp = StringSubstr(sms.text, 0, 32); str = " ";
   if(strtmp!="") str = strtmp;    
   ObjectSetString(0, "str20", OBJPROP_TEXT, str);
   strtmp = StringSubstr(sms.text,32, 32); str = " ";
   if(strtmp!="") str = strtmp;    
   ObjectSetString(0, "str21", OBJPROP_TEXT, str);
   strtmp = StringSubstr(sms.text,64, 32); str = " ";
   if(strtmp!="") str = strtmp;    
   ObjectSetString(0, "str22", OBJPROP_TEXT, str);
   strtmp = StringSubstr(sms.text,96, 32); str = " ";
   if(strtmp!="") str = strtmp;    
   ObjectSetString(0, "str23", OBJPROP_TEXT, str);
   strtmp = StringSubstr(sms.text,128,32); str = " ";
   if(strtmp!="") str = strtmp;    
   ObjectSetString(0, "str24", OBJPROP_TEXT, str);  
}

Si nous envoyons maintenant un SMS au modem, il s'affichera dans le tableau :

Nouveau message SMS

Fig. 11. Le message SMS entrant tel qu'affiché dans la fenêtre du terminal

Veuillez noter que tous les SMS entrants sont affichés dans l'onglet « Expert Advisors » sous la forme suivante : <index_in_modem_memory>text_of_the_message :

Le SMS affiché dans l'onglet «Expert Advisors»

Fig. 12. Le SMS entrant tel qu'affiché dans l'onglet «Expert Advisors»

Le mot « fermer » sera utilisé comme commande pour conclure des transactions. Il doit être suivi de l'espace et du paramètre - le symbole de la position qui doit être fermée, ou « tout » au cas où vous auriez besoin de fermer toutes les positions. La casse n'a pas d'importance car avant de traiter le texte du message, nous utilisons la fonction StringToUpper(). Lors de l'analyse du message, assurez-vous de vérifier que le numéro de téléphone de l'expéditeur correspond au numéro de l'administrateur défini.

De plus, il convient de noter qu'il peut y avoir des cas où un message SMS est reçu avec un retard considérable (en raison de problèmes techniques du côté de l'opérateur, etc.). Dans de tels cas, vous ne pouvez pas prendre en compte la commande reçue dans le message car la situation du marché pourrait avoir changé. Dans cette optique, nous introduisons un autre paramètre d'entrée :

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

La valeur 600 suggère que les commandes qui ont mis plus de 600 secondes (10 minutes) à être livrées seront ignorées. Veuillez noter que la méthode de vérification du délai de livraison utilisée dans l'exemple implique que le centre de service SMS et l'appareil sur lequel fonctionne l'Expert Advisor sont situés dans le même fuseau horaire.

Pour traiter les commandes SMS, ajoutons le code suivant à la fonction 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");
   }   

Si le SMS a été délivré par l'administrateur, qu'il n'a pas expiré et qu'il représente une commande (contient le mot clé «Fermer»), nous envoyons son paramètre pour traitement par la fonction 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);
}

Cette fonction vérifie si une position ouverte correspond au paramètre (symbole) reçu dans la commande. Les positions qui satisfont à cette condition sont fermées à l'aide de la fonction 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);
}

Après un traitement réussi des commandes, la fonction de suivi des changements de position générera et enverra une notification par SMS.


9. Suppression de messages de la mémoire du modem

Veuillez noter que le gestionnaire de modem ne supprime pas de lui-même les messages SMS entrants. Par conséquent, lorsque la mémoire SMS est pleine au cours du temps, le gestionnaire appelle la fonction SMSMemoryFull() et lui transmet le nombre actuel de messages dans la mémoire du modem. Vous pouvez tous les supprimer ou le faire de manière sélective. Le modem n'acceptera aucun nouveau message jusqu'à ce que la mémoire soit libérée.

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

Vous pouvez également supprimer les messages SMS juste après leur traitement. Lorsque la fonction IncomingSMS() est appelée par le gestionnaire du modem, la structure INCOMING_SMS_STR passe l'index du message dans la mémoire du modem, ce qui permet de supprimer le message juste après le traitement, à l'aide de la fonction DelSMSbyIndex() :


Conclusion

Cet article a traité du développement de l'Expert Advisor qui utilise un modem GSM pour surveiller à distance le terminal de trading. Nous avons examiné les méthodes permettant d'obtenir des informations sur les positions ouvertes, les bénéfices actuels et d'autres données à l'aide de notifications SMS. Nous avons également implémenté les fonctions de base pour la gestion des positions ouvertes à l'aide de commandes SMS. L'exemple fourni comporte des commandes en anglais, mais vous pouvez également utiliser les commandes russes (pour ne pas perdre de temps à basculer entre différentes dispositions de clavier sur votre téléphone).

Enfin, vérifions le comportement de notre Expert Advisor face à un ancien téléphone portable mis sur le marché il y a plus de 10 ans. Appareil - Siemens M55. Connectons-le :

Les paramètres de Siemens M55 Siemens M55

Fig. 13. Connexion Siemens M55

Siemens M55, onglet «Expert Advisors»

Fig. 14. Initialisation réussie du Siemens M55, onglet «Expert Advisors»

Vous pouvez voir que tous les paramètres nécessaires ont été obtenus. Le seul problème, ce sont les données que nous obtenons des demandes USSD. Le fait est que Siemens M55 ne prend pas en charge la commande AT pour travailler avec les demandes USSD. En dehors de cela, sa fonctionnalité est aussi bonne que celle de n'importe quel modem actuel, il peut donc être utilisé pour travailler avec notre Expert Advisor.


Traduit du russe par MetaQuotes Ltd.
Article original : https://www.mql5.com/ru/articles/797

Fichiers joints |
trcomport.zip (30.99 KB)
modem.mqh (44.74 KB)
sms.mqh (9.46 KB)
gsminformer.mq5 (25.28 KB)
comport.mqh (5.18 KB)

Autres articles par cet auteur

Contrats à terme continus dans MetaTrader 5 Contrats à terme continus dans MetaTrader 5
La courte durée de vie des contrats à terme complique leur analyse technique. Il est difficile d'analyser techniquement des graphiques courts. Par exemple, le nombre de barres sur le graphique journalier du futur indice boursier ukrainien UX-9.13 est supérieur à 100. Par conséquent, le trader crée des contrats à terme synthétiques longs. Cet article explique comment assembler des contrats à terme avec des dates différentes dans le terminal MetaTrader 5.
Indicateur pour la cartographie Renko Indicateur pour la cartographie Renko
L'article décrit un exemple de graphique Renko et sa mise en œuvre dans MQL5 en tant qu'indicateur. Des modifications de cet indicateur le distinguent d'un graphique classique. Il peut être construit à la fois dans la fenêtre de l'indicateur et sur le graphique principal. De plus, il y a l'indicateur ZigZag. Vous pouvez y trouver quelques exemples de mise en œuvre du graphique.
Création de filtres numériques à temps Création de filtres numériques à temps
L'article décrit l'une des approches permettant de déterminer un signal utile (tendance) dans les données de flux. De petits tests de filtrage (lissage) appliqués aux cotations boursières démontrent le potentiel de création de filtres numériques sans retard (indicateurs) qui ne sont pas redessinés sur les dernières barres.
Livre de recettes MQL5 - Expert Advisor multi-devises et utilisation des commandes en attente dans MQL5 Livre de recettes MQL5 - Expert Advisor multi-devises et utilisation des commandes en attente dans MQL5
Cette fois, nous allons créer un Expert Advisor multi-devises avec un algorithme de trading basé sur le travail avec les ordres en attente Buy Stop et Sell Stop. Cet article aborde les questions suivantes : négocier dans une plage de temps spécifiée, passer/modifier/supprimer des ordres en attente, vérifier si la dernière position a été fermée au Take profit ou au Stop Loss et contrôler l'historique des transactions pour chaque symbole.