MetaTrader 5 herunterladen

Verwendung eines Cloud-Speichers für den Datenaustausch zwischen Terminals

30 August 2017, 09:18
Dmitriy Gizlyk
0
129

Einführung

Immer beliebter werden die wolkigen Technologien in der modernen Welt. Wir können Speichers von verschiedener Größe verwenden, sowohl kostenpflichtige, als auch kostenlose. Können wir sie in einem echten Traiding verwenden? In diesem Artikel wird die Technologie für den Datenaustausch zwischen Terminals durch die Verwendung wolkiger Speichers angeboten.

Sie können sich fragen: warum dazu überhaupt wolkige Speichers verwendet sollen? Es gibt schon doch die Technologien, welche die Terminals direkt verbinden. Aber ich denke, diese Art hat eine Reihe von Vorteilen. Erstens, es ist die Anonymität des Providers: die Benutzer dieser Information rufen nicht seinen Computer auf, sondern einen wolkigen Server. So wird der Computer des Providers vor den Virusangriffen geschützt, und er muss nicht ständig ans Netz angeschlossen sein. Es ist genug, angeschlossen zu sein, um die Mitteilungen zum Server zu senden. Zweitens, es kann in "der Wolke" eine unbeschränkte Anzahl der Provider sein. Und drittens, mit der Zunahme der Anzahl der Benutzer wird es nicht erfordert, die Leistungen des Providers zu erhöhen.

Als Beispiel werden wir einen kostenlosen wolkigen Speicher vom Unternehmen Google in Größe von 15 Gigabyte verwenden. Für unsere Ziele ist es durchaus genug.

1. Ein wenig Theorie

Die Anmeldung in Google drive ist nach dem Protokoll OAuth 2.0 organisiert. Es ist ein offenes Anmeldung-Protokoll, welches ermöglicht, den anderen Anwendungen und Webseiten einen eingeschränkten Zugang zu den geschützten Ressourcen der autorisierten Benutzer herzustellen, und dabei ohne Pflicht, die Zugangsdaten zu geben. Das grundlegende Scenario des Zuganges nach dem Protokoll OAuth 2.0 besteht aus 4 Etappen.

  1. Am Anfang muss man die Daten für die Anmeldung (der Identifikator des Kunden und das Geheimnis des Kunden) bekommen. Diese Daten werden von der Webseite generiert und dadurch sind sie der Webseite und der Anwendung bekannt.
  2. Bevor die Anwendung den Zugang zu den persönlichen Daten bekommen kann, muss sie den Token-Zugang bekommen. Ein solcher Token kann verschiedene Zugangsstufen anbieten, der von der Variable scope bestimmt wird. Während des Aufrufs des Token-Zuganges kann die Anwendung eine oder mehrere Werte im Parameter scope absenden. Für die Bildung dieses Aufrufs kann die Anwendung sowohl den Systembrowser, als auch die Aufrufen der Webdienste verwenden. Einige Aufrufen fordern die Etappe der Authentifizierung, auf der der Benutzer sich mit seinem Account anmeldet. Nach der Anmeldung im System wird es vom Benutzer gefragt, ob er bereit ist, die Genehmigung für diese Anwendung zu geben. Dieser Prozess heißt Akzeptieren. Wenn der Benutzer es genehmigt hat, gibt der Autorisation-Server der Anwendung den Anmeldungscode, nach dem die Anwendung den Token-Zugang bekommen kann. Wenn der Benutzer es nicht genehmigt hat, gibt der Server den Fehler zurück.
  3. Nachdem wie die Anwendung den Token-Zugang bekommt, sendet es den Token in den Titel der Autorisation HTTP ab. Die Punkte des Zuganges sind gültig nur für die Auswahl der Operationen und der Ressourcen, die im Parameter des Aufrufs scope beschrieben sind. Zum Beispiel, wenn der Token des Zuganges für Google Drive aktiviert ist, stellt er keinen Zugang zu den Kontakten Google her. Jedoch kann die Anwendung diesen Token-Zugang zu Google Drive mehrmals, um die Ausführung für die erlaubten Operationen durchzuführen.
  4. Die Token haben eine eingeschränkte Laufzeit. Wenn die Anwendung den Zugang nach der Laufzeit eines Token-Zugangs gefordert hat, kann sie eine Token-Erneuerung bekommen, die der Anwendung ermöglicht, neu Token-Zugangs zu bekommen.

Scenario des Zuganges

2. Die Organisation des Zuganges zum Google Drive

 Für die Arbeit mit Google drive brauchen wir einen Account im Google — neu, oder schon aktuellen.

Bevor die Entwicklung des Codes unseres Programms beginnt, werden wir eine Vorbereitung auf der Webseite Google durchführen. Dazu gehen wir in die Konsole der Hersteller (für den Zugang zu der Konsole wird erfordert, sich nochmal im Account anzumelden).

Wir erstellen ein neues Projekt für unsere Anwendung. Wir gehen ins Bedienfeld der Projekte (die Schaltfläche "Select a project" oder die Tastenkombination "Ctrl + O"). Auf dem Bedienfeld der Projekte werden wir das neue Projekt (die Schaltfläche "+") erstellen.

Das Bedienfeld der Projekte

In der geöffneten Seite geben wir den Namen unseres Projektes ein, akzeptieren die Bedingungen der Nutzung und bestätigen die Erstellung.

Das neue Projekt

Dann werden wir das Projekt im Feld der Projekte auswählen und wir werden zu ihm Google Drive API hinzufügen. Dafür werden wir in der Bibliothek API des Managers "Drive API" auswählen und dann aktivieren den gegebenen API auf der öffnenden Seite durch das Drücken auf "Enable.

Die Bibliothek APIDie Aktivierung API

Die wieder öffnende Seite wird uns beraten, dass wir für die Nutzung API die Zugangsdaten brauchen. Wir erzeugen sie (die Schaltfläche "Create credentials").

Warnung

Die Konsole Google bietet an, den Meister für die Auswahl der Authentifizierungsart zu verwenden, aber wir brauchen das nicht. Wir drücken direkt auf "client ID". Im folgenden Fenster wird uns Google wieder eine Warnung über die Einstellungsnotwendigkeit der Zugangsbestätigungsseite, was wir eigentlich durch das Drücken auf die Schaltfläche " Configure consent screen" durchführen werden.

Warnung

In der geöffneten Seite kann man alle Felder standardmäßig lassen, dabei nur das Feld "Product name shown to users" ausfüllen (der Name des Programms, der dem Benutzer dargestellt wird). Danach werden wir den Typ unserer Anwendung als " Other " eingeben, wir geben einen Namen unserem Kunden ein und drücken auf die Schaltfläche "Create" (Erstellen). Als Antwort auf unsere Aktionen wird der Service "client ID" und "client secret" Codes erzeugen. Man kann sie kopieren, aber es ist gar nicht nötig: der Service ermöglicht sie in Form von der Datei json zu laden. Wir drücken "Ok" und dann laden die Datei json mit den Daten für den Zugang zur lokalen Platte.

Auf diesem Punkt geht die Vorbereitung auf der Seite des Services zu Ende, und wir können mit der Schreibung unserer Programme beginnen.

3. Wir erstellen die Brücke zwischen den lokalen Programmen und Google drive

Für die Lösung dieser Aufgabe habe ich ein Extra-Programm erstellt, so eine eigentümliche Brücke, die die Aufrufe und die Daten vom EA oder vom Indikator MetaTrader bekommen wird, sie bearbeitet sie mit Google drive und gibt diese Daten in die Programme MetaTrader zurück. Die Attraktivität einer solchen Methode, liegt erstens darin, dass Google die Bibliotheken für die Arbeit mit Google drive auf C# anbietet. Es erleichtert wesentlich die Entwicklung. Zweitens wird die Nutzung eines anderen Programms das Terminal von den "aufwendigen" Wechsel-Operationen mit einem äußerlichen Service befreien. Und drittens lässt es unser Programm vom Plattform los und macht das cross-platform, mit der Option, die Anwendungen auch unter MetaTrader 4 und MetaTrader 5 zu verwenden.

