Travailler avec le modem GSM d'un Expert Advisors de MQL5
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.
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ément | Description |
---|---|
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 » :
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 :
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 :
Fig. 4. Paramètres de la demande USSD de solde disponible
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 :
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 ».
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 :
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 :
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; } } } }
où 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 :
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 :
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 :
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 :
Fig. 13. Connexion Siemens M55
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
- Applications de trading gratuites
- Plus de 8 000 signaux à copier
- Actualités économiques pour explorer les marchés financiers
Vous acceptez la politique du site Web et les conditions d'utilisation