Esempio n. 1
0
 protected override IWebSocket OnGetTradesWebSocket(Action <KeyValuePair <string, ExchangeTrade> > callback, params string[] symbols)
 {
     return(ConnectWebSocket("/", (_socket, msg) =>
     {
         JToken token = JToken.Parse(msg.ToStringFromUTF8());
         if (token["type"].ToStringInvariant() != "ticker")
         {
             return Task.CompletedTask;                                                //the ticker channel provides the trade information as well
         }
         if (token["time"] == null)
         {
             return Task.CompletedTask;
         }
         ExchangeTrade trade = ParseTradeWebSocket(token);
         string symbol = token["product_id"].ToStringInvariant();
         callback(new KeyValuePair <string, ExchangeTrade>(symbol, trade));
         return Task.CompletedTask;
     }, async(_socket) =>
     {
         var subscribeRequest = new
         {
             type = "subscribe",
             product_ids = symbols,
             channels = new object[]
             {
                 new
                 {
                     name = "ticker",
                     product_ids = symbols
                 }
             }
         };
         await _socket.SendMessageAsync(subscribeRequest);
     }));
 }
        private IEnumerable <ExchangeTrade> ParseTradesWebSocket(JToken token)
        {
            var trades = new List <ExchangeTrade>();

            if (token.Count() > 1 && token["error_msg"] != null)
            {
                Logger.Warn(token["error_msg"].ToStringInvariant());
            }
            else
            {
                foreach (var t in token)
                {
                    var ts = TimeSpan.Parse(t[3].ToStringInvariant()) + chinaTimeOffset;
                    if (ts < TimeSpan.FromHours(0))
                    {
                        ts += TimeSpan.FromHours(24);
                    }
                    var dt    = CryptoUtility.UtcNow.Date.Add(ts);
                    var trade = new ExchangeTrade()
                    {
                        Id        = t[0].ConvertInvariant <long>(),
                        Price     = t[1].ConvertInvariant <decimal>(),
                        Amount    = t[2].ConvertInvariant <decimal>(),
                        Timestamp = dt,
                        IsBuy     = t[4].ToStringInvariant().EqualsWithOption("bid"),
                    };
                    trades.Add(trade);
                }
            }

            return(trades);
        }
        protected override async Task OnGetHistoricalTradesAsync(Func <IEnumerable <ExchangeTrade>, bool> callback, string symbol, DateTime?startDate = null, DateTime?endDate = null)
        {
            symbol = NormalizeSymbol(symbol);
            List <ExchangeTrade> trades = new List <ExchangeTrade>();
            // Not directly supported, but we'll return the max and filter if necessary
            JToken token = await MakeJsonRequestAsync <JToken>("/trades/" + symbol + "?limit=2000", null, null, "POST");

            token = token.First.First;      // bunch of nested
            foreach (JToken prop in token)
            {
                ExchangeTrade trade = ParseTrade(prop);
                if (startDate != null)
                {
                    if (trade.Timestamp >= startDate)
                    {
                        trades.Add(trade);
                    }
                }
                else
                {
                    trades.Add(trade);
                }
            }
            var rc = callback?.Invoke(trades);
        }
        private IEnumerable <ExchangeTrade> ParseTradesWebSocket(JToken token)
        {
            var trades = new List <ExchangeTrade>();

            foreach (var t in token)
            {
                var timeOffset = TimeSpan.FromHours(-8); //China time to utc, no DST correction needed
                var ts         = TimeSpan.Parse(t[3].ToStringInvariant()) + timeOffset;
                if (ts < TimeSpan.FromHours(0))
                {
                    ts += TimeSpan.FromHours(24);
                }
                var dt    = CryptoUtility.UtcNow.Date.Add(ts);
                var trade = new ExchangeTrade()
                {
                    Id        = t[0].ConvertInvariant <long>(),
                    Price     = t[1].ConvertInvariant <decimal>(),
                    Amount    = t[2].ConvertInvariant <decimal>(),
                    Timestamp = dt,
                    IsBuy     = t[4].ToStringInvariant().EqualsWithOption("bid"),
                };
                trades.Add(trade);
            }

            return(trades);
        }