Wie ich schon früher gesagt habe, wird das Brückenprogramm auf C# unter Verwendung der Bibliotheken von Google geschrieben. Wir erstellen das Projekt Windows Form in VisualStudio, und direkt mit Hilfe von NuGet fügen zu ihm die Bibliothek Google.Apis.Drive.v3 hinzu.

Weiterhin erstellen wir für die Arbeit mit Google drive die Klasse GoogleDriveClass

class GoogleDriveClass
    {
        static string[] Scopes = { DriveService.Scope.DriveFile };  //Das Array für die Arbeit mit den Dateien
        static string ApplicationName = "Google Drive Bridge";      //Die Namen des Programms
        public static UserCredential credential = null;             //Die Keys der Autorisation
        public static string extension = ".gdb";                    //Die Erweiterung für die speichernden Dateien
    }

Erstmal werden wir die Autorisation-Funktion in dem Service erstellen. Darin wird von uns früher gespeicherte Datei json mit dem Zugangscode verwendet. In meinen Fall ist es "client-secret.json". Wenn Sie die Datei unter anderem Namen gespeichert haben, so geben Sie den entsprechenden Namen für die Datei im Code der Funktion ein. Nach der Ladung der Daten für die Autorisation wird die asynchrone Funktion der Autorisation auf dem Service aufgerufen. Falls eine erfolgreiche Autorisation im Objekt credential stattfindet, wird Token für den nachfolgenden Zugang erhalten. Während der Arbeit in C# vergessen wir nicht über die Bearbeitung der Ausnahmen: Falls eine Ausnahme auftritt, wird das Objekt credential auf 0 zurückgesetzt. 

        public bool Authorize()
        {
            using (System.IO.FileStream stream =
                     new System.IO.FileStream("client-secret.json", System.IO.FileMode.Open, System.IO.FileAccess.Read))
            {
                try
                {
                    string credPath = System.Environment.CurrentDirectory.ToString();
                    credPath = System.IO.Path.Combine(credPath, "drive-bridge.json");

                    credential = GoogleWebAuthorizationBroker.AuthorizeAsync(
                        GoogleClientSecrets.Load(stream).Secrets,
                        GoogleDriveClass.Scopes,
                        "example.bridge@gmail.com",
                        CancellationToken.None,
                        new FileDataStore(credPath, true)).Result;
                }
                catch (Exception)
                {
                    credential = null;
                }

            }
            return (credential != null);
        }

Bei der Arbeit mit Google drive muss unsere "Brücke" zwei Funktionen erfüllen: die Aufnahme der Daten auf die Platte und das Ablesen einer nötigen Datei von ihr. Wir betrachten sie gründlicher. Um diese Funktionen einfach zu realisieren, die auf den ersten Blick sehr einfach sind, müssen wir eine ganze Reihe von Prozeduren schreiben. Alles handelt sich darum, dass das Dateisystem Google drive sich von der Version unterscheidet, mit der wir normalerweise arbeiten. Die Namen und die Erweiterungen der Dateien sind hier in Form von einzelnen Aufnahmen nur für die Unterstützung der gewohnheitsmäßigen Vorstellung. In Wirklichkeit, bei der Speicherung wird jeder Datei einen einzigartigen Identifikator zugeordnet, unter dem er gespeichert wird. So kann der Benutzer die unbeschränkte Anzahl der Dateien mit einem und derselben Namen und Erweiterung speichern. Also, bevor wir diese Datei aufrufen, müssen wir seinen Identifikator im wolkigen Speicher erfahren. Dazu werden wir die Liste aller Dateien auf der Platte laden und vergleichen ihre Namen mit dem gegebenen Namen nacheinander.

Für die Erhaltung der Datei-Liste wird die Funktion GetFileList verantwortlich sein, die die Liste der Klassen Google.Apis.Drive.v3.Data.File zurückgeben wird. Für die Erhaltung der Datei-Liste aus Google drive werden wir die Klasse Google.Apis.Drive.v3.DriveService aus den früher geladenen Bibliotheken benutzen. Bei der Initialization dieser Klasse werden wir in sie den Token, der bei der Autorisation bekommen wurde, und den Namen unseres Projektes übergeben. Die bekommende Liste werden wir in der zurückgebende Variabel result speichern, und falls eine Ausnahme auftritt, wird die Variabel auf 0 zurückgesetzt. Aufrufen und Bearbeiten werden wir die Datei-Liste je nach dem Bedarf in anderen Funktionen unseres Programms.

using File = Google.Apis.Drive.v3.Data.File;
        public IList<File> GetFileList()
        {
            IList<File> result = null;
            if (credential == null)
                this.Authorize();
            if (credential == null)
            {
                return result;
            }
            // Create Drive API service.
            using (Google.Apis.Drive.v3.DriveService service = new Google.Apis.Drive.v3.DriveService(new BaseClientService.Initializer()
            {
                HttpClientInitializer = credential,
                ApplicationName = ApplicationName,
            }))
            {
                try
                {
                    // Define parameters of request.
                    FilesResource.ListRequest listRequest = service.Files.List();
                    listRequest.PageSize = 1000;
                    listRequest.Fields = "nextPageToken, files(id, name, size)";

                    // List files.
                    result = listRequest.Execute().Files;
                }
                catch (Exception)
                {
                    return null;
                }
            }
            return result;
        }


3.1. Die Aufnahme der Daten im wolkigen Speicher

Für die Aufnahme der Datei im wolkigen Speicher werden wir die Funktion FileCreate erstellen. Die Eingangsparameter dieser Funktion — ist der Name der Datei und ihr Inhalt. Sie wird den logischen Wert vom Ergebnis der Operation und den Identifikator der Datei auf der Platte zurückgeben, falls er erfolgreich erstellt wird. Für die Erstellung der Datei wird schon die uns bekannte Klasse Google.Apis.Drive.v3.DriveService verantwortlich sein, und für die Absendung des Aufrufs werden wir die Klasse Google.Apis.Drive.v3.FilesResource.CreateMediaUpload verwenden. In den Parametern der Datei geben wir ein, dass es eine einfache Textdatei sein wird und wir erlauben sein Kopieren.

       public bool FileCreate(string name, string value, out string id)
        {
            bool result = false;
            id = null;
            if (credential == null)
                this.Authorize();
            if (credential == null)
            {
                return result;
            }
            using (var service = new Google.Apis.Drive.v3.DriveService(new BaseClientService.Initializer()
            {
                HttpClientInitializer = credential,
                ApplicationName = ApplicationName,
            }))

            {
                var body = new File();
                body.Name = name;
                body.MimeType = "text/json";
                body.ViewersCanCopyContent = true;

                byte[] byteArray = Encoding.Default.GetBytes(value);
                using (var stream = new System.IO.MemoryStream(byteArray))
                {
                    Google.Apis.Drive.v3.FilesResource.CreateMediaUpload request = service.Files.Create(body, stream, body.MimeType);
                    if (request.Upload().Exception == null)
                    { id = request.ResponseBody.Id; result = true; }
                }
            }
            return result;
        }

Der nächste Schritt nach der Erstellung der Datei wird seine Erneuerungsfunktion. Erinnern uns an die Ziele unseres Programms und die Besonderheiten des Dateisystems Google drive zurück. Wir schreiben das Programm für den Datenaustausch zwischen einigen Terminalen, die auf verschiedenen Computern sind. Und wir können nicht wissen, in welchem Moment und wie viel Terminals unsere Informationen erfordern werden. Aber die Besonderheiten des Dateisystems des wolkigen Speichers ermöglichen uns einige Dateien mit den identischen Namen und Erweiterungen zu erstellen. Das ermöglicht uns zuerst die neue Datei mit den neuen Daten zu erstellen, und erst dann die veraltenden Daten aus dem wolkigen Speicher zu entfernen. Das erledigt eben unsere neue Funktion FileUpdate. Ihre Eingangsparameter werden der Name der Datei und ihr Inhalt sein, und sie wird den logischen Wert vom Ergebnis der Operation zurückgeben.

