Example #1
0
        public override IWebSocket GetDepthWebSocket(Action <ExchangeDepth> callback, int maxCount = 20, params Symbol[] symbols)
        {
            /*
             * {[
             * {
             * "binary": 0,
             * "channel": "addChannel",
             * "data": {
             * "result": true,
             * "channel": "ok_sub_spot_bch_btc_depth_5"
             * }
             * }
             * ]}
             *
             * {[
             * {
             * "data": {
             * "asks": [
             * [
             * "8364.1163",
             * "0.005"
             * ],
             *
             * ],
             * "bids": [
             * [
             * "8335.99",
             * "0.01837999"
             * ],
             * [
             * "8335.9899",
             * "0.06"
             * ],
             * ],
             * "timestamp": 1526734386064
             * },
             * "binary": 0,
             * "channel": "ok_sub_spot_btc_usdt_depth_20"
             * }
             * ]}
             *
             */

            return(ConnectWebSocketOkex(async(_socket) =>
            {
                //todo symbols = await AddMarketSymbolsToChannel(_socket, $"ok_sub_spot_{{0}}_depth_{maxCount}", symbols);
            }, (_socket, symbol, sArray, token) =>
            {
                ExchangeDepth book = token.ParseDepthFromJTokenArrays(null, sequence: "timestamp", maxCount: maxCount); //todo:改为symbol
                callback(book);
                return Task.CompletedTask;
            }));
        }
Example #2
0
        /// <summary>
        ///     Common order book parsing method, most exchanges use "asks" and "bids" with
        ///     arrays of length 2 for price and amount (or amount and price)
        /// </summary>
        /// <param name="token">Token</param>
        /// <param name="symbol"></param>
        /// <param name="asks">Asks key</param>
        /// <param name="bids">Bids key</param>
        /// <param name="timestampType"></param>
        /// <param name="maxCount">Max count</param>
        /// <param name="sequence"></param>
        /// <returns>Order book</returns>
        internal static ExchangeDepth ParseDepthFromJTokenArrays
        (
            this JToken token,
            Symbol symbol,
            string asks                 = "asks",
            string bids                 = "bids",
            string sequence             = "ts",
            TimestampType timestampType = TimestampType.None,
            int maxCount                = 100
        )
        {
            var book = new ExchangeDepth
            {
                SequenceId     = token[sequence].ConvertInvariant <long>(),
                LastUpdatedUtc = CryptoUtility.ParseTimestamp(token[sequence], timestampType),
                Symbol         = symbol
            };

            foreach (JArray array in token[asks])
            {
                var depth = new ExchangeOrderPrice
                {
                    Price  = array[0].ConvertInvariant <decimal>(),
                    Amount = array[1].ConvertInvariant <decimal>()
                };
                book.Asks[depth.Price] = depth;
                if (book.Asks.Count == maxCount)
                {
                    break;
                }
            }

            foreach (JArray array in token[bids])
            {
                var depth = new ExchangeOrderPrice
                {
                    Price  = array[0].ConvertInvariant <decimal>(),
                    Amount = array[1].ConvertInvariant <decimal>()
                };
                book.Bids[depth.Price] = depth;
                if (book.Bids.Count == maxCount)
                {
                    break;
                }
            }

            return(book);
        }
Example #3
0
        /// <summary>
        ///     Common order book parsing method, checks for "amount" or "quantity" and "price"
        ///     elements
        /// </summary>
        /// <param name="token">Token</param>
        /// <param name="asks">Asks key</param>
        /// <param name="bids">Bids key</param>
        /// <param name="price">Price key</param>
        /// <param name="amount">Quantity key</param>
        /// <param name="sequence">Sequence key</param>
        /// <param name="maxCount">Max count</param>
        /// <returns>Order book</returns>
        internal static ExchangeDepth ParseDepthFromJTokenDictionaries
        (
            this JToken token,
            string asks     = "asks",
            string bids     = "bids",
            string price    = "price",
            string amount   = "amount",
            string sequence = "ts",
            int maxCount    = 100
        )
        {
            var book = new ExchangeDepth {
                SequenceId = token[sequence].ConvertInvariant <long>()
            };

            foreach (var ask in token[asks])
            {
                var depth = new ExchangeOrderPrice
                {
                    Price  = ask[price].ConvertInvariant <decimal>(),
                    Amount = ask[amount].ConvertInvariant <decimal>()
                };
                book.Asks[depth.Price] = depth;
                if (book.Asks.Count == maxCount)
                {
                    break;
                }
            }

            foreach (var bid in token[bids])
            {
                var depth = new ExchangeOrderPrice
                {
                    Price  = bid[price].ConvertInvariant <decimal>(),
                    Amount = bid[amount].ConvertInvariant <decimal>()
                };
                book.Bids[depth.Price] = depth;
                if (book.Bids.Count == maxCount)
                {
                    break;
                }
            }

            return(book);
        }
