diff --git a/Binance.API.Csharp.Client.Domain/Abstract/ApiClientAbstract.cs b/Binance.API.Csharp.Client.Domain/Abstract/ApiClientAbstract.cs index 03868dd..a0c0c60 100644 --- a/Binance.API.Csharp.Client.Domain/Abstract/ApiClientAbstract.cs +++ b/Binance.API.Csharp.Client.Domain/Abstract/ApiClientAbstract.cs @@ -50,7 +50,7 @@ public abstract class ApiClientAbstract /// Key used to authenticate within the API. /// API secret used to signed API calls. /// API based url. - public ApiClientAbstract(string apiKey, string apiSecret, string apiUrl = @"https://www.binance.com", string webSocketEndpoint = @"wss://stream.binance.com:9443/ws/", bool addDefaultHeaders = true) + public ApiClientAbstract(string apiKey, string apiSecret, string apiUrl = @"https://us.binance.com", string webSocketEndpoint = @"wss://stream.binance.com:9443/ws/", bool addDefaultHeaders = true) { _apiUrl = apiUrl; _apiKey = apiKey; diff --git a/Binance.API.Csharp.Client.Domain/Interfaces/IApiClient.cs b/Binance.API.Csharp.Client.Domain/Interfaces/IApiClient.cs index 65dc01f..b9461db 100644 --- a/Binance.API.Csharp.Client.Domain/Interfaces/IApiClient.cs +++ b/Binance.API.Csharp.Client.Domain/Interfaces/IApiClient.cs @@ -1,6 +1,8 @@ using Binance.API.Csharp.Client.Models.Enums; using Binance.API.Csharp.Client.Models.WebSocket; +using System; using System.Threading.Tasks; +using WebSocketSharp; using static Binance.API.Csharp.Client.Domain.Abstract.ApiClientAbstract; namespace Binance.API.Csharp.Client.Domain.Interfaces @@ -18,22 +20,30 @@ public interface IApiClient /// Task CallAsync(ApiMethod method, string endpoint, bool isSigned = false, string parameters = null); - /// - /// Connects to a Websocket endpoint. - /// - /// Type used to parsed the response message. - /// Paremeters to send to the Websocket. - /// Deletage to callback after receive a message. - /// Specifies if needs to use a custom parser for the response message. - void ConnectToWebSocket(string parameters, MessageHandler messageDelegate, bool useCustomParser = false); + /// + /// Connects to a Websocket endpoint. + /// + /// Type used to parsed the response message. + /// Paremeters to send to the Websocket. + /// Deletage to callback after receive a message. + /// Specifies if needs to use a custom parser for the response message. + WebSocket ConnectToWebSocket( + string parameters, + MessageHandler messageDelegate, + Action onClose, + bool useCustomParser = false); - /// - /// Connects to a UserData Websocket endpoint. - /// - /// Paremeters to send to the Websocket. - /// Deletage to callback after receive a account info message. - /// Deletage to callback after receive a trade message. - /// Deletage to callback after receive a order message. - void ConnectToUserDataWebSocket(string parameters, MessageHandler accountHandler, MessageHandler tradeHandler, MessageHandler orderHandler); + /// + /// Connects to a UserData Websocket endpoint. + /// + /// Paremeters to send to the Websocket. + /// Deletage to callback after receive a account info message. + /// Deletage to callback after receive a trade message. + /// Deletage to callback after receive a order message. + WebSocket ConnectToUserDataWebSocket(string parameters, + MessageHandler accountHandler, + MessageHandler tradeHandler, + MessageHandler orderHandler, + Action onClose); } } \ No newline at end of file diff --git a/Binance.API.Csharp.Client.Domain/Interfaces/IBinanceClient.cs b/Binance.API.Csharp.Client.Domain/Interfaces/IBinanceClient.cs index 5312215..a33f67c 100644 --- a/Binance.API.Csharp.Client.Domain/Interfaces/IBinanceClient.cs +++ b/Binance.API.Csharp.Client.Domain/Interfaces/IBinanceClient.cs @@ -7,6 +7,7 @@ using System; using System.Collections.Generic; using System.Threading.Tasks; +using WebSocketSharp; using static Binance.API.Csharp.Client.Domain.Abstract.ApiClientAbstract; namespace Binance.API.Csharp.Client.Domain.Interfaces @@ -85,8 +86,7 @@ public interface IBinanceClient /// Indicates how long an order will remain active before it is executed or expires. /// Specific number of milliseconds the request is valid for. /// - Task PostNewOrder(string symbol, decimal quantity, decimal price, OrderSide side, OrderType orderType = OrderType.LIMIT, TimeInForce timeInForce = TimeInForce.GTC, decimal icebergQty = 0m, long recvWindow = 6000000); - + Task PostNewOrder(string symbol, decimal quantity, decimal price, OrderSide side, OrderType orderType = OrderType.LIMIT, TimeInForce timeInForce = TimeInForce.GTC, string clientOrderId = null, decimal icebergQty = 0m, long recvWindow = 5000); /// /// Test new order creation and signature/recvWindow long. Creates and validates a new order but does not send it into the matching engine. /// @@ -98,8 +98,7 @@ public interface IBinanceClient /// Indicates how long an order will remain active before it is executed or expires. /// Specific number of milliseconds the request is valid for. /// - Task PostNewOrderTest(string symbol, decimal quantity, decimal price, OrderSide side, OrderType orderType = OrderType.LIMIT, TimeInForce timeInForce = TimeInForce.GTC, decimal icebergQty = 0m, long recvWindow = 6000000); - + Task PostNewOrderTest(string symbol, decimal quantity, decimal price, OrderSide side, OrderType orderType = OrderType.LIMIT, TimeInForce timeInForce = TimeInForce.GTC, string clientOrderId = null, decimal icebergQty = 0m, long recvWindow = 5000); /// /// Check an order's status. /// @@ -151,7 +150,7 @@ public interface IBinanceClient /// Ticker symbol. /// Specific number of milliseconds the request is valid for. /// - Task> GetTradeList(string symbol, long recvWindow = 6000000); + Task> GetTradeList(string symbol, int limit, long fromId, long recvWindow = 5000); /// /// Submit a withdraw request. @@ -210,27 +209,31 @@ public interface IBinanceClient #endregion #region WebSocket + /// /// Listen to the Depth endpoint. /// /// Ticker symbol. + /// /// Handler to be used when a message is received. - void ListenDepthEndpoint(string symbol, MessageHandler messageHandler); + WebSocket ListenDepthEndpoint(string symbol, MessageHandler messageHandler, Action onClose); /// /// Listen to the Kline endpoint. /// /// Ticker symbol. /// Time interval to retreive. + /// /// Handler to be used when a message is received. - void ListenKlineEndpoint(string symbol, TimeInterval interval, MessageHandler messageHandler); + WebSocket ListenKlineEndpoint(string symbol, TimeInterval interval, MessageHandler messageHandler, Action onClose); /// /// Listen to the Trades endpoint. /// /// Ticker symbol. + /// /// Handler to be used when a message is received. - void ListenTradeEndpoint(string symbol, MessageHandler messageHandler); + WebSocket ListenTradeEndpoint(string symbol, MessageHandler messageHandler, Action onClose); /// /// Listen to the User Data endpoint. @@ -239,7 +242,7 @@ public interface IBinanceClient /// Handler to be used when a trade message is received. /// Handler to be used when a order message is received. /// - string ListenUserDataEndpoint(MessageHandler accountInfoHandler, MessageHandler tradesHandler, MessageHandler ordersHandler); + string ListenUserDataEndpoint(MessageHandler accountInfoHandler, MessageHandler tradesHandler, MessageHandler ordersHandler, Action onClose); #endregion } } diff --git a/Binance.API.Csharp.Client.Models/Account/Trade.cs b/Binance.API.Csharp.Client.Models/Account/Trade.cs index f01c9fd..bec3ddb 100644 --- a/Binance.API.Csharp.Client.Models/Account/Trade.cs +++ b/Binance.API.Csharp.Client.Models/Account/Trade.cs @@ -6,6 +6,8 @@ public class Trade { [JsonProperty("id")] public int Id { get; set; } + [JsonProperty("orderId")] + public int OrderId { get; set; } [JsonProperty("price")] public decimal Price { get; set; } [JsonProperty("qty")] diff --git a/Binance.API.Csharp.Client.Models/WebSocket/AccountUpdatedMessage.cs b/Binance.API.Csharp.Client.Models/WebSocket/AccountUpdatedMessage.cs index ba0997b..1b7d0fc 100644 --- a/Binance.API.Csharp.Client.Models/WebSocket/AccountUpdatedMessage.cs +++ b/Binance.API.Csharp.Client.Models/WebSocket/AccountUpdatedMessage.cs @@ -17,12 +17,14 @@ public class AccountUpdatedMessage public int BuyerCommission { get; set; } [JsonProperty("s")] public int SellerCommission { get; set; } - [JsonProperty("t")] + [JsonProperty("T")] public bool CanTrade { get; set; } - [JsonProperty("w")] + [JsonProperty("W")] public bool CanWithdraw { get; set; } - [JsonProperty("d")] + [JsonProperty("D")] public bool CanDeposit { get; set; } + [JsonProperty("u")] + public long TimeLastAccountUpdate { get; set; } [JsonProperty("B")] public IEnumerable Balances { get; set; } } diff --git a/Binance.API.Csharp.Client.Models/WebSocket/DepthMessage.cs b/Binance.API.Csharp.Client.Models/WebSocket/DepthMessage.cs index 4598ad4..3f4c8a7 100644 --- a/Binance.API.Csharp.Client.Models/WebSocket/DepthMessage.cs +++ b/Binance.API.Csharp.Client.Models/WebSocket/DepthMessage.cs @@ -12,4 +12,10 @@ public class DepthMessage public IEnumerable Bids { get; set; } public IEnumerable Asks { get; set; } } + public class DepthPartialMessage + { + public int UpdateId { get; set; } + public IEnumerable Bids { get; set; } + public IEnumerable Asks { get; set; } + } } diff --git a/Binance.API.Csharp.Client.Models/WebSocket/OrderOrTradeUpdatedMessage.cs b/Binance.API.Csharp.Client.Models/WebSocket/OrderOrTradeUpdatedMessage.cs index dabe14d..0a3a450 100644 --- a/Binance.API.Csharp.Client.Models/WebSocket/OrderOrTradeUpdatedMessage.cs +++ b/Binance.API.Csharp.Client.Models/WebSocket/OrderOrTradeUpdatedMessage.cs @@ -11,7 +11,7 @@ public class OrderOrTradeUpdatedMessage [JsonProperty("s")] public string Symbol { get; set; } [JsonProperty("c")] - public string NewClientOrderId { get; set; } + public string NewClientOrderId { get; set; } [JsonProperty("S")] public string Side { get; set; } [JsonProperty("o")] @@ -22,6 +22,15 @@ public class OrderOrTradeUpdatedMessage public decimal OriginalQuantity { get; set; } [JsonProperty("p")] public decimal Price { get; set; } + [JsonProperty("P")] + public decimal StopPrice { get; set; } + [JsonProperty("F")] + public decimal IcebergQuantity { get; set; } + [JsonProperty("g")] + public int g { get; set; } + + [JsonProperty("C")] + public string OriginalClientOrderId { get; set; } [JsonProperty("x")] public string ExecutionType { get; set; } [JsonProperty("X")] @@ -29,7 +38,7 @@ public class OrderOrTradeUpdatedMessage [JsonProperty("r")] public string RejectReason { get; set; } [JsonProperty("i")] - public int Orderid { get; set; } + public int OrderId { get; set; } [JsonProperty("l")] public decimal LastFilledTradeQuantity { get; set; } [JsonProperty("z")] @@ -44,7 +53,22 @@ public class OrderOrTradeUpdatedMessage public long TradeTime { get; set; } [JsonProperty("t")] public int TradeId { get; set; } + [JsonProperty("I")] + public int I { get; set; } + [JsonProperty("w")] + public bool IsOrderWorking { get; set; } [JsonProperty("m")] public bool BuyerIsMaker { get; set; } + [JsonProperty("M")] + public bool M { get; set; } + [JsonProperty("O")] + public int O { get; set; } + [JsonProperty("Z")] + public decimal Z { get; set; } + + public override string ToString() + { + return JsonConvert.SerializeObject(this, Formatting.Indented); + } } } diff --git a/Binance.API.Csharp.Client.Test/Binance.API.Csharp.Client.Test.csproj b/Binance.API.Csharp.Client.Test/Binance.API.Csharp.Client.Test.csproj index 4757297..0b646ed 100644 --- a/Binance.API.Csharp.Client.Test/Binance.API.Csharp.Client.Test.csproj +++ b/Binance.API.Csharp.Client.Test/Binance.API.Csharp.Client.Test.csproj @@ -51,6 +51,9 @@ + + ..\packages\WebSocketSharp-NonPreRelease.1.0.0\lib\net35\websocket-sharp.dll + diff --git a/Binance.API.Csharp.Client.Test/BinanceTest.cs b/Binance.API.Csharp.Client.Test/BinanceTest.cs index 03d6216..350fbac 100644 --- a/Binance.API.Csharp.Client.Test/BinanceTest.cs +++ b/Binance.API.Csharp.Client.Test/BinanceTest.cs @@ -23,6 +23,13 @@ public void GetServerTime() { var serverTime = binanceClient.GetServerTime().Result; } + + [TestMethod] + public void ExchangeInfo() + { + var rules = binanceClient.GetTradingRulesAsync().Result; + Thread.Sleep(1000); + } #endregion #region Market Data diff --git a/Binance.API.Csharp.Client.Test/packages.config b/Binance.API.Csharp.Client.Test/packages.config index 213bc4b..7a4429d 100644 --- a/Binance.API.Csharp.Client.Test/packages.config +++ b/Binance.API.Csharp.Client.Test/packages.config @@ -3,4 +3,5 @@ + \ No newline at end of file diff --git a/Binance.API.Csharp.Client/ApiClient.cs b/Binance.API.Csharp.Client/ApiClient.cs index 7601abe..856a8f2 100644 --- a/Binance.API.Csharp.Client/ApiClient.cs +++ b/Binance.API.Csharp.Client/ApiClient.cs @@ -22,10 +22,11 @@ public class ApiClient : ApiClientAbstract, IApiClient /// Key used to authenticate within the API. /// API secret used to signed API calls. /// API base url. - public ApiClient(string apiKey, string apiSecret, string apiUrl = @"https://www.binance.com", string webSocketEndpoint = @"wss://stream.binance.com:9443/ws/", bool addDefaultHeaders = true) : base(apiKey, apiSecret, apiUrl, webSocketEndpoint, addDefaultHeaders) + public ApiClient(string apiKey, string apiSecret, string apiUrl = @"https://us.binance.com", string webSocketEndpoint = @"wss://stream.binance.com:9443/ws/", bool addDefaultHeaders = true) : base(apiKey, apiSecret, apiUrl, webSocketEndpoint, addDefaultHeaders) { } + /// /// Calls API Methods. /// @@ -42,7 +43,7 @@ public async Task CallAsync(ApiMethod method, string endpoint, bool isSign if (isSigned) { // Joining provided parameters - parameters += (!string.IsNullOrWhiteSpace(parameters) ? "×tamp=" : "timestamp=") + Utilities.GenerateTimeStamp(DateTime.Now.ToUniversalTime()); + parameters += (!string.IsNullOrWhiteSpace(parameters) ? "×tamp=" : "timestamp=") + (Utilities.GenerateTimeStamp(DateTime.Now.ToUniversalTime())); // Creating request signature var signature = Utilities.GenerateSignature(_apiSecret, parameters); @@ -86,7 +87,6 @@ public async Task CallAsync(ApiMethod method, string endpoint, bool isSign } catch { } } - throw new Exception(string.Format("Api Error Code: {0} Message: {1}", eCode, eMsg)); } @@ -97,7 +97,7 @@ public async Task CallAsync(ApiMethod method, string endpoint, bool isSign /// Paremeters to send to the Websocket. /// Deletage to callback after receive a message. /// Specifies if needs to use a custom parser for the response message. - public void ConnectToWebSocket(string parameters, MessageHandler messageHandler, bool useCustomParser = false) + public WebSocket ConnectToWebSocket(string parameters, MessageHandler messageHandler, Action onClose = null, bool useCustomParser = false) { var finalEndpoint = _webSocketEndpoint + parameters; @@ -105,12 +105,19 @@ public void ConnectToWebSocket(string parameters, MessageHandler messageHa ws.OnMessage += (sender, e) => { - dynamic eventData; + dynamic eventData = null; if (useCustomParser) { var customParser = new CustomParser(); - eventData = customParser.GetParsedDepthMessage(JsonConvert.DeserializeObject(e.Data)); + var datum = JsonConvert.DeserializeObject(e.Data); + if (datum is JObject jobject) + { + if (jobject["lastUpdateId"] != null) + eventData = customParser.GetParsedDepthPartialMessage(jobject); + else + eventData = customParser.GetParsedDepthMessage(jobject); + } } else { @@ -123,15 +130,17 @@ public void ConnectToWebSocket(string parameters, MessageHandler messageHa ws.OnClose += (sender, e) => { _openSockets.Remove(ws); + onClose?.Invoke(e); }; ws.OnError += (sender, e) => - { - _openSockets.Remove(ws); + { + if (ws.ReadyState != WebSocketState.Open) + _openSockets.Remove(ws); }; - + ws.OnOpen += (s, e) => _openSockets.Add(ws); ws.Connect(); - _openSockets.Add(ws); + return ws; } /// @@ -141,7 +150,7 @@ public void ConnectToWebSocket(string parameters, MessageHandler messageHa /// Deletage to callback after receive a account info message. /// Deletage to callback after receive a trade message. /// Deletage to callback after receive a order message. - public void ConnectToUserDataWebSocket(string parameters, MessageHandler accountHandler, MessageHandler tradeHandler, MessageHandler orderHandler) + public WebSocket ConnectToUserDataWebSocket(string parameters, MessageHandler accountHandler, MessageHandler tradeHandler, MessageHandler orderHandler, Action onClose = null) { var finalEndpoint = _webSocketEndpoint + parameters; @@ -151,7 +160,7 @@ public void ConnectToUserDataWebSocket(string parameters, MessageHandler(e.Data); - switch (eventData.e) + switch ((string)eventData.e.Value) { case "outboundAccountInfo": accountHandler(JsonConvert.DeserializeObject(e.Data)); @@ -174,15 +183,17 @@ public void ConnectToUserDataWebSocket(string parameters, MessageHandler { _openSockets.Remove(ws); + onClose?.Invoke(e); }; ws.OnError += (sender, e) => { - _openSockets.Remove(ws); + if (ws.ReadyState != WebSocketState.Open) + _openSockets.Remove(ws); }; - + ws.OnOpen += (s, e) => _openSockets.Add(ws); ws.Connect(); - _openSockets.Add(ws); + return ws; } } } diff --git a/Binance.API.Csharp.Client/Binance.API.Csharp.Client.csproj b/Binance.API.Csharp.Client/Binance.API.Csharp.Client.csproj index d0d89bd..4088a05 100644 --- a/Binance.API.Csharp.Client/Binance.API.Csharp.Client.csproj +++ b/Binance.API.Csharp.Client/Binance.API.Csharp.Client.csproj @@ -72,5 +72,6 @@ Binance.API.Csharp.Client.Models + \ No newline at end of file diff --git a/Binance.API.Csharp.Client/BinanceClient.cs b/Binance.API.Csharp.Client/BinanceClient.cs index c37503a..b204c71 100644 --- a/Binance.API.Csharp.Client/BinanceClient.cs +++ b/Binance.API.Csharp.Client/BinanceClient.cs @@ -12,6 +12,7 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using WebSocketSharp; namespace Binance.API.Csharp.Client { @@ -58,6 +59,12 @@ private void ValidateOrderValue(string symbol, OrderType orderType, decimal unit } } + //quantity needs to be conform to this regex "^([0-9]{1,20})(\.[0-9]{1,20})?$" -> 20 decimal digits + if (quantity != decimal.Round(quantity, 20)) + { + throw new ArgumentException("Quantity parameter should have no mora than 20 decimal digits."); + } + // Validating Trading Rules if (_tradingRules != null) { @@ -88,11 +95,22 @@ private void ValidateOrderValue(string symbol, OrderType orderType, decimal unit } } - private void LoadTradingRules() + public void LoadTradingRules() { var apiClient = new ApiClient("", "", EndPoints.TradingRules, addDefaultHeaders: false); _tradingRules = apiClient.CallAsync(ApiMethod.GET, "").Result; } + + public async Task GetTradingRulesAsync() + { + if (_tradingRules != null) + return _tradingRules; + var result = await _apiClient.CallAsync(ApiMethod.GET, EndPoints.TradingRules, false); + + _tradingRules = result; + + return _tradingRules; + } #endregion #region General @@ -243,16 +261,22 @@ public async Task> GetOrderBookTicker() /// Indicates how long an order will remain active before it is executed or expires. /// Specific number of milliseconds the request is valid for. /// - public async Task PostNewOrder(string symbol, decimal quantity, decimal price, OrderSide side, OrderType orderType = OrderType.LIMIT, TimeInForce timeInForce = TimeInForce.GTC, decimal icebergQty = 0m, long recvWindow = 5000) + public async Task PostNewOrder(string symbol, decimal quantity, decimal price, OrderSide side, OrderType orderType = OrderType.LIMIT, TimeInForce timeInForce = TimeInForce.GTC, string clientOrderId = null, decimal icebergQty = 0m, long recvWindow = 5000) { //Validates that the order is valid. ValidateOrderValue(symbol, orderType, price, quantity, icebergQty); - - var args = $"symbol={symbol.ToUpper()}&side={side}&type={orderType}&quantity={quantity}" + //quantity needs to be conform to this regex ^([0-9]{1,20})(\.[0-9]{1,20})?$ -> 20 decimal digits + //remove trailing zeros from quantity and price + quantity = quantity / 1.000000000000000000000000000000m; + price = price / 1.000000000000000000000000000000m; + var args = $"symbol={symbol.ToUpper()}&side={side}&type={orderType}" + + $"&quantity={quantity}" + (orderType == OrderType.LIMIT ? $"&timeInForce={timeInForce}" : "") + (orderType == OrderType.LIMIT ? $"&price={price}" : "") + + (clientOrderId != null ? $"&newClientOrderId={clientOrderId}" : "") + (icebergQty > 0m ? $"&icebergQty={icebergQty}" : "") + $"&recvWindow={recvWindow}"; + var result = await _apiClient.CallAsync(ApiMethod.POST, EndPoints.NewOrder, true, args); return result; @@ -269,16 +293,21 @@ public async Task PostNewOrder(string symbol, decimal quantity, decima /// Indicates how long an order will remain active before it is executed or expires. /// Specific number of milliseconds the request is valid for. /// - public async Task PostNewOrderTest(string symbol, decimal quantity, decimal price, OrderSide side, OrderType orderType = OrderType.LIMIT, TimeInForce timeInForce = TimeInForce.GTC, decimal icebergQty = 0m, long recvWindow = 5000) + public async Task PostNewOrderTest(string symbol, decimal quantity, decimal price, OrderSide side, OrderType orderType = OrderType.LIMIT, TimeInForce timeInForce = TimeInForce.GTC, string clientOrderId = null, decimal icebergQty = 0m, long recvWindow = 5000) { //Validates that the order is valid. ValidateOrderValue(symbol, orderType, price, quantity, icebergQty); + //quantity needs to be conform to this regex ^([0-9]{1,20})(\.[0-9]{1,20})?$ -> 20 decimal digits + quantity = quantity / 1.000000000000000000000000000000m; + price = price / 1.000000000000000000000000000000m; + var args = $"symbol={symbol.ToUpper()}&side={side}&type={orderType}" + + $"&quantity={quantity}" + + (orderType == OrderType.LIMIT ? $"&timeInForce={timeInForce}" : "") + + (orderType == OrderType.LIMIT ? $"&price={price}" : "") + + (clientOrderId != null ? $"&newClientOrderId ={clientOrderId}" : "") + + (icebergQty > 0m ? $"&icebergQty={icebergQty}" : "") + + $"&recvWindow={recvWindow}"; - var args = $"symbol={symbol.ToUpper()}&side={side}&type={orderType}&quantity={quantity}" - + (orderType == OrderType.LIMIT ? $"&timeInForce={timeInForce}" : "") - + (orderType == OrderType.LIMIT ? $"&price={price}" : "") - + (icebergQty > 0m ? $"&icebergQty={icebergQty}" : "") - + $"&recvWindow={recvWindow}"; var result = await _apiClient.CallAsync(ApiMethod.POST, EndPoints.NewOrderTest, true, args); return result; @@ -360,14 +389,12 @@ public async Task CancelOrder(string symbol, long? orderId = null /// Ticker symbol. /// Specific number of milliseconds the request is valid for. /// - public async Task> GetCurrentOpenOrders(string symbol, long recvWindow = 5000) + public async Task> GetCurrentOpenOrders(string symbol = null, long recvWindow = 5000) { - if (string.IsNullOrWhiteSpace(symbol)) - { - throw new ArgumentException("symbol cannot be empty. ", "symbol"); - } - var result = await _apiClient.CallAsync>(ApiMethod.GET, EndPoints.CurrentOpenOrders, true, $"symbol={symbol.ToUpper()}&recvWindow={recvWindow}"); + var args = $"recvWindow={recvWindow}" + + (!string.IsNullOrEmpty(symbol) ? $"&symbol={symbol.ToUpper()}" : ""); + var result = await _apiClient.CallAsync>(ApiMethod.GET, EndPoints.CurrentOpenOrders, true, args); return result; } @@ -410,18 +437,34 @@ public async Task GetAccountInfo(long recvWindow = 5000) /// Ticker symbol. /// Specific number of milliseconds the request is valid for. /// - public async Task> GetTradeList(string symbol, long recvWindow = 5000) + public async Task> GetTradeList(string symbol, int limit, long fromId, long recvWindow = 5000) { if (string.IsNullOrWhiteSpace(symbol)) { throw new ArgumentException("symbol cannot be empty. ", "symbol"); } - - var result = await _apiClient.CallAsync>(ApiMethod.GET, EndPoints.TradeList, true, $"symbol={symbol.ToUpper()}&recvWindow={recvWindow}"); + var para = $"symbol={symbol.ToUpper()}&limit={limit}&fromId={fromId}&recvWindow={recvWindow}"; + var result = await _apiClient.CallAsync>(ApiMethod.GET, EndPoints.TradeList, true, para); return result; } + /// + /// Get trades for a specific account and symbol. + /// + /// Ticker symbol. + /// Specific number of milliseconds the request is valid for. + /// + public async Task> GetTradeList(string symbol, int limit, long recvWindow = 5000) + { + if (string.IsNullOrWhiteSpace(symbol)) + { + throw new ArgumentException("symbol cannot be empty. ", "symbol"); + } + var para = $"symbol={symbol.ToUpper()}&limit={limit}&recvWindow={recvWindow}"; + var result = await _apiClient.CallAsync>(ApiMethod.GET, EndPoints.TradeList, true, para); + return result; + } /// /// Submit a withdraw request. /// @@ -558,12 +601,13 @@ public async Task CloseUserStream(string listenKey) #endregion #region Web Socket Client + /// /// Listen to the Depth endpoint. /// /// Ticker symbol. /// Handler to be used when a message is received. - public void ListenDepthEndpoint(string symbol, ApiClientAbstract.MessageHandler depthHandler) + public WebSocket ListenDepthEndpoint(string symbol, ApiClientAbstract.MessageHandler depthHandler, Action onClose = null) { if (string.IsNullOrWhiteSpace(symbol)) { @@ -571,7 +615,19 @@ public void ListenDepthEndpoint(string symbol, ApiClientAbstract.MessageHandler< } var param = symbol + "@depth"; - _apiClient.ConnectToWebSocket(param, depthHandler, true); + return _apiClient.ConnectToWebSocket(param, depthHandler, onClose, true); + } + + public WebSocket ListenPartialDepthEndPoint(string symbol, int levels, ApiClientAbstract.MessageHandler depthHandler, Action onClose = null) + { + if (string.IsNullOrWhiteSpace(symbol)) + { + throw new ArgumentException("symbol cannot be empty. ", "symbol"); + } + if (levels != 5 && levels != 10 && levels != 20) + throw new ArgumentException("Valid values for level are 5,10 or 20"); + var param = symbol.ToLower() + "@depth" + levels; + return _apiClient.ConnectToWebSocket(param, depthHandler, onClose, true); } /// @@ -580,7 +636,7 @@ public void ListenDepthEndpoint(string symbol, ApiClientAbstract.MessageHandler< /// Ticker symbol. /// Time interval to retreive. /// Handler to be used when a message is received. - public void ListenKlineEndpoint(string symbol, TimeInterval interval, ApiClientAbstract.MessageHandler klineHandler) + public WebSocket ListenKlineEndpoint(string symbol, TimeInterval interval, ApiClientAbstract.MessageHandler klineHandler, Action onClose = null) { if (string.IsNullOrWhiteSpace(symbol)) { @@ -588,7 +644,7 @@ public void ListenKlineEndpoint(string symbol, TimeInterval interval, ApiClientA } var param = symbol + $"@kline_{interval.GetDescription()}"; - _apiClient.ConnectToWebSocket(param, klineHandler); + return _apiClient.ConnectToWebSocket(param, klineHandler, onClose); } /// @@ -596,7 +652,7 @@ public void ListenKlineEndpoint(string symbol, TimeInterval interval, ApiClientA /// /// Ticker symbol. /// Handler to be used when a message is received. - public void ListenTradeEndpoint(string symbol, ApiClientAbstract.MessageHandler tradeHandler) + public WebSocket ListenTradeEndpoint(string symbol, ApiClientAbstract.MessageHandler tradeHandler, Action onClose = null) { if (string.IsNullOrWhiteSpace(symbol)) { @@ -604,7 +660,7 @@ public void ListenTradeEndpoint(string symbol, ApiClientAbstract.MessageHandler< } var param = symbol + "@aggTrade"; - _apiClient.ConnectToWebSocket(param, tradeHandler); + return _apiClient.ConnectToWebSocket(param, tradeHandler, onClose); } /// @@ -614,11 +670,11 @@ public void ListenTradeEndpoint(string symbol, ApiClientAbstract.MessageHandler< /// Handler to be used when a trade message is received. /// Handler to be used when a order message is received. /// - public string ListenUserDataEndpoint(ApiClientAbstract.MessageHandler accountInfoHandler, ApiClientAbstract.MessageHandler tradesHandler, ApiClientAbstract.MessageHandler ordersHandler) + public string ListenUserDataEndpoint(ApiClientAbstract.MessageHandler accountInfoHandler, ApiClientAbstract.MessageHandler tradesHandler, ApiClientAbstract.MessageHandler ordersHandler, Action onClose = null) { var listenKey = StartUserStream().Result.ListenKey; - _apiClient.ConnectToUserDataWebSocket(listenKey, accountInfoHandler, tradesHandler, ordersHandler); + _apiClient.ConnectToUserDataWebSocket(listenKey, accountInfoHandler, tradesHandler, ordersHandler, onClose); return listenKey; } diff --git a/Binance.API.Csharp.Client/Utils/CustomParser.cs b/Binance.API.Csharp.Client/Utils/CustomParser.cs index fb96404..f32e577 100644 --- a/Binance.API.Csharp.Client/Utils/CustomParser.cs +++ b/Binance.API.Csharp.Client/Utils/CustomParser.cs @@ -2,6 +2,7 @@ using Binance.API.Csharp.Client.Models.WebSocket; using Newtonsoft.Json.Linq; using System.Collections.Generic; +using System.Globalization; using System.Linq; namespace Binance.API.Csharp.Client.Utils @@ -28,12 +29,20 @@ public OrderBook GetParsedOrderBook(dynamic orderBookData) foreach (JToken item in ((JArray)orderBookData.bids).ToArray()) { - bids.Add(new OrderBookOffer() { Price = decimal.Parse(item[0].ToString()), Quantity = decimal.Parse(item[1].ToString()) }); + bids.Add(new OrderBookOffer + { + Price = decimal.Parse(item[0].ToString(), CultureInfo.InvariantCulture), + Quantity = decimal.Parse(item[1].ToString(), CultureInfo.InvariantCulture) + }); } foreach (JToken item in ((JArray)orderBookData.asks).ToArray()) { - asks.Add(new OrderBookOffer() { Price = decimal.Parse(item[0].ToString()), Quantity = decimal.Parse(item[1].ToString()) }); + asks.Add(new OrderBookOffer + { + Price = decimal.Parse(item[0].ToString(), CultureInfo.InvariantCulture), + Quantity = decimal.Parse(item[1].ToString(), CultureInfo.InvariantCulture) + }); } result.Bids = bids; @@ -56,16 +65,16 @@ public IEnumerable GetParsedCandlestick(dynamic candlestickData) result.Add(new Candlestick() { OpenTime = long.Parse(item[0].ToString()), - Open = decimal.Parse(item[1].ToString()), - High = decimal.Parse(item[2].ToString()), - Low = decimal.Parse(item[3].ToString()), - Close = decimal.Parse(item[4].ToString()), - Volume = decimal.Parse(item[5].ToString()), + Open = decimal.Parse(item[1].ToString(), CultureInfo.InvariantCulture), + High = decimal.Parse(item[2].ToString(), CultureInfo.InvariantCulture), + Low = decimal.Parse(item[3].ToString(), CultureInfo.InvariantCulture), + Close = decimal.Parse(item[4].ToString(), CultureInfo.InvariantCulture), + Volume = decimal.Parse(item[5].ToString(), CultureInfo.InvariantCulture), CloseTime = long.Parse(item[6].ToString()), - QuoteAssetVolume = decimal.Parse(item[7].ToString()), + QuoteAssetVolume = decimal.Parse(item[7].ToString(), CultureInfo.InvariantCulture), NumberOfTrades = int.Parse(item[8].ToString()), - TakerBuyBaseAssetVolume = decimal.Parse(item[9].ToString()), - TakerBuyQuoteAssetVolume = decimal.Parse(item[10].ToString()) + TakerBuyBaseAssetVolume = decimal.Parse(item[9].ToString(), CultureInfo.InvariantCulture), + TakerBuyQuoteAssetVolume = decimal.Parse(item[10].ToString(), CultureInfo.InvariantCulture) }); } @@ -87,12 +96,54 @@ public DepthMessage GetParsedDepthMessage(dynamic messageData) foreach (JToken item in ((JArray)messageData.b).ToArray()) { - bids.Add(new OrderBookOffer() { Price = decimal.Parse(item[0].ToString()), Quantity = decimal.Parse(item[1].ToString()) }); + bids.Add(new OrderBookOffer + { + Price = decimal.Parse(item[0].ToString(), CultureInfo.InvariantCulture), + Quantity = decimal.Parse(item[1].ToString(), CultureInfo.InvariantCulture) + }); } foreach (JToken item in ((JArray)messageData.a).ToArray()) { - asks.Add(new OrderBookOffer() { Price = decimal.Parse(item[0].ToString()), Quantity = decimal.Parse(item[1].ToString()) }); + asks.Add(new OrderBookOffer + { + Price = decimal.Parse(item[0].ToString(), CultureInfo.InvariantCulture), + Quantity = decimal.Parse(item[1].ToString(), CultureInfo.InvariantCulture) + }); + } + + result.Bids = bids; + result.Asks = asks; + + return result; + } + + public DepthPartialMessage GetParsedDepthPartialMessage(dynamic messageData) + { + var result = new DepthPartialMessage + { + UpdateId = messageData.lastUpdateId + }; + + var bids = new List(); + var asks = new List(); + + foreach (JToken item in ((JArray)messageData.bids).ToArray()) + { + bids.Add(new OrderBookOffer() + { + Price = decimal.Parse(item[0].ToString(), CultureInfo.InvariantCulture), + Quantity = decimal.Parse(item[1].ToString(), CultureInfo.InvariantCulture) + }); + } + + foreach (JToken item in ((JArray)messageData.asks).ToArray()) + { + asks.Add(new OrderBookOffer() + { + Price = decimal.Parse(item[0].ToString(), CultureInfo.InvariantCulture), + Quantity = decimal.Parse(item[1].ToString(), CultureInfo.InvariantCulture) + }); } result.Bids = bids; diff --git a/Binance.API.Csharp.Client/Utils/EndPoints.cs b/Binance.API.Csharp.Client/Utils/EndPoints.cs index 681a066..75ef8b5 100644 --- a/Binance.API.Csharp.Client/Utils/EndPoints.cs +++ b/Binance.API.Csharp.Client/Utils/EndPoints.cs @@ -17,7 +17,7 @@ public static class EndPoints public static readonly string TickerPriceChange24H = "/api/v1/ticker/24hr"; public static readonly string AllPrices = "/api/v1/ticker/allPrices"; public static readonly string OrderBookTicker = "/api/v1/ticker/allBookTickers"; - public static readonly string TradingRules = "https://gist.githubusercontent.com/Ninj0r/3029b9d635f8f81f5ffab9cc9df5cc61/raw/810530a2118e5d8cdcfcc4d220349976a0acf131/tradingRules_20171022.json"; + public static readonly string TradingRules = "/api/v1/exchangeInfo"; #endregion #region Account Endpoints diff --git a/Binance.API.Csharp.Client/Utils/Utilities.cs b/Binance.API.Csharp.Client/Utils/Utilities.cs index ef4159a..c56ff64 100644 --- a/Binance.API.Csharp.Client/Utils/Utilities.cs +++ b/Binance.API.Csharp.Client/Utils/Utilities.cs @@ -10,6 +10,7 @@ namespace Binance.API.Csharp.Client.Utils /// public static class Utilities { + public static long DeltaTimeAdjustment { get; set; } = 0; /// /// Gets a HMACSHA256 signature based on the API Secret. /// @@ -36,7 +37,7 @@ public static string GenerateSignature(string apiSecret, string message) public static string GenerateTimeStamp(DateTime baseDateTime) { var dtOffset = new DateTimeOffset(baseDateTime); - return dtOffset.ToUnixTimeMilliseconds().ToString(); + return (dtOffset.ToUnixTimeMilliseconds() + DeltaTimeAdjustment).ToString(); } ///