Am Anfang der Funktion werden wir die Text-Variabel new_id erklären und wir rufen die früher erstellten Funktion FileCreate auf, die die neue Datei der Daten in der Wolke erstellt und gibt den Identifikator der neuen Datei in unsere Variabel zurück.

Dann werden wir die Liste aller Dateien in der Wolke aus der Funktion GetFileList bekommen und wir werden sie abwechselnd mit dem Namen und dem Identifikator der wieder erstellten Datei vergleichen. Alle unnötigen Duplikate werden aus dem Speicher entfernt. Hier werden wir wieder die bekannte Klasse Google.Apis.Drive.v3.DriveService verwenden, und die Aufrufe schicken wir mit Hilfe von der Klasse Google.Apis.Drive.v3.FilesResource.DeleteRequest.

        public bool FileUpdate(string name, string value)
        {
            bool result = false;
            if (credential == null)
                this.Authorize();
            if (credential == null)
            {
                return result;
            }

            string new_id;
            if (FileCreate(name, value, out new_id))
            {
                IList<File> files = GetFileList();
                if (files != null && files.Count > 0)
                {
                    result = true;
                    try
                    {
                        using (var service = new DriveService(new BaseClientService.Initializer()
                        {
                            HttpClientInitializer = credential,
                            ApplicationName = ApplicationName,
                        }))
                        {
                            foreach (var file in files)
                            {
                                if (file.Name == name && file.Id != new_id)
                                {
                                    try
                                    {
                                        Google.Apis.Drive.v3.FilesResource.DeleteRequest request = service.Files.Delete(file.Id);
                                        string res = request.Execute();
                                    }
                                    catch (Exception)
                                    {
                                        continue;
                                    }
                                }
                            }
                        }
                    }
                    catch (Exception)
                    {
                        return result;
                    }
                }
            }
            return result;
        }

3.2. Das Ablesen der Daten aus dem wolkigen Speicher

Wir haben schon die Funktionen für die Aufnahme der Daten im wolkigen Speicher erstellt. Jetzt ist es die Zeit, sie zurück zu zahlen. Wir erinnern uns daran, dass man den Identifikator der Datei in der Wolke bekommen muss, bevor diese Datei geladen wird. Diese Aufgabe erledigt die Funktion GetFileID. Ihr Eingangsparameter — ist der Name der Anfangsdatei, und der zurückgegebene Wert — ist sein Identifikator. Die logische Konstruktion der Funktion ist unkompliziert: wir bekommen die Liste der Dateien aus der Funktion GetFileList und durch eine einfache Sortierung suchen die erste Datei mit dem gesuchten Namen. Dabei muss man verstehen, dass es am wahrscheinlichsten die älteste Datei ist. Es gibt das Risiko, dass gerade zu gleicher Zeit die neue Datei mit den nötigen Parametern gespeichert wird oder bei seiner Ladung ist ein Fehler aufgetreten. Wir werden diese Risiken zwecks der Erhaltung der vollen Informationen eingehen. Die letzten Veränderungen werden bei der folgenden Erneuerung geladen. Wir erinnern uns doch daran, dass alle unnötigen Duplikate in der Funktion FileUpdate nach der Erstellung der neuen Datei der Daten entfernt werden.

        public string GetFileId(string name)
        {
            string result = null;
            IList<File> files = GetFileList();

            if (files != null && files.Count > 0)
            {
                foreach (var file in files)
                {
                    if (file.Name == name)
                    {
                        result = file.Id;
                        break;
                    }
                }
            }
            return result;
        }

Nach der Erhaltung des Identifikators der Datei können wir von ihm die nötige Information bekommen. Dazu werden wir die Funktion FileRead schreiben, in die wir den Identifikator der nötigen Datei übergeben, und die Funktion wird seinen Inhalt zurückgeben. Falls es schief geht, wird die Funktion die leere Zeile zurückgeben. Genauso wie früher, brauchen wir die Klasse Google.Apis.Drive.v3.DriveService für die Erstellung der Verbindung und die Klasse Google.Apis.Drive.v3.FilesResource.GetRequest für die Erstellung des Aufrufs.

        public string FileRead(string id)
        {
            if (String.IsNullOrEmpty(id))
            {
                return ("Errore. File not found");
            }
            bool result = false;
            string value = null;
            if (credential == null)
                this.Authorize();
            if (credential != null)
            {
                using (var service = new DriveService(new BaseClientService.Initializer()
                {
                    HttpClientInitializer = credential,
                    ApplicationName = ApplicationName,
                }))
                {
                    Google.Apis.Drive.v3.FilesResource.GetRequest request = service.Files.Get(id);
                    using (var stream = new MemoryStream())
                    {
                        request.MediaDownloader.ProgressChanged += (IDownloadProgress progress) =>
                        {
                            if (progress.Status == DownloadStatus.Completed)
                                result = true;
                        };
                        request.Download(stream);

                        if (result)
                        {
                            int start = 0;
                            int count = (int)stream.Length;
                            value = Encoding.Default.GetString(stream.GetBuffer(), start, count);
                        }
                    }
                }
            }
            return value;
        }

3.3. Die Erstellung des Wechselwirkungsblocks mit den Anwendungen des Terminals

Jetzt, wenn wir schon die Verbindung unseres Programms mit dem wolkigen Speicher Google Drive hergestellt haben, ist es die Zeit, ihn mit den Anwendungen MetaTrader zu verbinden. Denn gerade daran liegt seine Hauptaufgabe. Ich habe mich entschieden, diese Vereinigung mit Hilfe von den benannten Kanälen herzustellen. Die Arbeit mit ihnen wurde schon auf der Webseite beschrieben, und in der Sprache MQL5 haben die Hersteller die Klasse CFilePipe für die Arbeit mit einem solchen Verbindungstyp hinzugefügt. Es wird unsere Arbeit bei der Erstellung dieser Anwendung erleichtern.

Im Terminal können gleichzeitig ein paar Anwendungen gestartet sein. Deshalb muss unsere "Brücke" können, ein paar Anschlüsse gleichzeitig zu bearbeiten. Wir verwenden dafür die Modelle der asynchron mehrfädigen Programmierung.

Wir stellen direkt die Art der Nachrichten ein, die zwischen der Brücke und Anwendung ausgetauscht werden. Für das Lesen der Datei aus der Wolke muss man den Befehl und den Namen der Datei geben. Für die Aufnahme der Datei in die Wolke muss man den Befehl, den Namen der Datei und ihren Inhalt geben. Da wir Daten im Kanal mit einem Strom übergeben werden, so ist es zweckmäßiger, die ganze Information mit einer Zeile zu übergeben. Für die Teilung der Felder in der Zeile habe ich die Trenne ";" verwendet.

Am Anfang werden wir die globalen Variabel erklären:

  • Drive — ist die von uns früher erstellte Klasse für die Arbeit mit dem wolkigen Speicher;
  • numThreads — gibt die Anzahl der gleichzeitigen Ströme ein;
  • pipeName — ist eine Linie-variabel, die den Namen unserer Kanäle gespeichert hat;
  • servers — das Array der Operationsströme.
        GoogleDriveClass Drive = new GoogleDriveClass();
        private static int numThreads = 10;
        private static string pipeName = "GoogleBridge";
        static Thread[] servers;

Wir werden die Funktion des Starts der Operationsströme PipesCreate erstellen. Darin initialisieren wir das Array unserer Ströme und starten sie im Loop. Beim Start jedes Stroms wird die Funktion ServerThread aufgerufen, die die Funktionen in unseren Strömen initialisiert.

        public void PipesCreate()
        {
            int i;
            servers = new Thread[numThreads];

            for (i = 0; i < numThreads; i++)
            {
                servers[i] = new Thread(ServerThread);
                servers[i].Start();
            }
        }

