Example #1
0
 public void PrintSymbols(IExchangeAPI api)
 {
     api.GetSymbols().ToList().ForEach(s => Console.WriteLine(s));
 }
 /// <summary>
 /// Constructor
 /// </summary>
 /// <param name="api">Exchange API</param>
 /// <param name="symbol">The symbol to trade by default, can be null</param>
 public ExchangeInfo(IExchangeAPI api, string symbol = null)
 {
     API       = api;
     Symbols   = api.GetSymbols().ToArray();
     TradeInfo = new ExchangeTradeInfo(this, symbol);
 }
        public ActionResult BacktesterResults(string exchange, string coinsToBuy, string baseCurrency, bool saveSignals, decimal startingWallet, decimal tradeAmount, DateTime?from = null, DateTime?to = null, string candleSize = "5", string strategy = "all")
        {
            var strategies = new JObject();

            var coins = new List <string>();

            if (String.IsNullOrEmpty(coinsToBuy))
            {
                IExchangeAPI api           = ExchangeAPI.GetExchangeAPI(exchange);
                var          exchangeCoins = api.GetSymbolsMetadataAsync().Result.Where(m => m.BaseCurrency == baseCurrency);
                foreach (var coin in exchangeCoins)
                {
                    coins.Add(api.ExchangeSymbolToGlobalSymbol(coin.MarketName));
                }
            }
            else
            {
                Char     delimiter       = ',';
                String[] coinsToBuyArray = coinsToBuy.Split(delimiter);
                foreach (var coin in coinsToBuyArray)
                {
                    coins.Add(coin.ToUpper());
                }
            }

            var backtestOptions = new BacktestOptions
            {
                DataFolder   = Global.DataPath,
                Exchange     = (Exchange)Enum.Parse(typeof(Exchange), exchange, true),
                Coins        = coins,
                CandlePeriod = Int32.Parse(candleSize)
            };

            if (from.HasValue)
            {
                backtestOptions.StartDate = from.Value;
            }

            if (to.HasValue)
            {
                backtestOptions.EndDate = to.Value;
                backtestOptions.EndDate = backtestOptions.EndDate.AddDays(1).AddMinutes(-1);
            }

            if (tradeAmount == 0m)
            {
                tradeAmount = backtestOptions.StakeAmount;
            }

            if (startingWallet == 0m)
            {
                startingWallet = backtestOptions.StartingWallet;
            }

            var cts             = new CancellationTokenSource();
            var parallelOptions = new ParallelOptions
            {
                CancellationToken      = cts.Token,
                MaxDegreeOfParallelism = Environment.ProcessorCount
            };

            Parallel.ForEach(BacktestFunctions.GetTradingStrategies(), parallelOptions, async tradingStrategy =>
            {
                if (strategy != "all")
                {
                    var base64EncodedBytes = Convert.FromBase64String(strategy);
                    if (tradingStrategy.Name != Encoding.UTF8.GetString(base64EncodedBytes))
                    {
                        return;
                    }
                }
                var result = await BacktestFunctions.BackTestJson(tradingStrategy, backtestOptions, Global.DataStoreBacktest, baseCurrency, saveSignals, startingWallet, tradeAmount);
                for (int i = 0; i < result.Count(); i++)
                {
                    if (i == 0)
                    {
                        await Runtime.GlobalHubBacktest.Clients.All.SendAsync("SendSummary", JsonConvert.SerializeObject(result[i]));
                    }
                    else
                    {
                        await Runtime.GlobalHubBacktest.Clients.All.SendAsync("Send", JsonConvert.SerializeObject(result[i]));
                    }
                }
            });

            return(new JsonResult(strategies));
        }
