Книга ордеров GDAX/Coinbase API Level3 — пропуск сообщений

Я использую GDAX API Websocket Stream, чтобы попытаться создать копию из полной книги заказов LEVEL3.

У меня есть очень простая реализация с использованием WebSocketSharp, и я в основном делаю что-то вроде этого.

private readonly WebSocket _webSocket = new WebSocket("wss://ws-feed.gdax.com");

_webSocket.OnMessage += WebSocket_OnMessage;
_webSocket.Connect();
_webSocket.Send(JsonConvert.SerializeObject(new BeginSubscriptionMessage()));

private void WebSocket_OnMessage(object sender, MessageEventArgs e)
{
    var message = JsonConvert.DeserializeObject<BaseMessage>(e.Data);
    switch (message.Type)
    {   
        case "match": //A trade occurred between two orders. 
            MatchMessage matchMessage = JsonConvert.DeserializeObject<MatchMessage>(e.Data);
            _receivedMatchQueue.Enqueue(matchMessage);
            break;
        case "received": //A valid order has been received and is now active. This message is emitted for every single valid order as soon as the matching engine receives it whether it fills immediately or not.
            ReceivedMessage receivedMessage = JsonConvert.DeserializeObject<ReceivedMessage>(e.Data);
            _receivedMessageQueue.Enqueue(receivedMessage);
            break;
        case "open": //The order is now open on the order book. This message will only be sent for orders which are not fully filled immediately. remaining_size will indicate how much of the order is unfilled and going on the book.
            OpenMessage openMessage = JsonConvert.DeserializeObject<OpenMessage>(e.Data);
            _receivedOpenQueue.Enqueue(openMessage);
            break;
        case "done": //The order is no longer on the order book. Sent for all orders for which there was a received message. This message can result from an order being canceled or filled. 
            DoneMessage doneMessage = JsonConvert.DeserializeObject<DoneMessage>(e.Data);
            _receivedDoneQueue.Enqueue(doneMessage);
            break;
        case "change": //Existing order has been changed
            ChangeMessage changeMessage = JsonConvert.DeserializeObject<ChangeMessage>(e.Data);
            _receivedChangeQueue.Enqueue(changeMessage);
            break;
        case "activate": //Stop order placed
            //Console.WriteLine("Stop Order Placed");
            //ActivateMessage activateMessage = JsonConvert.DeserializeObject<ActivateMessage>(e.Data);

            break;
        case "subscriptions":
            break;
        case "ticker":
            TickerMessage tickerMessage = JsonConvert.DeserializeObject<TickerMessage>(e.Data);
            _receivedTickerQueue.Enqueue(tickerMessage);
            break;
        case "l2update":

            break;
    }
}

Проблема, с которой я сталкиваюсь, заключается в том, что когда я смотрю на порядковые номера, полученные через сообщения RECEIVED и OPEN, я вижу, что они не являются последовательными (на основе следующей информации) предполагает, что сообщения пропускаются.

В основном вы получаете что-то вроде этого

Open Message SequenceId: 5359746354
Open Message SequenceId: 5359746358
Open Message SequenceId: 5359746361
Open Message SequenceId: 5359746363
Open Message SequenceId: 5359746365
Open Message SequenceId: 5359746370
Open Message SequenceId: 5359746372

Я попытался протестировать это на Azure, просто чтобы убедиться, что это не было ограничением пропускной способности с моей стороны, и результаты были в основном схожими.

Итак, учитывая это, как можно создать полную книгу заказов «в реальном времени», используя «полный» поток веб-сокетов, если сообщения будут удалены? Могу ли я просто безопасно игнорировать их? Или мне просто как-то очистить потерянные значения?

Любые советы от тех, кто делал что-то подобное, будут очень признательны.


person Maxim Gershkovich    schedule 12.03.2018    source источник


Ответы (1)


Скорее всего, сообщения не удаляются, у вас просто неправильное представление о том, какую «последовательность» представляют эти порядковые номера.

Как указано в документации API

Большинство сообщений канала содержат порядковый номер. Порядковые номера представляют собой увеличивающиеся целочисленные значения для каждого продукта, при этом каждое новое сообщение имеет ровно 1 порядковый номер, чем предыдущее.

Таким образом, каждый канал имеет отдельный порядковый номер для каждого продукта (например, ETH-USD), а не для каждого типа сообщения (например, «открыть» или «получить»). Предположим, вы подписались на «полный» канал для продуктов ETH-USD и ETH-EUR. Тогда вы должны ожидать последовательность, подобную этой:

receive `ETH-EUR` X
open `ETH-EUR` X + 1
receive `ETH-USD` Y
done `ETH-EUR` X + 2
open `ETH-USD` Y + 1

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

Код подтверждения:

class Program {
    private static readonly WebSocket _webSocket = new WebSocket("wss://ws-feed.gdax.com");
    private static long _lastSequence = 0;
    private static readonly HashSet<string> _expectedTypes = new HashSet<string>(
        new[] { "received", "open", "done", "match", "change", "activate" });

    static void Main(string[] args) {
        var subMsg = "{\"type\": \"subscribe\",\"product_ids\": [\"ETH-USD\"],\"channels\": [\"full\"]}";
        _webSocket.OnMessage += WebSocket_OnMessage;
        _webSocket.Connect();
        _webSocket.Send(subMsg);
        Console.ReadKey();
    }        

    private static void WebSocket_OnMessage(object sender, MessageEventArgs e) {
        var message = JsonConvert.DeserializeObject<BaseMessage>(e.Data);
        if (_expectedTypes.Contains(message.Type)) {
            lock (typeof(Program)) {
                if (_lastSequence == 0)
                    _lastSequence = message.Sequence;
                else {
                    if (message.Sequence > _lastSequence + 1) {
                        Debugger.Break(); // never hits, so nothing is dropped
                    }
                    _lastSequence = message.Sequence;
                }
            }
        }
    }
}

public class BaseMessage {
    [JsonProperty("type")]
    public string Type { get; set; }

    [JsonProperty("product_id")]
    public string ProductId { get; set; }

    [JsonProperty("sequence")]
    public long Sequence { get; set; }
}
person Evk    schedule 15.03.2018
comment
Спасибо большое, вы абсолютно правы. Я концептуально неправильно понял, как работает последовательность сообщений, и предположил, что это «для каждого канала подписки» (т.е. тикер, совпадение и т. д.), что, очевидно, не так. Что меня сбивало с толку, так это то, что иногда сообщения приходили «не по порядку», поэтому я не сразу понял связь между последовательностями, которые я видел. - person Maxim Gershkovich; 16.03.2018