Auch entsteht beim Start jedes Stroms ein genannter Kanal und es wird die asynchrone Funktion der Verbindungserwartung des Kunden zum Kanal gestartet. Beim Anschluss des Kunden zum Kanal wird die Funktion Connected aufgerufen, dafür erstellen wir den Delegate AsyncCallback asyn_connected. Wenn eine Ausnahme auftritt, wird der Strom neu gestartet.

        private void ServerThread()
        {
            NamedPipeServerStream pipeServer =
                new NamedPipeServerStream(pipeName, PipeDirection.InOut, numThreads, PipeTransmissionMode.Message, PipeOptions.Asynchronous);

            int threadId = Thread.CurrentThread.ManagedThreadId;
            // Wait for a client to connect
            AsyncCallback asyn_connected = new AsyncCallback(Connected);
            try
            {
                pipeServer.BeginWaitForConnection(asyn_connected, pipeServer);
            }
            catch (Exception)
            {
                servers[threadId].Suspend();
                servers[threadId].Start();
            }
        }

Bei der Verbindung des Kunden zum genannten Kanal prüfen wir den Zustand des Kanals und falls eine Ausnahme gibt, starten wir den Strom neu. Wenn die Verbindung fest ist, starten wir die Ablesen-Funktion des Aufrufs von der Anwendung. Wenn die Ablesen-Funktion false zurückgibt, starten wir die Verbindung neu.

        private void Connected(IAsyncResult pipe)
        {
            if (!pipe.IsCompleted)
                return;
            bool exit = false;
            try
            {
                NamedPipeServerStream pipeServer = (NamedPipeServerStream)pipe.AsyncState;
                try
                {
                    if (!pipeServer.IsConnected)
                        pipeServer.WaitForConnection();
                }
                catch (IOException)
                {
                    AsyncCallback asyn_connected = new AsyncCallback(Connected);
                    pipeServer.Dispose();
                    pipeServer = new NamedPipeServerStream(pipeName, PipeDirection.InOut, numThreads, PipeTransmissionMode.Message, PipeOptions.Asynchronous);
                    pipeServer.BeginWaitForConnection(asyn_connected, pipeServer);
                    return;
                }
                while (!exit && pipeServer.IsConnected)
                {
                    // Read the request from the client. Once the client has
                    // written to the pipe its security token will be available.

                    while (pipeServer.IsConnected)
                    {
                        if (!ReadMessage(pipeServer))
                        {
                            exit = true;
                            break;
                        }
                    }
                    //Wait for a client to connect
                    AsyncCallback asyn_connected = new AsyncCallback(Connected);
                    pipeServer.Disconnect();
                    pipeServer.BeginWaitForConnection(asyn_connected, pipeServer);
                    break;
                }
            }
            finally
            {
                exit = true;
            }
        }

Das Ablesen und Bearbeiten des Aufrufs von der Anwendung wird die Funktion ReadMessage durchführen. Mit dem Parameter in die Funktion wird ein Link auf das Objekt des Stroms übergeben. Das Ergebnis der Funktion — ist der logische Wert über die Ausführung der Operation. Am Anfang liest die Funktion den Aufruf von der Anwendung aus dem genannten Kanal ab und teilt ihn in die Felder. Dann erkennt sie den Befehl und führt die notwendigen Handlungen aus.

In der Funktion habe ich 3 Befehle vorgesehen:

  • Close — die Schließung der aktuellen Verbindung;
  • Read — das Ablesen der Datei aus der Wolke;
  • Write — die Aufnahme der Datei in die Wolke.

Für die Schließung der aktuellen Verbindung ist es genug, mit der Funktion den Wert false zurückzugeben, den Rest wird die aufgerufene Funktion Connected machen.

Für die Ausführung des Ablesen-Aufrufs der Datei aus der Wolke bestimmen wir den Identifikator der Datei und wir lesen seinen Inhalt durch die höher beschriebenen Funktionen GetFileID und FileRead ab.

Für die Ausführung der Aufnahme-Funktion der Datei in der Wolke rufen wir die erstellte Funktion FileUpdate auf.

Und natürlich vergessen wir nicht über die Bearbeitung der Ausnahmen. Falls eine Ausnahme auftritt, führen wir eine neue Anmeldung auf Google durch.

        private bool ReadMessage(PipeStream pipe)
        {
            if (!pipe.IsConnected)
                return false;

            byte[] arr_read = new byte[1024];
            string message = null;
            int length;
            do
            {
                length = pipe.Read(arr_read, 0, 1024);
                if (length > 0)
                    message += Encoding.Default.GetString(arr_read, 0, length);
            } while (length >= 1024 && pipe.IsConnected);
            if (message == null)
                return true;

            if (message.Trim() == "Close\0")
                return false;

            string result = null;
            string[] separates = { ";" };
            string[] arr_message = message.Split(separates, StringSplitOptions.RemoveEmptyEntries);
            if (arr_message[0].Trim() == "Read")
            {
                try
                {
                    result = Drive.FileRead(Drive.GetFileId(arr_message[1].Trim() + GoogleDriveClass.extension));
                }
                catch (Exception e)
                {
                    result = "Error " + e.ToString();
                    Drive.Authorize();
                }
                return WriteMessage(pipe, result);
            }

            if (arr_message[0].Trim() == "Write")
            {
                try
                {
                    result = (Drive.FileUpdate(arr_message[1].Trim() + GoogleDriveClass.extension, arr_message[2].Trim()) ? "Ok" : "Error");
                }
                catch (Exception e)
                {
                    result = "Error " + e.ToString();
                    Drive.Authorize();
                }

                return WriteMessage(pipe, result);
            }
            return true;
        }

Nach der Bearbeitung der Aufrufe müssen wir das Ausführungsergebnis der Operation an die Anwendung zurückgeben. Wir erstellen die Funktion WriteMessage. Ihre Parameter — der Link auf das Objekt des aktuellen genannten Kanals und die Mitteilung, die an die Anwendung abgesandt wird. Die Funktion wird den logischen Wert über das Ausführungsergebnis der Operation zurückgeben.

        private bool WriteMessage(PipeStream pipe, string message)
        {
            if (!pipe.IsConnected)
                return false;
            if (message == null || message.Count() == 0)
                message = "Empty";
            byte[] arr_bytes = Encoding.Default.GetBytes(message);
            try
            {
                pipe.Flush();
                pipe.Write(arr_bytes, 0, arr_bytes.Count());
                pipe.Flush();
            }
            catch (IOException)
            {
                return false;
            }
            return true;
        }

Jetzt, wenn wir alle nötigen Funktionen beschrieben haben, bleibt es übrig, die Funktion PipesCreate zu starten. Ich habe das Projekt Windows Form erstellt, deshalb starte ich diese Funktion aus der Funktion Form1.

        public Form1()
        {
            InitializeComponent();
            PipesCreate();
        }

Es bleibt noch, das Projekt neu zu kompilieren, und die Datei json in den Ordner mit dem Programm zu kopieren, welcher die Daten für den Zugang zum wolkigen Speicher hat. 
  

4. Wir erstellen die Anwendung in MetaTrader

Wir gehen zur praktischen Anwendung unseres Programms. Erstmal würde ich vorschlagen, ein Programm zum Kopieren von einfachen graphischen Objekten zu schreiben.

4.1. Die Klasse für die Arbeit mit graphischen Objekten

Zunächst denken wir nach, welche konkrete Information wir über das Objekt uns übergeben müssen, um das auf einem anderen Chart zu reproduzieren. Wahrscheinlich muss man als erste den Typ des Objektes und seinen Namen für die Identifizierung übergeben. Es werden auch die Farbe des Objektes und seine Koordinaten benötigt. Und hier entsteht die erste Frage: wie viel und welche Koordinaten übergebt werden müssen? Zum Beispiel, bei der Sendung der Informationen über die senkrechte Linie ist es genug, nur das Datum zu übergeben. Für eine horizontale Linie muss man nicht das Datum, und den Preis übergeben. Für eine Trend Linie werden zwei Paare Koordinaten benötigt — sowohl das Datum, als auch der Preis, dazu noch die Information darüber, nach links oder nach rechts die Linie fortgesetzt wird. Verschiedenen Objekten haben sowohl die allgemeinen, als auch die einzigartigen Parameter. Aber in MQL5 werden alle Objekte durch vier Funktionen: ObjectCreate, ObjectSetInteger, ObjectSetDouble und ObjectSetString erstellt und verändert. Wir nehmen den gleichen Weg und werden den Typ des Parameters, die Eigenschaft und den Wert übergeben.