Esempio n. 5
0
        /// <summary>
        /// Max returns is trades from the last hour only
        /// </summary>
        /// <param name="callback"></param>
        /// <param name="symbol"></param>
        /// <param name="startDate"></param>
        /// <returns></returns>
        protected override async Task OnGetHistoricalTradesAsync(Func <IEnumerable <ExchangeTrade>, bool> callback, string symbol, DateTime?startDate = null, DateTime?endDate = null)
        {
            symbol = NormalizeSymbol(symbol);
            List <ExchangeTrade> trades = new List <ExchangeTrade>();
            // Not directly supported so we'll return what they have and filter if necessary
            JToken token = await MakeJsonRequestAsync <JToken>("/exchange/last_trades?currencyPair=" + symbol + "&minutesOrHour=false");

            token = CheckError(token);
            foreach (JToken trade in token)
            {
                ExchangeTrade rc = ParseTrade(trade);
                if (startDate != null)
                {
                    if (rc.Timestamp > startDate)
                    {
                        trades.Add(rc);
                    }
                }
                else
                {
                    trades.Add(rc);
                }
            }
            callback?.Invoke(trades);
        }
Esempio n. 6
0
        protected override async Task OnGetHistoricalTradesAsync(Func <IEnumerable <ExchangeTrade>, bool> callback, string marketSymbol, DateTime?startDate = null, DateTime?endDate = null, int?limit = null)
        {
            List <ExchangeTrade> trades = new List <ExchangeTrade>();
            // TODO: Can't get Hitbtc to return other than the last 50 trades even though their API says it should (by orderid or timestamp). When passing either of these parms, it still returns the last 50
            // So until there is an update, that's what we'll go with
            // UPDATE: 2020/01/19 https://api.hitbtc.com/ GET /api/2/public/trades/{symbol} limit default: 100 max value:1000
            //
            //var maxRequestLimit = 1000; //hard coded for now, should add limit as an argument
            var maxRequestLimit = (limit == null || limit < 1 || limit > 1000) ? 1000 : (int)limit;
            //note that sort must come after limit, else returns default 100 trades, sort default is DESC
            JToken obj = await MakeJsonRequestAsync <JToken>("/public/trades/" + marketSymbol + "?limit=" + maxRequestLimit + "?sort=DESC");

            //JToken obj = await MakeJsonRequestAsync<JToken>("/public/trades/" + marketSymbol);
            if (obj.HasValues)
            {
                foreach (JToken token in obj)
                {
                    ExchangeTrade trade = ParseExchangeTrade(token);
                    if (startDate == null || trade.Timestamp >= startDate)
                    {
                        trades.Add(trade);
                    }
                }
                if (trades.Count != 0)
                {
                    callback(trades);                     //no need to OrderBy or OrderByDescending, handled by sort=DESC or sort=ASC
                    //callback(trades.OrderBy(t => t.Timestamp));
                }
            }
        }
        protected override async Task OnGetHistoricalTradesAsync(Func <IEnumerable <ExchangeTrade>, bool> callback, string symbol, DateTime?startDate = null, DateTime?endDate = null)
        {
            List <ExchangeTrade> trades = new List <ExchangeTrade>();
            long?lastTradeID            = null;
            // TODO: Can't get Hitbtc to return other than the last 50 trades even though their API says it should (by orderid or timestamp). When passing either of these parms, it still returns the last 50
            // So until there is an update, that's what we'll go with
            JToken obj = await MakeJsonRequestAsync <JToken>("/public/trades/" + symbol);

            if (obj.HasValues)
            {
                foreach (JToken token in obj)
                {
                    ExchangeTrade trade = ParseExchangeTrade(token);
                    lastTradeID = trade.Id;
                    if (startDate == null || trade.Timestamp >= startDate)
                    {
                        trades.Add(trade);
                    }
                }
                if (trades.Count != 0)
                {
                    callback(trades.OrderBy(t => t.Timestamp));
                }
            }
        }
        /// <summary>
        /// Parse a trade
        /// </summary>
        /// <param name="token">Token</param>
        /// <param name="amountKey">Amount key</param>
        /// <param name="priceKey">Price key</param>
        /// <param name="typeKey">Type key</param>
        /// <param name="timestampKey">Timestamp key</param>
        /// <param name="timestampType">Timestamp type</param>
        /// <param name="idKey">Id key</param>
        /// <param name="typeKeyIsBuyValue">Type key buy value</param>
        /// <returns>Trade</returns>
        internal static ExchangeTrade ParseTrade(this JToken token, object amountKey, object priceKey, object typeKey,
                                                 object timestampKey, TimestampType timestampType, object idKey = null, string typeKeyIsBuyValue = "buy")
        {
            ExchangeTrade trade = new ExchangeTrade
            {
                Amount = token[amountKey].ConvertInvariant <decimal>(),
                Price  = token[priceKey].ConvertInvariant <decimal>(),
                IsBuy  = (token[typeKey].ToStringInvariant().EqualsWithOption(typeKeyIsBuyValue))
            };

            trade.Timestamp = (timestampKey == null ? CryptoUtility.UtcNow : CryptoUtility.ParseTimestamp(token[timestampKey], timestampType));
            if (idKey == null)
            {
                trade.Id = trade.Timestamp.Ticks;
            }
            else
            {
                try
                {
                    trade.Id = (long)token[idKey].ConvertInvariant <ulong>();
                }
                catch
                {
                    // dont care
                }
            }
            return(trade);
        }
        protected override IDisposable OnGetTradesWebSocket(Action <KeyValuePair <string, ExchangeTrade> > callback, params string[] symbols)
        {
            /*
             * {"table":"trade","action":"partial","keys":[],
             * "types":{"timestamp":"timestamp","symbol":"symbol","side":"symbol","size":"long","price":"float","tickDirection":"symbol","trdMatchID":"guid","grossValue":"long","homeNotional":"float","foreignNotional":"float"},
             * "foreignKeys":{"symbol":"instrument","side":"side"},
             * "attributes":{"timestamp":"sorted","symbol":"grouped"},
             * "filter":{"symbol":"XBTUSD"},
             * "data":[{"timestamp":"2018-07-06T08:31:53.333Z","symbol":"XBTUSD","side":"Buy","size":10000,"price":6520,"tickDirection":"PlusTick","trdMatchID":"a296312f-c9a4-e066-2f9e-7f4cf2751f0a","grossValue":153370000,"homeNotional":1.5337,"foreignNotional":10000}]}
             */
            if (callback == null || symbols == null || !symbols.Any())
            {
                return(null);
            }

            return(ConnectWebSocket(string.Empty, (msg, _socket) =>
            {
                try
                {
                    var str = msg.UTF8String();
                    JToken token = JToken.Parse(str);

                    if (token["table"] == null)
                    {
                        return;
                    }

                    var action = token["action"].ToStringInvariant();
                    JArray data = token["data"] as JArray;
                    foreach (var t in data)
                    {
                        var symbol = t["symbol"].ToStringInvariant();
                        var trade = new ExchangeTrade()
                        {
                            Amount = t["size"].ConvertInvariant <decimal>(),
                            //Id = t["trdMatchID"].ToStringInvariant(),
                            IsBuy = t["side"].ToStringLowerInvariant().EqualsWithOption("buy"),
                            Price = t["price"].ConvertInvariant <decimal>(),
                            Timestamp = t["timestamp"].ConvertInvariant <DateTime>(),
                        };
                        callback(new KeyValuePair <string, ExchangeTrade>(symbol, trade));
                    }
                }
                catch
                {
                    // TODO: Handle exception
                }
            }, (_socket) =>
            {
                if (symbols.Length == 0)
                {
                    symbols = GetSymbols().ToArray();
                }

                string combined = string.Join(",", symbols.Select(s => "\"trade:" + this.NormalizeSymbol(s) + "\""));
                string msg = $"{{\"op\":\"subscribe\",\"args\":[{combined}]}}";
                _socket.SendMessage(msg);
            }));
        }
