Получение потока цен через WebSocket на С#.

 

Здравствуйте, уважаемые. Возник интерес получать котировки с различных источников (в т.ч. валютная биржа LMAX). Поскольку разные брокеры подключены к разным ECN, поставщикам ликвидности, лучше получать котировки непосредственно от самих ECN. Но там есть ограничение - большинство поставщиков не поделятся с нами своими котировками, пока мы не подключимся к ним напрямую. Но есть пара мест, откуда котировки взять-таки можно, в т.ч. и с "глубиной рынка". Например, LMAX стримит свою ликвидность в виджеты типа такого https://s3-eu-west-1.amazonaws.com/lmax-widget3/website-widget-vwap.html (в выходные он неактивен, т.к. нет котировок, в будни лого исчезнет и появится стакан). Есть еще пара ECN, которые стримят свои котировки с глубиной рынка.

Котировки в виджет стримятся через вебсокет, т.е. напрямую через  WebRequest() их вытащить нельзя, необходимо подписаться на события вебсокета. И вот тут у меня возникает темный лес, т.к. я слабо (почти НЕ)владею C# и тем более веб технологиями :) нашел 2 примера получения данных через веб-сокеты, но ни один не работает как нужно.

Пример: собсно создание клиента и подключение к сокету, выглядит так. Здесь(после копания в исходниках виджета и попытках хоть что-то для себя прояснить), получилось отправлять котировки в виджет, но не получать :) т.е. совершенно непонятно какой запрос на сервер необходимо отправить, что бы получить котировки. Если же отправить сами котировки, то сервер выдаст положительный ответ и они на нем появятся. На скринах виден ответ сервера, так же если через хром посмотреть поток сокета - там появляется инфа о подключении. А когда рынок активен еще и отправленные котировки пихаются в поток и отображаются в виджете :) Есть еще второй пример, подключение через сокет, но тот пока вообще никак не работает, думаю, этого хватит.

Если есть у кого-то идеи как можно вытянуть котировки с виджета (мб. прямо из мт и winapi, или C#), буду благодарен :)

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

 

В этом фрейме в активное время отображается поток котировок и другая инфа, например мое подключение: 

 

 

Не все понял. Но давайте подождем понедельника, и активности котировок. Попробую тоже посмотреть. Интересны и др. прямые источники котировок. Они без задержек, кстати?

Лет 5-6 назад получал реал-тайм информацию (не рынок) с какого-то сайта, через VBA Excel. Сейчас уже не помню подробности, каким образом. Подключался через свое окно в браузере в кот работал скрипт, взаимодействующий со страницей с информацией. А скрипт уже отдавал в Excel.

Вообще, интересный вопрос.

 
Yuriy Asaulenko:

Не все понял. Но давайте подождем понедельника, и активности котировок. Попробую посмотреть. Интересны и др. прямые источники котировок. Они без задержек, кстати?

Лет 5-6 назад получал реал-тайм информацию (не рынок) с какого-то сайта, через VBA Excel. Сейчас уже не помню подробности, каким образом. Подключался через свое окно в браузере в кот работал скрипт, взаимодействующий со страницей с информацией. А скрипт уже отдавал в Excel.

Вообще, интересный вопрос.

Да, нужно понедельника дождаться. Там сразу увидите что можно впихнуть в виджет ноли через предложенный запрос :) Похоже на то, что без задержек, сравнивал с котировками в мт4, приходят быстро...

Вопрос интересный, т.к. можно тянуть не только с виджетов, но и с веб-терминалов.. 

 
Maxim Dmitrievsky:

Вопрос интересный, т.к. можно тянуть не только с виджетов, но и с веб-терминалов.. 

Если возможно, киньте в личку др. ссылки. Не со всякой страницы можно вытянуть инфу. Подключиться - подключитесь, а формат данных зашит, скажем, в Флэше. И абзац.
 
У LMAX есть АПИ - виджеты никакие не нужны.
 
Dmitriy Skub:
У LMAX есть АПИ - виджеты никакие не нужны.
так там за бапки все. Любая маркет дата от поставщика стоит бапки, либо счет от 10к
 
Yuriy Asaulenko:
Если возможно, киньте в личку др. ссылки. Не со всякой страницы можно вытянуть инфу. Подключиться - подключитесь, а формат данных зашит, скажем, в Флэше. И абзац.
Кинул. Ну вот и проверяем.. в принципе сокет видно в данном случае, поток видно.. Вопрос как его парсить правильно, я конечно не спец но имею мнение, что все что видно глазами может быть получено программным путем :) Флэш же тоже откуда-то данные будет тянуть..
 
Dmitriy Skub:
У LMAX есть АПИ - виджеты никакие не нужны.
Подозреваю, что для АПИ нужен аккаунт.
 
Maxim Dmitrievsky:
Кинул. Ну вот и проверяем.. в принципе сокет видно в данном случае, поток видно.. Вопрос как его парсить правильно, я конечно не спец но имею мнение, что все что видно глазами может быть получено программным путем :) Флэш же тоже откуда-то данные будет тянуть..
м.б. получено при известном протоколе обмена. Из Флеша вы его не добудете, или это сродни криптографии. :)
 

Пример коннекта намбер 2. Типо по правилам, для того что бы законнектиться к сокету, необходимо отправить хедеры на переход с http на wss, и потом уже через tcp общаться с сокетом... Не понимаю зачем делать такой коннект через Socket, когда через WebSocket получается гораздо проще. Тут возникает ошибка неверный запрос, т.е. рукопожатия не происходит, вроде бы хедеры все правильно заполнил. Может быть ошибка в том, что подключение идет с ssl, и нужно получать не GetStream, а GetSslstream... Т.к. ws это незащищенное соединение, а wss защищенное. ну и еще много разных "может", без бутылки сложно разобраться, ну или без вашей помощи :) Вверху кода ссылка на оригинал примера, который я уже несколько модифицировал.

//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:
так там за бапки все. Любая маркет дата от поставщика стоит бапки, либо счет от 10к
Ну да, жадные они, что поделаешь)) Но, если хотите арбитражить, то апи единственный вариант из доступных.ИМХО.
Причина обращения: