MetaTrader 5 herunterladen

Arbeit mit einem GSM-Modem über einen Expert Advisor in MQL5

20 Mai 2016, 13:23
Serhii Shevchuk
0
249

Einleitung

Es gibt derzeit ausreichend viele Möglichkeiten für die bequeme Remote-Überwachung eines Handelskontos: mobile Terminals, Push-Benachrichtigungen, Arbeiten mit ICQ. Doch all das erfordert eine Internetverbindung. Dieser Beitrag beschreibt die Erstellung eines Expert Advisors, der es Ihnen ermöglicht, mithilfe von Anrufen und SMS mit Ihrem Handelsterminal in Verbindung zu bleiben, auch wenn keine mobile Internetverbindung verfügbar ist. Zusätzlich kann Sie dieser Expert Advisor über den Verlust oder die Wiederherstellung der Verbindung mit dem Handelsserver benachrichtigen.

Für diesen Zweck reicht praktisch jedes GSM-Modem sowie die meisten Telefone mit Modemfunktion aus. Zu Illustrationszwecken habe ich mich für das Huawei E1550 entschieden, da dieses Modem eines der am weitesten verbreiteten Geräte seiner Art ist. Am Ende des Beitrags werden wir außerdem versuchen, das Modem durch ein altes Mobiltelefon, ein Siemens M55, zu ersetzen (Herstellungsjahr 2003), und sehen, was passiert.

Doch zunächst ein paar Worte zum Senden eines Bytes Daten von einem Expert Advisor an ein Modem.


1. Arbeit mit einem COM-Port

Nach dem Anschließen des Modems an Ihren Computer und der Installation aller erforderlichen Treiber sehen Sie einen virtuellen COM-Port im System. Alle zukünftigen Operationen mit dem Modem werden über diesen Port ausgeführt. Dementsprechend müssen Sie zunächst Zugriff auf den COM-Port erhalten, um Daten mit dem Modem austauschen zu können.

Darstellung des Modems im Gerätemanager

Abb. 1. Huawei-Modem ist an den Port COM3 angeschlossen

Hier brauchen wir die DLL-Bibliothek TrComPort.dll, die zusammen mit den Quelldateien frei im Internet verfügbar ist. Sie wird zum Konfigurieren des COM-Ports, dem Abfragen seines Zustands sowie zum Empfangen und Versenden von Daten genutzt. Dafür nutzen wir die folgenden Funktionen:

#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

Die Typen der übertragenen Dateien mussten ein wenig angepasst werden, um mit MQL5 kompatibel zu sein.

Die Struktur TrComPortParameters sieht so aus:

struct TrComPortParameters
{
   uint   DesiredParams;
   int    BaudRate;           // Übertragungsgeschwindigkeit
   int    DefaultTimeout;     // Standard-Timeout (in Millisekunden)
   uchar  ByteSize;           // Datengröße(4-8)
   uchar  StopBits;           // Menge der Stopp-Bits   uchar  CheckParity;        // Paritätsprüfung(0-nein,1-ja)
   uchar  Parity;             // Paritätstyp   uchar  RtsControl;         // Ursprünglicher RTS-Zustand
   uchar  DtrControl;         // Ursprünglicher DTR-Zustand
};

Die meisten Geräte arbeiten mit den folgenden Einstellungen: 8 Datenbits, keine Paritätsprüfung, 1 Stop-Bit. Deshalb ist es sinnvoll, aus allen Parametern des COM-Ports nur die Nummer des COM-Ports und die Übertragungsrate zu den Parametern des Expert Advisors hinzuzufügen:

input ComPortList    inp_com_port_index=COM3;   // Auswahl des COM-Ports
input BaudRateList   inp_com_baudrate=_9600bps; // Übertragungsrate

Die Initialisierungsfunktion des COM-Ports sieht anschließend so aus:

//+------------------------------------------------------------------+
//| Initialisierung des COM-Ports                                     |
//+------------------------------------------------------------------+
bool InitComPort()
  {
   rx_cnt=0;
   tx_cnt=0;
   tx_err=0;
//--- Versuch, Port zu öffnen
   PortID=TrComPortOpen(inp_com_port_index);
   if(PortID!=inp_com_port_index)
     {
      Print("Fehler beim Öffnen des COM-Ports"+DoubleToString(inp_com_port_index+1,0));
      return(false);
     }
   else
     {
      Print("COM-Port"+DoubleToString(inp_com_port_index+1,0)+" erfolgreich geöffnet");
      //--- Abfrage aller Parameter, Bestimmung aller Flags
      com_par.DesiredParams=tcpmpBaudRate|tcpmpDefaultTimeout|tcpmpByteSize|tcpmpStopBits|tcpmpCheckParity|tcpmpParity|tcpmpEnableRtsControl|tcpmpEnableDtrControl;
      //--- Lesen aktueller Parameter 
      if(TrComPortGetConfig(PortID,com_par)==-1)
         ,bnreturn(false);//Lesefehler
      //
      com_par.ByteSize=8;                //8 Bit
      com_par.Parity=0;                  //keine Paritätsprüfung
      com_par.StopBits=0;                //1 Stop-Bit
      com_par.DefaultTimeout=100;        //100 ms Timeout
      com_par.BaudRate=inp_com_baudrate; //Geschwindigkeit - aus den Parametern des Expert Advisors
      //---
      if(TrComPortSetConfig(PortID,com_par)==-1)
         return(false);//Schreibfehler
     }
   return(true);
  }

Bei erfolgreicher Initialisierung speichert die Variable PortID den Identifikator des offenen COM-Ports.

Es sollte festgehalten werden, dass Identifikatoren ab Null nummeriert werden. Somit ist der Identifikator des Ports COM3 2. Da der Port nun offen ist, können wir Daten mit dem Modem austauschen. Und, nebenbei bemerkt, nicht nur mit einem Modem. Der Zugriff auf den COM-Port aus dem Expert Advisor eröffnet allen, die gut im "Löten" sind, großartige Möglichkeiten, kreativ zu werden: Sie können den Expert Advisor mit einer LED oder einem Lauftext verbinden, um das Kapital oder die Marktpreise bestimmter Währungspaare anzeigen zu lassen.

Die Funktion TrComPortGetQueue wird genutzt, um Details der Daten in die Warteschlange des Empfängers und Transmitters des COM-Ports aufzunehmen:

int TrComPortGetQueue(
   int   portid,           // COM-Port-Identifikator
   uint& input_queue,      // Menge der Bytes im Eingangspuffer
   uint& output_queue      // Menge der Bytes im Ausgangspuffer
   );

Bei einem Fehler gibt sie einen negativen Wert des Fehlercodes aus. Eine detaillierte Beschreibung der Fehlercodes finden sie im Archiv mit den Quellcodes der Bibliothek TrComPort.dll.

Wenn die Funktion eine andere Datenmenge als Null im Empfänger-Puffer ausgibt, müssen diese Daten gelesen werden. Zu diesem Zweck nutzen wir die Funktion TrComPortReadArray:

int TrComPortReadArray(
   int portid,             // Port-Identifikator
   uchar& buffer[],        // Pointer zum zu lesenden Puffer
   uint length,            // Menge der Datenbytes
   int timeout             // Ausführungs-Timeout (in ms)
   );

Bei einem Fehler gibt sie einen negativen Wert des Fehlercodes aus. Die Menge der Datenbytes muss dem durch die Funktion TrComPortGetQueue ausgegebenen Wert entsprechen.

Um das Standard-Timeout zu nutzen (festgelegt bei der Initialisierung des COM-Ports), müssen Sie den Wert -1 übergeben.

Um Daten an den COM-Port zu übertragen, nutzen wir die Funktion TrComPortWriteArray:

int TrComPortWriteArray(
   int portid,             // Port-Identifikator
   uchar& buffer[],        // Pointer zum ursprünglichen Puffer
   uint length,            // Menge der Datenbytes
   int timeout             // Ausführungs-Timeout (in ms)
   );