Esempio n. 10
0
        protected override async Task <IWebSocket> OnGetTradesWebSocketAsync(Func <KeyValuePair <string, ExchangeTrade>, Task> callback, params string[] marketSymbols)
        {
            Dictionary <int, Tuple <string, long> > messageIdToSymbol = new Dictionary <int, Tuple <string, long> >();

            return(await ConnectWebSocketAsync(string.Empty, async (_socket, msg) =>
            {
                JToken token = JToken.Parse(msg.ToStringFromUTF8());
                int msgId = token[0].ConvertInvariant <int>();

                if (msgId == 1010 || token.Count() == 2)                 // "[7,2]"
                {
                    // this is a heartbeat message
                    return;
                }

                var seq = token[1].ConvertInvariant <long>();
                var dataArray = token[2];
                foreach (var data in dataArray)
                {
                    var dataType = data[0].ToStringInvariant();
                    if (dataType == "i")
                    {
                        var marketInfo = data[1];
                        var market = marketInfo["currencyPair"].ToStringInvariant();
                        messageIdToSymbol[msgId] = new Tuple <string, long>(market, 0);
                    }
                    else if (dataType == "t")
                    {
                        if (messageIdToSymbol.TryGetValue(msgId, out Tuple <string, long> symbol))
                        {                           //   0        1                 2                  3         4          5
                            // ["t", "<trade id>", <1 for buy 0 for sell>, "<price>", "<size>", <timestamp>]
                            ExchangeTrade trade = data.ParseTrade(amountKey: 4, priceKey: 3, typeKey: 2, timestampKey: 5,
                                                                  timestampType: TimestampType.UnixSeconds, idKey: 1, typeKeyIsBuyValue: "1");
                            await callback(new KeyValuePair <string, ExchangeTrade>(symbol.Item1, trade));
                        }
                    }
                    else if (dataType == "o")
                    {
                        continue;
                    }
                    else
                    {
                        continue;
                    }
                }
            }, async (_socket) =>
            {
                if (marketSymbols == null || marketSymbols.Length == 0)
                {
                    marketSymbols = (await GetMarketSymbolsAsync()).ToArray();
                }
                // subscribe to order book and trades channel for each symbol
                foreach (var sym in marketSymbols)
                {
                    await _socket.SendMessageAsync(new { command = "subscribe", channel = NormalizeMarketSymbol(sym) });
                }
            }));
        }