Example #4
0
 /// <summary>
 /// Constructor
 /// </summary>
 /// <param name="api">Exchange API</param>
 /// <param name="marketSymbol">The market symbol to trade by default, can be null</param>
 public ExchangeInfo(IExchangeAPI api, string marketSymbol = null)
 {
     API           = api;
     MarketSymbols = api.GetMarketSymbolsAsync().Sync().ToArray();
     TradeInfo     = new ExchangeTradeInfo(this, marketSymbol);
 }
        /// <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).</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">Ticker symbols or null/empty for all of them (if supported)</param>
        /// <returns>Web socket, call Dispose to close</returns>
        public static IWebSocket GetOrderBookWebSocket(this IExchangeAPI api, Action <ExchangeOrderBook> callback, int maxCount = 20, params string[] symbols)
        {
            // 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.
            var fullBooks           = new ConcurrentDictionary <string, ExchangeOrderBook>();
            var freshBooksQueue     = new Dictionary <string, Queue <ExchangeOrderBook> >();
            var fullBookRequestLock = new HashSet <string>();

            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 freshBook)
            {
                bool foundFullBook = fullBooks.TryGetValue(freshBook.Symbol, out ExchangeOrderBook fullOrderBook);

                switch (api.Name)
                {
                // 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
                case ExchangeName.Bittrex:
                case ExchangeName.Binance:
                case ExchangeName.Poloniex:
                {
                    if (!foundFullBook)
                    {
                        // attempt to find the right queue to put the partial order book in to be processed later
                        lock (freshBooksQueue)
                        {
                            if (!freshBooksQueue.TryGetValue(freshBook.Symbol, out Queue <ExchangeOrderBook> freshQueue))
                            {
                                // no queue found, make a new one
                                freshBooksQueue[freshBook.Symbol] = freshQueue = new Queue <ExchangeOrderBook>();
                            }
                            freshQueue.Enqueue(freshBook);
                        }

                        bool makeRequest;
                        lock (fullBookRequestLock)
                        {
                            makeRequest = fullBookRequestLock.Add(freshBook.Symbol);
                        }
                        if (makeRequest)
                        {
                            // we are the first to see this symbol, make a full request to API
                            fullBooks[freshBook.Symbol] = fullOrderBook = await api.GetOrderBookAsync(freshBook.Symbol, maxCount);

                            fullOrderBook.Symbol = freshBook.Symbol;
                            // now that we have the full order book, we can process it (and any books in the queue)
                        }
                        else
                        {
                            // stop processing, other code will take these items out of the queue later
                            return;
                        }
                    }

                    // check if any old books for this symbol, if so process them first
                    lock (freshBooksQueue)
                    {
                        if (freshBooksQueue.TryGetValue(freshBook.Symbol, out Queue <ExchangeOrderBook> freshQueue))
                        {
                            while (freshQueue.Count != 0)
                            {
                                updateOrderBook(fullOrderBook, freshQueue.Dequeue());
                            }
                        }
                    }
                    break;
                }

                // First response from exchange will be the full order book.
                // Subsequent updates will be deltas, at least some exchanges have their heads on straight
                case ExchangeName.BitMEX:
                case ExchangeName.Okex:
                case ExchangeName.Coinbase:
                {
                    if (!foundFullBook)
                    {
                        fullBooks[freshBook.Symbol] = fullOrderBook = freshBook;
                    }
                    else
                    {
                        updateOrderBook(fullOrderBook, freshBook);
                    }

                    break;
                }

                // Websocket always returns full order book
                case ExchangeName.Huobi:
                {
                    fullBooks[freshBook.Symbol] = fullOrderBook = freshBook;
                    break;
                }

                default:
                    throw new NotSupportedException("Full order book web socket not supported for exchange " + api.Name);
                }

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

            IWebSocket socket = api.GetOrderBookDeltasWebSocket(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 (freshBooksQueue)
                {
                    freshBooksQueue.Clear();
                }
                lock (fullBookRequestLock)
                {
                    fullBookRequestLock.Clear();
                }
                return(Task.CompletedTask);
            };
            return(socket);
        }
        public async Task <Tuple <DateTime, DateTime> > CacheAllData(ExchangeAPI api, Exchange exchange)
        {
            Global.Logger.Information($"Starting CacheAllData");
            var watch1 = System.Diagnostics.Stopwatch.StartNew();

            var exchangeCoins = api.GetMarketSymbolsMetadataAsync().Result.Where(m => m.BaseCurrency == Global.Configuration.TradeOptions.QuoteCurrency);

            // If there are items on the only trade list remove the rest
            if (Global.Configuration.TradeOptions.OnlyTradeList.Count > 0)
            {
                exchangeCoins = exchangeCoins.Where(m => Global.Configuration.TradeOptions.OnlyTradeList.Any(c => c.Contains(m.MarketSymbol))).ToList();
            }

            var currentExchangeOption = Global.Configuration.ExchangeOptions.FirstOrDefault();

            IExchangeAPI realExchange = ExchangeAPI.GetExchangeAPI(api.Name);

            var returns = new Tuple <DateTime, DateTime>(DateTime.MinValue, DateTime.MinValue);

            foreach (var coin in exchangeCoins)
            {
                var symbol = coin.MarketSymbol;

                if (realExchange is ExchangeBinanceAPI)
                {
                    symbol = await api.ExchangeMarketSymbolToGlobalMarketSymbolAsync(symbol);
                }

                var backtestOptions = new BacktestOptions
                {
                    DataFolder   = Global.DataPath,
                    Exchange     = exchange,
                    Coin         = symbol,
                    CandlePeriod = Int32.Parse(currentExchangeOption.SimulationCandleSize)
                };

                var key1  = api.Name + backtestOptions.Coin + backtestOptions.CandlePeriod;
                var data1 = Global.AppCache.Get <List <Candle> >(key1);
                if (data1 != null)
                {
                    returns = new Tuple <DateTime, DateTime>(data1.First().Timestamp, data1.Last().Timestamp);
                    continue;
                }

                Candle databaseFirstCandle = Global.DataStoreBacktest.GetBacktestFirstCandle(backtestOptions).Result;
                Candle databaseLastCandle  = Global.DataStoreBacktest.GetBacktestLastCandle(backtestOptions).Result;

                if (databaseFirstCandle == null || databaseLastCandle == null)
                {
                    continue;
                }

                backtestOptions.StartDate = databaseFirstCandle.Timestamp;
                backtestOptions.EndDate   = databaseLastCandle.Timestamp;

                var candleProvider = new DatabaseCandleProvider();
                var _candle15      = candleProvider.GetCandles(backtestOptions, Global.DataStoreBacktest).Result;
                _candle15 = await _candle15.FillCandleGaps((Period)Enum.Parse(typeof(Period), backtestOptions.CandlePeriod.ToString(), true));

                Global.AppCache.Remove(backtestOptions.Coin + backtestOptions.CandlePeriod);
                Global.AppCache.Add(api.Name + backtestOptions.Coin + backtestOptions.CandlePeriod, _candle15, new MemoryCacheEntryOptions());

                Global.Logger.Information($"   Cached {key1}");

                backtestOptions.CandlePeriod = 1;

                var key2 = api.Name + backtestOptions.Coin + backtestOptions.CandlePeriod;
                if (Global.AppCache.Get <List <Candle> >(key2) != null)
                {
                    continue;
                }

                Candle database1FirstCandle = Global.DataStoreBacktest.GetBacktestFirstCandle(backtestOptions).Result;
                Candle database1LastCandle  = Global.DataStoreBacktest.GetBacktestLastCandle(backtestOptions).Result;

                if (database1FirstCandle == null || database1LastCandle == null)
                {
                    continue;
                }

                backtestOptions.StartDate = database1FirstCandle.Timestamp;
                backtestOptions.EndDate   = database1LastCandle.Timestamp;

                var _candle1 = candleProvider.GetCandles(backtestOptions, Global.DataStoreBacktest).Result;
                _candle1 = await _candle1.FillCandleGaps((Period)Enum.Parse(typeof(Period), backtestOptions.CandlePeriod.ToString(), true));

                Global.AppCache.Remove(backtestOptions.Coin + backtestOptions.CandlePeriod);
                Global.AppCache.Add(api.Name + backtestOptions.Coin + backtestOptions.CandlePeriod, _candle1, new MemoryCacheEntryOptions());

                Global.Logger.Information($"   Cached {key2}");

                returns = new Tuple <DateTime, DateTime>(backtestOptions.StartDate, backtestOptions.EndDate);
            }

            watch1.Stop();
            Global.Logger.Warning($"Ended CacheAllData in #{watch1.Elapsed.TotalSeconds} seconds");

            return(returns);
        }