Anwendungsbeispiel. Als Antwort auf die Nachricht "Hello World!" senden wir "Have a nice day!".

uchar rx_buf[1024];
uchar tx_buf[1024];
string rx_str;
int rxn, txn;
TrComPortGetQueue(PortID, rxn, txn);
if(rxn>0)
{  //--- im Empfänger-Puffer empfangene Daten
   //--- Lesen
   TrComPortReadArray(PortID, rx_buf, rxn, -1);
   //--- in einen String konvertieren
   rx_str = CharArrayToString(rx_buf,0,rxn,CP_ACP);
   //--- Prüfen der empfangenen Nachricht (erwartete Nachricht "Hello World!")
   if(StringFind(rx_str,"Hello world!",0)!=-1)
   {//--- Vorbereiten der Antwort bei Entsprechung
      string tx_str = "Have a nice day!";
      int len = StringLen(tx_str);//Abrufen der Länge in Zeichen
      //--- Konvertieren zu uchar-Puffer
      StringToCharArray(tx_str, tx_buf, 0, len, CP_ACP);
      //--- Senden an den Port
      if(TrComPortWriteArray(PortID, tx_buf, len, -1)<0) Print("Fehler beim Schreiben in den Port");
   }
}

Achten Sie besonders auf die Funktion zum Schließen des Ports:

int TrComPortClose(
   int portid         // Port-Identifikator
   );  

Diese Funktion muss im Deinitialisierungsprozess des Expert Advisors immer vorhanden sein. In den meisten Fällen ist ein Port, der offen gelassen wurde, erst nach einem Neustart des Systems wieder verfügbar. Sogar das Aus- und Einschalten des Modems hilft nicht immer.


2. AT-Befehle und Arbeiten mit dem Modem

Die Arbeit mit einem Modem wird mithilfe von AT-Befehlen durchgeführt. Jene unter Ihnen, die schon einmal mobiles Internet über einen Computer genutzt haben, erinnern sich vermutlich an den sogenannten "String zur Initialisierung des Modems", der etwa so aussieht: AT+CGDCONT=1,"IP","internet". Das ist einer der AT-Befehle. Fast alle von ihnen beginnen mit dem Präfix AT und enden mit 0x0d (Zeilenumbruch).

Wir werden den minimalen Satz von AT-Befehlen verwenden, der für die Umsetzung der gewünschten Funktionalität benötigt wird. Dies reduziert den Aufwand zum Sicherstellen der Kompatibilität des Befehlssatzes mit unterschiedlichen Geräten.

Unten sehen Sie die Liste der von unserem Handler für die Arbeit mit dem Modem verwendeten AT-Befehle:

 Befehl Beschreibung
  ATE1                                    
  Echo aktivieren
  AT+CGMI
  Herstellernamen abrufen
  AT+CGMM
  Gerätemodell abrufen
  AT^SCKS
  SIM-Kartenstatus abrufen
  AT^SYSINFO
  Systeminformationen abrufen
  AT+CREG
  Netzwerkregistrierungsstatus abrufen
  AT+COPS
  Namen des aktuellen Mobilfunkanbieters abrufen
  AT+CMGF
  Zwischen Text-/PDU-Modi umschalten
  AT+CLIP
  Anruferidentifizierung aktivieren
  AT+CPAS
  Modemstatus abrufen
  AT+CSQ
  Signalqualität abrufen
  AT+CUSD
  USSD-Anfrage senden
  AT+CALM
  Stumm-Modus aktivieren (gilt für Telefone)
  AT+CBC
  Batteriestatus abrufen (gilt für Telefone)
  AT+CSCA
  Nummer des SMS-Servicecenters abrufen
  AT+CMGL
  Liste von SMS-Nachrichten abrufen
  AT+CPMS
  Speicher für SMS auswählen
  AT+CMGD
  SMS aus dem Speicher löschen
  AT+CMGR
  SMS aus dem Speicher lesen
  AT+CHUP
  Eingehenden Anruf abweisen
  AT+CMGS
  SMS senden


Ich werde nicht vom Thema abschweifen, indem ich auf die Details der Arbeit mit AT-Befehlen eingehe. Es gibt zahlreiche relevante Informationen in technischen Foren. Außerdem wurde alles bereits umgesetzt und zum Erstellen eines Expert Advisors, der mit einem Modem arbeiten kann, müssen wir lediglich eine Header-Datei einfügen und vorgefertigte Funktionen und Strukturen nutzen. Darauf werde ich nun im Detail eingehen.


2,1. Funktionen

Initialisierung des COM-Ports:

bool InitComPort();

Ausgegebener Wert: true bei erfolgreicher Initialisierung, ansonsten false. Aufruf aus der Funktion OnInit() vor der Initialisierung des Modems.

Deinitialisierung des COM-Ports:

void DeinitComPort();

Ausgegebener Wert: keiner. Aufruf aus der Funktion OnDeinit().

Initialisierung des Modems:

void InitModem();

Ausgegebener Wert: keiner. Aufruf aus der Funktion OnInit() nach erfolgreicher Initialisierung des COM-Ports.

Handler von Modem-Ereignissen:

void ModemTimerProc();

Ausgegebener Wert: keiner. Aufruf aus der Funktion OnTimer() in 1-sekündigen Intervallen.

Lesen von SMS-Meldungen nach Index aus dem Speicher des Modems:

bool ReadSMSbyIndex(
   int index,             // Index der SMS im Speicher des Modems
   INCOMING_SMS_STR& sms  // Pointer zu der Struktur, in die die Nachricht verschoben wird
   );

Ausgegebener Wert: true bei erfolgreichem Lesen, ansonsten false.

Löschen von SMS-Meldungen nach Index aus dem Speicher des Modems:

bool DelSMSbyIndex(
   int index              // Index der SMS im Speicher des Modems
   );

Ausgegebener Wert: true bei erfolgreichem Löschen, ansonsten false.

Konvertieren des Index der Verbindungsqualität in einen String:

string rssi_to_str(
   int rssi               // Index der Verbindungsqualität, Werte 0..31, 99
   );

Ausgegebener Wert: ein String, z. B. "-55 dBm".

SMS senden:

bool SendSMS(
   string da,      // Telefonnummer des Empfängers im internationalen Format
   string text,    // Text der Nachricht, lateinische Zeichen und Zahlen, maximale Länge - 158 Zeichen
   bool flash      // Flag der Flash-Nachricht
   );

Ausgegebener Wert: true bei erfolgreichem Senden, ansonsten false. SMS können nur gesendet werden, wenn sie in lateinischen Zeichen verfasst sind. Kyrillische Zeichen werden nur für eingehende SMS unterstützt. Bei flash=true wird eine Flash-Nachricht gesendet.


2,2. Ereignisse (durch Modem-Handler aufgerufene Funktionen)

Aktualisieren der Daten in der Zustandsstruktur des Modems:

void ModemChState();

Übergebene Parameter: keine. Wenn diese Funktion durch den Modem-Handler aufgerufen wird, bedeutet dies für gewöhnlich, dass Daten in der Modemstruktur aktualisiert wurden (die Beschreibung der Struktur folgt weiter unten).

Eingehender Anruf:

void IncomingCall(
   string number          // Anrufernummer
   );

Übergebene Parameter: Anrufernummer. Wenn diese Funktion durch den Modem-Handler aufgerufen wird, bedeutet dies für gewöhnlich, dass der eingehende Anruf von der Nummer 'number' angenommen und abgelehnt wurde.

Neue eingehende SMS:

void IncomingSMS(
   INCOMING_SMS_STR& sms  // Struktur der SMS
   );