Esempio n. 11
0
        protected override IWebSocket OnGetTradesWebSocket(Action <KeyValuePair <string, ExchangeTrade> > callback, params string[] marketSymbols)
        {
            Dictionary <int, string> channelIdToSymbol = new Dictionary <int, string>();

            if (marketSymbols == null || marketSymbols.Length == 0)
            {
                marketSymbols = GetMarketSymbolsAsync().Sync().ToArray();
            }
            return(ConnectWebSocket("/2", (_socket, msg) => //use websocket V2 (beta, but millisecond timestamp)
            {
                JToken token = JToken.Parse(msg.ToStringFromUTF8());
                if (token is JArray array)
                {
                    if (token[1].ToStringInvariant() == "hb")
                    {
                        // heartbeat
                    }
                    else if (token.Last.Last.HasValues == false)
                    {
                        //[29654, "tu", [270343572, 1532012917722, -0.003, 7465.636738]] "te"=temp/intention to execute "tu"=confirmed and ID is definitive
                        //chan id, -- , [ID       , timestamp    , amount, price      ]]
                        if (channelIdToSymbol.TryGetValue(array[0].ConvertInvariant <int>(), out string symbol))
                        {
                            if (token[1].ToStringInvariant() == "tu")
                            {
                                ExchangeTrade trade = ParseTradeWebSocket(token.Last);
                                if (trade != null)
                                {
                                    callback(new KeyValuePair <string, ExchangeTrade>(symbol, trade));
                                }
                            }
                        }
                    }
                    else
                    {
                        //parse snapshot here if needed
                    }
                }
                else if (token["event"].ToStringInvariant() == "subscribed" && token["channel"].ToStringInvariant() == "trades")
                {
                    //{"event": "subscribed","channel": "trades","chanId": 29654,"symbol": "tBTCUSD","pair": "BTCUSD"}
                    int channelId = token["chanId"].ConvertInvariant <int>();
                    channelIdToSymbol[channelId] = token["pair"].ToStringInvariant();
                }
                return Task.CompletedTask;
            }, async(_socket) =>
            {
                foreach (var marketSymbol in marketSymbols)
                {
                    await _socket.SendMessageAsync(new { @event = "subscribe", channel = "trades", symbol = marketSymbol });
                }
            }));
        }
 private ExchangeTrade ParseTrade(JToken token)
 {
     // [{ "TradePairId":100,"Label":"LTC/BTC","Type":"Sell","Price":0.00006000, "Amount":499.99640000,"Total":0.02999978,"Timestamp": 1418297368}, ...]
     ExchangeTrade trade = new ExchangeTrade()
     {
         Timestamp = DateTimeOffset.FromUnixTimeSeconds(token["Timestamp"].ConvertInvariant<long>()).DateTime,
         Amount = token["Amount"].ConvertInvariant<decimal>(),
         Price = token["Price"].ConvertInvariant<decimal>(),
         IsBuy = token["Type"].ToStringInvariant().Equals("Buy")
     };
     return trade;
 }