Example #4
0
        /// <summary>
        ///     Place a limit order by first querying the order book and then placing the order for a threshold below the bid or
        ///     above the ask that would fully fulfill the amount.
        ///     The order book is scanned until an amount of bids or asks that will fulfill the order is found and then the order
        ///     is placed at the lowest bid or highest ask price multiplied
        ///     by priceThreshold.
        /// </summary>
        /// <param name="api"></param>
        /// <param name="symbol">Symbol to sell</param>
        /// <param name="amount">Amount to sell</param>
        /// <param name="isBuy">True for buy, false for sell</param>
        /// <param name="orderBookCount">Amount of bids/asks to request in the order book</param>
        /// <param name="priceThreshold">
        ///     Threshold below the lowest bid or above the highest ask to set the limit order price at. For buys, this is
        ///     converted to 1 / priceThreshold.
        ///     This can be set to 0 if you want to set the price like a market order.
        /// </param>
        /// <param name="thresholdToAbort">
        ///     If the lowest bid/highest ask price divided by the highest bid/lowest ask price is below this threshold, throw an
        ///     exception.
        ///     This ensures that your order does not buy or sell at an extreme margin.
        /// </param>
        /// <param name="abortIfOrderBookTooSmall">
        ///     Whether to abort if the order book does not have enough bids or ask amounts to
        ///     fulfill the order.
        /// </param>
        /// <returns>Order result</returns>
        public static async Task <List <ExchangeOrderResult> > PlaceSafeMarketOrderAsync(
            this ExchangeAPI api, string symbol,
            decimal amount, bool isBuy, int orderBookCount = 100, decimal priceThreshold = 0.9m,
            decimal thresholdToAbort = 0.75m, bool abortIfOrderBookTooSmall = false)
        {
            if (priceThreshold > 0.9m)
            {
                throw new APIException(
                          "You cannot specify a price threshold above 0.9m, otherwise there is a chance your order will never be fulfilled. For buys, this is " +
                          "converted to 1.0m / priceThreshold, so always specify the value below 0.9m");
            }
            if (priceThreshold <= 0m)
            {
                priceThreshold = 1m;
            }
            else if (isBuy && priceThreshold > 0m)
            {
                priceThreshold = 1.0m / priceThreshold;
            }

            ExchangeDepth book = null; //todo await api.GetDepthAsync(symbol, orderBookCount);

            if (book == null || isBuy && book.Asks.Count == 0 || !isBuy && book.Bids.Count == 0)
            {
                throw new APIException($"Error getting order book for {symbol}");
            }

            var counter   = 0m;
            var highPrice = decimal.MinValue;
            var lowPrice  = decimal.MaxValue;

            if (isBuy)
            {
                foreach (var ask in book.Asks.Values)
                {
                    counter  += ask.Amount;
                    highPrice = Math.Max(highPrice, ask.Price);
                    lowPrice  = Math.Min(lowPrice, ask.Price);
                    if (counter >= amount)
                    {
                        break;
                    }
                }
            }
            else
            {
                foreach (var bid in book.Bids.Values)
                {
                    counter  += bid.Amount;
                    highPrice = Math.Max(highPrice, bid.Price);
                    lowPrice  = Math.Min(lowPrice, bid.Price);
                    if (counter >= amount)
                    {
                        break;
                    }
                }
            }

            if (abortIfOrderBookTooSmall && counter < amount)
            {
                throw new APIException(
                          $"{(isBuy ? "Buy" : "Sell")} order for {symbol} and amount {amount} cannot be fulfilled because the order book is too thin.");
            }
            if (lowPrice / highPrice < thresholdToAbort)
            {
                throw new APIException(
                          $"{(isBuy ? "Buy" : "Sell")} order for {symbol} and amount {amount} would place for a price below threshold of {thresholdToAbort}, aborting.");
            }

            var request = new ExchangeOrderRequest
            {
                Amount            = amount,
                OrderType         = OrderType.Limit,
                Price             = CryptoUtility.RoundAmount((isBuy ? highPrice : lowPrice) * priceThreshold),
                ShouldRoundAmount = true,
                Symbol            = null //todo symbol
            };
            var result = await api.PlaceOrdersAsync(request);

            // wait about 10 seconds until the order is fulfilled
            var       i        = 0;
            const int maxTries = 20; // 500 ms for each try

            for (; i < maxTries; i++)
            {
                await Task.Delay(500);
            }
            //TODO
            //result = await api.GetCanceledOrdersAsync(result.OrderId, symbol);
            //switch (result.Result)
            //{
            //    case ExchangeAPIOrderResult.Filled:
            //    case ExchangeAPIOrderResult.Canceled:
            //    case ExchangeAPIOrderResult.Error:
            //        break;
            //}

            if (i == maxTries)
            {
                throw new APIException(
                          $"{(isBuy ? "Buy" : "Sell")} order for {symbol} and amount {amount} timed out and may not have been fulfilled");
            }

            return(result);
        }