Übergebene Parameter: Struktur der SMS (die Beschreibung der Struktur folgt weiter unten). Wenn diese Funktion durch den Modem-Handler aufgerufen wird, bedeutet dies für gewöhnlich, dass sich im Speicher des Modems eine oder mehrere neue SMS befinden. Wenn die Menge ungelesener Nachrichten größer als eins ist, wird die aktuellste Nachricht an diese Funktion übergeben.

SMS-Speicher voll:

void SMSMemoryFull(
   int n                  // Menge der SMS im Speicher des Modems
   );

Übergebene Parameter: Menge der Nachrichten im Speicher des Modems. Wenn diese Funktion durch den Modem-Handler aufgerufen wird, bedeutet das für gewöhnlich, dass der SMS-Speicher voll ist und das Modem keine neuen Nachrichten annehmen wird, bis der Speicher freigegeben wird.


2,3. Zustandsstruktur von Parametern des Modems

struct MODEM_STR
{
   bool     init_ok;          // Erforderliches Minimum initialisiert
   //
   string   manufacturer;     // Hersteller
   string   device;           // Modell
   int      sim_stat;         // SIM-Status
   int      net_reg;          // Zustand der Netzwerkregistrierung
   int      status;           // Modemstatus
   string   op;               // Betreiber
   int      rssi;             // Signalqualität
   string   sms_sca;          // Nummer des SMS-Centers
   int      bat_stat;         // Batteriezustand
   int      bat_charge;       // Batterieladung in Prozent (abhängig von bat_stat)
   //
   double   bal;              // Guthaben
   string   exp_date;         // Ablaufdatum der Nummer
   int      sms_free;         // Verfügbare SMS im Paket
   int      sms_free_cnt;     // Zähler verbrauchter SMS im Paket
   //
   int      sms_mem_size;     // Größe des SMS-Speichers
   int      sms_mem_used;     // Größe des verbrauchten SMS-Speichers
   //
   string   incoming;         // Anrufernummer
};

MODEM_STR modem;

Diese Struktur wird ausschließlich durch den Handler der Modem-Ereignisse ausgefüllt und darf von anderen Funktionen nur zum Lesen genutzt werden.

Nachfolgend sehen Sie die Beschreibung der Elemente der Struktur:

 ElementBeschreibung
  modem.init_ok
  Angabe, dass das Modem erfolgreich initialisiert wurde.
  Der ursprüngliche Wert false wird nach abgeschlossener Initialisierung zu true.
  modem.manufacturer
  Hersteller des Modems, z. B. "huawei".
  Der ursprüngliche Wert ist "n/a".
  modem.device
  Modemmodell, z. B. "E1550"
  Der ursprüngliche Wert ist "n/a".
  modem.sim_stat
  SIM-Kartenstatus. Kann die folgenden Werte haben:
  -1 - keine Daten
   0 - Karte fehlt, gesperrt oder fehlerhaft
   1 - Karte verfügbar
  modem.net_reg
  Netzwerkregistrierungsstatus. Kann die folgenden Werte haben:
  -1 - keine Daten
   0 - nicht registriert
   1 - registriert
  2 - Suche läuft
   3 - verboten
   4 - Zustand nicht definiert
   5 - registriert in Roaming
  modem.status
  Modemstatus. Kann die folgenden Werte haben:
  -1 - Initialisierung
   0 - bereit
   1 - Fehler
   2 - Fehler
   3 - eingehender Anruf
   4 - aktiver Anruf
  modem.op
  Aktueller Mobilfunkbetreiber.
  Kann entweder dem Betreibernamen entsprechen (z. B. "MTS UKR")
  oder dem internationalen Betreibercode (z. B. "25501").
 Der ursprüngliche Wert ist "n/a".
  modem.rssi
  Index der Signalqualität. Kann die folgenden Werte haben:
  -1 - keine Daten
   0 - Signal -113 dBm oder niedriger
   1 - Signal -111 dBm
   2...30 - Signal -109...-53 dBm
  31 - Signal -51 dBm oder höher
  99 - keine Daten
  Nutzen Sie die Funktion rssi_to_str() für die Konvertierung in einen String.
  modem.sms_sca
  Nummer des SMS-Servicecenters. Im SIM-Kartenspeicher enthalten.
  Wird für die Erzeugung einer ausgehenden SMS benötigt.
  In seltenen Fällen, in denen die Nummer nicht im SIM-Kartenspeicher
gespeichert ist, wird sie durch die Nummer in den Eingabeparametern des Expert Advisors ersetzt.
  modem.bat_stat
  Batteriestatus des Modems (gilt nur für Telefone).
  Kann die folgenden Werte haben:
  -1 - keine Daten
   0 - das Gerät wird durch die Batterie betrieben
   1 - die Batterie ist verfügbar, aber das Gerät wird nicht durch die Batterie betrieben
   2 - keine Batterie
   3 - Fehler
  modem.bat_charge
  Batterieladung in Prozent.
  Mögliche Werte zwischen 0 und 100.
  modem.bal
  Guthaben. Der Wert ergibt sich
  aus der Antwort des Betreibers auf die entsprechende USSD-Anfrage.
  Ursprünglicher Wert (vor der Initialisierung): -10000.
  modem.exp_date
  Ablaufdatum der Mobilnummer. Der Wert ergibt sich
  aus der Antwort des Betreibers auf die entsprechende USSD-Anfrage.
  Der ursprüngliche Wert ist "n/a".
  modem.sms_free
  Menge verfügbarer SMS im Paket. Wird als Differenz zwischen dem
  ursprünglichen Wert und dem Zähler der verbrauchen SMS im Paket berechnet.
  modem.sms_free_cnt
  Zähler verbrauchter SMS im Paket. Der Wert ergibt sich
  aus der Antwort des Betreibers auf die entsprechende USSD-Anfrage. Der ursprüngliche Wert ist -1.
  modem.sms_mem_size
  Größe des SMS-Speichers des Modems.
  modem.sms_mem_used
  Verbrauchter SMS-Speicher des Modems.
  modem.incoming
  Nummer des letzten Anrufers.
  Der ursprüngliche Wert ist "n/a".


2,4. Struktur der SMS

//+------------------------------------------------------------------+
//| SMS-Struktur                                            |
//+------------------------------------------------------------------+
struct INCOMING_SMS_STR
{
   int index;                //Index im Speicher des Modems
   string sca;               //SMS-Center-Nummer des Absenders
   string sender;            //Nummer des Absenders
   INCOMING_CTST_STR scts;   //Zeit-Label des SMS-Centers
   string text;              //Nachrichtentext
};

Das Zeit-Label des SMS-Centers ist die Zeit, zu der eine bestimmte Nachricht des Absenders im SMS-Center eingegangen ist. Die Struktur des Zeit-Labels sieht so aus:

//+------------------------------------------------------------------+
//| Struktur des Zeit-Labels                                             |
//+------------------------------------------------------------------+
struct INCOMING_CTST_STR
{
   datetime time;            // Zeit
   int gmt;                  // Zeitzone
};

Die Zeitzone wird in 15-minütigen Intervallen ausgedrückt. Beispielsweise entspricht der Wert 8 der Zeitzone GMT+02:00.

Der Text von empfangenen SMS kann in lateinischen sowie kyrillischen Zeichen verfasst sein. Für eingehende Nachrichten werden 7-Bit- und UCS2-Kodierungen unterstützt. Lange Nachrichten werden nicht zusammengeführt (da dieser Vorgang für kurze Befehle vorgesehen ist).

SMS können nur gesendet werden, wenn sie in lateinischen Zeichen verfasst sind. Die maximale Nachrichtenlänge beträgt 158 Zeichen. Längere Nachrichten werden ohne die Zeichen gesendet, die die angegebene Anzahl überschreiten.


3. Entwickeln eines Expert Advisors

Für den Anfang müssen Sie die Datei TrComPort.dll in den Ordner Libraries kopieren und die Dateien ComPort.mqh, modem.mqh und sms.mqh in den Ordner Include legen.