Esempio n. 13
0
        protected override IDisposable OnGetTradesWebSocket(Action <KeyValuePair <string, ExchangeTrade> > callback, params string[] symbols)
        {
            if (callback == null)
            {
                return(null);
            }

            /*
             * {
             * "e": "trade",     // Event type
             * "E": 123456789,   // Event time
             * "s": "BNBBTC",    // Symbol
             * "t": 12345,       // Trade ID
             * "p": "0.001",     // Price
             * "q": "100",       // Quantity
             * "b": 88,          // Buyer order Id
             * "a": 50,          // Seller order Id
             * "T": 123456785,   // Trade time
             * "m": true,        // Is the buyer the market maker?
             * "M": true         // Ignore.
             * }
             */

            string url = GetWebSocketStreamUrlForSymbols("@trade", symbols);

            return(ConnectWebSocket(url, (msg, _socket) =>
            {
                try
                {
                    JToken token = JToken.Parse(msg.UTF8String());
                    string name = token["stream"].ToStringInvariant();
                    token = token["data"];
                    string symbol = NormalizeSymbol(name.Substring(0, name.IndexOf('@')));

                    // buy=0 -> m = true (The buyer is maker, while the seller is taker).
                    // buy=1 -> m = false(The seller is maker, while the buyer is taker).
                    ExchangeTrade trade = new ExchangeTrade
                    {
                        Amount = token["q"].ConvertInvariant <decimal>(),
                        Id = token["t"].ConvertInvariant <long>(),
                        IsBuy = !token["m"].ConvertInvariant <bool>(),
                        Price = token["p"].ConvertInvariant <decimal>(),
                        Timestamp = CryptoUtility.UnixTimeStampToDateTimeMilliseconds(token["E"].ConvertInvariant <long>())
                    };
                    callback(new KeyValuePair <string, ExchangeTrade>(symbol, trade));
                }
                catch
                {
                }
            }));
        }
Esempio n. 14
0
        protected override IWebSocket OnGetTradesWebSocket(Func <KeyValuePair <string, ExchangeTrade>, Task> callback, params string[] marketSymbols)
        {
            /*
             * request:
             * {"op": "subscribe", "args": ["spot/trade:BTC-USD"]}
             */

            return(ConnectWebSocketOkex(async(_socket) =>
            {
                marketSymbols = await AddMarketSymbolsToChannel(_socket, "spot/trade:{0}", marketSymbols);
            }, async(_socket, symbol, sArray, token) =>
            {
                ExchangeTrade trade = ParseTradeWebSocket(token);
                await callback(new KeyValuePair <string, ExchangeTrade>(symbol, trade));
            }));
        }