Example #7
0
 public Data GetGata(string market, IExchangeAPI exchange)
 {
     return(new Data {
         DateTime = DateTime.Now, Price = GetFirstAsk(market, exchange)
     });
 }
Example #8
0
 public void Save()
 {
     stExchange = API;
 }
Example #9
0
        public override async Task RunCommand()
        {
            var marketSymbol  = "BTC-USD";
            var marketSymbol2 = "XXBTZUSD";

            IExchangeAPI
                apiCoinbase = await ExchangeAPI.GetExchangeAPIAsync(ExchangeName.Coinbase),
                apiGemini   = await ExchangeAPI.GetExchangeAPIAsync(ExchangeName.Gemini),
                apiKraken   = await ExchangeAPI.GetExchangeAPIAsync(ExchangeName.Kraken),
                apiBitfinex = await ExchangeAPI.GetExchangeAPIAsync(ExchangeName.Bitfinex);

            //TODO: Make this multi-threaded and add parameters
            Console.WriteLine("Use CTRL-C to stop.");

            while (true)
            {
                var ticker = await apiCoinbase.GetTickerAsync(marketSymbol);

                var orders = await apiCoinbase.GetOrderBookAsync(marketSymbol);

                var askAmountSum = orders.Asks.Values.Sum(o => o.Amount);
                var askPriceSum  = orders.Asks.Values.Sum(o => o.Price);
                var bidAmountSum = orders.Bids.Values.Sum(o => o.Amount);
                var bidPriceSum  = orders.Bids.Values.Sum(o => o.Price);

                var ticker2 = await apiGemini.GetTickerAsync(marketSymbol);

                var orders2 = await apiGemini.GetOrderBookAsync(marketSymbol);

                var askAmountSum2 = orders2.Asks.Values.Sum(o => o.Amount);
                var askPriceSum2  = orders2.Asks.Values.Sum(o => o.Price);
                var bidAmountSum2 = orders2.Bids.Values.Sum(o => o.Amount);
                var bidPriceSum2  = orders2.Bids.Values.Sum(o => o.Price);

                var ticker3 = await apiKraken.GetTickerAsync(marketSymbol2);

                var orders3 = await apiKraken.GetOrderBookAsync(marketSymbol2);

                var askAmountSum3 = orders3.Asks.Values.Sum(o => o.Amount);
                var askPriceSum3  = orders3.Asks.Values.Sum(o => o.Price);
                var bidAmountSum3 = orders3.Bids.Values.Sum(o => o.Amount);
                var bidPriceSum3  = orders3.Bids.Values.Sum(o => o.Price);

                var ticker4 = await apiBitfinex.GetTickerAsync(marketSymbol);

                var orders4 = await apiBitfinex.GetOrderBookAsync(marketSymbol);

                var askAmountSum4 = orders4.Asks.Values.Sum(o => o.Amount);
                var askPriceSum4  = orders4.Asks.Values.Sum(o => o.Price);
                var bidAmountSum4 = orders4.Bids.Values.Sum(o => o.Amount);
                var bidPriceSum4  = orders4.Bids.Values.Sum(o => o.Price);

                Console.Clear();
                Console.WriteLine("GDAX: {0,13:N}, {1,15:N}, {2,8:N}, {3,13:N}, {4,8:N}, {5,13:N}", ticker.Last,
                                  ticker.Volume.QuoteCurrencyVolume, askAmountSum, askPriceSum, bidAmountSum, bidPriceSum);
                Console.WriteLine("GEMI: {0,13:N}, {1,15:N}, {2,8:N}, {3,13:N}, {4,8:N}, {5,13:N}", ticker2.Last,
                                  ticker2.Volume.QuoteCurrencyVolume, askAmountSum2, askPriceSum2, bidAmountSum2, bidPriceSum2);
                Console.WriteLine("KRAK: {0,13:N}, {1,15:N}, {2,8:N}, {3,13:N}, {4,8:N}, {5,13:N}", ticker3.Last,
                                  ticker3.Volume.QuoteCurrencyVolume, askAmountSum3, askPriceSum3, bidAmountSum3, bidPriceSum3);
                Console.WriteLine("BITF: {0,13:N}, {1,15:N}, {2,8:N}, {3,13:N}, {4,8:N}, {5,13:N}", ticker4.Last,
                                  ticker4.Volume.QuoteCurrencyVolume, askAmountSum4, askPriceSum4, bidAmountSum4, bidPriceSum4);
                Thread.Sleep(IntervalMs);
            }
        }