Anschließen erstellen wir mithilfe des Wizards einen neuen Expert Advisor und fügen das Minimum für die Arbeit mit dem Modem hinzu. Das bedeutet:

Fügen Sie modem.mqh ein:

#include <modem.mqh>

Fügen Sie die Eingabeparameter hinzu:

input string         str00="Einstellungen des COM-Ports";
input ComPortList    inp_com_port_index=COM3;   // Auswahl des COM-Ports
input BaudRateList   inp_com_baudrate=_9600bps; // Übertragungsgeschwindigkeit
//
input string         str01="Modem";
input int            inp_refr_period=3;         // Modem-Abfrageperiode, Sek
input int            inp_ussd_request_tout=20;  // Timeout für die Antwort auf eine USSD-Anfrage, Sek
input string         inp_sms_service_center=""; // Nummer des SMS-Servicecenters
//
input string         str02="Guthaben";
input int            inp_refr_bal_period=12;    // Abfrageperiode, Std
input string         inp_ussd_get_balance="";   // USSD-Anfrage des Guthabens
input string         inp_ussd_bal_suffix="";    // Suffix des Guthabens
input string         inp_ussd_exp_prefix="";    // Präfix des Ablaufdatums der Nummer
//
input string         str03="Menge der SMS im Paket";
input int            inp_refr_smscnt_period=6;  // Anfrageperiode, Std
input string         inp_ussd_get_sms_cnt="";   // USSD-Anfrage des Status der Services im Paket
input string         inp_ussd_sms_suffix="";    // Suffix des SMS-Zählers
input int            inp_free_sms_daily=0;      // Tägliches SMS-Limit

Durch den Modem-Handler aufgerufene Ereignisse:

//+------------------------------------------------------------------+
//| Aufruf bei Eingang einer neuen SMS                        |
//+------------------------------------------------------------------+
void IncomingSMS(INCOMING_SMS_STR& sms)
{  
} 

//+------------------------------------------------------------------+
//| SMS-Speicher ist voll                                               |
//+------------------------------------------------------------------+
void SMSMemoryFull(int n)
{
}

//+------------------------------------------------------------------+
//| Aufruf bei Eingang eines eingehenden Anrufs                           |
//+------------------------------------------------------------------+
void IncomingCall(string number)
{ 
}  

//+------------------------------------------------------------------+
//| Aufruf nach Aktualisierung der Daten in der Struktur des Modemstatus         |
//+------------------------------------------------------------------+
void ModemChState()
{
   static bool init_ok = false;
   if(modem.init_ok==true && init_ok==false)
   {
      Print("Modem erfolgreich initialisiert");      
      init_ok = true;
   }
}

Die Initialisierung des COM-Ports und des Modems zusammen mit einem Timer mit 1-sekündigem Intervall müssen zur Funktion OnInit() hinzugefügt werden:

int OnInit()
{  //---Initialisierung des COM-Ports
   if(InitComPort()==false)
   {
      Print("Fehler beim Initialisieren des COM-Ports"+DoubleToString(inp_com_port_index+1,0)+" port");
      return(INIT_FAILED);
   }      
   //--- Initialisierung des Modems
   InitModem();
   //--- Setzen des Timers
   EventSetTimer(1); //1-sekündiges Intervall
   //      
   return(INIT_SUCCEEDED);
}

Der Modem-Handler muss in der Funktion OnTimer() aufgerufen werden:

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

Die Deinitialisierung des COM-Ports muss in der Funktion OnDeinit() aufgerufen werden:

void OnDeinit(const int reason)
{
//--- Timer zerstören
   EventKillTimer();
   DeinitComPort();   
}

Wir kompilieren den Code und sehen: 0 error(s).

Führen Sie den Expert Advisor nun aus, aber denken Sie daran, den DLL-Import zuzulassen, und wählen Sie den mit dem Modem verbundenen COM-Port. Sie sollten die folgenden Nachrichten in der Registerkarte "Expert Advisors" sehen:

Erste Ausführung

Abb. 2. Nachrichten des Expert Advisors nach erfolgreicher Ausführung

Wenn sie die gleichen Nachrichten erhalten, bedeutet dies, dass Ihr Modem (Telefon) für die Arbeit mit diesem Expert Advisor geeignet ist. In diesem Fall fahren wir fort.

Lassen Sie uns eine Tabelle für die Visualisierung der Parameter des Modems zeichnen. Sie wird sich in der linken oberen Ecke des Terminal-Fensters befinden, unterhalb der OHLC-Zeile. Als Schriftart für die Tabelle wird eine Monospace-Schriftart verwendet, z. B. "Courier New".

//+------------------------------------------------------------------+
//| TextXY                                                           |
//+------------------------------------------------------------------+
void TextXY(string ObjName,string Text,int x,int y,color TextColor)
  {
//--- Anzeige des Textstrings
   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);
  }
//+------------------------------------------------------------------+
//| Zeichen der Tabelle der Modemparameter                            |
//+------------------------------------------------------------------+
void DrawTab()
  {
   int   x=20, //horizontale Einrückung
   y = 20,     //vertikale Einrückung
   dy = 15;    //Schritt entlang der Y-Achse
//--- Zeichnen des Hintergrunds
   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);
//--- Parameter des Ports
   TextXY("str0",  "Port:            ", x, y, clrWhite); y+=dy;
   TextXY("str1",  "Geschwindigkeit:           ", 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;
//--- Parameter des Modems
   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",  "Betreiber:        ", 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", "Modemstatus:    ", x, y, clrWhite); y+=(dy*3)/2;
//--- Guthaben
   TextXY("str13", "Guthaben:         ", x, y, clrWhite); y+=dy;
   TextXY("str14", "Ablaufdatum: ", x, y, clrWhite); y+=dy;
   TextXY("str15", "Freie SMS:        ", x, y, clrWhite); y+=(dy*3)/2;
//--- Nummer des letzten eingehenden Anrufs
   TextXY("str16","Eingehend:        ",x,y,clrWhite); y+=(dy*3)/2;
//--- Parameter der letzten eingegangenen SMS
   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 der letzten erhaltenen SMS
   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);
  }

Zum Aktualisieren der Daten in der Tabelle nutzen wir die Funktion RefreshTab():