Wir erstellen die Aufzählung der Typen des Parameters.

enum ENUM_SET_TYPE
  {
   ENUM_SET_TYPE_INTEGER=0,
   ENUM_SET_TYPE_DOUBLE=1,
   ENUM_SET_TYPE_STRING=2
  };

Wir erstellen die Klasse CCopyObject für die Bearbeitung der Informationen über Objekte. Bei der Initialization wird ihr ein String-Parameter übergeben. Nachhinein wird sie die Objekte identifizieren, die auf dem Chart von unserer Klasse erstellt wurden. Wir werden diesen Wert in der Variabel der Klasse s_ObjectsID speichern.

class CCopyObject
  {
private:
   string            s_ObjectsID;

public:
                     CCopyObject(string objectsID="CopyObjects");
                    ~CCopyObject();
  };
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CCopyObject::CCopyObject(string objectsID="CopyObjects")
  {
   s_ObjectsID = (objectsID==NULL || objectsID=="" ? "CopyObjects" : objectsID);
  }

4.1.1. Der Funktionen-Block für die Informationssammlung über das Objekt

Erstmal werden wir die Funktion CreateMessage erstellen. Ihr Parameter — ist der Identifikator des nötigen Charts, und sie wird den Textwert zurückgeben, um die Absendung in den wolkigen Speicher mit dem Verzeichnis der Parameter des Objektes und ihre Werten durchzuführen. Die zurückgegebene Zeile muss strukturiert sein, damit man diese Daten später durchlesen kann. Wir nehmen an, dass die Daten über jedes Objekt aus den geschweiften Klammern genommen werden, als Teile zwischen den Parametern wird das Zeichen "|" dienen, und als Teile zwischen dem Parameter und seinem Wert wird — das Zeichen "=" dienen. Am Anfang der Beschreibung jedes Objektes werden sein Name und der Typ angegeben, und dann wird die Funktion der Beschreibung des Objektes entsprechend seinem Typ aufgerufen.

string CCopyObject::CreateMessage(long chart)
  {
   string result = NULL;
   int total = ObjectsTotal(chart, 0);
   for(int i=0;i<total;i++)
     {
      string name = ObjectName(chart, i, 0);
      switch((ENUM_OBJECT)ObjectGetInteger(chart,name,OBJPROP_TYPE))
        {
         case OBJ_HLINE:
           result+="{NAME="+name+"|TYPE="+IntegerToString(OBJ_HLINE)+"|"+HLineToString(chart, name)+"}";
           break;
         case OBJ_VLINE:
           result+="{NAME="+name+"|TYPE="+IntegerToString(OBJ_VLINE)+"|"+VLineToString(chart, name)+"}";
           break;
         case OBJ_TREND:
           result+="{NAME="+name+"|TYPE="+IntegerToString(OBJ_TREND)+"|"+TrendToString(chart, name)+"}";
           break;
         case OBJ_RECTANGLE:
           result+="{NAME="+name+"|TYPE="+IntegerToString(OBJ_RECTANGLE)+"|"+RectangleToString(chart, name)+"}";
           break;
        }
     }
   return result;
  }

So wird es für die Beschreibung der horizontalen Linie die Funktion HLineToString aufgerufen. Ihre Parameter werden der Identifikator des Charts und der Name des Objektes sein. Die Funktion wird die strukturierte Zeile mit dem Parameter des Objektes zurückgeben. Zum Beispiel, für die horizontale Linie muss man den Preis, die Farbe, den Stil und die Dicke der Linie übergeben, und auch eingeben, ob die Linie vor dem Chart oder im Hintergrund dargestellt werden soll. Dabei vergessen wir nicht, vor der Eigenschaft des Parameters seinen Typ aus der früher erstellten Aufzählung zu bezeichnen.

string CCopyObject::HLineToString(long chart,string name)
  {
   string result = NULL;
   if(ObjectFind(chart,name)!=0)
      return result;
   
   result+=IntegerToString(ENUM_SET_TYPE_DOUBLE)+"="+IntegerToString(OBJPROP_PRICE)+"=0="+DoubleToString(ObjectGetDouble(chart,name,OBJPROP_PRICE,0))+"|";
   result+=IntegerToString(ENUM_SET_TYPE_INTEGER)+"="+IntegerToString(OBJPROP_COLOR)+"=0="+IntegerToString(ObjectGetInteger(chart,name,OBJPROP_COLOR,0))+"|";
   result+=IntegerToString(ENUM_SET_TYPE_INTEGER)+"="+IntegerToString(OBJPROP_STYLE)+"=0="+IntegerToString(ObjectGetInteger(chart,name,OBJPROP_STYLE,0))+"|";
   result+=IntegerToString(ENUM_SET_TYPE_INTEGER)+"="+IntegerToString(OBJPROP_BACK)+"=0="+IntegerToString(ObjectGetInteger(chart,name,OBJPROP_BACK,0))+"|";
   result+=IntegerToString(ENUM_SET_TYPE_INTEGER)+"="+IntegerToString(OBJPROP_WIDTH)+"=0="+IntegerToString(ObjectGetInteger(chart,name,OBJPROP_WIDTH,0))+"|";
   result+=IntegerToString(ENUM_SET_TYPE_STRING)+"="+IntegerToString(OBJPROP_TEXT)+"=0="+ObjectGetString(chart,name,OBJPROP_TEXT,0)+"|";
   result+=IntegerToString(ENUM_SET_TYPE_STRING)+"="+IntegerToString(OBJPROP_TOOLTIP)+"=0="+ObjectGetString(chart,name,OBJPROP_TOOLTIP,0);
   return result;
  }

Genauso erstellen wir die Funktionen für die Beschreibung anderer Typen der Objekte. In meinem Beispiel ist es VLineToString für die senkrechte Linie, TrendToString für die Trend-Linien und RectangleToString — für das Rechteck. Sie können den Code dieser Funktionen im hinzugefügten Code der Klasse erfahren.

4.1.2. Die Ausgabefunktion der Objekte auf das Chart

Wir haben schon die Funktion der Informationssammlung erstellt. Jetzt werden wir uns mit der Funktion beschäftigen, die die Mitteilung lesen wird und die Objekte auf das Chart liefert: DrawObjects . Ihre Parameter — ist der Identifikator des Charts, auf dem man die Objekte und die bekommende Mitteilung liefern muss. Die Funktion wird den logischen Wert über das Ausführungsergebnis der Operation zurückgeben.

Der Algorithmus der Funktion hat ein paar Stufen:

  • Die Teilung der Zeilen-Mitteilung ins Zeilenarray nach den Objekten;
  • Die Teilung jedes Elementes des Objektearrays um das Parameterarrays;
  • Im Parameterarrays suchen wir den Namen und den Typ des Objektes, zum Namen fügen wir unseren Identifikator hinzu;
  • Wir suchen auf dem Chart das Objekt mit dem bekommenden Namen; wenn das Objekt sich nicht in dem Hauptunterfenster befindet oder sein Typ unterscheidet sich von dem angegebenen Typ in der Mitteilung, dann wird er entfernt;
  • Wir erstellen ein neues Objekt auf dem Chart, wenn es noch kein Objekt gibt oder er wurde im vorhergehenden Punkt entfernt;
  • Dann versetzen wir die Eigenschaften des Objektes, die in der Mitteilung erhalten wurden, auf das Objekt unseres Charts (es wird von der zusätzlichen Funktion CopySettingsToObject durchgeführt);
  • Wir entfernen die unnötigen Objekte vom Chart (es wird von der zusätzlichen Funktion DeleteExtraObjects durchgeführt). 
