Recuperando um fluxo de preços via WebSocket em C#.

 

Olá, Prezados Senhores. Interessado em receber citações de diferentes fontes (incluindo o intercâmbio LMAX). Como diferentes corretores estão conectados a diferentes ECNs, fornecedores de liquidez, é melhor obter citações diretamente dos próprios ECNs. Mas há uma limitação aí - a maioria dos fornecedores não compartilhará conosco suas citações, a menos que nos conectemos diretamente a eles. Mas há alguns lugares onde podemos obter cotações, incluindo "profundidade de mercado". Por exemplo, o LMAX está transmitindo sua liquidez em widgets como https://s3-eu-west-1.amazonaws.com/lmax-widget3/website-widget-vwap.html (está inativo nos fins de semana porque não há cotações, nos dias de semana o logotipo desaparecerá e aparecerá um lugar no mercado). Há também um par de ECNs que transmitem suas cotações com profundidade de mercado.

As citações em widget são transmitidas via websocket, ou seja, não podem ser obtidas via WebRequest() diretamente, é necessário inscrever-se em eventos websocket. E aqui é onde estou na floresta escura, pois não estou (quase) familiarizado com C# e, além disso, com tecnologias web :) encontrei 2 exemplos de como obter dados através de tomadas web, mas nenhum deles funciona corretamente.

Exemplo: criar cliente e conectar-se ao soquete, parece ser assim. Eu a recebi (depois de pesquisar em fontes widget e tentar entender algo), eu posso enviar uma cotação para widget, mas não receber :) ou seja, não está claro qual solicitação deve ser enviada ao servidor para receber uma cotação. Se enviarmos as citações propriamente ditas, o servidor responderá positivamente e elas aparecerão no widget. Nas capturas de tela você pode ver a resposta do servidor, se você olhar através da rosca do soquete com seu navegador, as informações sobre a conexão também aparecerão. E quando o mercado estiver ativo, as cotações enviadas serão baixadas e exibidas no widget :) Tenho outro exemplo, a conexão via soquete, mas até agora não funciona.

Se alguém tem alguma idéia de como receber uma cotação de widget (pode ser diretamente da MT e winapi, ou C#), eu ficaria grato :)

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 quadro exibe um fluxo de citações e outras informações como minha conexão em tempo ativo:

 

Eu não entendo tudo. Mas vamos esperar por segunda-feira, e citar a atividade. Vou tentar olhar também. Outras fontes diretas de citações também são interessantes. A propósito, sem atrasos?

Há cerca de 5-6 anos, eu estava recebendo informações em tempo real (não do mercado) de algum website, através do VBA Excel. Agora não me lembro dos detalhes de como isso foi feito. Eu me conectei através da janela do meu navegador com um script que interagia com a página de informações. O roteiro estava se comunicando com a página, e depois foi enviado para o Excel.

Na verdade, é uma pergunta interessante.

 
Yuriy Asaulenko:

Eu não entendo tudo. Mas vamos esperar por segunda-feira, e citar a atividade. Vou tentar ver. Outras fontes diretas de citações também são interessantes. A propósito, eles estão sem atrasos?

Há cerca de 5-6 anos, eu estava recebendo informações em tempo real (não do mercado) de algum website, através do VBA Excel. Agora não me lembro dos detalhes de como isso foi feito. Eu me conectei através da janela do meu navegador com um script que interagia com a página de informações. E o roteiro já o estava alimentando para o Excel.

Na verdade, é uma pergunta interessante.

Sim, eu tenho que esperar por segunda-feira. Aí você verá imediatamente o que você pode encher nos zeros widget através da consulta proposta :) Parece ser sem atrasos, em comparação com as citações em mt4, vêm rapidamente...

Pergunta interessante, pois é possível puxar não só de widgets, mas também de terminais web...

 
Maxim Dmitrievsky:

A questão é interessante, pois você pode puxar não só dos widgets, mas também dos terminais web...

Se possível, por favor, envie-me alguns outros links. Nem todas as páginas podem ser usadas para puxar informações. Conectar - você se conecta, mas o formato dos dados está escrito em, digamos, Flash. E parágrafo.
 
O LMAX tem um API - você não precisa de nenhum widget.
 
Dmitriy Skub:
O LMAX tem um API - você não precisa de nenhum widget.
por isso é tudo pela massa. Qualquer data de mercado de um fornecedor custa uma massa, ou uma fatura de 10k.
 
Yuriy Asaulenko:
Se possível, por favor, envie-me alguns outros links. Nem todas as páginas podem ser usadas para extrair informações dela. Conectar - você se conecta, mas o formato dos dados é armazenado em, digamos, Flash. E parágrafo.
Já o fiz. Bem, aqui nós verificamos... Basicamente, um soquete é visível neste caso, um fio é visível... A questão é como analisá-lo corretamente. É claro que não sou especialista, mas acredito que tudo o que pode ser visto com os olhos pode ser obtido por software :) Flash também puxará dados de algum lugar...
 
Dmitriy Skub:
O LMAX tem um API - você não precisa de nenhum widget.
Eu suspeito que você precisa de uma conta para o API.
 
Maxim Dmitrievsky:
Eu a joguei. Bem, aqui nós verificamos... em princípio um soquete é visível neste caso, um fio é visível... Questionar como analisar corretamente, é claro que não sou especialista, mas tenho a opinião de que tudo o que é visível aos olhos pode ser obtido por software :) Flash também puxará dados de algum lugar...
talvez obtido com um protocolo de intercâmbio conhecido. Você não obterá isso do Flash, ou é semelhante à criptografia. :)
 

Exemplo de conexão número 2. De acordo com as regras, para se conectar a um soquete, é necessário enviar cabeçalhos para mudar de http para wss, e depois já comunicar com o soquete via tcp... Não entendo porque tal conexão é feita via Socket quando é muito mais fácil via WebSocket. O erro aqui é um pedido incorreto, ou seja, não acontece nenhum aperto de mão, eu acho que preenchi todos os cabeçalhos corretamente. Talvez o erro seja que a conexão é com a ssl, e não é GetStream, mas GetSslstream... Como ws é uma conexão não segura, e wss é segura. E há muitos outros "talvez", é difícil de entender sem uma garrafa, ou sem sua ajuda :) No topo do código está um link para o exemplo original, que eu já modifiquei um pouco.

//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:
por isso é tudo pela massa. Qualquer data de mercado de um fornecedor custa uma massa, ou uma fatura de 10k ou mais.
Bem, sim, eles são gananciosos, o que você pode fazer))). Mas se você quiser arbitrar, a api é a única opção disponível.