Récupération d'un flux de prix via WebSocket en C#.

 

Bonjour, chers Messieurs. Intéressé à recevoir des cotations de différentes sources (y compris la bourse LMAX). Étant donné que différents courtiers sont connectés à différents ECN, fournisseurs de liquidités, il est préférable d'obtenir des cotations directement auprès des ECN eux-mêmes. Mais il y a là une limite - la plupart des fournisseurs ne partageront pas leurs devis avec nous, à moins que nous ne nous connections directement à eux. Mais il y a plusieurs endroits où nous pouvons obtenir des devis, y compris la "profondeur du marché". Par exemple, LMAX diffuse ses liquidités dans des widgets comme https://s3-eu-west-1.amazonaws.com/lmax-widget3/website-widget-vwap.html (il est inactif le week-end car il n'y a pas de cotations, en semaine le logo disparaît et une place de marché apparaît). Il existe également quelques ECN qui diffusent leurs cotations avec la profondeur du marché.

Les citations dans le widget sont diffusées via websocket, c'est-à-dire qu'elles ne peuvent pas être obtenues directement via WebRequest(), il est nécessaire de s'abonner aux événements websocket. Et c'est ici que je suis dans la forêt noire, car je ne suis pas (ou presque) familier avec C# et de plus avec les technologies web :) j'ai trouvé 2 exemples d'obtention de données via des sockets web, mais aucun ne fonctionne correctement.

Exemple : créer un client et se connecter à un socket, ressemble à ceci. Je l'ai reçu (après avoir creusé dans les sources du widget et essayé de comprendre quelque chose), je peux envoyer un devis au widget, mais pas le recevoir :) c'est-à-dire qu'il n'est pas clair quelle requête doit être envoyée au serveur pour recevoir un devis. Si nous envoyons les citations elles-mêmes, le serveur répondra positivement et elles apparaîtront dans le widget. Dans les captures d'écran, vous pouvez voir la réponse du serveur. Si vous regardez le fil de discussion de la socket dans Chrome, vous verrez des informations sur la connexion. Et lorsque le marché est actif, les cotations envoyées seront téléchargées et affichées dans le widget :) J'ai un autre exemple, la connexion par socket, mais cela ne fonctionne pas jusqu'à présent.