Esempio n. 15
0
        protected override IWebSocket OnGetTradesWebSocket(Action <KeyValuePair <string, ExchangeTrade> > callback, params string[] marketSymbols)
        {
            /*
             * request:
             * {"op": "subscribe", "args": ["swap/trade:BTC-USD-SWAP"]}
             */

            return(ConnectWebSocketOkex(async(_socket) =>
            {
                marketSymbols = await AddMarketSymbolsToChannel(_socket, "swap/trade:{0}-SWAP", marketSymbols);
            }, (_socket, symbol, sArray, token) =>
            {
                ExchangeTrade trade = ParseTradeWebSocket(token);
                callback(new KeyValuePair <string, ExchangeTrade>(symbol, trade));
                return Task.CompletedTask;
            }));
        }
Esempio n. 16
0
        private IEnumerable <ExchangeTrade> ParseTradesWebsocket(JToken token)
        {
            //{ "amount":"0.0372","price": "7509.7","tid": 153806522,"date": 1532103901,"type": "sell","trade_type": "ask"},{"amount": "0.0076", ...
            var trades = new List <ExchangeTrade>();

            foreach (var t in token)
            {
                var trade = new ExchangeTrade()
                {
                    Amount    = t["amount"].ConvertInvariant <decimal>(),
                    Price     = t["price"].ConvertInvariant <decimal>(),
                    Id        = t["tid"].ConvertInvariant <long>(),
                    Timestamp = CryptoUtility.UnixTimeStampToDateTimeSeconds(t["date"].ConvertInvariant <long>()),
                    IsBuy     = t["type"].ToStringInvariant() == "buy"
                };
                trades.Add(trade);
            }
            return(trades);
        }
Esempio n. 17
0
        protected internal override async Task OnGetHistoricalTradesAsync(Func <IEnumerable <ExchangeTrade>, bool> callback, string symbol, DateTime?startDate = null, DateTime?endDate = null)
        {
            List <ExchangeTrade> trades = new List <ExchangeTrade>();
            // TODO: Not directly supported so the best we can do is get their Max 200 and check the timestamp if necessary
            JToken result = await MakeJsonRequestAsync <JToken>("/public/getmarkethistory?market=" + symbol + "&count=200");

            foreach (JToken token in result)
            {
                ExchangeTrade trade = ParseTrade(token);
                if (startDate == null || trade.Timestamp >= startDate)
                {
                    trades.Add(trade);
                }
            }
            if (trades.Count != 0)
            {
                callback(trades);
            }
        }
Esempio n. 18
0
 protected override async Task <IWebSocket> OnGetTradesWebSocketAsync(Func <KeyValuePair <string, ExchangeTrade>, Task> callback, params string[] marketSymbols)
 {
     if (marketSymbols == null || marketSymbols.Length == 0)
     {
         marketSymbols = (await GetMarketSymbolsAsync()).ToArray();
     }
     return(await ConnectPublicWebSocketAsync("/", async (_socket, msg) =>
     {
         JToken token = JToken.Parse(msg.ToStringFromUTF8());
         if (token["type"].ToStringInvariant() == "error")
         {                 // {{ "type": "error", "message": "Failed to subscribe", "reason": "match is not a valid channel" }}
             Logger.Info(token["message"].ToStringInvariant() + ": " + token["reason"].ToStringInvariant());
             return;
         }
         if (token["type"].ToStringInvariant() != "match")
         {
             return;                                                               //the ticker channel provides the trade information as well
         }
         if (token["time"] == null)
         {
             return;
         }
         ExchangeTrade trade = ParseTradeWebSocket(token);
         string marketSymbol = token["product_id"].ToStringInvariant();
         await callback(new KeyValuePair <string, ExchangeTrade>(marketSymbol, trade));
     }, async (_socket) =>
     {
         var subscribeRequest = new
         {
             type = "subscribe",
             product_ids = marketSymbols,
             channels = new object[]
             {
                 new
                 {
                     name = "matches",
                     product_ids = marketSymbols
                 }
             }
         };
         await _socket.SendMessageAsync(subscribeRequest);
     }));
 }