//+------------------------------------------------------------------+
//| Aktualisieren der Werte in der Tabelle                  |
//+------------------------------------------------------------------+
void RefreshTab()
  {
   string str;
//--- Index des COM-Ports:
   str="COM"+DoubleToString(PortID+1,0);
   ObjectSetString(0,"str0",OBJPROP_TEXT,"Port:            "+str);
//--- Übertragungsgeschwindigkeit:
   str=DoubleToString(inp_com_baudrate,0)+" bps";
   ObjectSetString(0,"str1",OBJPROP_TEXT,"Geschwindigkeit:           "+str);
//--- Anzahl erhaltener Bytes:
   str=DoubleToString(rx_cnt,0)+" bytes";
   ObjectSetString(0,"str2",OBJPROP_TEXT,"Rx:              "+str);
//--- Anzahl übertragener Bytes:
   str=DoubleToString(tx_cnt,0)+" bytes";
   ObjectSetString(0,"str3",OBJPROP_TEXT,"Tx:              "+str);
//--- Anzahl der Port-Fehler:
   str=DoubleToString(tx_err,0);
   ObjectSetString(0,"str4",OBJPROP_TEXT,"Err:             "+str);
//--- Modemhersteller und -modell:
   str=modem.manufacturer+" "+modem.device;
   ObjectSetString(0,"str5",OBJPROP_TEXT,"Modem:           "+str);
//--- SIM-Kartenstatus:
   string sim_stat_str[2]={"Fehler","Ok"};
   if(modem.sim_stat==-1)
      str="n/a";
   else
      str=sim_stat_str[modem.sim_stat];
   ObjectSetString(0,"str6",OBJPROP_TEXT,"SIM:             "+str);
//--- Netzwerkregistrierung:
   string net_reg_str[6]={"Keine","Ok","Suche...","Verboten","Unbekannt","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 des Mobilfunkbetreibers:
   ObjectSetString(0,"str8",OBJPROP_TEXT,"Betreiber:        "+modem.op);
//--- Nummer des SMS-Servicecenters
   ObjectSetString(0,"str9",OBJPROP_TEXT,"SMSC:            "+modem.sms_sca);
//--- Signalstärke:
   if(modem.rssi==-1)
      str="n/a";
   else
      str=rssi_to_str(modem.rssi);
   ObjectSetString(0,"str10",OBJPROP_TEXT,"RSSI:            "+str);
//--- Batteriestatus (gilt für Telefone):
   string bat_stats_str[4]={"Ok, ","Ok, ","Keine","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);
//--- Modemstatus:
   string modem_stat_str[5]={"Bereit","Err","Err","Eingehender Anruf","Aktiver Anruf"};
   if(modem.status==-1)
      str="init...";
   else
     {
      if(modem.status>4 || modem.status<0)
         Print("Unbekannter Modemstatus: "+DoubleToString(modem.status,0));
      else
         str=modem_stat_str[modem.status];
     }
   ObjectSetString(0,"str12",OBJPROP_TEXT,"Modemstatus:    "+str);
//--- Guthaben:
   if(modem.bal==-10000)
      str="n/a";
   else
      str=DoubleToString(modem.bal,2)+" "+inp_ussd_bal_suffix;
   ObjectSetString(0,"str13",OBJPROP_TEXT,"Guthaben:         "+str);
//--- Ablaufdatum der Mobilnummer:
   ObjectSetString(0,"str14",OBJPROP_TEXT,"Ablaufdatum: "+modem.exp_date);
//--- verfügbare SMS im Paket:
   if(modem.sms_free<0)
      str="n/a";
   else
      str=DoubleToString(modem.sms_free,0);
   ObjectSetString(0,"str15",OBJPROP_TEXT,"Freie SMS:        "+str);
//--- SMS-Speicher voll:
   if(sms_mem_full==true)
      str="Ja";
   else
      str="Nein";
   ObjectSetString(0,"str17",OBJPROP_TEXT,"SMS mem full:    "+str);
//---
   ChartRedraw(0);
  }

Die Funktion DelTab() löscht die Tabelle:

//+------------------------------------------------------------------+
//| Löschen der Tabelle                                               |
//+------------------------------------------------------------------+
void DelTab()
  {
   for(int i=0; i<25; i++)
      ObjectDelete(0,"str"+DoubleToString(i,0));
   ObjectDelete(0,"bgnd000");
  }

Lassen Sie uns Funktionen zum Arbeiten mit der Tabelle zu den Ereignis-Handlern OnInit() und OnDeinit() sowie der Funktion ModemChState() hinzufügen:

//+------------------------------------------------------------------+
//| Initialisierungsfunktion des Expert Advisors         |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- Initialisierung des COM-Ports
   if(InitComPort()==false)
     {
      Print("Fehler beim Initialisieren des COM-Ports"+DoubleToString(inp_com_port_index+1,0));
      return(INIT_FAILED);
     }
//---
   DrawTab();
//--- Initialisierung des Modems
   InitModem();
//--- Setzen des Timers 
   EventSetTimer(1);//1 second interval
//---
   RefreshTab();
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Deinitialisierungsfunktion des Expert Advisors    |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//--- Timer zerstören
   EventKillTimer();
   DeinitComPort();
   DelTab();
  }
//+------------------------------------------------------------------+
//| ModemChState                                                     |
//+------------------------------------------------------------------+
void ModemChState()
  {
   static bool init_ok=false;
//Print("Modemstatus geändert");
   if(modem.init_ok==true && init_ok==false)
     {
      Print("Modem erfolgreich initialisiert");
      init_ok=true;
     }
//---
   RefreshTab();
  }

Außerdem erweitern wir die Funktion IncomingCall() um die Möglichkeit, die Nummer des letzten eingehenden Anrufs in der Tabelle zu aktualisieren:

//+------------------------------------------------------------------+
//| Aufruf beim Erhalt eines eingehenden Anrufs                           |
//+------------------------------------------------------------------+
void IncomingCall(string number)
{    
   //--- Aktualisieren der Nummer des letzten eingehenden Anrufs:
   ObjectSetString(0, "str16",OBJPROP_TEXT, "Eingehend:        "+number);   
} 

Kompilieren Sie nun den Code und führen Sie den Expert Advisor aus. Im Terminal-Fenster sollten Sie den folgenden Bericht sehen:

Parameter des Modemstatus

Abb. 3. Parameter des Modems

Versuchen Sie, das Modem anzurufen. Der Anruf wird abgewiesen und Ihre Nummer erscheint in der Zeile "Incoming".


4. Arbeiten mit USSD-Anfragen

Guthaben, das nicht rechtzeitig aufgeladen wurde, kann den Betrieb eines Expert Advisors zum ungünstigsten Zeitpunkt unterbrechen. Deshalb ist die Funktion zum Überprüfen des Guthabens eine der wichtigsten. Zum Überprüfen des Guthabens nutzen wir für gewöhnlich USSD-Anfragen. Außerdem werden wir USSD-Anfragen nutzen, um Informationen über die Anzahl der verfügbaren SMS im Paket zu erhalten.

Daten zum Erzeugen von Anfragen und Verarbeiten von Antworten befinden sich in den Eingabeparametern:

input string         str02="=== Guthaben ======";
input int            inp_refr_bal_period=12;  //Anfrageperiode, Std
input string         inp_ussd_get_balance=""; //USSD-Anfrage des Guthabens
input string         inp_ussd_bal_suffix="";  //Suffix des Guthabens
input string         inp_ussd_exp_prefix="";  //Präfix des Ablaufdatums der Nummer
//
input string         str03="= Anzahl der SMS im Paket ==";
input int            inp_refr_smscnt_period=6;//Anfrageperiode, Std
input string         inp_ussd_get_sms_cnt=""; //USSD-Anfrage für Servicestatus des Pakets
input string         inp_ussd_sms_suffix="";  //Suffix des SMS-Zählers
input int            inp_free_sms_daily=0;    //tägliches SMS-Limit

Wenn die Anfragenummer nicht festgelegt ist, wird die Anfrage nicht verarbeitet. Alternativ kann die Anfrage sofort nach der Initialisierung des Modems gesendet werden und wird nach dem angegebenen Zeitraum erneut gesendet. Außerdem wird die Anfrage nicht verarbeitet, wenn Ihr Modem (Telefon) die erforderlichen AT-Befehle nicht unterstützt (dies betrifft alte Mobiltelefone). 

Nehmen wir an, dass Sie auf die Abfrage des Guthabens die folgende Antwort von Ihrem Betreiber erhalten:

7.13 UAH, läuft ab am 22.05.2014. Tarif - Super MTS 3D Null 25.

Um zu gewährleisten, dass der Handler die Antwort korrekt verarbeitet, muss das Suffix des Guthabens mit "UAH" festgelegt werden und das Präfix des Ablaufdatums der Nummer muss "läuft ab am" sein.

Da wir erwarten, dass unser Expert Advisor ziemlich häufig SMS versenden wird, wäre es gut, ein SMS-Paket von Ihrem Anbieter zu kaufen, das heißt, einen Service, bei dem Sie eine bestimmte Menge SMS für eine geringe Gebühr erhalten. In diesem Fall ist es äußerst hilfreich, zu wissen, wie viele SMS des Pakets noch verfügbar sind. Dies lässt sich ebenfalls mithilfe einer USSD-Anfrage umsetzen. Der Betreiber antwortet für gewöhnlich mit der Menge der verbrauchten SMS anstatt der Menge der verfügbaren.

Nehmen wir an, sie erhalten die folgende Antwort von Ihrem Betreiber:

Guthaben: 69 Minuten Ortsanrufe für heute. Heute verbraucht: 0 SMS und 0 MB.

In diesem Fall muss das Suffix des SMS-Zählers "SMS" sein und das tägliche Limit muss gemäß den Bedingungen des SMS-Paket festgelegt werden. Wenn Sie beispielsweise 30 SMS pro Tag zur Verfügung haben und die Anfrage den Wert 10 ausgibt, bedeutet das, dass sie 30-10=20 SMS zur Verfügung haben. Diese Zahl wird durch den Handler im entsprechenden Element der Struktur des Modemstatus platziert.

ACHTUNG! Seien Sie bei USSD-Anfragenummern äußerst vorsichtig! Das Senden einer falschen Anfrage kann unerwünschte Folgen haben, bspw. die Aktivierung eines unerwünschten zahlungspflichtigen Dienstes!

Damit unser Expert Advisor anfangen kann, mit USSD-Anfragen zu arbeiten, müssen wir einfach die entsprechenden Eingabeparameter festlegen.

Beispielsweise sind die Parameter des ukrainischen Mobilfunkanbieters MTS Ukraine die folgenden:

Parameter der Anfrage für das verfügbare Guthaben

Abb. 4. Parameter der USSD-Anfrage für das verfügbare Guthaben

Parameter der Anfrage für die Menge der verfügbaren SMS im Paket

Abb. 5. Parameter der USSD-Anfrage für die Menge der verfügbaren SMS im Paket

Legen Sie Werte fest, die Ihrem Mobilfunkanbieter entsprechen. Anschließend werden das verfügbare Guthaben Ihres Mobilfunkkontos und die Menge der verfügbaren SMS in der Tabelle des Modemstatus angezeigt:

Verfügbares Guthaben

Abb. 6. Aus USSD-Anfragen erhaltene Parameter

Als ich diesen Beitrag schrieb, versendete mein Mobilfunkanbieter Weihnachtswerbung anstatt des Ablaufdatums der Nummer. Folglich konnte der Handler den Wert des Datums nicht abrufen, weshalb wir in der Zeile "Expiration date" "n/a" sehen. Bitte beachten Sie, dass alle Antworten der Betreiber in der Registerkarte "Expert Advisors" angezeigt werden.

Antworten des Betreibers

Abb. 7. Antworten des Betreibers in der Registerkarte "Expert Advisors"


5. Senden von SMS

Wir fangen an, hilfreiche Funktionen hinzuzufügen, beispielsweise das Senden von SMS mit dem aktuellen Gewinn, dem Eigenkapital und der Menge offener Positionen. Das Senden wird durch einen eingehenden Anruf eingeleitet.

Eine solche Antwort wird natürlich nur bei der Nummer des Administrators erwartet, deshalb erhalten wir einen weiteren Eingabeparameter:

input string         inp_admin_number="+XXXXXXXXXXXX";//Telefonnummer des Administrators

Die Nummer muss im internationalen Format mit einem "+" vor der Nummer eingegeben werden.

Die Überprüfung der Nummer sowie die Erzeugung des SMS-Textes und der Versand müssen zum Handler für eingehende Anrufe hinzugefügt werden:

//+------------------------------------------------------------------+
//| Aufruf bei Empfang eines eingehenden Anrufs                           |
//+------------------------------------------------------------------+
void IncomingCall(string number)
{
   bool result;
   if(number==inp_admin_number)
   {
      Print("Telefonnummer des Administrators. Sende SMS.");
      //
      string mob_bal="";
      if(modem.bal!=-10000)//Guthaben
         mob_bal = "\n(m.bal="+DoubleToString(modem.bal,2)+")";
      result = SendSMS(inp_admin_number, "Konto: "+DoubleToString(AccountInfoInteger(ACCOUNT_LOGIN),0)   
               +"\nGewinn: "+DoubleToString(AccountInfoDouble(ACCOUNT_PROFIT),2)
               +"\nEigenkapital: "+DoubleToString(AccountInfoDouble(ACCOUNT_EQUITY),2)
               +"\nPositionen: "+DoubleToString(PositionsTotal(),0)
               +mob_bal
               , false);
      if(result==true)
         Print("SMS erfolgreich gesendet");
      else
         Print("Fehler beim Senden der SMS");               
   }   
   else
      Print("Nicht autorisierte Nummer ("+number+")"); 
   //--- Aktualisieren der Nummer des letzten eingehenden Anrufs:
   ObjectSetString(0, "str16",OBJPROP_TEXT, "Eingehend:        "+number);   
}  

Wenn nun ein Anruf des Modems von der Nummer des Administrators inp_admin_number eingeht, wird als Antwort eine SMS gesendet:

SMS als Antwort auf einen eingehenden Anruf

Abb. 8. Vom Expert Advisor gesendete SMS als Antwort auf den Anruf von der Telefonnummer des Administrators

Hier sehen wir die aktuellen Werte des Gewinns und des Eigenkapitals sowie die Menge offener Positionen und das Guthaben des Mobilfunkkontos.


6. Überwachen der Verbindung zum Handelsserver

Lassen Sie uns Benachrichtigungen für den Fall einer unterbrochenen und wiederhergestellten Verbindung zum Handelsserver hinzufügen. Zu diesem Zweck prüfen wir alle 10 Sekunden die Verbindung zum Handelsserver mithilfe von TerminalInfoInteger() mit dem Eigenschaftsidentifikator TERMINAL_CONNECTED.

Zum Herausfiltern kurzzeitiger Unterbrechungen der Verbindung nutzen wir die Hysterese, die zur Liste der Eingabeparameter hinzugefügt werden muss:

input int            inp_conn_hyst=6; //Hysterese, x10 Sek

Der Wert 6 bedeutet, dass die Verbindung als verloren betrachtet wird, wenn es länger als 6*10=60 Sekunden keine Verbindung gibt. Ebenso gilt die Verbindung als wiederhergestellt, wenn sie länger als 60 Sekunden verfügbar ist. Die lokale Zeit der ersten registrierten Unterbrechung der Verbindung gilt als Zeitpunkt des Verbindungsverlustes und die lokale Zeit, zu der die Verbindung wieder verfügbar war, gilt als Zeitpunkt der Wiederherstellung.

Dazu fügen wir den folgenden Code zur Funktion OnTimer() hinzu:

   static int s10 = 0;//Vorteiler um 10 Sekunden
   static datetime conn_time;
   static datetime disconn_time;
   if(++s10>=10)
   {//--- einmal alle 10 Sekunden
      s10 = 0;
      //
      if((bool)TerminalInfoInteger(TERMINAL_CONNECTED)==true)
      {
         if(cm.conn_cnt==0)             //erste erfolgreiche Anfrage in der Sequenz
            conn_time = TimeLocal();    //Speichern der Zeit
         if(cm.conn_cnt<inp_conn_hyst)
         {
            if(++cm.conn_cnt>=inp_conn_hyst)
            {//--- Verbindung wurde stabilisiert
               if(cm.connected == false)
               {//--- falls es vorher einen langfristigen Verbindungsverlust gab
                  cm.connected = true;
                  cm.new_state = true;
                  cm.conn_time = conn_time;
               }
            }
         }
         cm.disconn_cnt = 0;
      }
      else
      {
         if(cm.disconn_cnt==0)          //erste fehlgeschlagene Anfrage in der Sequenz
            disconn_time = TimeLocal(); //Speichern der Zeit
         if(cm.disconn_cnt<inp_conn_hyst)
         {
            if(++cm.disconn_cnt>=inp_conn_hyst)
            {//--- langfristiger Verbindungsverlust
               if(cm.connected == true)
               {//--- falls die Verbindung vorher stabil war
                  cm.connected = false;
                  cm.new_state = true;
                  cm.disconn_time = disconn_time;
               }
            }
         }
         cm.conn_cnt = 0;
      }
   }
   //
   if(cm.new_state == true)
   {//--- Verbindungsstatus geändert
      if(cm.connected == true)
      {//--- Verbindung verfügbar
         string str = "Verbunden "+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);//Senden der Nachricht
      }
      else
      {//--- keine Verbindung
         string str = "Getrennt "+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);//Senden der Nachricht
      }
      cm.new_state = false;
   }