Example #10
0
        /// <summary>
        /// Get full order book bids and asks via web socket. This is efficient and will only use the order book deltas.
        /// </summary>
        /// <param name="callback">Callback of symbol, order book</param>
        /// <param name="maxCount">Max count of bids and asks - not all exchanges will honor this parameter</param>
        /// <param name="symbol">Ticker symbols or null/empty for all of them (if supported)</param>
        /// <returns>Web socket, call Dispose to close</returns>
        public static IDisposable GetOrderBookWebSocket(this IExchangeAPI api, Action <ExchangeOrderBook> callback, int maxCount = 20, params string[] symbols)
        {
            // Gets a delta socket for a collection of order books, then maintains a full order book.
            // The suggested way to use this is:
            // 1. Open this socket and begin buffering events you receive
            // 2. Get a depth snapshot of the order books you care about
            // 3. Drop any event where SequenceNumber is less than or equal to the snapshot last update id
            // Notes:
            // * Confirm with the Exchange's API docs whether the data in each event is the absolute quantity or differential quantity
            // * If the quantity is 0, remove the price level
            // * Receiving an event that removes a price level that is not in your local order book can happen and is normal.
            //

            Dictionary <string, ExchangeOrderBook> fullBooks = new Dictionary <string, ExchangeOrderBook>();

            void innerCallback(ExchangeOrderBook book)
            {
                if (api.Name == ExchangeName.Binance)
                {
                    // see if we have a full order book for the symbol
                    if (!fullBooks.TryGetValue(book.Symbol, out ExchangeOrderBook fullBook))
                    {
                        fullBooks[book.Symbol] = fullBook = api.GetOrderBook(book.Symbol, 1000);
                        fullBook.Symbol        = book.Symbol;
                    }

                    // update deltas as long as the full book is at or before the delta timestamp
                    if (fullBook.SequenceId <= book.SequenceId)
                    {
                        foreach (var ask in book.Asks)
                        {
                            if (ask.Value.Amount <= 0m || ask.Value.Price <= 0m)
                            {
                                fullBook.Asks.Remove(ask.Value.Price);
                            }
                            else
                            {
                                fullBook.Asks[ask.Value.Price] = ask.Value;
                            }
                        }
                        foreach (var bid in book.Bids)
                        {
                            if (bid.Value.Amount <= 0m || bid.Value.Price <= 0m)
                            {
                                fullBook.Bids.Remove(bid.Value.Price);
                            }
                            else
                            {
                                fullBook.Bids[bid.Value.Price] = bid.Value;
                            }
                        }
                        fullBook.SequenceId = book.SequenceId;
                    }
                    callback(fullBook);
                }
                else if (api.Name == ExchangeName.Okex || api.Name == ExchangeName.BitMEX)
                {
                    if (!fullBooks.TryGetValue(book.Symbol, out ExchangeOrderBook fullBook))
                    {
                        fullBooks[book.Symbol] = book;
                        fullBook.Symbol        = book.Symbol;
                    }

                    foreach (var ask in book.Asks)
                    {
                        if (ask.Value.Amount <= 0m || ask.Value.Price <= 0m)
                        {
                            fullBook.Asks.Remove(ask.Value.Price);
                        }
                        else
                        {
                            fullBook.Asks[ask.Value.Price] = ask.Value;
                        }
                    }
                    foreach (var bid in book.Bids)
                    {
                        if (bid.Value.Amount <= 0m || bid.Value.Price <= 0m)
                        {
                            fullBook.Bids.Remove(bid.Value.Price);
                        }
                        else
                        {
                            fullBook.Bids[bid.Value.Price] = bid.Value;
                        }
                    }

                    callback(fullBook);
                }
                else if (api.Name == ExchangeName.Huobi)
                {
                    fullBooks[book.Symbol] = book;
                }
            };

            return(api.GetOrderBookDeltasWebSocket(innerCallback, maxCount, symbols));
        }