Esempio n. 19
0
        private IEnumerable <ExchangeTrade> ParseTradesWebSocket(JToken token)
        {
            var trades = new List <ExchangeTrade>();

            foreach (var t in token)
            {
                var trade = new ExchangeTrade()
                {
                    Amount = t["amount"].ConvertInvariant <decimal>(),
                    // System.OverflowException: Value was either too large or too small for an Int64.
                    // Id = x["id"].ConvertInvariant<long>(),
                    IsBuy     = t["direction"].ToStringLowerInvariant().EqualsWithOption("buy"),
                    Price     = t["price"].ConvertInvariant <decimal>(),
                    Timestamp = CryptoUtility.UnixTimeStampToDateTimeMilliseconds(t["ts"].ConvertInvariant <long>())
                };
                trades.Add(trade);
            }

            return(trades);
        }
Esempio n. 20
0
        private IEnumerable <ExchangeTrade> ParseTradesWebSocket(JToken token)
        {
            var trades = new List <ExchangeTrade>();

            foreach (var t in token)
            {
                var ts    = TimeSpan.Parse(t[3].ToStringInvariant());
                var dt    = DateTime.Today.Add(ts).ToUniversalTime();
                var trade = new ExchangeTrade()
                {
                    Id        = t[0].ConvertInvariant <long>(),
                    Price     = t[1].ConvertInvariant <decimal>(),
                    Amount    = t[2].ConvertInvariant <decimal>(),
                    Timestamp = dt,
                    IsBuy     = t[4].ToStringInvariant().EqualsWithOption("bid"),
                };
                trades.Add(trade);
            }

            return(trades);
        }
        protected override async Task OnGetHistoricalTradesAsync(Func <IEnumerable <ExchangeTrade>, bool> callback, string symbol, DateTime?sinceDateTime = null)
        {
            List <ExchangeTrade> trades = new List <ExchangeTrade>();
            long?  lastTradeId          = null;
            JToken obj;
            bool   running = true;

            // Abucoins uses a page curser based on trade_id to iterate history. Keep paginating until startDate is reached or we run out of data
            while (running)
            {
                obj = await MakeJsonRequestAsync <JToken>("/products/" + symbol + "/trades" + (lastTradeId == null ? string.Empty : "?before=" + lastTradeId));

                if ((running = obj.HasValues))
                {
                    lastTradeId = obj.First()["trade_id"].ConvertInvariant <long>();
                    foreach (JToken token in obj)
                    {
                        ExchangeTrade trade = ParseExchangeTrade(token);
                        if (sinceDateTime == null || trade.Timestamp >= sinceDateTime)
                        {
                            trades.Add(trade);
                        }
                        else
                        {
                            // sinceDateTime has been passed, no more paging
                            running = false;
                            break;
                        }
                    }
                }
                if (trades.Count != 0 && !callback(trades.OrderBy(t => t.Timestamp)))
                {
                    return;
                }
                trades.Clear();
                await Task.Delay(1000);
            }
        }
 protected override IWebSocket OnGetTradesWebSocket(Func <KeyValuePair <string, ExchangeTrade>, Task> callback, params string[] marketSymbols)
 {
     if (marketSymbols == null || marketSymbols.Length == 0)
     {
         marketSymbols = GetMarketSymbolsAsync().Sync().ToArray();
     }
     return(ConnectWebSocket("/", async(_socket, msg) =>
     {
         JToken token = JToken.Parse(msg.ToStringFromUTF8());
         if (token["type"].ToStringInvariant() != "ticker")
         {
             return;                                                //the ticker channel provides the trade information as well
         }
         if (token["time"] == null)
         {
             return;
         }
         ExchangeTrade trade = ParseTradeWebSocket(token);
         string marketSymbol = token["product_id"].ToStringInvariant();
         await callback(new KeyValuePair <string, ExchangeTrade>(marketSymbol, trade));
     }, async(_socket) =>
     {
         var subscribeRequest = new
         {
             type = "subscribe",
             product_ids = marketSymbols,
             channels = new object[]
             {
                 new
                 {
                     name = "ticker",
                     product_ids = marketSymbols
                 }
             }
         };
         await _socket.SendMessageAsync(subscribeRequest);
     }));
 }