Die Struktur cm sieht so aus:

//+------------------------------------------------------------------+
//| Struktur der Überwachung der Verbindung zum Terminal             |
//+------------------------------------------------------------------+
struct CONN_MON_STR
  {
   bool              new_state;    //Flag der Änderung des Verbindungsstatus
   bool              connected;    //Verbindungsstatus
   int               conn_cnt;     //Zähler der erfolgreichen Verbindungsanfragen
   int               disconn_cnt;  //Zähler der fehlgeschlagenen Verbindungsanfragen
   datetime          conn_time;    //Zeit der Verbindungsherstellung
   datetime          disconn_time; //Zeit des Verbindungsverlusts
  };

CONN_MON_STR cm;//Struktur der Überwachung der Verbindung

Im Text der SMS geben wir den Zeitpunkt des Verlustes (oder der Wiederherstellung) der Verbindung zum Handelsserver an sowie die Zeit, in der die Verbindung verfügbar (oder nicht verfügbar) war, berechnet als Differenz zwischen der Zeit der Herstellung der Verbindung und der Zeit des Verlustes der Verbindung. Zum Konvertieren der Zeitdifferenz von Sekunden in dd hh:mm:ss fügen wir die Funktion dTimeToString() hinzu:

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

Um sicherzustellen, dass der Expert Advisor nicht jedes Mal eine SMS über die Verbindungsherstellung sendet, wenn der Expert Advisor ausgeführt wird, fügen wir die Funktion conn_mon_init() hinzu, die die Werte der Elemente der Struktur cm so festlegt, als wäre die Verbindung bereits hergestellt. In diesem Fall gilt die Verbindung zur lokalen Zeit der Ausführung des Expert Advisors als hergestellt. Diese Funktion muss aus der Funktion OnInit() aufgerufen werden.

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

