Recuperación de un flujo de precios vía WebSocket en C#.

 

Hola, estimados señores. Interesado en recibir cotizaciones de diferentes fuentes (incluyendo la bolsa LMAX). Dado que diferentes corredores están conectados a diferentes ECNs, proveedores de liquidez, es mejor obtener las cotizaciones directamente de los propios ECNs. Pero hay una limitación: la mayoría de los proveedores no comparten sus cotizaciones con nosotros a menos que nos conectemos con ellos directamente. Pero hay un par de lugares de los que podemos obtener cotizaciones, incluyendo la "profundidad del mercado". Por ejemplo, LMAX está transmitiendo su liquidez en widgets como https://s3-eu-west-1.amazonaws.com/lmax-widget3/website-widget-vwap.html (está inactivo los fines de semana porque no hay cotizaciones, los días laborables el logo desaparecerá y aparecerá un mercado). También hay un par de ECN que transmiten sus cotizaciones con profundidad de mercado.

Las citas en el widget se transmiten a través de websocket, es decir, no se pueden obtener a través de WebRequest() directamente, es necesario suscribirse a los eventos de websocket. Y aquí es donde estoy en la selva oscura, ya que no estoy (casi) familiarizado con C# y además con las tecnologías web :) encontré 2 ejemplos de obtención de datos a través de web sockets, pero ninguno de ellos funciona correctamente.

Ejemplo: crear un cliente y conectarse a un socket, se ve así. Lo recibí (después de indagar en las fuentes del widget y tratar de entender algo), puedo enviar una cotización al widget, pero no recibirla :) es decir, no está claro qué solicitud debe enviarse al servidor para recibir una cotización. Si enviamos las propias cotizaciones, el servidor responderá positivamente y aparecerán en el widget. En las capturas de pantalla puedes ver la respuesta del servidor, si miras a través del hilo del socket con tu navegador, también aparecerá información sobre la conexión. Y cuando el mercado esté activo, las cotizaciones enviadas se descargarán y se mostrarán en el widget :) Tengo otro ejemplo, conexión vía socket, pero no funciona hasta ahora.

Si alguien tiene alguna idea de cómo recibir un presupuesto desde el widget (puede ser directamente desde MT y winapi, o C#), se lo agradecería :)

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

Este marco muestra un flujo de citas y otra información como mi conexión en tiempo activo:

 

No lo entiendo todo. Pero esperemos al lunes y citemos la actividad. Yo también intentaré buscar. También son interesantes otras fuentes directas de citas. ¿No hay retrasos, por cierto?

Hace unos 5-6 años, obtenía información en tiempo real (no del mercado) de algún sitio web, a través de VBA Excel. Ahora no recuerdo los detalles de cómo se hizo. Me conecté a través de la ventana de mi navegador con un script que interactuaba con la página de información. El script se comunicaba con la página y luego se enviaba a Excel.

En realidad, es una pregunta interesante.

 
Yuriy Asaulenko:

No lo entiendo todo. Pero esperemos al lunes y citemos la actividad. Trataré de ver. También son interesantes otras fuentes directas de citas. Por cierto, ¿están sin retrasos?

Hace unos 5-6 años, obtenía información en tiempo real (no del mercado) de algún sitio web, a través de VBA Excel. Ahora no recuerdo los detalles de cómo se hizo. Me conecté a través de la ventana de mi navegador con un script que interactuaba con la página de información. El script ya estaba trabajando con la página y la alimentaba a Excel.

En realidad, es una pregunta interesante.

Sí, tengo que esperar al lunes. Ahí verás inmediatamente lo que puedes meter en los ceros del widget a través de la consulta propuesta :) Parece que no hay retrasos, en comparación con las cotizaciones en mt4, vienen rápidamente...

Interesante pregunta, ya que es posible tirar no sólo de los widgets, sino también de los terminales web...

 
Maxim Dmitrievsky:

La pregunta es interesante, ya que es posible tirar no sólo de los widgets, sino también de los terminales web...

Si es posible, envíenme otros enlaces. No todas las páginas pueden utilizarse para extraer información. Conectar - se conecta, pero el formato de los datos se escribe, por ejemplo, en Flash. Y el párrafo.
 
LMAX tiene una API - no necesitas ningún widget.
 
Dmitriy Skub:
LMAX tiene una API - no necesitas ningún widget.
así que todo es por la pasta. Cualquier fecha de mercado de un proveedor cuesta pasta, o una factura de 10k.
 
Yuriy Asaulenko:
Si es posible, envíenme otros enlaces. No todas las páginas pueden utilizarse para extraer información de ellas. Conectar: se conecta, pero el formato de los datos se almacena en, por ejemplo, Flash. Y el párrafo.
Lo he hecho. Bueno, aquí lo comprobamos... Básicamente, un socket es visible en este caso, un hilo es visible... La cuestión es cómo analizarlo correctamente. Por supuesto, no soy un experto, pero creo que todo lo que se puede ver con los ojos se puede obtener mediante software :) Flash también sacará datos de algún sitio...
 
Dmitriy Skub:
LMAX tiene una API - no necesitas ningún widget.
Sospecho que necesitas una cuenta para la API.
 
Maxim Dmitrievsky:
Lo he lanzado. Bueno aquí lo comprobamos... en principio un socket es visible en este caso, un hilo es visible... Pregunta cómo analizarlo correctamente, por supuesto que no soy un experto, pero tengo la opinión de que todo lo que es visible a los ojos se puede obtener por el software :) Flash también sacará datos de algún sitio...
puede obtenerse con un protocolo de intercambio conocido. No lo conseguirás con Flash, o es algo parecido a la criptografía. :)
 

Ejemplo de conexión número 2. Según las normas, para conectarse a un socket, hay que enviar cabeceras para pasar de http a wss, y luego ya comunicarse con el socket vía tcp... No entiendo por qué esa conexión se hace vía Socket cuando es mucho más fácil vía WebSocket. El error aquí es una petición incorrecta, es decir, no se produce ningún handshake, creo que he rellenado todas las cabeceras correctamente. Quizás el error es que la conexión es con ssl, y no es GetStream, sino GetSslstream... Ya que ws es una conexión no segura, y wss es segura. Y hay muchos otros "quizás", es difícil de entender sin una botella, o sin tu ayuda :) En la parte superior del código hay un enlace al ejemplo original, que ya he modificado un poco.

//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:
así que todo es por la pasta. Cualquier fecha de mercado de un proveedor cuesta pasta, o una factura de 10k o más.
Bueno, sí, son codiciosos, qué se le va a hacer)). Pero si se quiere arbitrar, la api es la única opción disponible.