bool CCopyObject::DrawObjects(long chart,string message)
  {
   //--- Split message to objects
   StringTrimLeft(message);
   StringTrimRight(message);
   if(message==NULL || StringLen(message)<=0)
      return false;
   StringReplace(message,"{","");
   string objects[];
   if(StringSplit(message,'}',objects)<=0)
      return false;
   int total=ArraySize(objects);
   SObject Objects[];
   if(ArrayResize(Objects,total)<0)
      return false;
  
   //--- Split every object message to settings
   for(int i=0;i<total;i++)
     {
      string settings[];
      int total_settings=StringSplit(objects[i],'|',settings);
      //--- Search name and type of object
      int set=0;
      while(set<total_settings && Objects[i].name==NULL && Objects[i].type==-1)
        {
         string param[];
         if(StringSplit(settings[set],'=',param)<=1)
           {
            set++;
            continue;
           }
         string temp=param[0];
         StringTrimLeft(temp);
         StringTrimRight(temp);
         if(temp=="NAME")
           {
            Objects[i].name=param[1];
            StringTrimLeft(Objects[i].name);
            StringTrimRight(Objects[i].name);
            Objects[i].name=s_ObjectsID+Objects[i].name;
           }
         if(temp=="TYPE")
            Objects[i].type=(int)StringToInteger(param[1]);
         set++;
        }
      //--- if name or type of object not found go to next object
      if(Objects[i].name==NULL || Objects[i].type==-1)
         continue;
      //--- Search object on chart
      int subwindow=ObjectFind(chart,Objects[i].name);
      //--- if object found on chat but it not in main subwindow or its type is different we delete this oject from chart
      if(subwindow>0 || (subwindow==0 && ObjectGetInteger(chart,Objects[i].name,OBJPROP_TYPE)!=Objects[i].type))
        {
         if(!ObjectDelete(chart,Objects[i].name))
            continue;
         subwindow=-1;
        }
      //--- if object doesn't found create it on chart
      if(subwindow<0)
        {
         if(!ObjectCreate(chart,Objects[i].name,(ENUM_OBJECT)Objects[i].type,0,0,0))
            continue;
         ObjectSetInteger(chart,Objects[i].name,OBJPROP_HIDDEN,true);
         ObjectSetInteger(chart,Objects[i].name,OBJPROP_SELECTABLE,false);
         ObjectSetInteger(chart,Objects[i].name,OBJPROP_SELECTED,false);
        }      
      //---
      CopySettingsToObject(chart,Objects[i].name,settings);
     }
   //---
   DeleteExtraObjects(chart,Objects);
   return true;
  }

Die Zuweisungsfunktion der Eigenschaften, die in der Mitteilung bekommen wurden, dem Objekt des Charts ist universell und kann zu jedem Objekt-Typ angewendet werden. Mit den Parametern werden zu ihr der Identifikator des Charts, der Name des Objektes und das Zeilenarray übergeben. Jedes Element des Arrays wird in den Typ der Operation, der Eigenschaft, des Modifikators und des Wertes geteilt. Die bekommenden Werte werden dem Objekt durch die Funktion zugewiesen, die dem Typ der Operation entspricht. 

bool CCopyObject::CopySettingsToObject(long chart,string name,string &settings[])
  {
   int total_settings=ArraySize(settings);
   if(total_settings<=0)
      return false;
   
   for(int i=0;i<total_settings;i++)
     {
      string setting[];
      int total=StringSplit(settings[i],'=',setting);
      if(total<3)
         continue;
      switch((ENUM_SET_TYPE)StringToInteger(setting[0]))
        {
         case ENUM_SET_TYPE_INTEGER:
           ObjectSetInteger(chart,name,(ENUM_OBJECT_PROPERTY_INTEGER)StringToInteger(setting[1]),(int)(total==3 ? 0 : StringToInteger(setting[2])),StringToInteger(setting[total-1]));
           break;
         case ENUM_SET_TYPE_DOUBLE:
           ObjectSetDouble(chart,name,(ENUM_OBJECT_PROPERTY_DOUBLE)StringToInteger(setting[1]),(int)(total==3 ? 0 : StringToInteger(setting[2])),StringToDouble(setting[total-1]));
           break;
         case ENUM_SET_TYPE_STRING:
           ObjectSetString(chart,name,(ENUM_OBJECT_PROPERTY_STRING)StringToInteger(setting[1]),(int)(total==3 ? 0 : StringToInteger(setting[2])),setting[total-1]);
           break;
        }
     }
   return true;
  }

Nach der Lieferung der Objekte auf das Chart muss man die auf dem Chart existierenden Objekte mit den Objekten vergleichen, die in die Mitteilung übergeben wurden. Vom Chart werden "die unnötigen" Objekte entfernt, die den geforderten Identifikator enthalten, aber in der Mitteilung (es sind die Objekte, die vom Provider entfernt wurden) fehlen. Dafür ist die Funktion DeleteExtraObjects verantwortlich. Ihre Parameter werden der Identifikator des Charts und das Array der Struktur sein, die den Namen und den Typ des Objektes enthält.

void CCopyObject::DeleteExtraObjects(long chart,SObject &Objects[])
  {
   int total=ArraySize(Objects);
   for(int i=0;i<ObjectsTotal(chart,0);i++)
     {
      string name=ObjectName(chart,i,0);
      if(StringFind(name,s_ObjectsID)!=0)
         continue;
      bool found=false;
      for(int obj=0;(obj<total && !found);obj++)
        {
         if(name==Objects[obj].name && ObjectGetInteger(chart,name,OBJPROP_TYPE)==Objects[obj].type)
           {
            found=true;
            break;
           }
        }
      if(!found)
        {
         if(ObjectDelete(chart,name))
            i--;
        }
     }
   return;
  }

4.2. Der Anwendung-Provider

Wir kommen allmählich zum Schluss. Erstellen wir den Programm-Provider, der die Daten über die Objekte in den wolkigen Speicher sammeln und schicken wird. Wir werden dazu eine Form des Experten auswählen. Es wird nur ein äußerlicher Parameter sein: die logische Variabel SendAtStart, die bestimmt, ob die Daten direkt nach der Ladung des Programms ins Terminal abgesendet werden müssen.

sinput bool       SendAtStart =  true; //Send message at Init

Im Kopf der Anwendung fügen wir die nötigen Bibliotheken hinzu - das ist die oben erstellte Klasse für die Arbeit mit den graphischen Objekten und die grundlegende Klasse für die Arbeit mit den genannten Kanälen. Auch werden wir den Namen des Kanals verordnen, zu dem die Anwendung verbunden wird.

#include <CopyObject.mqh>
#include <Files\FilePipe.mqh>

#define                     Connection       "\\\\.\\pipe\\GoogleBridge"

In globalen Variablen werden wir die Klasse für die Arbeit mit den graphischen Objekten, die Zeile-Variabel für die Speicherung der letzten abgesandten Mitteilung und das Array des Typ uchar erklären, in das wir den Befehl für die Schließung der Verbindung mit dem wolkigen Speicher aufnehmen werden.

CCopyObject *CopyObjects;
string PrevMessage;
uchar Close[];

In der Funktion OnInit initialisieren wir die global Variabel und wir werden die Funktion der Informationsabsendung in den wolkigen Speicher (beim Bedarf) starten. 

int OnInit()
  {
//---
   CopyObjects = new CCopyObject();
   PrevMessage="Init";
   StringToCharArray(("Close"),Close,0,WHOLE_ARRAY,CP_UTF8);
   if(SendAtStart)
      SendMessage(ChartID());
//---
   return(INIT_SUCCEEDED);
  }

In der Funktion OnDeinit werden wir das Objekt der Klasse für die Arbeit mit den graphischen Objekten entfernen.

void OnDeinit(const int reason)
  {
//---
   if(CheckPointer(CopyObjects)!=POINTER_INVALID)
      delete CopyObjects;
  }

Die Funktion für die Absendung der informativen Mitteilungen in den wolkigen Speicher wird aus der Funktion OnChartEven beim Auftritt eines der Erstellungsereignisse aufgerufen, oder bei einer Veränderung oder Entfernung des Objektes auf dem Chart.

void OnChartEvent(const int id,
                  const long &lparam,
                  const double &dparam,
                  const string &sparam)
  {
//---
   int count=10;
   switch(id)
     {
      case CHARTEVENT_OBJECT_CREATE:
      case CHARTEVENT_OBJECT_DELETE:
      case CHARTEVENT_OBJECT_CHANGE:
      case CHARTEVENT_OBJECT_DRAG:
      case CHARTEVENT_OBJECT_ENDEDIT:
        while(!SendMessage(ChartID()) && !IsStopped() && count>=0)
           {
            count--;
            Sleep(500);
           }
        break;
     }      
  }