Nun können Sie den Expert Advisor kompilieren und ausführen. Versuchen Sie anschließend, Ihren Computer vom Internet zu trennen. In 60 (plus/minus 10) Sekunden erhalten Sie eine Nachricht, dass die Verbindung zum Server unterbrochen wurde. Verbinden Sie sich wieder mit dem Internet. In 60 Sekunden erhalten Sie eine Nachricht über die Wiederherstellung der Verbindung mit Angabe der Gesamtdauer der Unterbrechung der Verbindung:

Nachricht über verlorene Verbindung Nachricht über wiederhergestellte Verbindung

Abb. 9. Textnachrichten über den Verlust und die Wiederherstellung der Verbindung zum Server


7. Senden von Berichten über Öffnen und Schließen von Positionen

Um das Öffnen und Schließen von Positionen zu überwachen, fügen wir den folgenden Code zur Funktion OnTradeTransaction() hinzu:

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

wobei ps der Puffer der Strukturen POS_STR ist:

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

Der Puffer wird für den Fall benötigt, dass innerhalb einer kurzen Zeitspanne mehr als eine Position geschlossen (oder geöffnet) wurde. Wenn eine Position geöffnet oder geschlossen wird, nachdem der Abschluss zur Historie hinzugefügt wurde, erhalten wir alle erforderlichen Parameter und setzen das Flag new_event.

Nachfolgend sehen Sie den Code, der zur Funktion OnTimer() hinzugefügt wird, um new_event-Flags zu überwachen und SMS-Berichte zu erzeugen:

   //--- Verarbeitung der Öffnung/Schließung von Positionen
   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());//Abrufen der Historie für die letzte Stunde
         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);
   }

Nun können Sie den Expert Advisor kompilieren und ausführen. Versuchen wir, AUDCAD mit einer Losgröße von 0.14 zu kaufen. Der Expert Advisor sendet die folgende SMS: "Buy 0.14 AUDCAD, price=0.96538, entry: in". Nach einiger Zeit schließen wir die Position und erhalten die folgende Textnachricht über die Schließung der Position:

Nachricht über die Öffnung der Position Nachricht über die Schließung der Position

Abb. 10. Textnachrichten über die Öffnung (entry: in) und Schließung (entry: out) der Position


8. Verarbeiten eingehender SMS für die Verwaltung offener Positionen

Bisher hat unser Expert Advisor nur SMS an die Telefonnummer des Administrators gesendet. Bringen wir ihm nun bei, SMS-Befehle zu erhalten und auszuführen. Dies kann bspw. beim Schließen aller oder einiger offenen Positionen nützlich sein. Wie wir wissen, gibt es nichts Besseres als eine rechtzeitig geschlossene Position.

Doch zunächst müssen wir sicherstellen, dass SMS korrekt empfangen werden. Dazu erweitern wir die Funktion IncomingSMS() um die Anzeige der zuletzt empfangenen Nachricht:

//+------------------------------------------------------------------+
//| Aufruf bei Empfang einer neuen SMS                        |
//+------------------------------------------------------------------+
void IncomingSMS(INCOMING_SMS_STR& sms)
{   string str, strtmp;
   //Nummer, von der die letzte empfangene SMS gesendet wurde:
   ObjectSetString(0, "str18", OBJPROP_TEXT, "SMS-Nummer:      "+sms.sender);
   //Datum und Zeit des Versands der letzten empfangenen SMS:
   str = TimeToString(sms.scts.time,TIME_DATE|TIME_SECONDS);
   ObjectSetString(0, "str19", OBJPROP_TEXT, "SMS-Datum/-Zeit:   "+str);
   //Text der letzten empfangenen SMS:
   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);  
}

Wenn wir nun eine SMS an das Modem senden, wird sie in der Tabelle angezeigt:

Neue SMS

Abb. 11. Anzeige der eingehenden SMS im Terminal-Fenster

Bitte beachten Sie, dass alle eingehenden SMS in der Registerkarte "Expert Advisors" in folgender Form angezeigt werden: <Index_im_Speicher_des_Modems>Text_der_Nachricht:

Anzeige der SMS in der Registerkarte "Expert Advisors"

Abb. 12. Anzeige der eingegangenen SMS in der Registerkarte "Expert Advisors"

Das Wort "close" wir als Befehl zum Schließen von Abschlüssen verwendet. Darauf müssen ein Leerzeichen und der Parameter folgen – das Symbol der zu schließenden Position bzw. "all", falls Sie alle Positionen schließen müssen. Die Großschreibung spielt keine Rolle, da wir vor der Verarbeitung des Texts der Nachricht die Funktion StringToUpper() anwenden. Wenn Sie die Nachricht analysieren, stellen Sie sicher, dass die Telefonnummer des Absenders der festgelegten Nummer des Administrators entspricht.

Außerdem muss beachtet werden, dass es Fälle geben kann, in denen eine SMS mit einer beträchtlichen Verzögerung empfangen wird (aufgrund technischer Störungen auf Betreiberseite usw.). In solchen Fällen können Sie den in dieser Nachricht enthaltenen Befehl nicht berücksichtigen, da sich die Situation auf dem Markt möglicherweise verändert hat. Deshalb führen wir einen weiteren Eingabeparameter ein:

input int            inp_sms_max_old=600; //Ablauf von SMS-Befehlen, Sek

Der Wert 600 besagt, dass Befehle, deren Zustellung länger als 600 Sekunden (10 Minuten) gedauert hat, ignoriert werden. Bitte beachten Sie, dass die Methode der Überprüfung der Zustellungszeit in diesem Beispiel voraussetzt, dass das SMS-Servicecenter und das Gerät, auf dem der Expert Advisor ausgeführt wird, sich in der gleichen Zeitzone befinden.

