Arbeit mit einem GSM-Modem über einen Expert Advisor in MQL5
Serhii Shevchuk | 20 Mai, 2016
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.
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:
Element | Beschreibung |
---|---|
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:
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:
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:
Abb. 4. Parameter der USSD-Anfrage für das verfügbare Guthaben
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:
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.
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:
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:
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:
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:
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:
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:
Abb. 13. Verbinden des Siemens M55
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.