Si quelqu'un a une idée de la manière de recevoir un devis à partir d'un widget (que ce soit directement à partir de MT et winapi, ou en C#), je lui en serais reconnaissant :)

using System;
using System.Threading.Tasks;
namespace SenseConsoleApp
{
    class Program
    {
        static void Main(string[] args)
        {
            GetDocList();
            Console.ReadLine();
        }
        static async Task<string> GetDocList()
        {
            var client = new SenseWebSocketClient(new Uri("wss://data-fix.smt-data.com/lmax"));
            Console.WriteLine("Connecting to Qlik Sense...");
            Console.WriteLine("Getting document list...");
            var docs = await client.GetDocList();
            Console.WriteLine(docs);
            return docs;
        }
    }
}

using System;   
 using System.Net.WebSockets;  
 using System.Text;  
 using System.Threading;  
 using System.Threading.Tasks;

 namespace SenseConsoleApp  
 {
   
   public class SenseWebSocketClient  
   {  
     private ClientWebSocket _client;
    
     public Uri _senseServerURI;  
     public SenseWebSocketClient(Uri senseServerURI)  
     {  
       _client = new ClientWebSocket();
       _senseServerURI = senseServerURI;  
     }  
     public async Task<string> GetDocList()  
     {
            
       string cmd = "{\"channel\":\"/fixprof/depthmax/EURUSD\",\"data\":[\"EURUSD\",0,0,0,0,0,0,0,0,0,0,[[0,0],[0.0,0.0],[0,0],[0,0],[0,0],[0,0],[0,0],[0.0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],[[0,0],[0,0],[0,0],[0,0],[0.0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]]],\"id\":\"56kcb\"}";  // это строка с запросом к серверу, т.е. по факту мы отправляем на него котировки
       await _client.ConnectAsync(_senseServerURI, CancellationToken.None);  
       await SendCommand(cmd);  
       var docList = await Receive();  
       return docList;  // ответ сервера
     }  
     private async Task ConnectToSenseServer()  
     {  
       await _client.ConnectAsync(_senseServerURI, CancellationToken.None);  
     }  
     private async Task SendCommand(string jsonCmd)  
     {  
       ArraySegment<byte> outputBuffer = new ArraySegment<byte>(Encoding.UTF8.GetBytes(jsonCmd));  
       await _client.SendAsync(outputBuffer, WebSocketMessageType.Text, true, CancellationToken.None);  
     }  
     private async Task<string> Receive()  
     {  
       var receiveBufferSize = 1536;  
       byte[] buffer = new byte[receiveBufferSize];  
       var result = await _client.ReceiveAsync (new ArraySegment<byte>(buffer), CancellationToken.None);  
       var resultJson = (new UTF8Encoding()).GetString(buffer);  
       return resultJson;  
     }  
   }  
 }  

Ce cadre affiche un flux de citations et d'autres informations comme ma connexion au moment actif :

 

Je ne comprends pas tout. Mais attendons lundi, et citons l'activité. Je vais essayer de regarder aussi. D'autres sources directes de citations sont également intéressantes. Pas de retard, d'ailleurs ?

Il y a environ 5-6 ans, je recevais des informations en temps réel (pas le marché) d'un site web, via VBA Excel. Je ne me souviens plus des détails de cette opération. Je me suis connecté via la fenêtre de mon navigateur avec un script qui interagissait avec la page d'information. Le script communiquait avec la page, puis il était envoyé à Excel.

En fait, c'est une question intéressante.

 
Yuriy Asaulenko:

Je ne comprends pas tout. Mais attendons lundi, et citons l'activité. Je vais essayer de voir. D'autres sources directes de citations sont également intéressantes. Sont-ils sans retard, d'ailleurs ?

Il y a environ 5-6 ans, je recevais des informations en temps réel (pas le marché) d'un site web, via VBA Excel. Je ne me souviens plus des détails de cette opération. Je me suis connecté via la fenêtre de mon navigateur avec un script qui interagissait avec la page d'information. Le script travaillait déjà avec la page et la transmettait à Excel.

En fait, c'est une question intéressante.

Oui, je dois attendre lundi. Vous verrez immédiatement ce que vous pouvez faire entrer dans les zéros du widget grâce à la requête proposée :) Semble être sans retard, par rapport aux cotations dans mt4, viennent rapidement ...

Question intéressante car il est possible de tirer non seulement des widgets mais aussi des terminaux web...

 
Maxim Dmitrievsky:

La question est intéressante, puisque vous pouvez tirer non seulement des widgets, mais aussi des terminaux web...

Si possible, envoyez-moi d'autres liens. Toutes les pages ne peuvent pas être utilisées pour extraire des informations. Connecter - vous vous connectez, mais le format des données est écrit en Flash, par exemple. Et paragraphe.
 
LMAX dispose d'une API - vous n'avez pas besoin de widgets.
 
Dmitriy Skub:
LMAX dispose d'une API - vous n'avez pas besoin de widgets.
donc c'est tout pour la pâte. Toute date de marché d'un vendeur coûte du pognon, ou une facture de 10k.
 
Yuriy Asaulenko:
Si possible, envoyez-moi d'autres liens. Toutes les pages ne peuvent pas être utilisées pour en tirer des informations. Connecter - vous vous connectez, mais le format des données est stocké dans, disons, Flash. Et paragraphe.
Je l'ai fait. Eh bien, ici nous le vérifions... En gros, un socket est visible dans ce cas, un thread est visible... La question est de savoir comment l'analyser correctement. Bien sûr, je ne suis pas un expert mais je crois que tout ce que l'on peut voir avec les yeux peut être obtenu par un logiciel :) Flash va aussi tirer des données de quelque part...
 