Die Hauptoperationen werden in der Funktion SendMessage durchgeführt, deren Eingangsparameter als Identifikator des Charts für die Arbeit sein wird. Ihren Algorithmus kann man in einigen Schritten teilen:

  • Wir überprüfen den Zustand der Arbeitsklasse mit den graphischen Objekten, bei der Notwendigkeit initialisieren wir sie wieder;
  • Mit der Hilfe von der früher erstellten Funktion CreatMessage erfassen wir die Mitteilung für die Absendung in die Wolke. Wenn die Mitteilung leer ist oder gleich der letzten geschickten Mitteilung, so verlassen wir diese Funktion;
  • Aufgrund des Instruments des Charts erstellen wir den Namen der Datei, um das in die Wolke abzusenden;
  • Wir öffnen die Verbindung zu unserer Programm-Brücke durch den genannten Kanal;
  • Über der offenen Verbindung senden wir den Befehl auf die Absendung der Mitteilung in den wolkigen Speicher mit dem Hinweis auf den Dateinamen;
  • Nachdem wir die Meldung über die Ausführung des Befehls auf die Absendung erhalten haben, senden wir den Befehl auf die Schließung der Verbindung mit der Wolke und brechen die Verbindung zwischen dem genannten Kanal und der Anwendung-Brücke ab;
  • Vor dem Verlassen des Programms entfernen wir das Objekt der Arbeit mit den genannten Kanälen.
Im Laufe der Ausführung der Operationen liefern wir die informativen Mitteilungen in den Kommentaren zum Chart.
bool SendMessage(long chart)
  {
   Comment("Sending message");
   if(CheckPointer(CopyObjects)==POINTER_INVALID)
     {
      CopyObjects = new CCopyObject();
      if(CheckPointer(CopyObjects)==POINTER_INVALID)
         return false;
     }
   string message=CopyObjects.CreateMessage(chart);
   if(message==NULL || PrevMessage==message)
      return true;
   
   string Name=SymbolInfoString(ChartSymbol(chart),SYMBOL_CURRENCY_BASE)+SymbolInfoString(ChartSymbol(chart),SYMBOL_CURRENCY_PROFIT);
   CFilePipe *pipe=new CFilePipe();
   int handle=pipe.Open(Connection,FILE_WRITE|FILE_READ);
   if(handle<=0)
     {
      Comment("Pipe doesn't found");
      delete pipe;
      return false;
     }
   uchar iBuffer[];
   int size=StringToCharArray(("Write;"+Name+";"+message),iBuffer,0,WHOLE_ARRAY,CP_UTF8);
   if(pipe.WriteArray(iBuffer)<=0)
     {
      Comment("Error of sending request");
      pipe.Close();
      delete pipe;
      return false;
     }
   ArrayFree(iBuffer);
   uint res=0;
   do
     {
      res=pipe.ReadArray(iBuffer);
     }
   while(res==0 && !IsStopped());
   
   if(res>0)
     {
      string result=CharArrayToString(iBuffer,0,WHOLE_ARRAY,CP_UTF8);
      if(result!="Ok")
        {
         Comment(result);
         pipe.WriteArray(Close);
         pipe.Close();
         delete pipe;
         return false;
        }
     }
   PrevMessage=message;
   pipe.WriteArray(Close);
   pipe.Close();
   delete pipe;
   Comment("");
   return true;
  }

4.3. Der Anwendung-Benutzer

Zum Schluss werden wir den Anwendung-Benutzer erstellen, der die Informationen aus dem wolkigen Speicher bekommen wird, und dann die graphischen Objekte auf dem Chart erstellen und verändern. Genauso wie in der vorhergehenden Anwendung, im Kopf werden wir die notwendigen Bibliotheken hinzufügen und wir werden den Namen des verwendeten Kanals bezeichnen.

#include <CopyObject.mqh>
#include <Files\FilePipe.mqh>

#define                     Connection       "\\\\.\\pipe\\GoogleBridge"

Die Anwendung hat 3 äußerlichen Parameter: die Zeit in Sekunden, die die Periodizität der Datenerneuerung vom wolkigen Speicher zeigt, der Identifikator der Objekte auf dem Chart und der logische Wert, welcher bedeutet, dass alle erstellten Objekte vom Chart bei der Schließung der Anwendung entfernt werden müssen.

sinput int        RefreshTime =  10; //Time to refresh data, sec
sinput string     ObjectsID   =  "GoogleDriveBridge";
sinput bool       DeleteAtClose = true;   //Delete objects from chart at close program

In globalen Variablen werden wir (Genauso wie bei dem Anwendung-Provider) die Klasse für die Arbeit mit den graphischen Objekten, die Zeile-Variabel für die Speicherung der letzten abgesandten Mitteilung und das Array des Typ uchar erklären, in das wir den Befehl für die Schließung der Verbindung mit dem wolkigen Speicher aufnehmen werden. Zusätzlich fügen wir die logische Variabel über den Zustand des Timers und die Variabel für die Speicherung der Zeit der letzten Erneuerung und die Lieferung des letzten Kommentars zu dem Chart hinzu.

CCopyObject *CopyObjects;
string PrevMessage;
bool timer;
datetime LastRefresh,CommentStart;
uchar Close[];

In der Funktion OnInit werden wir die Initialization der global Variabel und des Timers durchführen.

int OnInit()
  {
//---
   CopyObjects = new CCopyObject(ObjectsID);
   PrevMessage="Init";
   timer=EventSetTimer(1);
   if(!timer)
     {
      Comment("Error of set timer");
      CommentStart=TimeCurrent();
     }
   LastRefresh=0;
   StringToCharArray(("Close"),Close,0,WHOLE_ARRAY,CP_UTF8);
   
//---
   return(INIT_SUCCEEDED);
  }

In der Funktion der Deinitialisierung OnDeinit werden wir das Objekt der Klasse für die Arbeit mit den graphischen Objekten entfernen, wir stoppen den Timer, reinigen die Kommentare und notfalls werden wir vom Chart die Objekte entfernen, die von der Anwendung erstellt wurden.

void OnDeinit(const int reason)
  {
//---
   if(CheckPointer(CopyObjects)!=POINTER_INVALID)
      delete CopyObjects;
   EventKillTimer();
   Comment("");
   if(DeleteAtClose)
     {
      for(int i=0;i<ObjectsTotal(0,0);i++)
        {
         string name=ObjectName(0,i,0);
         if(StringFind(name,ObjectsID,0)==0)
           {
            if(ObjectDelete(0,name))
               i--;
           }
        }
     }
  }

In der Funktion OnTick prüfen wir den Zustand des Timers und falls es nötig ist, aktivieren wir es nochmal.

void OnTick()
  {
//---
   if(!timer)
     {
      timer=EventSetTimer(1);
      if(!timer)
        {
         Comment("Error of set timer");
         CommentStart=TimeCurrent();
        }
      OnTimer();
     }
  }

In der Funktion OnTimer löschen wir die Kommentare, die auf dem Chart länger als 10 Sekunden sind, und wir werden die Ablesungsfunktion der Datei der Daten aus dem wolkigen Speicher ( ReadMessage ) aufrufen. Nach der erfolgreichen Ladung der Informationen wird die Zeit der letzten Daten-Erneuerung aktualisiert.

void OnTimer()
  {
//---
   if((TimeCurrent()-CommentStart)>10)
     {
      Comment("");
     }
   if((TimeCurrent()-LastRefresh)>=RefreshTime)
     {
      if(ReadMessage(ChartID()))
        {
         LastRefresh=TimeCurrent();
        }
     }
  }

Die Haupthandlungen nach der Ladung der Daten aus dem wolkigen Speicher und die Lieferung der Objekte auf das Chart werden in der Funktion ReadMessage durchgeführt. Diese Funktion hat nur ein Parameter — der Identifikator des Charts, mit dem die Funktion arbeiten wird. Die Operationen, die in der Funktion durchgeführt werden, kann man in einigen Stufen teilen:

  • Nach dem Instrument des Charts bilden wir den Namen der Datei für die Ablesung aus der Wolke;
  • Wir öffnen den genannten Verbindungskanal mit der Programm-Brücke;
  • Wir senden den Aufruf auf die Ablesung der Daten aus dem wolkigen Speicher mit dem Hinweis der geforderten Datei;
  • Wir lesen das Ergebnis der Bearbeitung des Aufrufs ab;
  • Wir senden den Befehl auf die Schließung der Verbindung mit der Wolke und brechen die Verbindung zwischen dem genannten Kanal und der Anwendung-Brücke ab;
  • Wir vergleichen das bekommende Ergebnis mit der vorhergehenden Mitteilung. Wenn die Daten identisch sind, dann verlassen wir die Funktion;
  • Wir übergeben die bekommende Mitteilung in die Funktion DrawObject des Objektes der Bearbeitungsklasse der graphischen Elemente;
  • Nach der erfolgreichen Bearbeitung der Mitteilung werden wir sie in der Variabel PrevMessage speichern, um sie nachfolgend mit den bekommenden Daten zu vergleichen.
bool ReadMessage(long chart)
  {
   string Name=SymbolInfoString(ChartSymbol(chart),SYMBOL_CURRENCY_BASE)+SymbolInfoString(ChartSymbol(chart),SYMBOL_CURRENCY_PROFIT);
   CFilePipe *pipe=new CFilePipe();
   if(CheckPointer(pipe)==POINTER_INVALID)
      return false;
  
   int handle=pipe.Open(Connection,FILE_WRITE|FILE_READ);
   if(handle<=0)
     {
      Comment("Pipe doesn't found");
      CommentStart=TimeCurrent();
      delete pipe;
      return false;
     }
   Comment("Send request");
   uchar iBuffer[];
   int size=StringToCharArray(("Read;"+Name+";"),iBuffer,0,WHOLE_ARRAY,CP_UTF8);
   if(pipe.WriteArray(iBuffer)<=0)
     {
      pipe.Close();
      delete pipe;
      return false;
     }
   Sleep(10);
   ArrayFree(iBuffer);
   Comment("Read message");
   
   uint res=0;
   do
     {
      res=pipe.ReadArray(iBuffer);
     }
   while(res==0 && !IsStopped());
   
   Sleep(10);
   Comment("Close connection");
   pipe.WriteArray(Close);
   pipe.Close();
   delete pipe;
   Comment("");
      
   string result=NULL;
   if(res>0)
     {
      result=CharArrayToString(iBuffer,0,WHOLE_ARRAY,CP_UTF8);
      if(StringFind(result,"Error",0)>=0)
        {
         Comment(result);
         CommentStart=TimeCurrent();
         return false;
        }
     }
   else
     {
      Comment("Empty message");
      return false;
     }
   
   if(result==PrevMessage)
      return true;
  
   if(CheckPointer(CopyObjects)==POINTER_INVALID)
     {
      CopyObjects = new CCopyObject();
      if(CheckPointer(CopyObjects)==POINTER_INVALID)
         return false;
     }
   if(CopyObjects.DrawObjects(chart,result))
     {
      PrevMessage=result;
     }
   else
     {
      return false;
     }
   return true;
  }

5. Der erste Start der Anwendung

Nach dieser langen Arbeit ist es die Zeit, die Ergebnisse unseres Werkes anzuschauen. Wir starten die Programm-Brücke. Dabei vergessen wir nicht zu prüfen, damit im Katalog mit dem Programm die Datei mit den Daten für die Verbindung zu unserem wolkigen Speicher client-secret.json wird, die vom Service Google erhalten wurden. Dann starten wir eine unsere Anwendung MetaTrader. Beim ersten Aufruf zur Wolke wird die Programm-Brücke die standardmäßige Internet-Anwendung mit der Identifizierungsseite des Zuganges zum Google-Account starten.

Die Seite des Zuganges zum Google-Account

Hier muss man die E-Mail-Adresse einführen, die bei der Anmeldung des Accounts Google eingegeben wurde, und dann zur nächsten Seite (die Schaltfläche NEXT) gehen. Auf der nächsten Seite führen wir das Kennwort zum Google-Account ein.

Das Kennwort zum Google-Account

Auf der nächsten Seite Google wird verlangen, die Zugriffsrechte der Anwendung zu unserem wolkigen Speicher zu bestätigen. Wir müssen diese erforderten Zugriffsrechten mal durchlesen und sie (die Schaltfläche ALLOW ) bestätigen.

Die Bestätigung der Zugriffsrechte

Als Ergebnis wird im Katalog mit unserer Programm-Brücke das Unterverzeichnis drive-bridge.json erstellt. Darin wird die Datei mit dem Zugangstoken zum wolkigen Speicher gespeichert bleiben. Im Folgenden muss es bei der Vervielfältigung der Anwendung auf einen anderen Computer, mit der Programm-Brücke zusammen auch dieses Unterverzeichnis kopiert werden. Es wird dann nicht mehr nötig, diese Prozedur zu wiederholen und die Daten für den Zugang zum wolkigen Speicher den dritten Personen zu übergeben.

Die Auflösung der Datei im Unterverzeichnis des Programms

Fazit

In diesem Artikel betrachteten wir ein Beispiel der Nutzung des wolkigen Speichers für praktischen Zielen. Die erstellte Programm-Brücke — ist das universelle Mittel für die Ladung der Daten in den wolkigen Speicher und ihre Rückladung in unsere Anwendung. Die angebotene Lösung für die Sendung der graphischen Objekte wird Ihnen ermöglichen mit Kollegen von den Ergebnissen der technischen Analyse auszutauschen. Vielleicht entscheidet sich jemand dafür und wird die Signale für das Traiding senden oder wird Übungen nach der technischen Analyse der Charts durchzuführen.

Ich wünsche Ihnen einen erfolgreichen Handel.

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

Beigefügte Dateien |
Provider.mq5 (4.71 KB)
Provider.ex5 (35.89 KB)
User.ex5 (32.23 KB)
User.mq5 (5.5 KB)
GoogleBridge.zip (2468.07 KB)
CopyObject.mqh (15.53 KB)
Cross-Plattform Expert Advisor: Zeitfilter Cross-Plattform Expert Advisor: Zeitfilter

Dieser Artikel beschreibt die Implementierung verschiedener Methoden einer Zeitfilterung für einen Cross-Plattform Expert Advisor. Die Klassen der Zeitfilter sind verantwortlich für die Prüfung, ob ein bestimmter Zeitpunkt in eine besondere Zeitkonfiguration fällt oder nicht.

Wir schreiben eine Scalping-Markttiefe aufgrund der graphischen Bibliothek CGraphic Wir schreiben eine Scalping-Markttiefe aufgrund der graphischen Bibliothek CGraphic

Im Artikel wird die grundlegende Funktional einer Scalping-Markttiefe erstellt. Es wird ein Ticks-Chart aufgrund der graphischen Bibliothek CGraphic erstellt und es wird mit einer Anfragen-Tabelle integriert. Mit Hilfe der beschriebenen Markttiefe kann man einen mächtigen Helfer für einen kurzfristigen Handel erstellen.

TradeObjects: die Automatisierung des Handels aufgrund der graphischen Objekte in MetaTrader TradeObjects: die Automatisierung des Handels aufgrund der graphischen Objekte in MetaTrader

Im Artikel wird eine einfache Erstellungsmethode eines automatischen Handelssystems nach der linearen Markierung des Charts betrachtet. Es wird ein fertiger Experte angeboten, der die Standardeigenschaften der Objekte MetaTrader 4 und 5 verwendet, und der auch Haupt-Handelsoperationen unterstützt.

Walk-Forward-Optimierung in MetaTrader 5 - mit eigenen Händen Walk-Forward-Optimierung in MetaTrader 5 - mit eigenen Händen

Im Artikel werden verschiedene Herangehensweisen betrachtet, die es erlauben, eine Walk-Forward-Optimierung mithilfe des eingebauten Testers und Hilfsbibliotheken in MQL genau zu emulieren.