Fügen wir den folgenden Code zur Funktion IncomingSMS() hinzu, um SMS-Befehle zu verarbeiten:

   if(sms.sender==inp_admin_number)
   {
      Print("SMS vom Administrator");
      datetime t = TimeLocal();
      //--- Prüfung auf Ablauf der Nachricht
      if(t-sms.scts.time<=inp_sms_max_old)
      {//--- Prüfung, ob die Nachricht ein Befehl ist
         string cmdstr = sms.text;
         StringToUpper(cmdstr);//alles in Großbuchstaben umwandeln
         int pos = StringFind(cmdstr, "CLOSE", 0);
         cmdstr = StringSubstr(cmdstr, pos+6, 6);
         if(pos>=0)
         {//--- Befehl. Senden zum Verarbeiten
            ClosePositions(cmdstr);            
         } 
      }
      else
         Print("Der SMS-Befehl ist abgelaufen");
   }   

Wenn die SMS vom Administrator gesendet wurde, nicht abgelaufen ist und einen Befehl darstellt (beinhaltet das Schlüsselwort "Close"), senden wir ihren Parameter zur Verarbeitung durch die Funktion ClosePositions():

uint ClosePositions(string sstr)
{//--- Schließen der angegebenen Funktionen
   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);
}

Diese Funktion prüft, ob es unter den offenen Positionen eine Entsprechung bezüglich des im Befehl erhaltenen Parameters (Symbols) gibt. Positionen, die diese Bedingung erfüllen, werden mithilfe der Funktion OrderClose() geschlossen:

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("Fehler beim Abrufen aktueller Preise");
      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 erfolgreich ausgeführt");
         return(true);
      }
   }
   else
   {
      Print("Order-Parameterfehler: ", GetLastError(),", Rückgabecode des Handelsservers: ", result.retcode);     
      return(false);
   }      
   //
   return(false);
}

Nach der erfolgreichen Verarbeitung von Ordern erzeugt und sendet die Funktion zur Überwachung von Positionsänderungen eine SMS.


9. Löschen von Nachrichten aus dem Speicher des Modems

Bitte beachten Sie, dass der Modem-Handler eingehende SMS nicht selbstständig löscht. Deshalb ruft der Handler die Funktion SMSMemoryFull() auf, wenn der SMS-Speicher mit der Zeit voll wird, und übergibt die aktuelle Menge der Nachrichten im Speicher des Modems an sie. Sie können entweder alle Nachrichten oder eine Auswahl löschen. Das Modem akzeptiert keine neuen Nachrichten, bis der Speicher freigegeben wurde.

//+------------------------------------------------------------------+
//| SMS-Speicher ist voll                                               |
//+------------------------------------------------------------------+
void SMSMemoryFull(int n)
{
   sms_mem_full = true;
   for(int i=0; i<n; i++)
   {//Entfernen aller SMS
      if(DelSMSbyIndex(i)==false)
         break;
      else
         sms_mem_full = false;   
   }
}

Sie können SMS ebenso gleich nach der Verarbeitung löschen. Wenn die Funktion IncomingSMS() durch den Modem-Handler aufgerufen wird, übergibt die Struktur INCOMING_SMS_STR den Index der Nachricht im Speicher des Modems, was das Löschen der Nachricht gleich nach ihrer Verarbeitung mithilfe der Funktion DelSMSbyIndex() ermöglicht:


Fazit

Dieser Beitrag hat die Entwicklung eines Expert Advisors behandelt, der ein GSM-Modem für die Fernüberwachung des Handelsterminals nutzt. Wir haben die Methoden zum Abrufen von Informationen über offene Positionen, aktuellen Gewinn und weitere Daten mithilfe von SMS-Benachrichtigungen betrachtet. Ebenso haben wir die grundlegenden Funktionen für die Verwaltung offener Positionen durch SMS-Befehle umgesetzt. The example provided features commands in English but you can use the Russian commands equally well (not to waste time on switching between different keyboard layouts in your phone).

Sehen wir uns zu guter Letzt das Verhalten unseres Expert Advisors an, wenn wir es mit einem Mobiltelefon zu tun haben, das vor über 10 Jahren eingeführt wurde. Das Gerät ist ein Siemens M55. Verbinden wir es:

Parameter des Siemens M55Siemens M55

Abb. 13. Verbinden des Siemens M55

Siemens M55, Registerkarte "Expert Advisors"

Abb. 14. Erfolgreiche Initialisierung des Siemens M55, Registerkarte "Expert Advisors"

Sie sehen, dass alle erforderlichen Parameter abgerufen wurden. Das einzige Problem sind die Daten, die wir aus USSD-Anfragen erhalten. Das Problem ist, dass das Siemens M55 keine AT-Befehle für die Arbeit mit USSD-Anfragen unterstützt. Davon abgesehen funktioniert es genauso gut wie jedes moderne Modem, also kann es für die Arbeit mit unserem Expert Advisor genutzt werden.


Übersetzt aus dem Russischen von MetaQuotes Software Corp.
Originalartikel: https://www.mql5.com/ru/articles/797

Beigefügte Dateien |
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)
Indikator für Renko-Diagramme Indikator für Renko-Diagramme

Dieser Beitrag beschreibt ein Beispiel für Renko-Diagramme und dessen Umsetzung als Indikator in MQL5. Dieser Indikator unterscheidet sich durch Modifikationen von einem herkömmlichen Diagramm. Er kann sowohl im Indikatorfenster als auch im Hauptdiagramm konstruiert werden. Außerdem gibt es noch den ZigZag-Indikator. Sie können einige Beispiele für die Umsetzung des Diagramms finden.

Das MQL5-Kochbuch – Mehrwährungsfähiger Expert Advisor und die Arbeit mit Pending Orders in MQL5 Das MQL5-Kochbuch – Mehrwährungsfähiger Expert Advisor und die Arbeit mit Pending Orders in MQL5

Diesmal werden wir einen mehrwährungsfähigen Expert Advisor mit einem Handelsalgorithmus erstellen, der auf der Arbeit mit den Pending Orders Buy Stop und Sell Stop basiert. Folgende Themen werden in diesem Beitrag erörtert: der Handel in einem festgelegten Zeitbereich, Platzieren/Modifizieren/Löschen von Pending Orders, die Prüfung, ob die letzte Position bei Take Profit oder Stop Loss geschlossen wurde, und die Kontrolle der Historie der Abschlüsse für jedes Symbol.

Bewertung und Auswahl von Variablen für Modelle für maschinelles Lernen Bewertung und Auswahl von Variablen für Modelle für maschinelles Lernen

Dieser Artikel konzentriert sich auf die Besonderheiten der Auswahl, Vorkonditionierung und Bewertung der Eingabevariablen (Prädiktoren) für den Einsatz in Modellen für maschinelles Lernen. Neue Ansätze und Möglichkeiten der tiefen Prädiktor Analyse und deren Einfluss auf mögliche Überanpassung von Modellen werden berücksichtigt. Das Gesamtergebnis der Verwendung von Modellen hängt weitgehend vom Ergebnis dieser Phase ab. Wir werden zwei Pakete analysieren, die neue und ursprüngliche Konzepte für die Auswahl der Prädiktoren bieten.

Selbst-organisierende Feature Maps (Kohonen Maps) - Wiederaufgreifen des Themas Selbst-organisierende Feature Maps (Kohonen Maps) - Wiederaufgreifen des Themas

Dieser Artikel beschreibt Techniken für die Arbeit mit Kohonen-Maps. Das Thema wird sowohl für Marktforscher mit Grundkenntnisse der Programmierung in MQL4 und MQL5 als auch erfahrene Programmierer, die Schwierigkeiten mit der Verbindung von Kohonen-Maps mit ihren Projekten haben, von Interesse sein.