Dmitriy Skub:
LMAX dispose d'une API - vous n'avez pas besoin de widgets.
Je pense que vous avez besoin d'un compte pour l'API.
 
Maxim Dmitrievsky:
Je l'ai jeté. Eh bien ici, nous le vérifions... en principe, une socket est visible ; dans ce cas, un thread est visible... Question comment l'analyser correctement, bien sûr je ne suis pas un expert mais j'ai une opinion que tout ce qui est visible aux yeux peut être obtenu par un logiciel :) Flash va aussi tirer des données de quelque part...
peut être obtenu avec un protocole d'échange connu. Vous ne l'obtiendrez pas de Flash, ou alors cela s'apparente à de la cryptographie. :)
 

Exemple de connexion numéro 2. Selon les règles, pour se connecter à un socket, il faut envoyer des en-têtes pour passer de http à wss, et ensuite déjà communiquer avec le socket via tcp... Je ne comprends pas pourquoi une telle connexion est établie via Socket alors que c'est beaucoup plus facile via WebSocket. L'erreur ici est une demande incorrecte, c'est-à-dire qu'il n'y a pas de poignée de main, je pense avoir rempli tous les en-têtes correctement. Peut-être que l'erreur est que la connexion est avec ssl, et que ce n'est pas GetStream, mais GetSslstream... Puisque ws est une connexion non sécurisée, et wss est sécurisé. Et il y a beaucoup d'autres "peut-être", c'est difficile à comprendre sans une bouteille, ou sans votre aide :) En haut du code se trouve un lien vers l'exemple original, que j'ai déjà quelque peu modifié.

//stackoverflow.com/questions/2064641/is-there-a-websocket-client-implemented-for-net
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Text;

namespace ssoc
{
    class Program
    {
        static void Main(string[] args)
        {
            Uri LMAXuri = new Uri("wss://data-fix.smt-data.com/lmax"); // путь к сокету
            Dictionary<string, string> headers = new Dictionary<string, string>(); //список хедеров для передачи серверу

            headers.Add("Host", "data-fix.smt-data.com");
            headers.Add("Connection", "Upgrade");
            headers.Add("Pragma", "no-cache");
            headers.Add("Cache-Control", "no-cache");
            headers.Add("Upgrade", "websocket");
            headers.Add("Origin", "https://s3-eu-west-1.amazonaws.com");
            headers.Add("Sec-WebSocket-Version", "13");
            headers.Add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.102 Safari/537.36");
            headers.Add("Accept-Encoding", "gzip, deflate, sdch");
            headers.Add("Accept-Language", "ru-RU,ru;q=0.8,en-US;q=0.6,en;q=0.4");
            headers.Add("Cookie", "connect.sid=s%3ALwAlO60b6DkqtFUZldvHp-kR.K3qfytDjKCo4cz0%2FxxHpZk1qGCyS9AWFPDv3ro2Yu%2BU");
            headers.Add("Sec-WebSocket-Key", "Kd6XNibByshdJKZA8qWmDA==");
            headers.Add("Sec-WebSocket-Extensions", "permessage-deflate; client_max_window_bits");

            WebSocket ws = new WebSocket(LMAXuri);
            ws.SetHeaders(headers); // установим свои хедерыб иначе будут исп. умолчания
            ws.Connect();
            string result = ws.Recv();
            Console.WriteLine(result);
            Console.ReadLine();

        }
    }

    public class WebSocket
    {
        private Uri mUrl;
        private TcpClient mClient;
        private NetworkStream mStream;
        private bool mHandshakeComplete;
        private Dictionary<string, string> mHeaders;

        public WebSocket(Uri url)
        {
            mUrl = url;

            string protocol = mUrl.Scheme;
            if (!protocol.Equals("ws") && !protocol.Equals("wss"))
                throw new ArgumentException("Unsupported protocol: " + protocol);
        }

