Recuperare un flusso di prezzi via WebSocket in C#.

 

Salve, cari signori. Interessato a ricevere quotazioni da diverse fonti (inclusa la borsa LMAX). Poiché diversi broker sono collegati a diversi ECN, fornitori di liquidità, è meglio ottenere le quotazioni direttamente dagli ECN stessi. Ma c'è una limitazione - la maggior parte dei fornitori non condividerà le loro quotazioni con noi a meno che non ci colleghiamo direttamente con loro. Ma ci sono un paio di posti da cui possiamo ottenere delle quotazioni, compresa la "profondità del mercato". Per esempio LMAX sta trasmettendo la sua liquidità in widget come https://s3-eu-west-1.amazonaws.com/lmax-widget3/website-widget-vwap.html (è inattivo nei fine settimana perché non ci sono quotazioni, nei giorni feriali il logo scomparirà e apparirà un market place). Ci sono anche un paio di ECN che trasmettono le loro quotazioni con la profondità del mercato.

Le quotazioni nel widget sono trasmesse via websocket, cioè non possono essere ottenute direttamente tramite WebRequest(), è necessario sottoscrivere gli eventi websocket. E qui è dove mi trovo nella foresta oscura, dato che non ho (quasi) familiarità con C# e inoltre con le tecnologie web :) ho trovato 2 esempi di ottenere dati attraverso i socket web, ma nessuno di essi funziona correttamente.

Esempio: creare il client e connettersi al socket, appare così. L'ho ricevuto (dopo aver scavato nei sorgenti del widget e aver cercato di capire qualcosa), posso inviare un preventivo al widget, ma non ricevere :) cioè non è chiaro quale richiesta deve essere inviata al server per ricevere un preventivo. Se mandiamo le citazioni stesse, il server risponderà positivamente e appariranno nel widget. Negli screenshot puoi vedere la risposta del server, se guardi attraverso il thread del socket in Chrome, vedrai le informazioni sulla connessione. E quando il mercato è attivo, le quotazioni inviate saranno scaricate e visualizzate nel widget :) Ho un altro esempio, connessione via socket, ma non funziona finora.

Se qualcuno ha qualche idea su come ricevere un preventivo da un widget (può essere direttamente da MT e winapi, o C#), gliene sarei 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;  
     }  
   }  
 }  

Questa cornice mostra un flusso di citazioni e altre informazioni come la mia connessione al tempo attivo:

 

Non capisco tutto. Ma aspettiamo lunedì, e citiamo l'attività. Proverò a cercare anch'io. Anche altre fonti dirette di citazioni sono interessanti. A proposito, nessun ritardo?

Circa 5-6 anni fa, stavo ottenendo informazioni in tempo reale (non il mercato) da qualche sito web, attraverso VBA Excel. Ora non ricordo i dettagli di come è stato fatto. Mi sono collegato tramite la finestra del mio browser con uno script che ha interagito con la pagina di informazioni. Lo script comunicava con la pagina e poi veniva inviato a Excel.

In realtà, è una domanda interessante.

 
Yuriy Asaulenko:

Non capisco tutto. Ma aspettiamo lunedì, e citiamo l'attività. Cercherò di vedere. Anche altre fonti dirette di citazioni sono interessanti. A proposito, sono senza ritardi?

Circa 5-6 anni fa, stavo ottenendo informazioni in tempo reale (non il mercato) da qualche sito web, attraverso VBA Excel. Ora non ricordo i dettagli di come è stato fatto. Mi sono collegato tramite la finestra del mio browser con uno script che ha interagito con la pagina di informazioni. Lo script stava già lavorando con la pagina e la stava alimentando a Excel.

In realtà, è una domanda interessante.

Sì, devo aspettare lunedì. Lì vedrai immediatamente cosa puoi stipare negli zeri del widget attraverso la query proposta :) Sembra essere senza ritardi, rispetto alle quotazioni in mt4, arrivano rapidamente...

Domanda interessante perché è possibile tirare non solo dai widget ma anche dai terminali web...

 
Maxim Dmitrievsky:

La domanda è interessante, dato che si può tirare non solo dai widget, ma anche dai terminali web...

Se possibile, inviatemi qualche altro link. Non tutte le pagine possono essere utilizzate per estrarre informazioni. Connect - ci si connette, ma il formato dei dati è scritto in, diciamo, Flash. E paragrafo.
 
LMAX ha un'API - non hai bisogno di nessun widget.
 
Dmitriy Skub:
LMAX ha un'API - non hai bisogno di nessun widget.
quindi è tutto per la pasta. Qualsiasi data di mercato da un venditore costa una grana, o una fattura da 10k.
 
Yuriy Asaulenko:
Se possibile, inviatemi qualche altro link. Non tutte le pagine possono essere utilizzate per estrarre informazioni da esse. Connetti - ti connetti, ma il formato dei dati è memorizzato in, diciamo, Flash. E paragrafo.
L'ho fatto. Bene, qui controlliamo... Fondamentalmente, un socket è visibile in questo caso, un thread è visibile... La questione è come analizzarlo correttamente. Naturalmente, non sono un esperto ma credo che tutto ciò che può essere visto con gli occhi può essere ottenuto dal software :) Flash estrarrà anche dati da qualche parte...
 
Dmitriy Skub:
LMAX ha un'API - non hai bisogno di nessun widget.
Sospetto che tu abbia bisogno di un account per l'API.
 
Maxim Dmitrievsky:
L'ho lanciato. Bene, qui controlliamo... in linea di principio un socket è visibile in questo caso, un thread è visibile... Domanda come analizzarlo correttamente, naturalmente non sono un esperto ma ho l'opinione che tutto ciò che è visibile agli occhi può essere ottenuto dal software :) Flash estrarrà anche dati da qualche parte...
forse ottenuto con un protocollo di scambio noto. Non lo otterrete da Flash, o è simile alla crittografia. :)
 

Esempio di connessione n. 2. Secondo le regole, per connettersi a un socket, è necessario inviare intestazioni per passare da http a wss, e poi già comunicare con il socket via tcp... Non capisco perché una tale connessione sia fatta via Socket quando è molto più facile via WebSocket. L'errore qui è una richiesta errata, cioè non avviene nessun handshake, penso di aver compilato tutte le intestazioni correttamente. Forse l'errore è che la connessione arriva con ssl, ed è necessario ottenere GetStream, non GetSlstream... Poiché ws è una connessione non protetta, e wss è protetta. E ci sono molti altri "forse", è difficile da capire senza una bottiglia, o senza il vostro aiuto :) In cima al codice c'è un link all'esempio originale, che ho già modificato un po'.

//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:
quindi è tutto per la pasta. Qualsiasi data di mercato da un venditore costa una grana, o una fattura di 10k o più.
Beh, sì, sono avidi, che ci vuoi fare)). Ma se volete arbitrare, l'api è l'unica opzione disponibile.