Example #11
0
        /// <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).</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">Ticker symbols or null/empty for all of them (if supported)</param>
        /// <returns>Web socket, call Dispose to close</returns>
        public static IDisposable GetOrderBookWebSocket(this IExchangeAPI api, Action <ExchangeOrderBook> callback, int maxCount = 20, params string[] symbols)
        {
            // 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.
            var fullBooks = new Dictionary <string, ExchangeOrderBook>();

            void innerCallback(ExchangeOrderBook freshBook)
            {
                bool foundFullBook = fullBooks.TryGetValue(freshBook.Symbol, out ExchangeOrderBook fullOrderBook);

                switch (api.Name)
                {
                // Fetch an initial book the first time and apply deltas on top
                case ExchangeName.Bittrex:
                case ExchangeName.Binance:
                case ExchangeName.Poloniex:
                {
                    // If we don't have an initial order book for this symbol, fetch it
                    if (!foundFullBook)
                    {
                        fullBooks[freshBook.Symbol] = fullOrderBook = api.GetOrderBook(freshBook.Symbol, 1000);
                        fullOrderBook.Symbol        = freshBook.Symbol;
                    }

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

                    break;
                }

                // First response from exchange will be the full order book.
                // Subsequent updates will be deltas
                case ExchangeName.BitMEX:
                case ExchangeName.Okex:
                {
                    if (!foundFullBook)
                    {
                        fullBooks[freshBook.Symbol] = fullOrderBook = freshBook;
                        fullOrderBook.Symbol        = freshBook.Symbol;
                    }
                    else
                    {
                        ApplyDelta(freshBook.Asks, fullOrderBook.Asks);
                        ApplyDelta(freshBook.Bids, fullOrderBook.Bids);
                    }

                    break;
                }

                // Websocket always returns full order book
                case ExchangeName.Huobi:
                    fullBooks[freshBook.Symbol] = fullOrderBook = freshBook;
                    break;
                }

                callback(fullOrderBook);
            }

            return(api.GetOrderBookDeltasWebSocket(innerCallback, maxCount, symbols));
        }
Example #12
0
 // Helper method for SELL orders (ASYNC)
 public static async Task <ExchangeOrderResult> SellAsync(this IExchangeAPI api, string symbol, decimal price, decimal amount)
 {
     return(await ApiHelper.PlaceOrderAsync(api, symbol, OrderSide.Sell, price, amount));
 }
Example #13
0
 // Helper method for SELL orders
 public static ExchangeOrderResult Sell(this IExchangeAPI api, string symbol, decimal price, decimal amount)
 {
     return(ApiHelper.PlaceOrder(api, symbol, OrderSide.Sell, price, amount));
 }
Example #14
0
        /// <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).</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">Ticker symbols or null/empty for all of them (if supported)</param>
        /// <returns>Web socket, call Dispose to close</returns>
        public static IWebSocket GetOrderBookWebSocket(this IExchangeAPI api, Action <ExchangeOrderBook> callback, int maxCount = 20, params string[] symbols)
        {
            // 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.
            var fullBooks = new ConcurrentDictionary <string, ExchangeOrderBook>();
            var 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.Symbol, out ExchangeOrderBook fullOrderBook);

                switch (api.Name)
                {
                // 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
                case ExchangeName.Bittrex:
                case ExchangeName.Binance:
                case ExchangeName.Poloniex:
                {
                    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.Symbol, out partialOrderBookQueue))
                        {
                            // no queue found, make a new one
                            partialOrderBookQueues[newOrderBook.Symbol] = 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.Symbol, maxCount);

                        fullOrderBook.Symbol           = newOrderBook.Symbol;
                        fullBooks[newOrderBook.Symbol] = 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.Symbol, out partialOrderBookQueue);
                    }

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

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

                // Websocket always returns full order book, WTF...?
                case ExchangeName.Huobi:
                {
                    fullBooks[newOrderBook.Symbol] = fullOrderBook = newOrderBook;
                    break;
                }

                default:
                    throw new NotSupportedException("Full order book web socket not supported for exchange " + api.Name);
                }

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

            IWebSocket socket = api.GetOrderBookDeltasWebSocket(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);
        }
Example #15
0
 /// <summary>
 /// Constructor
 /// </summary>
 /// <param name="api">Exchange API</param>
 /// <param name="marketSymbols">Market symbols</param>
 /// <param name="marketSymbol">The market symbol to trade by default, can be null</param>
 public ExchangeInfo(IExchangeAPI api, IReadOnlyCollection <string> marketSymbols, string marketSymbol = null)
 {
     API           = api;
     MarketSymbols = marketSymbols;
     TradeInfo     = new ExchangeTradeInfo(this, marketSymbol);
 }
        /// <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).</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">Ticker symbols or null/empty for all of them (if supported)</param>
        /// <returns>Web socket, call Dispose to close</returns>
        public static IWebSocket GetOrderBookWebSocket(this IExchangeAPI api, Action <ExchangeOrderBook> callback, int maxCount = 20, params string[] symbols)
        {
            // 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.
            var fullBooks = new ConcurrentDictionary <string, 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 freshBook)
            {
                bool foundFullBook = fullBooks.TryGetValue(freshBook.Symbol, out ExchangeOrderBook fullOrderBook);

                switch (api.Name)
                {
                // Fetch an initial book the first time and apply deltas on top
                case ExchangeName.Bittrex:
                case ExchangeName.Binance:
                case ExchangeName.Poloniex:
                {
                    // If we don't have an initial order book for this symbol, fetch it
                    if (!foundFullBook)
                    {
                        fullBooks[freshBook.Symbol] = fullOrderBook = await api.GetOrderBookAsync(freshBook.Symbol, 1000);

                        fullOrderBook.Symbol = freshBook.Symbol;
                    }
                    else
                    {
                        updateOrderBook(fullOrderBook, freshBook);
                    }
                    break;
                }

                // First response from exchange will be the full order book.
                // Subsequent updates will be deltas
                case ExchangeName.Okex:
                {
                    if (!foundFullBook)
                    {
                        fullBooks[freshBook.Symbol] = fullOrderBook = freshBook;
                    }
                    else
                    {
                        updateOrderBook(fullOrderBook, freshBook);
                    }

                    break;
                }

                // Websocket always returns full order book
                case ExchangeName.Huobi:
                {
                    fullBooks[freshBook.Symbol] = fullOrderBook = freshBook;
                    break;
                }
                }

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

            IWebSocket socket = api.GetOrderBookDeltasWebSocket(async(b) => await innerCallback(b), 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();
            };
            return(socket);
        }
Example #17
0
 public NewExchangeAPI(IExchangeAPI exchangeAPI)
 {
     this.API = exchangeAPI;
 }