Esempio n. 1
0
        private void GetOrderBook(IOrderBookProvider provider, OrderBookContext context)
        {
            if (context == null)
            {
                return;
            }

            try
            {
                var r = AsyncContext.Run(() => provider.GetOrderBookAsync(context));
                Assert.IsTrue(r != null);

                if (context.MaxRecordsCount.HasValue)
                {
                    Assert.IsTrue(r.Count == context.MaxRecordsCount.Value);
                }
                else
                {
                    Assert.IsTrue(r.Count > 0);
                }

                Trace.WriteLine($"Order book data ({r.Count(x => x.Type == OrderType.Ask)} asks, {r.Count(x => x.Type == OrderType.Bid)} bids): ");
                foreach (var obr in r)
                {
                    Trace.WriteLine($"{obr.UtcUpdated} | For {context.Pair.Asset1}: {obr.Type} {obr.Price.Display}, {obr.Volume} ");
                }
            }
            catch (Exception e)
            {
                Assert.Fail(e.Message);
            }
        }
        private void InternalGetOrderBook(IOrderBookProvider provider, OrderBookContext context, bool priceLessThan1)
        {
            var r = AsyncContext.Run(() => provider.GetOrderBookAsync(context));

            Assert.IsTrue(r != null, "Null response returned");

            if (r.IsReversed)
            {
                Trace.WriteLine("Asset pair is reversed");
            }

            // Assert.IsTrue(r.Pair.Equals(context.Pair), "Incorrect asset pair returned");

            Assert.IsTrue(r.Asks.Count > 0, "No asks returned");
            Assert.IsTrue(r.Bids.Count > 0, "No bids returned");
            Assert.IsTrue(r.Asks.Count <= context.MaxRecordsCount, "Incorrect number of ask order book records returned");
            Assert.IsTrue(r.Bids.Count <= context.MaxRecordsCount, "Incorrect number of bid order book records returned");

            //if (context.MaxRecordsCount == Int32.MaxValue)
            //    Assert.IsTrue(r.Count > 0, "No order book records returned");
            //else
            //    Assert.IsTrue(r.Asks.Count == context.MaxRecordsCount && r.Bids.Count == context.MaxRecordsCount, "Incorrect number of order book records returned");

            Trace.WriteLine($"Highest bid: {r.HighestBid}");
            Trace.WriteLine($"Lowest ask: {r.LowestAsk}");

            var records = new List <OrderBookRecord>()
            {
                r.LowestAsk, r.HighestBid
            };

            foreach (var record in records)
            {
                if (priceLessThan1) // Checks if the pair is reversed (price-wise).
                {
                    Assert.IsTrue(record.Price < 1, "Reverse check failed. Price is expected to be < 1");
                }
                else
                {
                    Assert.IsTrue(record.Price > 1, "Reverse check failed. Price is expected to be > 1");
                }
            }

            Trace.WriteLine($"Order book data ({r.Asks.Count} asks, {r.Bids.Count} bids): ");

            foreach (var obr in r.Asks.Concat(r.Bids))
            {
                Trace.WriteLine($"{obr.UtcUpdated} : {obr}");
            }
        }
Esempio n. 3
0
        private void InternalGetOrderBook(IOrderBookProvider provider, OrderBookContext context, bool priceLessThan1)
        {
            var r = AsyncContext.Run(() => provider.GetOrderBookAsync(context));

            Assert.IsTrue(r != null, "Null response returned");

            if (r.Pair.Reversed.Equals(context.Pair))
            {
                Trace.WriteLine("Asset pair is reversed");
            }

            // Assert.IsTrue(r.Pair.Equals(context.Pair), "Incorrect asset pair returned");

            if (context.MaxRecordsCount == Int32.MaxValue)
            {
                Assert.IsTrue(r.Count > 0, "No order book records returned");
            }
            else
            {
                Assert.IsTrue(r.Asks.Count == context.MaxRecordsCount && r.Bids.Count == context.MaxRecordsCount, "Incorrect number of order book records returned");
            }

            foreach (var record in r.Asks.Take(1).Concat(r.Bids.Take(1)))
            {
                if (priceLessThan1) // Checks if the pair is reversed (price-wise).
                {
                    Assert.IsTrue(record.Price < 1, "Reverse check failed. Price is expected to be < 1");
                }
                else
                {
                    Assert.IsTrue(record.Price > 1, "Reverse check failed. Price is expected to be > 1");
                }
            }

            Trace.WriteLine($"Order book data ({r.Asks.Count} asks, {r.Bids.Count} bids): ");

            foreach (var obr in r.Asks.Concat(r.Bids))
            {
                Trace.WriteLine($"{obr.UtcUpdated} | For {context.Pair.Asset1}: {obr.Type} {obr.Price.Display}, {obr.Volume} ");
            }
        }
        /// <summary>
        /// Get full order book bids and asks via web socket. This is efficient and will
        /// only use the order book deltas (if supported by the exchange). This method deals
        /// with the complexity of different exchanges sending order books that are full,
        /// partial or otherwise.
        /// </summary>
        /// <param name="callback">Callback containing full order book</param>
        /// <param name="maxCount">Max count of bids and asks - not all exchanges will honor this
        /// parameter</param>
        /// <param name="symbols">Order book symbols or null/empty for all of them (if supported)</param>
        /// <returns>Web socket, call Dispose to close</returns>
        public static IWebSocket GetFullOrderBookWebSocket(this IOrderBookProvider api, Action <ExchangeOrderBook> callback, int maxCount = 20, params string[] symbols)
        {
            if (api.WebSocketOrderBookType == WebSocketOrderBookType.None)
            {
                throw new NotSupportedException(api.GetType().Name + " does not support web socket order books");
            }

            // Notes:
            // * Confirm with the Exchange's API docs whether the data in each event is the absolute quantity or differential quantity
            // * Receiving an event that removes a price level that is not in your local order book can happen and is normal.
            ConcurrentDictionary <string, ExchangeOrderBook> fullBooks = new ConcurrentDictionary <string, ExchangeOrderBook>();
            Dictionary <string, Queue <ExchangeOrderBook> >  partialOrderBookQueues = new Dictionary <string, Queue <ExchangeOrderBook> >();

            void applyDelta(SortedDictionary <decimal, ExchangeOrderPrice> deltaValues, SortedDictionary <decimal, ExchangeOrderPrice> bookToEdit)
            {
                foreach (ExchangeOrderPrice record in deltaValues.Values)
                {
                    if (record.Amount <= 0 || record.Price <= 0)
                    {
                        bookToEdit.Remove(record.Price);
                    }
                    else
                    {
                        bookToEdit[record.Price] = record;
                    }
                }
            }

            void updateOrderBook(ExchangeOrderBook fullOrderBook, ExchangeOrderBook freshBook)
            {
                lock (fullOrderBook)
                {
                    // update deltas as long as the full book is at or before the delta timestamp
                    if (fullOrderBook.SequenceId <= freshBook.SequenceId)
                    {
                        applyDelta(freshBook.Asks, fullOrderBook.Asks);
                        applyDelta(freshBook.Bids, fullOrderBook.Bids);
                        fullOrderBook.SequenceId = freshBook.SequenceId;
                    }
                }
            }

            async Task innerCallback(ExchangeOrderBook newOrderBook)
            {
                // depending on the exchange, newOrderBook may be a complete or partial order book
                // ideally all exchanges would send the full order book on first message, followed by delta order books
                // but this is not the case

                bool foundFullBook = fullBooks.TryGetValue(newOrderBook.MarketSymbol, out ExchangeOrderBook fullOrderBook);

                switch (api.WebSocketOrderBookType)
                {
                case WebSocketOrderBookType.DeltasOnly:
                {
                    // Fetch an initial book the first time and apply deltas on top
                    // send these exchanges scathing support tickets that they should send
                    // the full book for the first web socket callback message
                    Queue <ExchangeOrderBook> partialOrderBookQueue;
                    bool requestFullOrderBook = false;

                    // attempt to find the right queue to put the partial order book in to be processed later
                    lock (partialOrderBookQueues)
                    {
                        if (!partialOrderBookQueues.TryGetValue(newOrderBook.MarketSymbol, out partialOrderBookQueue))
                        {
                            // no queue found, make a new one
                            partialOrderBookQueues[newOrderBook.MarketSymbol] = partialOrderBookQueue = new Queue <ExchangeOrderBook>();
                            requestFullOrderBook = !foundFullBook;
                        }

                        // always enqueue the partial order book, they get dequeued down below
                        partialOrderBookQueue.Enqueue(newOrderBook);
                    }

                    // request the entire order book if we need it
                    if (requestFullOrderBook)
                    {
                        fullOrderBook = await api.GetOrderBookAsync(newOrderBook.MarketSymbol, maxCount);

                        fullOrderBook.MarketSymbol           = newOrderBook.MarketSymbol;
                        fullBooks[newOrderBook.MarketSymbol] = fullOrderBook;
                    }
                    else if (!foundFullBook)
                    {
                        // we got a partial book while the full order book was being requested
                        // return out, the full order book loop will process this item in the queue
                        return;
                    }
                    // else new partial book with full order book available, will get dequeued below

                    // check if any old books for this symbol, if so process them first
                    // lock dictionary of queues for lookup only
                    lock (partialOrderBookQueues)
                    {
                        partialOrderBookQueues.TryGetValue(newOrderBook.MarketSymbol, out partialOrderBookQueue);
                    }

                    if (partialOrderBookQueue != null)
                    {
                        // lock the individual queue for processing, fifo queue
                        lock (partialOrderBookQueue)
                        {
                            while (partialOrderBookQueue.Count != 0)
                            {
                                updateOrderBook(fullOrderBook, partialOrderBookQueue.Dequeue());
                            }
                        }
                    }
                } break;

                case WebSocketOrderBookType.FullBookFirstThenDeltas:
                {
                    // First response from exchange will be the full order book.
                    // Subsequent updates will be deltas, at least some exchanges have their heads on straight
                    if (!foundFullBook)
                    {
                        fullBooks[newOrderBook.MarketSymbol] = fullOrderBook = newOrderBook;
                    }
                    else
                    {
                        updateOrderBook(fullOrderBook, newOrderBook);
                    }
                } break;

                case WebSocketOrderBookType.FullBookAlways:
                {
                    // Websocket always returns full order book, WTF...?
                    fullBooks[newOrderBook.MarketSymbol] = fullOrderBook = newOrderBook;
                } break;
                }

                fullOrderBook.LastUpdatedUtc = CryptoUtility.UtcNow;
                callback(fullOrderBook);
            }

            IWebSocket socket = api.GetOrderBookWebSocket(async(b) =>
            {
                try
                {
                    await innerCallback(b);
                }
                catch
                {
                }
            }, maxCount, symbols);

            socket.Connected += (s) =>
            {
                // when we re-connect, we must invalidate the order books, who knows how long we were disconnected
                //  and how out of date the order books are
                fullBooks.Clear();
                lock (partialOrderBookQueues)
                {
                    partialOrderBookQueues.Clear();
                }
                return(Task.CompletedTask);
            };
            return(socket);
        }
Esempio n. 5
0
 public static Task <ApiResponse <OrderBook> > GetOrderBookAsync(IOrderBookProvider provider, OrderBookContext context)
 {
     return(ApiHelpers.WrapExceptionAsync(() => provider.GetOrderBookAsync(context), nameof(GetOrderBook), provider, context));
 }