        public void SetHeaders(Dictionary<string, string> headers)
        {
            mHeaders = headers;
        }

        public void Connect()
        {
            string host = mUrl.DnsSafeHost;
            string path = mUrl.PathAndQuery;
            string origin = "http://" + host;
            
            mClient = CreateSocket(mUrl);
            mStream = mClient.GetStream();

            int port = ((IPEndPoint)mClient.Client.RemoteEndPoint).Port;
            
            if (port != 443)
                host = host + ":" + port;

            StringBuilder extraHeaders = new StringBuilder();
            if (mHeaders != null)
            {
                foreach (KeyValuePair<string, string> header in mHeaders)
                    extraHeaders.Append(header.Key + ": " + header.Value + "\r\n");
            }

            string request = "GET " + path + " HTTP/1.1\r\n" + extraHeaders.ToString() + "\r\n";
            byte[] sendBuffer = Encoding.UTF8.GetBytes(request);

            mStream.Write(sendBuffer, 0, sendBuffer.Length);

            StreamReader reader = new StreamReader(mStream);
            {
                string header = reader.ReadLine();
                Console.WriteLine(header);
                if (!header.Equals("HTTP/1.1 101 Web Socket Protocol Handshake"))
                    throw new IOException("Invalid handshake response");

                header = reader.ReadLine();
                if (!header.Equals("Upgrade: WebSocket"))
                    throw new IOException("Invalid handshake response");

                header = reader.ReadLine();
                if (!header.Equals("Connection: Upgrade"))
                    throw new IOException("Invalid handshake response");
            }

            mHandshakeComplete = true;
            Console.WriteLine("Сервер пожал нам руку: ", mHandshakeComplete);
        }

        public void Send(string str)
        {
            if (!mHandshakeComplete)
                throw new InvalidOperationException("Handshake not complete");

            byte[] sendBuffer = Encoding.UTF8.GetBytes(str);

            mStream.WriteByte(0x00);
            mStream.Write(sendBuffer, 0, sendBuffer.Length);
            mStream.WriteByte(0xff);
            mStream.Flush();
        }

        public string Recv()
        {
            if (!mHandshakeComplete)
                throw new InvalidOperationException("Handshake not complete");

            StringBuilder recvBuffer = new StringBuilder();

            BinaryReader reader = new BinaryReader(mStream);
            byte b = reader.ReadByte();
            if ((b & 0x80) == 0x80)
            {
                // Skip data frame
                int len = 0;
                do
                {
                    b = (byte)(reader.ReadByte() & 0x7f);
                    len += b * 128;
                } while ((b & 0x80) != 0x80);

                for (int i = 0; i < len; i++)
                    reader.ReadByte();
            }

            while (true)
            {
                b = reader.ReadByte();
                if (b == 0xff)
                    break;

                recvBuffer.Append(b);
            }

            return recvBuffer.ToString();
        }

        public void Close()
        {
            mStream.Dispose();
            mClient.Close();
            mStream = null;
            mClient = null;
        }

        private static TcpClient CreateSocket(Uri url)
        {
            string scheme = url.Scheme;
            string host = url.DnsSafeHost;

            int port = url.Port;
            if (port <= 0)
            {
                if (scheme.Equals("wss"))
                    port = 443;
                else if (scheme.Equals("ws"))
                    port = 80;
                else
                    throw new ArgumentException("Unsupported scheme");
            }
            if (scheme.Equals("ws"))
            {
                throw new NotImplementedException("SSL support not implemented yet");
            }
            else
                return new TcpClient(host, port);
        }
    }
}
 
Maxim Dmitrievsky:
donc c'est tout pour la pâte. Toute date de marché d'un vendeur coûte du pognon, ou une facture de 10k ou plus.
Eh bien, oui, ils sont cupides, qu'est-ce qu'on peut faire)). Mais si vous voulez arbitrer, l'api est la seule option disponible.