diff --git a/.gitignore b/.gitignore index 850eb6ce..028d0f73 100644 --- a/.gitignore +++ b/.gitignore @@ -263,4 +263,5 @@ __pycache__/ # Trader binary and log files *.bin *.log* -ExchangeSharpConsole/Properties/launchSettings.json +launchSettings.json +**/PublishProfiles/* diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 14f966c0..4f4209e7 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -10,10 +10,12 @@ Please follow these coding guidelines... - Only implement async methods as a general rule. Synchronous calls can be done by using the Sync extension method in ```CryptoUtility```. Saves a lot of duplicate code. - Use CryptoUtility.UtcNow instead of DateTime.UtcNow. This makes it easy to mock the date and time for unit or integration tests. - Use CryptoUtility.UnixTimeStampToDateTimeSeconds and CryptoUtility.UnixTimestampFromDateTimeSeconds, etc. for date conversions, keep all date in UTC. -- Follow these code style guidelines please (we're not monsters): +- Please follow these code style guidelines please (we're not monsters): - Tabs for indent. - Curly braces on separate lines. - - Wrap all if statements with curly braces, makes debug and set breakpoints much easier, along with adding new code to the if statement block. + - Wrap if, else if, and any loops or control logic with curly braces. Avoid `if (something) doSomething();` on same line or next line without curly braces. + - Append 'Async' to the end of the name of any method returning a Task - except for unit and integration test methods. + - Avoid using `.Sync()` or `.RunSynchronously()` - all code should use Tasks and/or async / await. When creating a new Exchange API, please do the following: - For reference comparisons, https://github.com/ccxt/ccxt is a good project to compare against when creating a new exchange. Use node.js in Visual Studio to debug through the code. diff --git a/ExchangeSharp/API/Common/BaseAPI.cs b/ExchangeSharp/API/Common/BaseAPI.cs index 70e0dcc6..e47cc2f0 100644 --- a/ExchangeSharp/API/Common/BaseAPI.cs +++ b/ExchangeSharp/API/Common/BaseAPI.cs @@ -313,12 +313,12 @@ public async Task GenerateNonceAsync() { await OnGetNonceOffset(); } + + object nonce; - lock (this) + while (true) { - object nonce; - - while (true) + lock (this) { // some API (Binance) have a problem with requests being after server time, subtract of offset can help DateTime now = CryptoUtility.UtcNow - NonceOffset; @@ -373,28 +373,28 @@ public async Task GenerateNonceAsync() case NonceStyle.Int32File: case NonceStyle.Int64File: - { - // why an API would use a persistent incrementing counter for nonce is beyond me, ticks is so much better with a sliding window... - // making it required to increment by 1 is also a pain - especially when restarting a process or rebooting. - string tempFile = Path.Combine(Path.GetTempPath(), PublicApiKey.ToUnsecureString() + ".nonce"); - if (!File.Exists(tempFile)) - { - File.WriteAllText(tempFile, "0"); - } - unchecked { - long longNonce = File.ReadAllText(tempFile).ConvertInvariant() + 1; - long maxValue = (NonceStyle == NonceStyle.Int32File ? int.MaxValue : long.MaxValue); - if (longNonce < 1 || longNonce > maxValue) + // why an API would use a persistent incrementing counter for nonce is beyond me, ticks is so much better with a sliding window... + // making it required to increment by 1 is also a pain - especially when restarting a process or rebooting. + string tempFile = Path.Combine(Path.GetTempPath(), PublicApiKey.ToUnsecureString() + ".nonce"); + if (!File.Exists(tempFile)) + { + File.WriteAllText(tempFile, "0"); + } + unchecked { - throw new APIException($"Nonce {longNonce.ToStringInvariant()} is out of bounds, valid ranges are 1 to {maxValue.ToStringInvariant()}, " + - $"please regenerate new API keys. Please contact {Name} API support and ask them to change to a sensible nonce algorithm."); + long longNonce = File.ReadAllText(tempFile).ConvertInvariant() + 1; + long maxValue = (NonceStyle == NonceStyle.Int32File ? int.MaxValue : long.MaxValue); + if (longNonce < 1 || longNonce > maxValue) + { + throw new APIException($"Nonce {longNonce.ToStringInvariant()} is out of bounds, valid ranges are 1 to {maxValue.ToStringInvariant()}, " + + $"please regenerate new API keys. Please contact {Name} API support and ask them to change to a sensible nonce algorithm."); + } + File.WriteAllText(tempFile, longNonce.ToStringInvariant()); + nonce = longNonce; } - File.WriteAllText(tempFile, longNonce.ToStringInvariant()); - nonce = longNonce; + break; } - break; - } case NonceStyle.ExpiresUnixMilliseconds: nonce = (long)now.UnixTimestampFromDateTimeMilliseconds(); @@ -415,13 +415,13 @@ public async Task GenerateNonceAsync() lastNonce = convertedNonce; break; } - - // wait 1 millisecond for a new nonce - Task.Delay(1).Sync(); } - return nonce; + // wait 1 millisecond for a new nonce + await Task.Delay(1); } + + return nonce; } /// @@ -496,7 +496,7 @@ public async Task MakeJsonRequestAsync(string url, string baseUrl = null, /// Callback for messages /// Connect callback /// Web socket - dispose of the wrapper to shutdown the socket - public IWebSocket ConnectWebSocket + public Task ConnectWebSocketAsync ( string url, Func messageCallback, @@ -524,7 +524,7 @@ public IWebSocket ConnectWebSocket wrapper.Disconnected += disconnectCallback; } wrapper.Start(); - return wrapper; + return Task.FromResult(wrapper); } /// diff --git a/ExchangeSharp/API/Exchanges/Binance/ExchangeBinanceAPI.cs b/ExchangeSharp/API/Exchanges/Binance/ExchangeBinanceAPI.cs index 5600bc80..5b8d2c1c 100644 --- a/ExchangeSharp/API/Exchanges/Binance/ExchangeBinanceAPI.cs +++ b/ExchangeSharp/API/Exchanges/Binance/ExchangeBinanceAPI.cs @@ -44,11 +44,11 @@ static ExchangeBinanceAPI() }; } - private string GetWebSocketStreamUrlForSymbols(string suffix, params string[] marketSymbols) + private async Task GetWebSocketStreamUrlForSymbolsAsync(string suffix, params string[] marketSymbols) { if (marketSymbols == null || marketSymbols.Length == 0) { - marketSymbols = GetMarketSymbolsAsync().Sync().ToArray(); + marketSymbols = (await GetMarketSymbolsAsync()).ToArray(); } StringBuilder streams = new StringBuilder("/stream?streams="); @@ -74,21 +74,21 @@ public ExchangeBinanceAPI() WebSocketOrderBookType = WebSocketOrderBookType.DeltasOnly; } - public override string ExchangeMarketSymbolToGlobalMarketSymbol(string marketSymbol) + public override Task ExchangeMarketSymbolToGlobalMarketSymbolAsync(string marketSymbol) { // All pairs in Binance end with BTC, ETH, BNB or USDT if (marketSymbol.EndsWith("BTC") || marketSymbol.EndsWith("ETH") || marketSymbol.EndsWith("BNB")) { string baseSymbol = marketSymbol.Substring(marketSymbol.Length - 3); - return ExchangeMarketSymbolToGlobalMarketSymbolWithSeparator((marketSymbol.Replace(baseSymbol, "") + GlobalMarketSymbolSeparator + baseSymbol), GlobalMarketSymbolSeparator); + return ExchangeMarketSymbolToGlobalMarketSymbolWithSeparatorAsync((marketSymbol.Replace(baseSymbol, "") + GlobalMarketSymbolSeparator + baseSymbol), GlobalMarketSymbolSeparator); } if (marketSymbol.EndsWith("USDT")) { string baseSymbol = marketSymbol.Substring(marketSymbol.Length - 4); - return ExchangeMarketSymbolToGlobalMarketSymbolWithSeparator((marketSymbol.Replace(baseSymbol, "") + GlobalMarketSymbolSeparator + baseSymbol), GlobalMarketSymbolSeparator); + return ExchangeMarketSymbolToGlobalMarketSymbolWithSeparatorAsync((marketSymbol.Replace(baseSymbol, "") + GlobalMarketSymbolSeparator + baseSymbol), GlobalMarketSymbolSeparator); } - return ExchangeMarketSymbolToGlobalMarketSymbolWithSeparator(marketSymbol.Substring(0, marketSymbol.Length - 3) + GlobalMarketSymbolSeparator + (marketSymbol.Substring(marketSymbol.Length - 3, 3)), GlobalMarketSymbolSeparator); + return ExchangeMarketSymbolToGlobalMarketSymbolWithSeparatorAsync(marketSymbol.Substring(0, marketSymbol.Length - 3) + GlobalMarketSymbolSeparator + (marketSymbol.Substring(marketSymbol.Length - 3, 3)), GlobalMarketSymbolSeparator); } /// @@ -225,7 +225,7 @@ protected override async Task> OnG protected override async Task OnGetTickerAsync(string marketSymbol) { JToken obj = await MakeJsonRequestAsync("/ticker/24hr?symbol=" + marketSymbol); - return ParseTicker(marketSymbol, obj); + return await ParseTickerAsync(marketSymbol, obj); } protected override async Task>> OnGetTickersAsync() @@ -236,32 +236,31 @@ protected override async Task>> foreach (JToken child in obj) { marketSymbol = child["symbol"].ToStringInvariant(); - tickers.Add(new KeyValuePair(marketSymbol, ParseTicker(marketSymbol, child))); + tickers.Add(new KeyValuePair(marketSymbol, await ParseTickerAsync(marketSymbol, child))); } return tickers; } - protected override IWebSocket OnGetTickersWebSocket(Action>> callback, params string[] symbols) + protected override Task OnGetTickersWebSocketAsync(Action>> callback, params string[] symbols) { - return ConnectWebSocket("/stream?streams=!ticker@arr", (_socket, msg) => + return ConnectWebSocketAsync("/stream?streams=!ticker@arr", async (_socket, msg) => { JToken token = JToken.Parse(msg.ToStringFromUTF8()); List> tickerList = new List>(); ExchangeTicker ticker; foreach (JToken childToken in token["data"]) { - ticker = ParseTickerWebSocket(childToken); + ticker = await ParseTickerWebSocketAsync(childToken); tickerList.Add(new KeyValuePair(ticker.MarketSymbol, ticker)); } if (tickerList.Count != 0) { callback(tickerList); } - return Task.CompletedTask; }); } - protected override IWebSocket OnGetTradesWebSocket(Func, Task> callback, params string[] marketSymbols) + protected override async Task OnGetTradesWebSocketAsync(Func, Task> callback, params string[] marketSymbols) { /* { @@ -281,10 +280,10 @@ protected override IWebSocket OnGetTradesWebSocket(Func + string url = await GetWebSocketStreamUrlForSymbolsAsync("@aggTrade", marketSymbols); + return await ConnectWebSocketAsync(url, async (_socket, msg) => { JToken token = JToken.Parse(msg.ToStringFromUTF8()); string name = token["stream"].ToStringInvariant(); @@ -297,14 +296,14 @@ protected override IWebSocket OnGetTradesWebSocket(Func callback, int maxCount = 20, params string[] marketSymbols) + protected override async Task OnGetDeltaOrderBookWebSocketAsync(Action callback, int maxCount = 20, params string[] marketSymbols) { if (marketSymbols == null || marketSymbols.Length == 0) { - marketSymbols = GetMarketSymbolsAsync().Sync().ToArray(); + marketSymbols = (await GetMarketSymbolsAsync()).ToArray(); } string combined = string.Join("/", marketSymbols.Select(s => this.NormalizeMarketSymbol(s).ToLowerInvariant() + "@depth@100ms")); - return ConnectWebSocket($"/stream?streams={combined}", (_socket, msg) => + return await ConnectWebSocketAsync($"/stream?streams={combined}", (_socket, msg) => { string json = msg.ToStringFromUTF8(); var update = JsonConvert.DeserializeObject(json); @@ -786,16 +785,16 @@ private bool ParseMarketStatus(string status) return isActive; } - private ExchangeTicker ParseTicker(string symbol, JToken token) + private async Task ParseTickerAsync(string symbol, JToken token) { // {"priceChange":"-0.00192300","priceChangePercent":"-4.735","weightedAvgPrice":"0.03980955","prevClosePrice":"0.04056700","lastPrice":"0.03869000","lastQty":"0.69300000","bidPrice":"0.03858500","bidQty":"38.35000000","askPrice":"0.03869000","askQty":"31.90700000","openPrice":"0.04061300","highPrice":"0.04081900","lowPrice":"0.03842000","volume":"128015.84300000","quoteVolume":"5096.25362239","openTime":1512403353766,"closeTime":1512489753766,"firstId":4793094,"lastId":4921546,"count":128453} - return this.ParseTicker(token, symbol, "askPrice", "bidPrice", "lastPrice", "volume", "quoteVolume", "closeTime", TimestampType.UnixMilliseconds); + return await this.ParseTickerAsync(token, symbol, "askPrice", "bidPrice", "lastPrice", "volume", "quoteVolume", "closeTime", TimestampType.UnixMilliseconds); } - private ExchangeTicker ParseTickerWebSocket(JToken token) + private async Task ParseTickerWebSocketAsync(JToken token) { string marketSymbol = token["s"].ToStringInvariant(); - return this.ParseTicker(token, marketSymbol, "a", "b", "c", "v", "q", "E", TimestampType.UnixMilliseconds); + return await this.ParseTickerAsync(token, marketSymbol, "a", "b", "c", "v", "q", "E", TimestampType.UnixMilliseconds); } private ExchangeOrderResult ParseOrder(JToken token) @@ -1060,9 +1059,9 @@ protected override async Task> OnGetDepositHist return transactions; } - protected override IWebSocket OnUserDataWebSocket(Action callback, string listenKey) + protected override async Task OnUserDataWebSocketAsync(Action callback, string listenKey) { - return ConnectWebSocket($"/ws/{listenKey}", (_socket, msg) => + return await ConnectWebSocketAsync($"/ws/{listenKey}", (_socket, msg) => { JToken token = JToken.Parse(msg.ToStringFromUTF8()); var eventType = token["e"].ToStringInvariant(); diff --git a/ExchangeSharp/API/Exchanges/BitBank/ExchangeBitBankAPI.cs b/ExchangeSharp/API/Exchanges/BitBank/ExchangeBitBankAPI.cs index 10f83b2d..3881c646 100644 --- a/ExchangeSharp/API/Exchanges/BitBank/ExchangeBitBankAPI.cs +++ b/ExchangeSharp/API/Exchanges/BitBank/ExchangeBitBankAPI.cs @@ -40,7 +40,7 @@ public ExchangeBitBankAPI() protected override async Task OnGetTickerAsync(string marketSymbol) { JToken token = await MakeJsonRequestAsync($"/{marketSymbol}/ticker"); - return ParseTicker(marketSymbol, token); + return await ParseTickerAsync(marketSymbol, token); } // Bitbank supports endpoint for getting all rates in one request, Using this endpoint is faster then ExchangeAPI's default implementation @@ -53,7 +53,7 @@ protected override async Task>> var result = new List>(); foreach (var symbol in symbols) { - var data = token[GlobalMarketSymbolToExchangeMarketSymbol(symbol)]; + var data = token[GlobalMarketSymbolToExchangeMarketSymbolAsync(symbol)]; var ticker = new ExchangeTicker() { Ask = data["sell"].ConvertInvariant(), @@ -207,8 +207,10 @@ protected override async Task OnWithdrawAsync(Exchan payload2.Add("amount", withdrawalRequest.Amount); payload2.Add("uuid", uuid); JToken token2 = await MakeJsonRequestAsync($"/user/request_withdrawal", baseUrl: BaseUrlPrivate, payload: payload2, requestMethod: "POST"); - var resp = new ExchangeWithdrawalResponse(); - resp.Id = token2["txid"].ToStringInvariant(); + var resp = new ExchangeWithdrawalResponse + { + Id = token2["txid"].ToStringInvariant() + }; var status = token2["status"].ToStringInvariant(); resp.Success = status != "REJECTED" && status != "CANCELED"; resp.Message = "{" + $"label:{token2["label"]}, fee:{token2["fee"]}" + "}"; @@ -305,8 +307,11 @@ protected override async Task ProcessRequestAsync(IHttpWebRequest request, Dicti } return; } - private ExchangeTicker ParseTicker(string symbol, JToken token) - => this.ParseTicker(token, symbol, "sell", "buy", "last", "vol", quoteVolumeKey: null, "timestamp", TimestampType.UnixMilliseconds); + + private async Task ParseTickerAsync(string symbol, JToken token) + { + return await this.ParseTickerAsync(token, symbol, "sell", "buy", "last", "vol", quoteVolumeKey: null, "timestamp", TimestampType.UnixMilliseconds); + } private string FormatPeriod(int ps) { @@ -382,13 +387,15 @@ private ExchangeOrderResult TradeHistoryToExchangeOrderResult(JToken token) // 2. GetOrder, PostOrder private ExchangeOrderResult ParseOrderCore(JToken token) { - var res = new ExchangeOrderResult(); - res.OrderId = token["order_id"].ToStringInvariant(); - res.MarketSymbol = token["pair"].ToStringInvariant(); - res.IsBuy = token["side"].ToStringInvariant() == "buy"; + var res = new ExchangeOrderResult + { + OrderId = token["order_id"].ToStringInvariant(), + MarketSymbol = token["pair"].ToStringInvariant(), + IsBuy = token["side"].ToStringInvariant() == "buy" + }; res.Fees = token["type"].ToStringInvariant() == "limit" ? MakerFee * res.Amount : TakerFee * res.Amount; res.Price = token["price"].ConvertInvariant(); - res.FillDate = token["executed_at"] == null ? default(DateTime) : token["executed_at"].ConvertInvariant().UnixTimeStampToDateTimeMilliseconds(); + res.FillDate = token["executed_at"] == null ? default : token["executed_at"].ConvertInvariant().UnixTimeStampToDateTimeMilliseconds(); res.FeesCurrency = res.MarketSymbol.Substring(0, 3); return res; } diff --git a/ExchangeSharp/API/Exchanges/BitMEX/ExchangeBitMEXAPI.cs b/ExchangeSharp/API/Exchanges/BitMEX/ExchangeBitMEXAPI.cs index 95504da4..27859c0b 100644 --- a/ExchangeSharp/API/Exchanges/BitMEX/ExchangeBitMEXAPI.cs +++ b/ExchangeSharp/API/Exchanges/BitMEX/ExchangeBitMEXAPI.cs @@ -49,12 +49,12 @@ public ExchangeBitMEXAPI() RateLimit = new RateGate(300, TimeSpan.FromMinutes(5)); } - public override string ExchangeMarketSymbolToGlobalMarketSymbol(string marketSymbol) + public override Task ExchangeMarketSymbolToGlobalMarketSymbolAsync(string marketSymbol) { throw new NotImplementedException(); } - public override string GlobalMarketSymbolToExchangeMarketSymbol(string marketSymbol) + public override Task GlobalMarketSymbolToExchangeMarketSymbolAsync(string marketSymbol) { throw new NotImplementedException(); } @@ -224,7 +224,7 @@ protected override async Task> OnGetMarketSymbolsMet return markets; } - protected override IWebSocket OnGetTradesWebSocket(Func, Task> callback, params string[] marketSymbols) + protected override Task OnGetTradesWebSocketAsync(Func, Task> callback, params string[] marketSymbols) { /* {"table":"trade","action":"partial","keys":[], @@ -235,7 +235,7 @@ protected override IWebSocket OnGetTradesWebSocket(Func + return ConnectWebSocketAsync(string.Empty, async (_socket, msg) => { var str = msg.ToStringFromUTF8(); JToken token = JToken.Parse(str); @@ -270,7 +270,7 @@ protected override IWebSocket OnGetTradesWebSocket(Func callback, int maxCount = 20, params string[] marketSymbols) + protected override async Task OnGetDeltaOrderBookWebSocketAsync(Action callback, int maxCount = 20, params string[] marketSymbols) { /* {"info":"Welcome to the BitMEX Realtime API.","version":"2018-06-29T18:05:14.000Z","timestamp":"2018-07-05T14:22:26.267Z","docs":"https://www.bitmex.com/app/wsAPI","limit":{"remaining":39}} @@ -280,9 +280,9 @@ protected override IWebSocket OnGetDeltaOrderBookWebSocket(Action + return await ConnectWebSocketAsync(string.Empty, (_socket, msg) => { var str = msg.ToStringFromUTF8(); JToken token = JToken.Parse(str); diff --git a/ExchangeSharp/API/Exchanges/Bitfinex/ExchangeBitfinexAPI.cs b/ExchangeSharp/API/Exchanges/Bitfinex/ExchangeBitfinexAPI.cs index 740a6ddd..f32172dd 100644 --- a/ExchangeSharp/API/Exchanges/Bitfinex/ExchangeBitfinexAPI.cs +++ b/ExchangeSharp/API/Exchanges/Bitfinex/ExchangeBitfinexAPI.cs @@ -155,7 +155,7 @@ protected override async Task> OnGetMarketSymbolsMet protected override async Task OnGetTickerAsync(string marketSymbol) { JToken ticker = await MakeJsonRequestAsync("/ticker/t" + marketSymbol); - return this.ParseTicker(ticker, marketSymbol, 2, 0, 6, 7); + return await this.ParseTickerAsync(ticker, marketSymbol, 2, 0, 6, 7); } protected override async Task>> OnGetTickersAsync() @@ -213,10 +213,10 @@ protected override async Task>> return tickers; } - protected override IWebSocket OnGetTickersWebSocket(Action>> callback, params string[] marketSymbols) + protected override async Task OnGetTickersWebSocketAsync(Action>> callback, params string[] marketSymbols) { Dictionary channelIdToSymbol = new Dictionary(); - return ConnectWebSocket(string.Empty, (_socket, msg) => + return await ConnectWebSocketAsync(string.Empty, async (_socket, msg) => { JToken token = JToken.Parse(msg.ToStringFromUTF8()); if (token is JArray array) @@ -226,7 +226,7 @@ protected override IWebSocket OnGetTickersWebSocket(Action> tickerList = new List>(); if (channelIdToSymbol.TryGetValue(array[0].ConvertInvariant(), out string symbol)) { - ExchangeTicker ticker = ParseTickerWebSocket(symbol, array); + ExchangeTicker ticker = await ParseTickerWebSocketAsync(symbol, array); if (ticker != null) { callback(new KeyValuePair[] { new KeyValuePair(symbol, ticker) }); @@ -240,7 +240,6 @@ protected override IWebSocket OnGetTickersWebSocket(Action(); channelIdToSymbol[channelId] = token["pair"].ToStringInvariant(); } - return Task.CompletedTask; }, async (_socket) => { marketSymbols = marketSymbols == null || marketSymbols.Length == 0 ? (await GetMarketSymbolsAsync()).ToArray() : marketSymbols; @@ -251,14 +250,14 @@ protected override IWebSocket OnGetTickersWebSocket(Action, Task> callback, params string[] marketSymbols) + protected override async Task OnGetTradesWebSocketAsync(Func, Task> callback, params string[] marketSymbols) { Dictionary channelIdToSymbol = new Dictionary(); if (marketSymbols == null || marketSymbols.Length == 0) { - marketSymbols = GetMarketSymbolsAsync().Sync().ToArray(); + marketSymbols = (await GetMarketSymbolsAsync()).ToArray(); } - return ConnectWebSocket("/2", async (_socket, msg) => //use websocket V2 (beta, but millisecond timestamp) + return await ConnectWebSocketAsync("/2", async (_socket, msg) => //use websocket V2 (beta, but millisecond timestamp) { JToken token = JToken.Parse(msg.ToStringFromUTF8()); if (token is JArray array) @@ -534,9 +533,9 @@ protected override async Task> OnGetCompletedOr return await GetOrderDetailsInternalV1(new string[] { marketSymbol }, afterDate); } - protected override IWebSocket OnGetCompletedOrderDetailsWebSocket(Action callback) + protected override Task OnGetCompletedOrderDetailsWebSocketAsync(Action callback) { - return ConnectWebSocket(string.Empty, (_socket, msg) => + return ConnectWebSocketAsync(string.Empty, (_socket, msg) => { JToken token = JToken.Parse(msg.ToStringFromUTF8()); if (token[1].ToStringInvariant() == "hb") @@ -1000,9 +999,9 @@ private ExchangeOrderResult ParseTrade(JToken trade, string symbol) }; } - private ExchangeTicker ParseTickerWebSocket(string symbol, JToken token) + private async Task ParseTickerWebSocketAsync(string symbol, JToken token) { - return this.ParseTicker(token, symbol, 3, 1, 7, 8); + return await this.ParseTickerAsync(token, symbol, 3, 1, 7, 8); } /// Gets the withdrawal fees for various currencies. diff --git a/ExchangeSharp/API/Exchanges/Bithumb/ExchangeBithumbAPI.cs b/ExchangeSharp/API/Exchanges/Bithumb/ExchangeBithumbAPI.cs index 16f349f5..4932973c 100644 --- a/ExchangeSharp/API/Exchanges/Bithumb/ExchangeBithumbAPI.cs +++ b/ExchangeSharp/API/Exchanges/Bithumb/ExchangeBithumbAPI.cs @@ -38,14 +38,14 @@ public override string NormalizeMarketSymbol(string marketSymbol) return marketSymbol; } - public override string ExchangeMarketSymbolToGlobalMarketSymbol(string marketSymbol) + public override Task ExchangeMarketSymbolToGlobalMarketSymbolAsync(string marketSymbol) { - return "KRW" + GlobalMarketSymbolSeparator + marketSymbol; + return Task.FromResult("KRW" + GlobalMarketSymbolSeparator + marketSymbol); } - public override string GlobalMarketSymbolToExchangeMarketSymbol(string marketSymbol) + public override Task GlobalMarketSymbolToExchangeMarketSymbolAsync(string marketSymbol) { - return marketSymbol.Substring(marketSymbol.IndexOf(GlobalMarketSymbolSeparator) + 1); + return Task.FromResult(marketSymbol.Substring(marketSymbol.IndexOf(GlobalMarketSymbolSeparator) + 1)); } private string StatusToError(string status) @@ -80,7 +80,7 @@ private async Task> MakeRequestBithumbAsync(string marketS return new Tuple(obj, marketSymbol); } - private ExchangeTicker ParseTicker(string marketSymbol, JToken data) + private async Task ParseTickerAsync(string marketSymbol, JToken data) { /* { @@ -97,7 +97,7 @@ private ExchangeTicker ParseTicker(string marketSymbol, JToken data) "fluctate_rate_24H": "0.02" } */ - ExchangeTicker ticker = this.ParseTicker(data, marketSymbol, "max_price", "min_price", "min_price", "min_price", "units_traded_24H"); + ExchangeTicker ticker = await this.ParseTickerAsync(data, marketSymbol, "max_price", "min_price", "min_price", "min_price", "units_traded_24H"); ticker.Volume.Timestamp = data.Parent.Parent["date"].ConvertInvariant().UnixTimeStampToDateTimeMilliseconds(); return ticker; } @@ -125,7 +125,7 @@ protected override async Task> OnGetMarketSymbolsAsync() protected override async Task OnGetTickerAsync(string marketSymbol) { var data = await MakeRequestBithumbAsync(marketSymbol, "/public/ticker/$SYMBOL$"); - return ParseTicker(data.Item2, data.Item1); + return await ParseTickerAsync(data.Item2, data.Item1); } protected override async Task>> OnGetTickersAsync() @@ -138,7 +138,7 @@ protected override async Task>> { if (token.Name != "date") { - ExchangeTicker ticker = ParseTicker(token.Name, token.Value); + ExchangeTicker ticker = await ParseTickerAsync(token.Name, token.Value); ticker.Volume.Timestamp = date; tickers.Add(new KeyValuePair(token.Name, ticker)); } diff --git a/ExchangeSharp/API/Exchanges/Bitstamp/ExchangeBitstampAPI.cs b/ExchangeSharp/API/Exchanges/Bitstamp/ExchangeBitstampAPI.cs index 6a710c77..170b57a6 100644 --- a/ExchangeSharp/API/Exchanges/Bitstamp/ExchangeBitstampAPI.cs +++ b/ExchangeSharp/API/Exchanges/Bitstamp/ExchangeBitstampAPI.cs @@ -127,7 +127,7 @@ protected override async Task OnGetTickerAsync(string marketSymb { // {"high": "0.10948945", "last": "0.10121817", "timestamp": "1513387486", "bid": "0.10112165", "vwap": "0.09958913", "volume": "9954.37332614", "low": "0.09100000", "ask": "0.10198408", "open": "0.10250028"} JToken token = await MakeBitstampRequestAsync("/ticker/" + marketSymbol); - return this.ParseTicker(token, marketSymbol, "ask", "bid", "last", "volume", null, "timestamp", TimestampType.UnixSeconds); + return await this.ParseTickerAsync(token, marketSymbol, "ask", "bid", "last", "volume", null, "timestamp", TimestampType.UnixSeconds); } protected override async Task OnGetOrderBookAsync(string marketSymbol, int maxCount = 100) @@ -507,13 +507,13 @@ protected override async Task OnWithdrawAsync(Exchan }; } - protected override IWebSocket OnGetTradesWebSocket(Func, Task> callback, params string[] marketSymbols) + protected override async Task OnGetTradesWebSocketAsync(Func, Task> callback, params string[] marketSymbols) { if (marketSymbols == null || marketSymbols.Length == 0) { - marketSymbols = GetMarketSymbolsAsync().Sync().ToArray(); + marketSymbols = (await GetMarketSymbolsAsync()).ToArray(); } - return ConnectWebSocket(null, messageCallback: async (_socket, msg) => + return await ConnectWebSocketAsync(null, messageCallback: async (_socket, msg) => { JToken token = JToken.Parse(msg.ToStringFromUTF8()); if (token["event"].ToStringInvariant() == "bts:error") diff --git a/ExchangeSharp/API/Exchanges/Bittrex/ExchangeBittrexAPI.cs b/ExchangeSharp/API/Exchanges/Bittrex/ExchangeBittrexAPI.cs index eb5f4770..4aaf0742 100644 --- a/ExchangeSharp/API/Exchanges/Bittrex/ExchangeBittrexAPI.cs +++ b/ExchangeSharp/API/Exchanges/Bittrex/ExchangeBittrexAPI.cs @@ -239,7 +239,7 @@ protected override async Task OnGetTickerAsync(string marketSymb { JToken ticker = await MakeJsonRequestAsync("/public/getmarketsummary?market=" + marketSymbol); //NOTE: Bittrex uses the term "BaseVolume" when referring to the QuoteCurrencyVolume - return this.ParseTicker(ticker[0], marketSymbol, "Ask", "Bid", "Last", "Volume", "BaseVolume", "Timestamp", TimestampType.Iso8601); + return await this.ParseTickerAsync(ticker[0], marketSymbol, "Ask", "Bid", "Last", "Volume", "BaseVolume", "Timestamp", TimestampType.Iso8601); } protected override async Task>> OnGetTickersAsync() @@ -251,7 +251,7 @@ protected override async Task>> { marketSymbol = ticker["MarketName"].ToStringInvariant(); //NOTE: Bittrex uses the term "BaseVolume" when referring to the QuoteCurrencyVolume - ExchangeTicker tickerObj = this.ParseTicker(ticker, marketSymbol, "Ask", "Bid", "Last", "Volume", "BaseVolume", "Timestamp", TimestampType.Iso8601); + ExchangeTicker tickerObj = await this.ParseTickerAsync(ticker, marketSymbol, "Ask", "Bid", "Last", "Volume", "BaseVolume", "Timestamp", TimestampType.Iso8601); tickerList.Add(new KeyValuePair(marketSymbol, tickerObj)); } return tickerList; diff --git a/ExchangeSharp/API/Exchanges/Bittrex/ExchangeBittrexAPI_WebSocket.cs b/ExchangeSharp/API/Exchanges/Bittrex/ExchangeBittrexAPI_WebSocket.cs index 6045095f..3e30daf1 100644 --- a/ExchangeSharp/API/Exchanges/Bittrex/ExchangeBittrexAPI_WebSocket.cs +++ b/ExchangeSharp/API/Exchanges/Bittrex/ExchangeBittrexAPI_WebSocket.cs @@ -50,15 +50,12 @@ public BittrexWebSocketManager() : base("https://socket.bittrex.com/signalr", "c /// Subscribe to all market summaries /// /// Callback + /// Symbols /// IDisposable to close the socket - public IWebSocket SubscribeToSummaryDeltas(Action callback) + public async Task SubscribeToSummaryDeltasAsync(Func callback, params string[] marketSymbols) { SignalrManager.SignalrSocketConnection conn = new SignalrManager.SignalrSocketConnection(this); - Task.Run(async () => await conn.OpenAsync("uS", (s) => - { - callback(s); - return Task.CompletedTask; - })); + await conn.OpenAsync("uS", callback); return conn; } @@ -68,7 +65,7 @@ public IWebSocket SubscribeToSummaryDeltas(Action callback) /// Callback /// The market symbols to subscribe to /// IDisposable to close the socket - public IWebSocket SubscribeToExchangeDeltas(Func callback, params string[] marketSymbols) + public async Task SubscribeToExchangeDeltasAsync(Func callback, params string[] marketSymbols) { SignalrManager.SignalrSocketConnection conn = new SignalrManager.SignalrSocketConnection(this); List paramList = new List(); @@ -76,19 +73,21 @@ public IWebSocket SubscribeToExchangeDeltas(Func callback, params { paramList.Add(new object[] { marketSymbol }); } - Task.Run(async () => await conn.OpenAsync("uE", async (s) => - { - await callback(s); - }, 0, paramList.ToArray())); + await conn.OpenAsync("uE", callback, 0, paramList.ToArray()); return conn; } } private BittrexWebSocketManager webSocket; - protected override IWebSocket OnGetTickersWebSocket(Action>> callback, params string[] symbols) + protected override async Task OnGetTickersWebSocketAsync(Action>> callback, params string[] marketSymbols) { - void innerCallback(string json) + HashSet filter = new HashSet(); + foreach (string marketSymbol in marketSymbols) + { + filter.Add(marketSymbol); + } + async Task innerCallback(string json) { #region sample json /* @@ -122,7 +121,11 @@ void innerCallback(string json) foreach (JToken ticker in token) { string marketName = ticker["M"].ToStringInvariant(); - var (baseCurrency, quoteCurrency) = ExchangeMarketSymbolToCurrencies(marketName); + if (filter.Count != 0 && !filter.Contains(marketName)) + { + continue; + } + var (baseCurrency, quoteCurrency) = await ExchangeMarketSymbolToCurrenciesAsync(marketName); decimal last = ticker["l"].ConvertInvariant(); decimal ask = ticker["A"].ConvertInvariant(); decimal bid = ticker["B"].ConvertInvariant(); @@ -148,10 +151,10 @@ void innerCallback(string json) } callback(freshTickers); } - return new BittrexWebSocketManager().SubscribeToSummaryDeltas(innerCallback); + return await new BittrexWebSocketManager().SubscribeToSummaryDeltasAsync(innerCallback, marketSymbols); } - protected override IWebSocket OnGetDeltaOrderBookWebSocket + protected override async Task OnGetDeltaOrderBookWebSocketAsync ( Action callback, int maxCount = 20, @@ -160,7 +163,7 @@ params string[] marketSymbols { if (marketSymbols == null || marketSymbols.Length == 0) { - marketSymbols = GetMarketSymbolsAsync().Sync().ToArray(); + marketSymbols = (await GetMarketSymbolsAsync()).ToArray(); } Task innerCallback(string json) { @@ -219,14 +222,14 @@ Task innerCallback(string json) return Task.CompletedTask; } - return new BittrexWebSocketManager().SubscribeToExchangeDeltas(innerCallback, marketSymbols); + return await new BittrexWebSocketManager().SubscribeToExchangeDeltasAsync(innerCallback, marketSymbols); } - protected override IWebSocket OnGetTradesWebSocket(Func, Task> callback, params string[] marketSymbols) + protected override async Task OnGetTradesWebSocketAsync(Func, Task> callback, params string[] marketSymbols) { if (marketSymbols == null || marketSymbols.Length == 0) { - marketSymbols = GetMarketSymbolsAsync().Sync().ToArray(); + marketSymbols = (await GetMarketSymbolsAsync()).ToArray(); } async Task innerCallback(string json) { @@ -246,7 +249,7 @@ async Task innerCallback(string json) })); } } - return new BittrexWebSocketManager().SubscribeToExchangeDeltas(innerCallback, marketSymbols); + return await new BittrexWebSocketManager().SubscribeToExchangeDeltasAsync(innerCallback, marketSymbols); } #endif diff --git a/ExchangeSharp/API/Exchanges/Coinbase/ExchangeCoinbaseAPI.cs b/ExchangeSharp/API/Exchanges/Coinbase/ExchangeCoinbaseAPI.cs index aa0dc8c6..37cbe150 100644 --- a/ExchangeSharp/API/Exchanges/Coinbase/ExchangeCoinbaseAPI.cs +++ b/ExchangeSharp/API/Exchanges/Coinbase/ExchangeCoinbaseAPI.cs @@ -230,7 +230,7 @@ protected override async Task> OnG protected override async Task OnGetTickerAsync(string marketSymbol) { JToken ticker = await MakeJsonRequestAsync("/products/" + marketSymbol + "/ticker"); - return this.ParseTicker(ticker, marketSymbol, "ask", "bid", "price", "volume", null, "time", TimestampType.Iso8601); + return await this.ParseTickerAsync(ticker, marketSymbol, "ask", "bid", "price", "volume", null, "time", TimestampType.Iso8601); } protected override async Task OnGetDepositAddressAsync(string symbol, bool forceRegenerate = false) @@ -265,7 +265,7 @@ protected override async Task>> List symbols = (await GetMarketSymbolsAsync()).ToList(); // stupid Coinbase does not have a one shot API call for tickers outside of web sockets - using (var socket = GetTickersWebSocket((t) => + using (var socket = await GetTickersWebSocketAsync((t) => { lock (tickers) { @@ -293,9 +293,9 @@ protected override async Task>> } } - protected override IWebSocket OnGetDeltaOrderBookWebSocket(Action callback, int maxCount = 20, params string[] marketSymbols) + protected override Task OnGetDeltaOrderBookWebSocketAsync(Action callback, int maxCount = 20, params string[] marketSymbols) { - return ConnectWebSocket(string.Empty, (_socket, msg) => + return ConnectWebSocketAsync(string.Empty, (_socket, msg) => { string message = msg.ToStringFromUTF8(); var book = new ExchangeOrderBook(); @@ -362,17 +362,16 @@ protected override IWebSocket OnGetDeltaOrderBookWebSocket(Action>> callback, params string[] marketSymbols) + protected override async Task OnGetTickersWebSocketAsync(Action>> callback, params string[] marketSymbols) { - return ConnectWebSocket("/", (_socket, msg) => + return await ConnectWebSocketAsync("/", async (_socket, msg) => { JToken token = JToken.Parse(msg.ToStringFromUTF8()); if (token["type"].ToStringInvariant() == "ticker") { - ExchangeTicker ticker = this.ParseTicker(token, token["product_id"].ToStringInvariant(), "best_ask", "best_bid", "price", "volume_24h", null, "time", TimestampType.Iso8601); + ExchangeTicker ticker = await this.ParseTickerAsync(token, token["product_id"].ToStringInvariant(), "best_ask", "best_bid", "price", "volume_24h", null, "time", TimestampType.Iso8601); callback(new List>() { new KeyValuePair(token["product_id"].ToStringInvariant(), ticker) }); } - return Task.CompletedTask; }, async (_socket) => { marketSymbols = marketSymbols == null || marketSymbols.Length == 0 ? (await GetMarketSymbolsAsync()).ToArray() : marketSymbols; @@ -393,13 +392,13 @@ protected override IWebSocket OnGetTickersWebSocket(Action, Task> callback, params string[] marketSymbols) + protected override async Task OnGetTradesWebSocketAsync(Func, Task> callback, params string[] marketSymbols) { if (marketSymbols == null || marketSymbols.Length == 0) { - marketSymbols = GetMarketSymbolsAsync().Sync().ToArray(); + marketSymbols = (await GetMarketSymbolsAsync()).ToArray(); } - return ConnectWebSocket("/", async (_socket, msg) => + return await ConnectWebSocketAsync("/", async (_socket, msg) => { JToken token = JToken.Parse(msg.ToStringFromUTF8()); if (token["type"].ToStringInvariant() == "error") diff --git a/ExchangeSharp/API/Exchanges/Cryptopia/ExchangeCryptopiaAPI.cs b/ExchangeSharp/API/Exchanges/Cryptopia/ExchangeCryptopiaAPI.cs index 9316bf32..cbc7a653 100644 --- a/ExchangeSharp/API/Exchanges/Cryptopia/ExchangeCryptopiaAPI.cs +++ b/ExchangeSharp/API/Exchanges/Cryptopia/ExchangeCryptopiaAPI.cs @@ -139,14 +139,17 @@ protected override async Task> OnGetMarketSymbolsMet protected override async Task OnGetTickerAsync(string marketSymbol) { JToken result = await MakeJsonRequestAsync("/GetMarket/" + NormalizeSymbolForUrl(marketSymbol)); - return ParseTicker(result); + return await ParseTickerAsync(result); } protected override async Task>> OnGetTickersAsync() { List> tickers = new List>(); JToken result = await MakeJsonRequestAsync("/GetMarkets"); - foreach (JToken token in result) tickers.Add(new KeyValuePair(token["Label"].ToStringInvariant(), ParseTicker(token))); + foreach (JToken token in result) + { + tickers.Add(new KeyValuePair(token["Label"].ToStringInvariant(), await ParseTickerAsync(token))); + } return tickers; } @@ -423,11 +426,11 @@ protected override async Task OnWithdrawAsync(Exchan #region Private Functions - private ExchangeTicker ParseTicker(JToken token) + private async Task ParseTickerAsync(JToken token) { // [{ "TradePairId":100,"Label":"LTC/BTC","AskPrice":0.00006000,"BidPrice":0.02000000,"Low":0.00006000,"High":0.00006000,"Volume":1000.05639978,"LastPrice":0.00006000,"BuyVolume":34455.678,"SellVolume":67003436.37658233,"Change":-400.00000000,"Open": 0.00000500,"Close": 0.00000600, "BaseVolume": 3.58675866,"BaseBuyVolume": 11.25364758, "BaseSellVolume": 3456.06746543 }, ... ] string marketSymbol = token["Label"].ToStringInvariant(); - return this.ParseTicker(token, marketSymbol, "AskPrice", "BidPrice", "LastPrice", "Volume", "BaseVolume"); + return await this.ParseTickerAsync(token, marketSymbol, "AskPrice", "BidPrice", "LastPrice", "Volume", "BaseVolume"); } private ExchangeTrade ParseTrade(JToken token) diff --git a/ExchangeSharp/API/Exchanges/Digifinex/ExchangeDigifinexAPI.cs b/ExchangeSharp/API/Exchanges/Digifinex/ExchangeDigifinexAPI.cs index b0dd7905..5ff85257 100644 --- a/ExchangeSharp/API/Exchanges/Digifinex/ExchangeDigifinexAPI.cs +++ b/ExchangeSharp/API/Exchanges/Digifinex/ExchangeDigifinexAPI.cs @@ -105,10 +105,10 @@ protected override JToken CheckJsonResponse(JToken result) #region Public APIs - ExchangeMarket ParseSymbol(JToken x) + private async Task ParseExchangeMarketAsync(JToken x) { var symbol = x["market"].ToStringUpperInvariant(); - var (baseCurrency, quoteCurrency) = ExchangeMarketSymbolToCurrencies(symbol); + var (baseCurrency, quoteCurrency) = await ExchangeMarketSymbolToCurrenciesAsync(symbol); return new ExchangeMarket { IsActive = true, @@ -125,7 +125,13 @@ ExchangeMarket ParseSymbol(JToken x) protected override async Task> OnGetMarketSymbolsMetadataAsync() { JToken obj = await MakeJsonRequestAsync("markets"); - return obj["data"].Select(x => ParseSymbol(x)); + JToken data = obj["data"]; + List results = new List(); + foreach (JToken token in data) + { + results.Add(await ParseExchangeMarketAsync(token)); + } + return results; } protected override async Task> OnGetMarketSymbolsAsync() @@ -133,12 +139,11 @@ protected override async Task> OnGetMarketSymbolsAsync() return (await GetMarketSymbolsMetadataAsync()).Select(x => x.MarketSymbol); } - - ExchangeTicker ParseTicker(JToken x) + private async Task ParseTickerAsync(JToken x) { var t = x["ticker"][0]; var symbol = t["symbol"].ToStringUpperInvariant(); - var (baseCurrency, quoteCurrency) = ExchangeMarketSymbolToCurrencies(symbol); + var (baseCurrency, quoteCurrency) = await ExchangeMarketSymbolToCurrenciesAsync(symbol); return new ExchangeTicker { @@ -160,7 +165,7 @@ ExchangeTicker ParseTicker(JToken x) protected override async Task OnGetTickerAsync(string marketSymbol) { JToken obj = await MakeJsonRequestAsync($"/ticker?symbol={marketSymbol}"); - return ParseTicker(obj); + return await ParseTickerAsync(obj); } protected override async Task OnGetOrderBookAsync(string marketSymbol, int maxCount = 100) @@ -387,13 +392,17 @@ protected override async Task OnCancelOrderAsync(string orderId, string marketSy #region WebSocket APIs - protected override IWebSocket OnGetTradesWebSocket(Func, Task> callback, params string[] marketSymbols) + protected override async Task OnGetTradesWebSocketAsync(Func, Task> callback, params string[] marketSymbols) { if (callback == null) + { return null; - else if (marketSymbols == null || marketSymbols.Length == 0) - marketSymbols = GetMarketSymbolsAsync().Sync().ToArray(); - return ConnectWebSocket(string.Empty, async (_socket, msg) => + } + else if (marketSymbols == null || marketSymbols.Length == 0) + { + marketSymbols = (await GetMarketSymbolsAsync()).ToArray(); + } + return await ConnectWebSocketAsync(string.Empty, async (_socket, msg) => { // { // "method": "trades.update", @@ -413,7 +422,7 @@ protected override IWebSocket OnGetTradesWebSocket(Func(msg, 2, msg.Length-2)).ToArray()).ToStringFromUTF8()); + JToken token = JToken.Parse(CryptoUtility.DecompressDeflate((new ArraySegment(msg, 2, msg.Length - 2)).ToArray()).ToStringFromUTF8()); if (token["method"].ToStringLowerInvariant() == "trades.update") { var args = token["params"]; @@ -422,46 +431,54 @@ protected override IWebSocket OnGetTradesWebSocket(Func( - symbol, new ExchangeTrade + if (clean) { - Id = trade["id"].ToStringInvariant(), - Timestamp = CryptoUtility.UnixTimeStampToDateTimeSeconds(0).AddSeconds(trade["time"].ConvertInvariant()), - Price = trade["price"].ConvertInvariant(), - Amount = trade["amount"].ConvertInvariant(), - IsBuy = isbuy, - Flags = flags, - })); + flags |= ExchangeTradeFlags.IsFromSnapshot; + if (i == x.Count - 1) + { + flags |= ExchangeTradeFlags.IsLastFromSnapshot; + } + } + await callback.Invoke(new KeyValuePair + ( + symbol, + new ExchangeTrade + { + Id = trade["id"].ToStringInvariant(), + Timestamp = CryptoUtility.UnixTimeStampToDateTimeSeconds(0).AddSeconds(trade["time"].ConvertInvariant()), + Price = trade["price"].ConvertInvariant(), + Amount = trade["amount"].ConvertInvariant(), + IsBuy = isBuy, + Flags = flags, + } + )); + } } } - }, async (_socket) => + }, + async (_socket2) => { var id = Interlocked.Increment(ref websocketMessageId); - await _socket.SendMessageAsync(new { id, method = "trades.subscribe", @params = marketSymbols } ); + await _socket2.SendMessageAsync(new { id, method = "trades.subscribe", @params = marketSymbols }); }); } - protected override IWebSocket OnGetDeltaOrderBookWebSocket(Action callback, int maxCount = 20, params string[] marketSymbols) + protected override async Task OnGetDeltaOrderBookWebSocketAsync(Action callback, int maxCount = 20, params string[] marketSymbols) { if (callback == null) { return null; } - return ConnectWebSocket(string.Empty, (_socket, msg) => + return await ConnectWebSocketAsync(string.Empty, (_socket, msg) => { //{ // "method": "depth.update", diff --git a/ExchangeSharp/API/Exchanges/Gemini/ExchangeGeminiAPI.cs b/ExchangeSharp/API/Exchanges/Gemini/ExchangeGeminiAPI.cs index f1d4f38b..37d5e066 100644 --- a/ExchangeSharp/API/Exchanges/Gemini/ExchangeGeminiAPI.cs +++ b/ExchangeSharp/API/Exchanges/Gemini/ExchangeGeminiAPI.cs @@ -32,13 +32,13 @@ public ExchangeGeminiAPI() MarketSymbolSeparator = string.Empty; } - private ExchangeVolume ParseVolume(JToken token, string symbol) + private async Task ParseVolumeAsync(JToken token, string symbol) { ExchangeVolume vol = new ExchangeVolume(); JProperty[] props = token.Children().ToArray(); if (props.Length == 3) { - var (baseCurrency, quoteCurrency) = ExchangeMarketSymbolToCurrencies(symbol); + var (baseCurrency, quoteCurrency) = await ExchangeMarketSymbolToCurrenciesAsync(symbol); vol.QuoteCurrency = quoteCurrency.ToUpperInvariant(); vol.QuoteCurrencyVolume = token[quoteCurrency.ToUpperInvariant()].ConvertInvariant(); vol.BaseCurrency = baseCurrency.ToUpperInvariant(); @@ -166,7 +166,7 @@ protected override async Task OnGetTickerAsync(string marketSymb Bid = obj["bid"].ConvertInvariant(), Last = obj["last"].ConvertInvariant() }; - t.Volume = ParseVolume(obj["volume"], marketSymbol); + t.Volume = await ParseVolumeAsync(obj["volume"], marketSymbol); return t; } @@ -284,7 +284,7 @@ protected override async Task OnCancelOrderAsync(string orderId, string marketSy await MakeJsonRequestAsync("/order/cancel", null, new Dictionary{ { "nonce", nonce }, { "order_id", orderId } }); } - protected override IWebSocket OnGetTradesWebSocket(Func, Task> callback, params string[] marketSymbols) + protected override async Task OnGetTradesWebSocketAsync(Func, Task> callback, params string[] marketSymbols) { //{ // "type": "l2_updates", @@ -356,9 +356,9 @@ protected override IWebSocket OnGetTradesWebSocket(Func + return await ConnectWebSocketAsync(BaseUrlWebSocket, messageCallback: async (_socket, msg) => { JToken token = JToken.Parse(msg.ToStringFromUTF8()); if (token["result"].ToStringInvariant() == "error") diff --git a/ExchangeSharp/API/Exchanges/Hitbtc/ExchangeHitbtcAPI.cs b/ExchangeSharp/API/Exchanges/Hitbtc/ExchangeHitbtcAPI.cs index 73b44857..b12a6659 100644 --- a/ExchangeSharp/API/Exchanges/Hitbtc/ExchangeHitbtcAPI.cs +++ b/ExchangeSharp/API/Exchanges/Hitbtc/ExchangeHitbtcAPI.cs @@ -131,7 +131,7 @@ protected override async Task> OnGetMarketSymbolsMet protected override async Task OnGetTickerAsync(string marketSymbol) { JToken obj = await MakeJsonRequestAsync("/public/ticker/" + marketSymbol); - return ParseTicker(obj, marketSymbol); + return await ParseTickerAsync(obj, marketSymbol); } protected override async Task>> OnGetTickersAsync() @@ -141,7 +141,7 @@ protected override async Task>> foreach (JToken token in obj) { string marketSymbol = NormalizeMarketSymbol(token["symbol"].ToStringInvariant()); - tickers.Add(new KeyValuePair(marketSymbol, ParseTicker(token, marketSymbol))); + tickers.Add(new KeyValuePair(marketSymbol, await ParseTickerAsync(token, marketSymbol))); } return tickers; } @@ -165,7 +165,10 @@ protected override async Task> OnGetRecentTradesAsync List trades = new List(); // Putting an arbitrary limit of 10 for 'recent' JToken obj = await MakeJsonRequestAsync("/public/trades/" + marketSymbol + "?limit=10"); - foreach (JToken token in obj) trades.Add(ParseExchangeTrade(token)); + foreach (JToken token in obj) + { + trades.Add(ParseExchangeTrade(token)); + } return trades; } @@ -440,13 +443,13 @@ protected override async Task OnWithdrawAsync(Exchan // working on it. Hitbtc has extensive support for sockets, including trading - protected override IWebSocket OnGetTradesWebSocket(Func, Task> callback, params string[] marketSymbols) + protected override async Task OnGetTradesWebSocketAsync(Func, Task> callback, params string[] marketSymbols) { if (marketSymbols == null || marketSymbols.Length == 0) { - marketSymbols = GetMarketSymbolsAsync().Sync().ToArray(); + marketSymbols = (await GetMarketSymbolsAsync()).ToArray(); } - return ConnectWebSocket(null, messageCallback: async (_socket, msg) => + return await ConnectWebSocketAsync(null, messageCallback: async (_socket, msg) => { JToken token = JToken.Parse(msg.ToStringFromUTF8()); if (token["error"] != null) @@ -591,10 +594,10 @@ public async Task AccountTransfer(string Symbol, decimal Amount, bool ToBa #region Private Functions - private ExchangeTicker ParseTicker(JToken token, string symbol) + private async Task ParseTickerAsync(JToken token, string symbol) { // [ {"ask": "0.050043","bid": "0.050042","last": "0.050042","open": "0.047800","low": "0.047052","high": "0.051679","volume": "36456.720","volumeQuote": "1782.625000","timestamp": "2017-05-12T14:57:19.999Z","symbol": "ETHBTC"} ] - return this.ParseTicker(token, symbol, "ask", "bid", "last", "volume", "volumeQuote", "timestamp", TimestampType.Iso8601); + return await this.ParseTickerAsync(token, symbol, "ask", "bid", "last", "volume", "volumeQuote", "timestamp", TimestampType.Iso8601); } private ExchangeTrade ParseExchangeTrade(JToken token) diff --git a/ExchangeSharp/API/Exchanges/Huobi/ExchangeHuobiAPI.cs b/ExchangeSharp/API/Exchanges/Huobi/ExchangeHuobiAPI.cs index d50a4f91..e0b534dc 100644 --- a/ExchangeSharp/API/Exchanges/Huobi/ExchangeHuobiAPI.cs +++ b/ExchangeSharp/API/Exchanges/Huobi/ExchangeHuobiAPI.cs @@ -42,7 +42,7 @@ public ExchangeHuobiAPI() WebSocketOrderBookType = WebSocketOrderBookType.FullBookAlways; } - public override string ExchangeMarketSymbolToGlobalMarketSymbol(string marketSymbol) + public override Task ExchangeMarketSymbolToGlobalMarketSymbolAsync(string marketSymbol) { if (marketSymbol.Length < 6) { @@ -50,9 +50,9 @@ public override string ExchangeMarketSymbolToGlobalMarketSymbol(string marketSym } else if (marketSymbol.Length == 6) { - return ExchangeMarketSymbolToGlobalMarketSymbolWithSeparator(marketSymbol.Substring(0, 3) + GlobalMarketSymbolSeparator + marketSymbol.Substring(3, 3), GlobalMarketSymbolSeparator); + return ExchangeMarketSymbolToGlobalMarketSymbolWithSeparatorAsync(marketSymbol.Substring(0, 3) + GlobalMarketSymbolSeparator + marketSymbol.Substring(3, 3), GlobalMarketSymbolSeparator); } - return ExchangeMarketSymbolToGlobalMarketSymbolWithSeparator(marketSymbol.Substring(3) + GlobalMarketSymbolSeparator + marketSymbol.Substring(0, 3), GlobalMarketSymbolSeparator); + return ExchangeMarketSymbolToGlobalMarketSymbolWithSeparatorAsync(marketSymbol.Substring(3) + GlobalMarketSymbolSeparator + marketSymbol.Substring(0, 3), GlobalMarketSymbolSeparator); } public override string PeriodSecondsToString(int seconds) @@ -199,7 +199,7 @@ protected override async Task OnGetTickerAsync(string marketSymb }} */ JToken ticker = await MakeJsonRequestAsync("/market/detail/merged?symbol=" + marketSymbol); - return this.ParseTicker(ticker["tick"], marketSymbol, "ask", "bid", "close", "amount", "vol", "ts", TimestampType.UnixMillisecondsDouble, idKey: "id"); + return await this.ParseTickerAsync(ticker["tick"], marketSymbol, "ask", "bid", "close", "amount", "vol", "ts", TimestampType.UnixMillisecondsDouble, idKey: "id"); } protected async override Task>> OnGetTickersAsync() @@ -213,16 +213,16 @@ protected async override Task>> symbol = child["symbol"].ToStringInvariant(); if (markets.ContainsKey(symbol)) { - tickers.Add(new KeyValuePair(symbol, this.ParseTicker(child, symbol, null, null, "close", "amount", "vol"))); + tickers.Add(new KeyValuePair(symbol, await this.ParseTickerAsync(child, symbol, null, null, "close", "amount", "vol"))); } } return tickers; } - protected override IWebSocket OnGetTradesWebSocket(Func, Task> callback, params string[] marketSymbols) + protected override async Task OnGetTradesWebSocketAsync(Func, Task> callback, params string[] marketSymbols) { - return ConnectWebSocket(string.Empty, async (_socket, msg) => + return await ConnectWebSocketAsync(string.Empty, async (_socket, msg) => { /* {"id":"id1","status":"ok","subbed":"market.btcusdt.trade.detail","ts":1527574853489} @@ -287,9 +287,9 @@ protected override IWebSocket OnGetTradesWebSocket(Func callback, int maxCount = 20, params string[] marketSymbols) + protected override async Task OnGetDeltaOrderBookWebSocketAsync(Action callback, int maxCount = 20, params string[] marketSymbols) { - return ConnectWebSocket(string.Empty, async (_socket, msg) => + return await ConnectWebSocketAsync(string.Empty, async (_socket, msg) => { /* {{ diff --git a/ExchangeSharp/API/Exchanges/Kraken/ExchangeKrakenAPI.cs b/ExchangeSharp/API/Exchanges/Kraken/ExchangeKrakenAPI.cs index 3a565a66..20dbb457 100644 --- a/ExchangeSharp/API/Exchanges/Kraken/ExchangeKrakenAPI.cs +++ b/ExchangeSharp/API/Exchanges/Kraken/ExchangeKrakenAPI.cs @@ -47,37 +47,41 @@ public ExchangeKrakenAPI() NonceStyle = NonceStyle.UnixMilliseconds; } - public override string ExchangeMarketSymbolToGlobalMarketSymbol(string marketSymbol) + public override async Task ExchangeMarketSymbolToGlobalMarketSymbolAsync(string marketSymbol) { if (exchangeSymbolToNormalizedSymbol.TryGetValue(marketSymbol, out string normalizedSymbol)) { int pos; if (marketSymbol.StartsWith("WAVES")) + { pos = 5; + } else + { pos = (normalizedSymbol.Length == 6 ? 3 : (normalizedSymbol.Length == 7 ? 4 : throw new InvalidOperationException("Cannot normalize symbol " + normalizedSymbol))); - return base.ExchangeMarketSymbolToGlobalMarketSymbolWithSeparator(normalizedSymbol.Substring(0, pos) + GlobalMarketSymbolSeparator + normalizedSymbol.Substring(pos), GlobalMarketSymbolSeparator); + } + return await base.ExchangeMarketSymbolToGlobalMarketSymbolWithSeparatorAsync(normalizedSymbol.Substring(0, pos) + GlobalMarketSymbolSeparator + normalizedSymbol.Substring(pos), GlobalMarketSymbolSeparator); } # region if the initial fails, we try to use another method - var symbols = GetMarketSymbolsMetadataAsync().Sync().ToList(); + var symbols = (await GetMarketSymbolsMetadataAsync()).ToList(); var symbol = symbols.FirstOrDefault(a => a.MarketSymbol.Replace("/", "").Equals(marketSymbol)); string _marketSymbol = symbol.BaseCurrency + symbol.QuoteCurrency; if (exchangeSymbolToNormalizedSymbol.TryGetValue(_marketSymbol, out normalizedSymbol)) { int pos = (normalizedSymbol.Length == 6 ? 3 : (normalizedSymbol.Length == 7 ? 4 : throw new InvalidOperationException("Cannot normalize symbol " + normalizedSymbol))); - return base.ExchangeMarketSymbolToGlobalMarketSymbolWithSeparator(normalizedSymbol.Substring(0, pos) + GlobalMarketSymbolSeparator + normalizedSymbol.Substring(pos), GlobalMarketSymbolSeparator); + return await base.ExchangeMarketSymbolToGlobalMarketSymbolWithSeparatorAsync(normalizedSymbol.Substring(0, pos) + GlobalMarketSymbolSeparator + normalizedSymbol.Substring(pos), GlobalMarketSymbolSeparator); } #endregion throw new ArgumentException($"Symbol {marketSymbol} not found in Kraken lookup table"); } - public override string GlobalMarketSymbolToExchangeMarketSymbol(string marketSymbol) + public override Task GlobalMarketSymbolToExchangeMarketSymbolAsync(string marketSymbol) { if (normalizedSymbolToExchangeSymbol.TryGetValue(marketSymbol.Replace(GlobalMarketSymbolSeparator.ToString(), string.Empty), out string exchangeSymbol)) { - return exchangeSymbol; + return Task.FromResult(exchangeSymbol); } // not found, reverse the pair @@ -85,7 +89,7 @@ public override string GlobalMarketSymbolToExchangeMarketSymbol(string marketSym marketSymbol = marketSymbol.Substring(idx + 1) + marketSymbol.Substring(0, idx); if (normalizedSymbolToExchangeSymbol.TryGetValue(marketSymbol.Replace(GlobalMarketSymbolSeparator.ToString(), string.Empty), out exchangeSymbol)) { - return exchangeSymbol; + return Task.FromResult(exchangeSymbol); } throw new ArgumentException($"Symbol {marketSymbol} not found in Kraken lookup table"); @@ -235,7 +239,7 @@ private ExchangeOrderResult ParseOrder(string orderId, JToken order) return orderResult; } - private ExchangeOrderResult ParseHistoryOrder(string orderId, JToken order) + private async Task ParseHistoryOrder(string orderId, JToken order) { // //{{ // "ordertxid": "ONKWWN-3LWZ7-4SDZVJ", @@ -253,9 +257,7 @@ private ExchangeOrderResult ParseHistoryOrder(string orderId, JToken order) //} // } - ExchangeOrderResult orderResult = new ExchangeOrderResult { OrderId = orderId }; - - + ExchangeOrderResult orderResult = new ExchangeOrderResult { OrderId = orderId }; orderResult.Result = ExchangeAPIOrderResult.Filled; orderResult.Message = ""; orderResult.OrderDate = CryptoUtility.UnixTimeStampToDateTimeSeconds(order["time"].ConvertInvariant()); @@ -270,7 +272,7 @@ private ExchangeOrderResult ParseHistoryOrder(string orderId, JToken order) orderResult.AmountFilled = order["vol"].ConvertInvariant(); orderResult.FillDate = CryptoUtility.UnixTimeStampToDateTimeSeconds(order["time"].ConvertInvariant()); - string[] pairs = ExchangeMarketSymbolToGlobalMarketSymbol(order["pair"].ToStringInvariant()).Split('-'); + string[] pairs = (await ExchangeMarketSymbolToGlobalMarketSymbolAsync(order["pair"].ToStringInvariant())).Split('-'); orderResult.FeesCurrency = pairs[1]; return orderResult; @@ -332,7 +334,7 @@ private async Task> QueryHistoryOrdersAsync(str { if (normalizedSymbol == null || order.Value["pair"].ToStringInvariant() == symbol.ToUpperInvariant()) { - orders.Add(ParseHistoryOrder(order.Name, order.Value)); + orders.Add(await ParseHistoryOrder(order.Name, order.Value)); } } } @@ -340,7 +342,7 @@ private async Task> QueryHistoryOrdersAsync(str { foreach (JProperty order in result) { - orders.Add(ParseHistoryOrder(order.Name, order.Value)); + orders.Add(await ParseHistoryOrder(order.Name, order.Value)); } } @@ -572,7 +574,7 @@ protected override async Task>> if (ticker == null) { // Some pairs like USDTZUSD are not found, but they can be found using Metadata. - var symbols = GetMarketSymbolsMetadataAsync().Sync().ToList(); + var symbols = (await GetMarketSymbolsMetadataAsync()).ToList(); var symbol = symbols.FirstOrDefault(a => a.MarketSymbol.Replace("/", "").Equals(marketSymbol)); ticker = apiTickers[symbol.BaseCurrency + symbol.QuoteCurrency]; } @@ -580,7 +582,7 @@ protected override async Task>> try { - tickers.Add(new KeyValuePair(marketSymbol, ConvertToExchangeTicker(marketSymbol, ticker))); + tickers.Add(new KeyValuePair(marketSymbol, await ConvertToExchangeTickerAsync(marketSymbol, ticker))); } catch { @@ -594,13 +596,13 @@ protected override async Task OnGetTickerAsync(string marketSymb { JToken apiTickers = await MakeJsonRequestAsync("/0/public/Ticker", null, new Dictionary { { "pair", NormalizeMarketSymbol(marketSymbol) } }); JToken ticker = apiTickers[marketSymbol]; - return ConvertToExchangeTicker(marketSymbol, ticker); + return await ConvertToExchangeTickerAsync(marketSymbol, ticker); } - private ExchangeTicker ConvertToExchangeTicker(string symbol, JToken ticker) + private async Task ConvertToExchangeTickerAsync(string symbol, JToken ticker) { decimal last = ticker["c"][0].ConvertInvariant(); - var (baseCurrency, quoteCurrency) = ExchangeMarketSymbolToCurrencies(symbol); + var (baseCurrency, quoteCurrency) = await ExchangeMarketSymbolToCurrenciesAsync(symbol); return new ExchangeTicker { MarketSymbol = symbol, @@ -828,13 +830,13 @@ protected override async Task OnCancelOrderAsync(string orderId, string marketSy await MakeJsonRequestAsync("/0/private/CancelOrder", null, payload); } - protected override IWebSocket OnGetTradesWebSocket(Func, Task> callback, params string[] marketSymbols) + protected override async Task OnGetTradesWebSocketAsync(Func, Task> callback, params string[] marketSymbols) { if (marketSymbols == null || marketSymbols.Length == 0) { - marketSymbols = GetMarketSymbolsAsync().Sync().ToArray(); + marketSymbols = (await GetMarketSymbolsAsync()).ToArray(); } - return ConnectWebSocket(null, messageCallback: async (_socket, msg) => + return await ConnectWebSocketAsync(null, messageCallback: async (_socket, msg) => { JToken token = JToken.Parse(msg.ToStringFromUTF8()); if (token.Type == JTokenType.Array && token[2].ToStringInvariant() == "trade") diff --git a/ExchangeSharp/API/Exchanges/KuCoin/ExchangeKuCoinAPI.cs b/ExchangeSharp/API/Exchanges/KuCoin/ExchangeKuCoinAPI.cs index 3a7efa81..28b4effa 100644 --- a/ExchangeSharp/API/Exchanges/KuCoin/ExchangeKuCoinAPI.cs +++ b/ExchangeSharp/API/Exchanges/KuCoin/ExchangeKuCoinAPI.cs @@ -165,7 +165,7 @@ protected override async Task OnGetTickerAsync(string marketSymb { //{ "code":"200000","data":{ "sequence":"1550467754497","bestAsk":"0.000277","size":"107.3627934","price":"0.000276","bestBidSize":"2062.7337015","time":1551735305135,"bestBid":"0.0002741","bestAskSize":"223.177"} } JToken token = await MakeJsonRequestAsync("/market/orderbook/level1?symbol=" + marketSymbol); - return this.ParseTicker(token, marketSymbol); + return await this.ParseTickerAsync(token, marketSymbol); } protected override async Task>> OnGetTickersAsync() @@ -203,7 +203,7 @@ protected override async Task>> foreach (JToken tick in token["ticker"]) { string marketSymbol = tick["symbol"].ToStringInvariant(); - tickers.Add(new KeyValuePair(marketSymbol, ParseTickers(tick, marketSymbol))); + tickers.Add(new KeyValuePair(marketSymbol, await ParseTickersAsync(tick, marketSymbol))); } return tickers; } @@ -251,11 +251,13 @@ protected override async Task> OnGetCandlesAsync(strin startDate = startDate ?? CryptoUtility.UtcNow.AddDays(-1); - var payload = new Dictionary(); - payload.Add("symbol", marketSymbol); - payload.Add("type", periodString); - payload.Add("startAt", (long)startDate.Value.UnixTimestampFromDateTimeSeconds()); // the nonce is milliseconds, this is seconds without decimal - payload.Add("endAt", (long)endDate.Value.UnixTimestampFromDateTimeSeconds()); // the nonce is milliseconds, this is seconds without decimal + var payload = new Dictionary + { + { "symbol", marketSymbol }, + { "type", periodString }, + { "startAt", (long)startDate.Value.UnixTimestampFromDateTimeSeconds() }, // the nonce is milliseconds, this is seconds without decimal + { "endAt", (long)endDate.Value.UnixTimestampFromDateTimeSeconds() } // the nonce is milliseconds, this is seconds without decimal + }; var addPayload = CryptoUtility.GetFormForPayload(payload, false); // The results of this Kucoin API call are also a mess. 6 different arrays (c,t,v,h,l,o) with the index of each shared for the candle values @@ -433,37 +435,36 @@ protected override async Task OnWithdrawAsync(Exchan #region Websockets - protected override IWebSocket OnGetTickersWebSocket(Action>> callback, params string[] marketSymbols) + protected override async Task OnGetTickersWebSocketAsync(Action>> callback, params string[] marketSymbols) { var websocketUrlToken = GetWebsocketBulletToken(); - return ConnectWebSocket( - $"?bulletToken={websocketUrlToken}&format=json&resource=api", (_socket, msg) => - { - JToken token = JToken.Parse(msg.ToStringFromUTF8()); - if (token["type"].ToStringInvariant() == "message") - { - var dataToken = token["data"]; - var marketSymbol = dataToken["symbol"].ToStringInvariant(); - ExchangeTicker ticker = this.ParseTicker(dataToken, marketSymbol, "sell", "buy", "lastDealPrice", "vol", "volValue", "datetime", TimestampType.UnixMilliseconds); - callback(new List>() { new KeyValuePair(marketSymbol, ticker) }); - } - - return Task.CompletedTask; - }, async (_socket) => - { - //need to subscribe to tickers one by one - marketSymbols = marketSymbols == null || marketSymbols.Length == 0 ? (await GetMarketSymbolsAsync()).ToArray() : marketSymbols; - var id = CryptoUtility.UtcNow.Ticks; - foreach (var marketSymbol in marketSymbols) - { - // subscribe to tick topic - await _socket.SendMessageAsync(new { id = id++, type = "subscribe", topic = $"/market/{marketSymbol}_TICK" }); - } - } - ); + return await ConnectWebSocketAsync + ( + $"?bulletToken={websocketUrlToken}&format=json&resource=api", async (_socket, msg) => + { + JToken token = JToken.Parse(msg.ToStringFromUTF8()); + if (token["type"].ToStringInvariant() == "message") + { + var dataToken = token["data"]; + var marketSymbol = dataToken["symbol"].ToStringInvariant(); + ExchangeTicker ticker = await this.ParseTickerAsync(dataToken, marketSymbol, "sell", "buy", "lastDealPrice", "vol", "volValue", "datetime", TimestampType.UnixMilliseconds); + callback(new List>() { new KeyValuePair(marketSymbol, ticker) }); + } + }, async (_socket) => + { + //need to subscribe to tickers one by one + marketSymbols = marketSymbols == null || marketSymbols.Length == 0 ? (await GetMarketSymbolsAsync()).ToArray() : marketSymbols; + var id = CryptoUtility.UtcNow.Ticks; + foreach (var marketSymbol in marketSymbols) + { + // subscribe to tick topic + await _socket.SendMessageAsync(new { id = id++, type = "subscribe", topic = $"/market/{marketSymbol}_TICK" }); + } + } + ); } - protected override IWebSocket OnGetTradesWebSocket(Func, Task> callback, params string[] marketSymbols) + protected override async Task OnGetTradesWebSocketAsync(Func, Task> callback, params string[] marketSymbols) { //{ // "id":"5c24c5da03aa673885cd67aa", @@ -485,65 +486,66 @@ protected override IWebSocket OnGetTradesWebSocket(Func + return await ConnectWebSocketAsync + ( + $"?token={websocketUrlToken}", async (_socket, msg) => - { - JToken token = JToken.Parse(msg.ToStringFromUTF8()); - if (token["type"].ToStringInvariant() == "message") - { - var dataToken = token["data"]; - var marketSymbol = token["data"]["symbol"].ToStringInvariant(); - var trade = dataToken.ParseTradeKucoin(amountKey: "size", priceKey: "price", typeKey: "side", - timestampKey: "time", TimestampType.UnixNanoseconds, idKey: "tradeId"); - await callback(new KeyValuePair(marketSymbol, trade)); - } - else if (token["type"].ToStringInvariant() == "error") - { - Logger.Info(token["data"].ToStringInvariant()); - } - }, async (_socket) => + { + JToken token = JToken.Parse(msg.ToStringFromUTF8()); + if (token["type"].ToStringInvariant() == "message") { - List marketSymbolsList = new List(marketSymbols == null || marketSymbols.Length == 0 ? - await GetMarketSymbolsAsync() : marketSymbols); - StringBuilder symbolsSB = new StringBuilder(); - var id = CryptoUtility.UtcNow.Ticks; // just needs to be a "Unique string to mark the request" - int tunnelInt = 0; - while (marketSymbolsList.Count > 0) - { // can only subscribe to 100 symbols per session (started w/ API 2.0) - var nextBatch = marketSymbolsList.GetRange(index: 0, count: 100); - marketSymbolsList.RemoveRange(index: 0, count: 100); - // create a new tunnel - await _socket.SendMessageAsync(new - { - id = id++, - type = "openTunnel", - newTunnelId = $"bt{tunnelInt}", - response = "true", - }); - // wait for tunnel to be created - await Task.Delay(millisecondsDelay: 1000); - // subscribe to Match Execution Data - await _socket.SendMessageAsync(new - { - id = id++, - type = "subscribe", - topic = $"/market/match:{ string.Join(",", nextBatch)}", - tunnelId = $"bt{tunnelInt}", - privateChannel = "false", //Adopted the private channel or not. Set as false by default. - response = "true", - }); - tunnelInt++; - } + var dataToken = token["data"]; + var marketSymbol = token["data"]["symbol"].ToStringInvariant(); + var trade = dataToken.ParseTradeKucoin(amountKey: "size", priceKey: "price", typeKey: "side", + timestampKey: "time", TimestampType.UnixNanoseconds, idKey: "tradeId"); + await callback(new KeyValuePair(marketSymbol, trade)); } - ); + else if (token["type"].ToStringInvariant() == "error") + { + Logger.Info(token["data"].ToStringInvariant()); + } + }, async (_socket) => + { + List marketSymbolsList = new List(marketSymbols == null || marketSymbols.Length == 0 ? + await GetMarketSymbolsAsync() : marketSymbols); + StringBuilder symbolsSB = new StringBuilder(); + var id = CryptoUtility.UtcNow.Ticks; // just needs to be a "Unique string to mark the request" + int tunnelInt = 0; + while (marketSymbolsList.Count > 0) + { // can only subscribe to 100 symbols per session (started w/ API 2.0) + var nextBatch = marketSymbolsList.GetRange(index: 0, count: 100); + marketSymbolsList.RemoveRange(index: 0, count: 100); + // create a new tunnel + await _socket.SendMessageAsync(new + { + id = id++, + type = "openTunnel", + newTunnelId = $"bt{tunnelInt}", + response = "true", + }); + // wait for tunnel to be created + await Task.Delay(millisecondsDelay: 1000); + // subscribe to Match Execution Data + await _socket.SendMessageAsync(new + { + id = id++, + type = "subscribe", + topic = $"/market/match:{ string.Join(",", nextBatch)}", + tunnelId = $"bt{tunnelInt}", + privateChannel = "false", //Adopted the private channel or not. Set as false by default. + response = "true", + }); + tunnelInt++; + } + } + ); } #endregion Websockets #region Private Functions - private ExchangeTicker ParseTicker(JToken token, string symbol) + private async Task ParseTickerAsync(JToken token, string symbol) { // //Get Ticker // { @@ -557,9 +559,10 @@ private ExchangeTicker ParseTicker(JToken token, string symbol) // "time": 1550653727731 //} - return this.ParseTicker(token, symbol, "bestAsk", "bestBid", "price", "bestAskSize"); + return await this.ParseTickerAsync(token, symbol, "bestAsk", "bestBid", "price", "bestAskSize"); } - private ExchangeTicker ParseTickers(JToken token, string symbol) + + private async Task ParseTickersAsync(JToken token, string symbol) { // { // "symbol": "LOOM-BTC", @@ -572,7 +575,7 @@ private ExchangeTicker ParseTickers(JToken token, string symbol) // "vol": "45161.5073", // "last": "0.00001204" //}, - return this.ParseTicker(token, symbol, "sell", "buy", "last", "vol"); + return await this.ParseTickerAsync(token, symbol, "sell", "buy", "last", "vol"); } // { "oid": "59e59b279bd8d31d093d956e", "type": "SELL", "userOid": null, "coinType": "KCS", "coinTypePair": "BTC", "direction": "SELL","price": 0.1,"dealAmount": 0,"pendingAmount": 100, "createdAt": 1508219688000, "updatedAt": 1508219688000 } diff --git a/ExchangeSharp/API/Exchanges/Livecoin/ExchangeLivecoinAPI.cs b/ExchangeSharp/API/Exchanges/Livecoin/ExchangeLivecoinAPI.cs index ea294e12..3fa46dc9 100644 --- a/ExchangeSharp/API/Exchanges/Livecoin/ExchangeLivecoinAPI.cs +++ b/ExchangeSharp/API/Exchanges/Livecoin/ExchangeLivecoinAPI.cs @@ -106,14 +106,17 @@ protected override async Task> OnGetMarketSymbolsMet protected override async Task OnGetTickerAsync(string marketSymbol) { JToken token = await MakeJsonRequestAsync("/exchange/ticker?currencyPair=" + marketSymbol.UrlEncode()); - return ParseTicker(token); + return await ParseTickerAsync(token); } protected override async Task>> OnGetTickersAsync() { List> tickers = new List>(); JToken token = await MakeJsonRequestAsync("/exchange/ticker"); - foreach (JToken tick in token) tickers.Add(new KeyValuePair(tick["symbol"].ToStringInvariant(), ParseTicker(tick))); + foreach (JToken tick in token) + { + tickers.Add(new KeyValuePair(tick["symbol"].ToStringInvariant(), await ParseTickerAsync(tick))); + } return tickers; } @@ -343,11 +346,11 @@ protected override async Task OnWithdrawAsync(Exchan #region Private Functions - private ExchangeTicker ParseTicker(JToken token) + private async Task ParseTickerAsync(JToken token) { // [{"symbol": "LTC/BTC","last": 0.00805061,"high": 0.00813633,"low": 0.00784855,"volume": 14729.48452951,"vwap": 0.00795126,"max_bid": 0.00813633,"min_ask": 0.00784855,"best_bid": 0.00798,"best_ask": 0.00811037}, ... ] string marketSymbol = token["symbol"].ToStringInvariant(); - return this.ParseTicker(token, marketSymbol, "best_ask", "best_bid", "last", "volume"); + return await this.ParseTickerAsync(token, marketSymbol, "best_ask", "best_bid", "last", "volume"); } private ExchangeTrade ParseTrade(JToken token) diff --git a/ExchangeSharp/API/Exchanges/OKGroup/ExchangeOKCoinAPI.cs b/ExchangeSharp/API/Exchanges/OKGroup/ExchangeOKCoinAPI.cs index 7976e594..e6033cec 100644 --- a/ExchangeSharp/API/Exchanges/OKGroup/ExchangeOKCoinAPI.cs +++ b/ExchangeSharp/API/Exchanges/OKGroup/ExchangeOKCoinAPI.cs @@ -20,7 +20,7 @@ public sealed partial class ExchangeOKCoinAPI : OKGroupCommon public override string BaseUrlV2 { get; set; } = "https://www.okcoin.com/v2/spot"; public override string BaseUrlV3 { get; set; } = "https://www.okcoin.com/api"; public override string BaseUrlWebSocket { get; set; } = "wss://real.okcoin.com:10442/ws/v3"; - protected override bool isFuturesAndSwapEnabled { get; } = false; + protected override bool IsFuturesAndSwapEnabled { get; } = false; } public partial class ExchangeName { public const string OKCoin = "OKCoin"; } diff --git a/ExchangeSharp/API/Exchanges/OKGroup/ExchangeOKExAPI.cs b/ExchangeSharp/API/Exchanges/OKGroup/ExchangeOKExAPI.cs index 4924ed37..35176a87 100644 --- a/ExchangeSharp/API/Exchanges/OKGroup/ExchangeOKExAPI.cs +++ b/ExchangeSharp/API/Exchanges/OKGroup/ExchangeOKExAPI.cs @@ -20,7 +20,7 @@ public sealed partial class ExchangeOKExAPI : OKGroupCommon public override string BaseUrlV2 { get; set; } = "https://www.okex.com/v2/spot"; public override string BaseUrlV3 { get; set; } = "https://www.okex.com/api"; public override string BaseUrlWebSocket { get; set; } = "wss://real.okex.com:10442/ws/v3"; - protected override bool isFuturesAndSwapEnabled { get; } = true; + protected override bool IsFuturesAndSwapEnabled { get; } = true; } public partial class ExchangeName { public const string OKEx = "OKEx"; } diff --git a/ExchangeSharp/API/Exchanges/OKGroup/OKGroupCommon.cs b/ExchangeSharp/API/Exchanges/OKGroup/OKGroupCommon.cs index b8a87018..af4b32a0 100644 --- a/ExchangeSharp/API/Exchanges/OKGroup/OKGroupCommon.cs +++ b/ExchangeSharp/API/Exchanges/OKGroup/OKGroupCommon.cs @@ -31,7 +31,12 @@ public abstract partial class OKGroupCommon : ExchangeAPI /// Base URL V3 for the OK group API /// public abstract string BaseUrlV3 { get; set; } - protected abstract bool isFuturesAndSwapEnabled { get; } + + /// + /// Are futures and swap enabled? + /// + protected abstract bool IsFuturesAndSwapEnabled { get; } + /// /// China time to utc, no DST correction needed /// @@ -136,7 +141,7 @@ protected override async Task> OnGetMarketSymbolsMet List markets = new List(); parseMarketSymbolTokens(await MakeJsonRequestAsync( "/spot/v3/instruments", BaseUrlV3)); - if (isFuturesAndSwapEnabled) + if (IsFuturesAndSwapEnabled) { parseMarketSymbolTokens(await MakeJsonRequestAsync( "/futures/v3/instruments", BaseUrlV3)); @@ -169,30 +174,31 @@ protected override async Task OnGetTickerAsync(string marketSymb { // V3: /api/swap/v3/instruments/BTC-USD-SWAP/ticker var data = await MakeRequestOkexAsync(marketSymbol, "/swap/v3/instruments/$SYMBOL$/ticker", baseUrl: BaseUrlV3); - return ParseTickerV3(data.Item2, data.Item1); + return await ParseTickerV3Async(data.Item2, data.Item1); } protected override async Task>> OnGetTickersAsync() - {// V3: /api/spot/v3/instruments/ticker (/api is already included in base URL) + { + // V3: /api/spot/v3/instruments/ticker (/api is already included in base URL) List> tickers = new List>(); parseData(await MakeRequestOkexAsync(null, "/spot/v3/instruments/ticker", BaseUrlV3)); - if (isFuturesAndSwapEnabled) + if (IsFuturesAndSwapEnabled) { parseData(await MakeRequestOkexAsync(null, "/futures/v3/instruments/ticker", BaseUrlV3)); parseData(await MakeRequestOkexAsync(null, "/swap/v3/instruments/ticker", BaseUrlV3)); } - void parseData(Tuple data) + async void parseData(Tuple data) { foreach (JToken token in data.Item1) { var marketSymbol = token["instrument_id"].ToStringInvariant(); - tickers.Add(new KeyValuePair(marketSymbol, ParseTickerV3(marketSymbol, token))); + tickers.Add(new KeyValuePair(marketSymbol, await ParseTickerV3Async(marketSymbol, token))); } } return tickers; } - protected override IWebSocket OnGetTradesWebSocket(Func, Task> callback, params string[] marketSymbols) + protected override async Task OnGetTradesWebSocketAsync(Func, Task> callback, params string[] marketSymbols) { /* spot request: @@ -216,19 +222,19 @@ protected override IWebSocket OnGetTradesWebSocket(Func - { - await AddMarketSymbolsToChannel(_socket, "/trade:{0}", marketSymbols); - }, async (_socket, symbol, sArray, token) => - { - ExchangeTrade trade = token.ParseTrade(amountKey: "size", priceKey: "price", - typeKey: "side", timestampKey: "timestamp", - timestampType: TimestampType.Iso8601, idKey: "trade_id"); - await callback(new KeyValuePair(symbol, trade)); - }); + return await ConnectWebSocketOkexAsync(async (_socket) => + { + await AddMarketSymbolsToChannel(_socket, "/trade:{0}", marketSymbols); + }, async (_socket, symbol, sArray, token) => + { + ExchangeTrade trade = token.ParseTrade(amountKey: "size", priceKey: "price", + typeKey: "side", timestampKey: "timestamp", + timestampType: TimestampType.Iso8601, idKey: "trade_id"); + await callback(new KeyValuePair(symbol, trade)); + }); } - protected override IWebSocket OnGetDeltaOrderBookWebSocket(Action callback, int maxCount = 20, params string[] marketSymbols) + protected override async Task OnGetDeltaOrderBookWebSocketAsync(Action callback, int maxCount = 20, params string[] marketSymbols) { /* request: @@ -266,7 +272,7 @@ protected override IWebSocket OnGetDeltaOrderBookWebSocket(Action + return await ConnectWebSocketOkexAsync(async (_socket) => { marketSymbols = await AddMarketSymbolsToChannel(_socket, "/depth:{0}", marketSymbols); }, (_socket, symbol, sArray, token) => @@ -483,19 +489,19 @@ protected override async Task> OnGetOpenOrderDe #region Private Functions - private ExchangeTicker ParseTicker(string symbol, JToken data) + private async Task ParseTickerAsync(string symbol, JToken data) { //{"date":"1518043621","ticker":{"high":"0.01878000","vol":"1911074.97335534","last":"0.01817627","low":"0.01813515","buy":"0.01817626","sell":"0.01823447"}} - return this.ParseTicker(data["ticker"], symbol, "sell", "buy", "last", "vol", null, "date", TimestampType.UnixSeconds); + return await this.ParseTickerAsync(data["ticker"], symbol, "sell", "buy", "last", "vol", null, "date", TimestampType.UnixSeconds); } - private ExchangeTicker ParseTickerV2(string symbol, JToken ticker) + private async Task ParseTickerV2Async(string symbol, JToken ticker) { // {"buy":"0.00001273","change":"-0.00000009","changePercentage":"-0.70%","close":"0.00001273","createdDate":1527355333053,"currencyId":535,"dayHigh":"0.00001410","dayLow":"0.00001174","high":"0.00001410","inflows":"19.52673814","last":"0.00001273","low":"0.00001174","marketFrom":635,"name":{},"open":"0.00001282","outflows":"52.53715678","productId":535,"sell":"0.00001284","symbol":"you_btc","volume":"5643177.15601228"} - return this.ParseTicker(ticker, symbol, "sell", "buy", "last", "volume", null, "createdDate", TimestampType.UnixMilliseconds); + return await this.ParseTickerAsync(ticker, symbol, "sell", "buy", "last", "volume", null, "createdDate", TimestampType.UnixMilliseconds); } - private ExchangeTicker ParseTickerV3(string symbol, JToken ticker) + private async Task ParseTickerV3Async(string symbol, JToken ticker) { /* [ @@ -532,7 +538,7 @@ private ExchangeTicker ParseTickerV3(string symbol, JToken ticker) } ] */ - return this.ParseTicker(ticker, symbol, askKey: "best_ask", bidKey: "best_bid", lastKey: "last", + return await this.ParseTickerAsync(ticker, symbol, askKey: "best_ask", bidKey: "best_bid", lastKey: "last", baseVolumeKey: "base_volume_24h", quoteVolumeKey: "quote_volume_24h", timestampKey: "timestamp", timestampType: TimestampType.Iso8601); } @@ -633,10 +639,10 @@ private ExchangeOrderResult ParseOrder(JToken token) return result; } - private IWebSocket ConnectWebSocketOkex(Func connected, Func callback, int symbolArrayIndex = 3) + private Task ConnectWebSocketOkexAsync(Func connected, Func callback, int symbolArrayIndex = 3) { Timer pingTimer = null; - return ConnectWebSocket(url: string.Empty, messageCallback: async (_socket, msg) => + return ConnectWebSocketAsync(url: string.Empty, messageCallback: async (_socket, msg) => { // https://github.com/okcoin-okex/API-docs-OKEx.com/blob/master/README-en.md // All the messages returning from WebSocket API will be optimized by Deflate compression @@ -683,9 +689,9 @@ private IWebSocket ConnectWebSocketOkex(Func connected, Func connected, Func callback, int symbolArrayIndex = 3) + private Task ConnectPrivateWebSocketOkexAsync(Func connected, Func callback, int symbolArrayIndex = 3) { - return ConnectWebSocketOkex(async (_socket) => + return ConnectWebSocketOkexAsync(async (_socket) => { await _socket.SendMessageAsync(GetAuthForWebSocket()); }, async (_socket, symbol, sArray, token) => @@ -705,7 +711,7 @@ private async Task AddMarketSymbolsToChannel(IWebSocket socket, string { if (marketSymbols == null || marketSymbols.Length == 0) { - marketSymbols = GetMarketSymbolsAsync().Sync().ToArray(); + marketSymbols = (await GetMarketSymbolsAsync()).ToArray(); } var spotSymbols = marketSymbols.Where(ms => ms.Split('-').Length == 2); var futureSymbols = marketSymbols.Where( diff --git a/ExchangeSharp/API/Exchanges/Poloniex/ExchangePoloniexAPI.cs b/ExchangeSharp/API/Exchanges/Poloniex/ExchangePoloniexAPI.cs index e2060f8e..d2b0033d 100644 --- a/ExchangeSharp/API/Exchanges/Poloniex/ExchangePoloniexAPI.cs +++ b/ExchangeSharp/API/Exchanges/Poloniex/ExchangePoloniexAPI.cs @@ -226,7 +226,7 @@ private void ParseCompletedOrderDetails(List orders, JToken } } - private ExchangeTicker ParseTickerWebSocket(string symbol, JToken token) + private async Task ParseTickerWebSocketAsync(string symbol, JToken token) { /* last: args[1], @@ -239,7 +239,7 @@ private ExchangeTicker ParseTickerWebSocket(string symbol, JToken token) high24hr: args[8], low24hr: args[9] */ - return this.ParseTicker(token, symbol, 2, 3, 1, 5, 6); + return await this.ParseTickerAsync(token, symbol, 2, 3, 1, 5, 6); } protected override async Task ProcessRequestAsync(IHttpWebRequest request, Dictionary payload) @@ -370,16 +370,16 @@ protected override async Task>> string marketSymbol = prop.Name; JToken values = prop.Value; //NOTE: Poloniex uses the term "caseVolume" when referring to the QuoteCurrencyVolume - ExchangeTicker ticker = this.ParseTicker(values, marketSymbol, "lowestAsk", "highestBid", "last", "quoteVolume", "baseVolume", idKey: "id"); + ExchangeTicker ticker = await this.ParseTickerAsync(values, marketSymbol, "lowestAsk", "highestBid", "last", "quoteVolume", "baseVolume", idKey: "id"); tickers.Add(new KeyValuePair(marketSymbol, ticker)); } return tickers; } - protected override IWebSocket OnGetTickersWebSocket(Action>> callback, params string[] symbols) + protected override async Task OnGetTickersWebSocketAsync(Action>> callback, params string[] symbols) { Dictionary idsToSymbols = new Dictionary(); - return ConnectWebSocket(string.Empty, (_socket, msg) => + return await ConnectWebSocketAsync(string.Empty, async (_socket, msg) => { JToken token = JToken.Parse(msg.ToStringFromUTF8()); if (token[0].ConvertInvariant() == 1002) @@ -389,11 +389,10 @@ protected override IWebSocket OnGetTickersWebSocket(Action> { - new KeyValuePair(symbol, ParseTickerWebSocket(symbol, array)) + new KeyValuePair(symbol, await ParseTickerWebSocketAsync(symbol, array)) }); } } - return Task.CompletedTask; }, async (_socket) => { var tickers = await GetTickersAsync(); @@ -406,16 +405,17 @@ protected override IWebSocket OnGetTickersWebSocket(Action, Task> callback, params string[] marketSymbols) + protected override async Task OnGetTradesWebSocketAsync(Func, Task> callback, params string[] marketSymbols) { Dictionary> messageIdToSymbol = new Dictionary>(); - return ConnectWebSocket(string.Empty, async (_socket, msg) => + return await ConnectWebSocketAsync(string.Empty, async (_socket, msg) => { JToken token = JToken.Parse(msg.ToStringFromUTF8()); int msgId = token[0].ConvertInvariant(); if (msgId == 1010 || token.Count() == 2) // "[7,2]" - { // this is a heartbeat message + { + // this is a heartbeat message return; } @@ -463,10 +463,10 @@ protected override IWebSocket OnGetTradesWebSocket(Func callback, int maxCount = 20, params string[] marketSymbols) + protected override async Task OnGetDeltaOrderBookWebSocketAsync(Action callback, int maxCount = 20, params string[] marketSymbols) { Dictionary> messageIdToSymbol = new Dictionary>(); - return ConnectWebSocket(string.Empty, (_socket, msg) => + return await ConnectWebSocketAsync(string.Empty, (_socket, msg) => { JToken token = JToken.Parse(msg.ToStringFromUTF8()); int msgId = token[0].ConvertInvariant(); diff --git a/ExchangeSharp/API/Exchanges/TuxExchange/ExchangeTuxExchangeAPI.cs b/ExchangeSharp/API/Exchanges/TuxExchange/ExchangeTuxExchangeAPI.cs index 60fa4c4c..fcbe7ed0 100644 --- a/ExchangeSharp/API/Exchanges/TuxExchange/ExchangeTuxExchangeAPI.cs +++ b/ExchangeSharp/API/Exchanges/TuxExchange/ExchangeTuxExchangeAPI.cs @@ -124,7 +124,7 @@ protected override async Task>> JToken token = await MakeJsonRequestAsync("/api?method=getticker"); foreach (JProperty prop in token) { - tickers.Add(new KeyValuePair(prop.Name, this.ParseTicker(prop.First, prop.Name, "lowestAsk", "highestBid", "last", "baseVolume", "quoteVolume", idKey: "id"))); + tickers.Add(new KeyValuePair(prop.Name, await this.ParseTickerAsync(prop.First, prop.Name, "lowestAsk", "highestBid", "last", "baseVolume", "quoteVolume", idKey: "id"))); } return tickers; } diff --git a/ExchangeSharp/API/Exchanges/Yobit/ExchangeYobitAPI.cs b/ExchangeSharp/API/Exchanges/Yobit/ExchangeYobitAPI.cs index a947e8d0..2f6fba12 100644 --- a/ExchangeSharp/API/Exchanges/Yobit/ExchangeYobitAPI.cs +++ b/ExchangeSharp/API/Exchanges/Yobit/ExchangeYobitAPI.cs @@ -105,7 +105,10 @@ protected override async Task> OnGetMarketSymbolsMet protected override async Task OnGetTickerAsync(string marketSymbol) { JToken token = await MakeJsonRequestAsync("/ticker/" + NormalizeMarketSymbol(marketSymbol), null, null, "POST"); - if (token != null && token.HasValues) return ParseTicker(token.First as JProperty); + if (token != null && token.HasValues) + { + return await ParseTickerAsync(token.First as JProperty); + } return null; } @@ -331,11 +334,11 @@ protected override async Task OnWithdrawAsync(Exchan #region Private Functions - private ExchangeTicker ParseTicker(JProperty prop) + private async Task ParseTickerAsync(JProperty prop) { // "ltc_btc":{ "high":105.41,"low":104.67,"avg":105.04,"vol":43398.22251455,"vol_cur":4546.26962359,"last":105.11,"buy":104.2,"sell":105.11,"updated":1418654531 } string marketSymbol = prop.Name.ToUpperInvariant(); - return this.ParseTicker(prop.First, marketSymbol, "sell", "buy", "last", "vol", "vol_cur", "updated", TimestampType.UnixSeconds); + return await this.ParseTickerAsync(prop.First, marketSymbol, "sell", "buy", "last", "vol", "vol_cur", "updated", TimestampType.UnixSeconds); } private ExchangeTrade ParseTrade(JToken prop) diff --git a/ExchangeSharp/API/Exchanges/ZBcom/ExchangeZBcomAPI.cs b/ExchangeSharp/API/Exchanges/ZBcom/ExchangeZBcomAPI.cs index df00c1d1..a54ad463 100644 --- a/ExchangeSharp/API/Exchanges/ZBcom/ExchangeZBcomAPI.cs +++ b/ExchangeSharp/API/Exchanges/ZBcom/ExchangeZBcomAPI.cs @@ -45,18 +45,18 @@ private async Task> MakeRequestZBcomAsync(string marketSym return new Tuple(obj, marketSymbol); } - private ExchangeTicker ParseTicker(string symbol, JToken data) + private async Task ParseTickerAsync(string symbol, JToken data) { // {{"ticker":{"vol":"18202.5979","last":"6698.2","sell":"6703.21","buy":"6693.2","high":"6757.69","low":"6512.69"},"date":"1531822098779"}} - ExchangeTicker ticker = this.ParseTicker(data["ticker"], symbol, "sell", "buy", "last", "vol"); + ExchangeTicker ticker = await this.ParseTickerAsync(data["ticker"], symbol, "sell", "buy", "last", "vol"); ticker.Volume.Timestamp = CryptoUtility.UnixTimeStampToDateTimeMilliseconds(data["date"].ConvertInvariant()); return ticker; } - private ExchangeTicker ParseTickerV2(string symbol, JToken data) + private async Task ParseTickerV2Async(string symbol, JToken data) { //{"hpybtc":{ "vol":"500450.0","last":"0.0000013949","sell":"0.0000013797","buy":"0.0000012977","high":"0.0000013949","low":"0.0000011892"}} - return this.ParseTicker(data.First, symbol, "sell", "buy", "last", "vol"); + return await this.ParseTickerAsync(data.First, symbol, "sell", "buy", "last", "vol"); } protected override async Task> OnGetMarketSymbolsAsync() @@ -113,7 +113,7 @@ protected override async Task> OnGetMarketSymbolsMet protected override async Task OnGetTickerAsync(string marketSymbol) { var data = await MakeRequestZBcomAsync(marketSymbol, "/ticker?market=$SYMBOL$"); - return ParseTicker(data.Item2, data.Item1); + return await ParseTickerAsync(data.Item2, data.Item1); } protected override async Task>> OnGetTickersAsync() @@ -143,19 +143,19 @@ protected override async Task>> //for some reason when returning tickers, the api doesn't include the symbol separator like it does everywhere else so we need to convert it to the correct format if (symbolLookup.Value.TryGetValue(token.Path, out string marketSymbol)) { - tickers.Add(new KeyValuePair(marketSymbol, ParseTickerV2(marketSymbol, token))); + tickers.Add(new KeyValuePair(marketSymbol, await ParseTickerV2Async(marketSymbol, token))); } } return tickers; } - protected override IWebSocket OnGetTradesWebSocket(Func, Task> callback, params string[] marketSymbols) + protected override async Task OnGetTradesWebSocketAsync(Func, Task> callback, params string[] marketSymbols) { if (marketSymbols == null || marketSymbols.Length == 0) { - marketSymbols = GetMarketSymbolsAsync().Sync().ToArray(); + marketSymbols = (await GetMarketSymbolsAsync()).ToArray(); } - return ConnectWebSocket(string.Empty, async (_socket, msg) => + return await ConnectWebSocketAsync(string.Empty, async (_socket, msg) => { JToken token = JToken.Parse(msg.ToStringFromUTF8()); if (token["dataType"].ToStringInvariant() == "trades") diff --git a/ExchangeSharp/API/Exchanges/_Base/ExchangeAPI.cs b/ExchangeSharp/API/Exchanges/_Base/ExchangeAPI.cs index e7639f76..84a958cd 100644 --- a/ExchangeSharp/API/Exchanges/_Base/ExchangeAPI.cs +++ b/ExchangeSharp/API/Exchanges/_Base/ExchangeAPI.cs @@ -109,12 +109,12 @@ await GetHistoricalTradesAsync((e) => protected virtual Task> OnGetMarginAmountsAvailableToTradeAsync(bool includeZeroBalances) => throw new NotImplementedException(); protected virtual Task OnGetOpenPositionAsync(string marketSymbol) => throw new NotImplementedException(); protected virtual Task OnCloseMarginPositionAsync(string marketSymbol) => throw new NotImplementedException(); - protected virtual IWebSocket OnGetTickersWebSocket(Action>> tickers, params string[] marketSymbols) => throw new NotImplementedException(); - protected virtual IWebSocket OnGetTradesWebSocket(Func, Task> callback, params string[] marketSymbols) => throw new NotImplementedException(); - protected virtual IWebSocket OnGetDeltaOrderBookWebSocket(Action callback, int maxCount = 20, params string[] marketSymbols) => throw new NotImplementedException(); - protected virtual IWebSocket OnGetOrderDetailsWebSocket(Action callback) => throw new NotImplementedException(); - protected virtual IWebSocket OnGetCompletedOrderDetailsWebSocket(Action callback) => throw new NotImplementedException(); - protected virtual IWebSocket OnUserDataWebSocket(Action callback, string listenKey) => throw new NotImplementedException(); + protected virtual Task OnGetTickersWebSocketAsync(Action>> tickers, params string[] marketSymbols) => throw new NotImplementedException(); + protected virtual Task OnGetTradesWebSocketAsync(Func, Task> callback, params string[] marketSymbols) => throw new NotImplementedException(); + protected virtual Task OnGetDeltaOrderBookWebSocketAsync(Action callback, int maxCount = 20, params string[] marketSymbols) => throw new NotImplementedException(); + protected virtual Task OnGetOrderDetailsWebSocketAsync(Action callback) => throw new NotImplementedException(); + protected virtual Task OnGetCompletedOrderDetailsWebSocketAsync(Action callback) => throw new NotImplementedException(); + protected virtual Task OnUserDataWebSocketAsync(Action callback, string listenKey) => throw new NotImplementedException(); #endregion API implementation @@ -154,7 +154,7 @@ protected async Task ClampOrderQuantity(string marketSymbol, decimal ou /// Exchange market symbol /// Separator /// Global symbol - protected string ExchangeMarketSymbolToGlobalMarketSymbolWithSeparator(string marketSymbol, char separator = GlobalMarketSymbolSeparator) + protected async Task ExchangeMarketSymbolToGlobalMarketSymbolWithSeparatorAsync(string marketSymbol, char separator = GlobalMarketSymbolSeparator) { if (string.IsNullOrEmpty(marketSymbol)) { @@ -163,9 +163,9 @@ protected string ExchangeMarketSymbolToGlobalMarketSymbolWithSeparator(string ma string[] pieces = marketSymbol.Split(separator); if (MarketSymbolIsReversed) { - return ExchangeCurrencyToGlobalCurrency(pieces[0]).ToUpperInvariant() + GlobalMarketSymbolSeparator + ExchangeCurrencyToGlobalCurrency(pieces[1]).ToUpperInvariant(); + return (await ExchangeCurrencyToGlobalCurrencyAsync(pieces[0])).ToUpperInvariant() + GlobalMarketSymbolSeparator + (await ExchangeCurrencyToGlobalCurrencyAsync(pieces[1])).ToUpperInvariant(); } - return ExchangeCurrencyToGlobalCurrency(pieces[1]).ToUpperInvariant() + GlobalMarketSymbolSeparator + ExchangeCurrencyToGlobalCurrency(pieces[0]).ToUpperInvariant(); + return (await ExchangeCurrencyToGlobalCurrencyAsync(pieces[1])).ToUpperInvariant() + GlobalMarketSymbolSeparator + (await ExchangeCurrencyToGlobalCurrencyAsync(pieces[0])).ToUpperInvariant(); } /// @@ -341,14 +341,14 @@ public static IExchangeAPI[] GetExchangeAPIs() /// /// Exchange currency /// Global currency - public string ExchangeCurrencyToGlobalCurrency(string currency) + public Task ExchangeCurrencyToGlobalCurrencyAsync(string currency) { currency = (currency ?? string.Empty); foreach (KeyValuePair kv in ExchangeGlobalCurrencyReplacements[GetType()]) { currency = currency.Replace(kv.Key, kv.Value); } - return currency.ToUpperInvariant(); + return Task.FromResult(currency.ToUpperInvariant()); } /// @@ -399,7 +399,7 @@ public virtual string NormalizeMarketSymbol(string marketSymbol) /// /// Exchange symbol /// Global symbol - public virtual string ExchangeMarketSymbolToGlobalMarketSymbol(string marketSymbol) + public virtual async Task ExchangeMarketSymbolToGlobalMarketSymbolAsync(string marketSymbol) { string modifiedMarketSymbol = marketSymbol; char separator; @@ -408,7 +408,7 @@ public virtual string ExchangeMarketSymbolToGlobalMarketSymbol(string marketSymb if (string.IsNullOrWhiteSpace(MarketSymbolSeparator)) { // we must look it up via metadata, most often this call will be cached and fast - ExchangeMarket marketSymbolMetadata = GetExchangeMarketFromCacheAsync(marketSymbol).Sync(); + ExchangeMarket marketSymbolMetadata = await GetExchangeMarketFromCacheAsync(marketSymbol); if (marketSymbolMetadata == null) { throw new InvalidDataException($"No market symbol metadata returned or unable to find symbol metadata for {marketSymbol}"); @@ -420,7 +420,7 @@ public virtual string ExchangeMarketSymbolToGlobalMarketSymbol(string marketSymb { separator = MarketSymbolSeparator[0]; } - return ExchangeMarketSymbolToGlobalMarketSymbolWithSeparator(modifiedMarketSymbol, separator); + return await ExchangeMarketSymbolToGlobalMarketSymbolWithSeparatorAsync(modifiedMarketSymbol, separator); } /// @@ -440,7 +440,7 @@ public virtual string CurrenciesToExchangeMarketSymbol(string baseCurrency, stri /// /// Market symbol /// Base and quote currency - public virtual (string baseCurrency, string quoteCurrency) ExchangeMarketSymbolToCurrencies(string marketSymbol) + public virtual async Task<(string baseCurrency, string quoteCurrency)> ExchangeMarketSymbolToCurrenciesAsync(string marketSymbol) { marketSymbol.ThrowIfNullOrWhitespace(nameof(marketSymbol)); @@ -450,7 +450,7 @@ public virtual (string baseCurrency, string quoteCurrency) ExchangeMarketSymbolT try { // we must look it up via metadata, most often this call will be cached and fast - ExchangeMarket marketSymbolMetadata = GetExchangeMarketFromCacheAsync(marketSymbol).Sync(); + ExchangeMarket marketSymbolMetadata = await GetExchangeMarketFromCacheAsync(marketSymbol); if (marketSymbolMetadata == null) { throw new InvalidDataException($"No market symbol metadata returned or unable to find symbol metadata for {marketSymbol}"); @@ -472,7 +472,7 @@ public virtual (string baseCurrency, string quoteCurrency) ExchangeMarketSymbolT /// /// Global market symbol /// Exchange market symbol - public virtual string GlobalMarketSymbolToExchangeMarketSymbol(string marketSymbol) + public virtual Task GlobalMarketSymbolToExchangeMarketSymbolAsync(string marketSymbol) { if (string.IsNullOrWhiteSpace(marketSymbol)) { @@ -491,7 +491,7 @@ public virtual string GlobalMarketSymbolToExchangeMarketSymbol(string marketSymb { marketSymbol = GlobalCurrencyToExchangeCurrency(marketSymbol.Substring(pos + 1)) + MarketSymbolSeparator + GlobalCurrencyToExchangeCurrency(marketSymbol.Substring(0, pos)); } - return (MarketSymbolIsUppercase ? marketSymbol.ToUpperInvariant() : marketSymbol.ToLowerInvariant()); + return Task.FromResult(MarketSymbolIsUppercase ? marketSymbol.ToUpperInvariant() : marketSymbol.ToLowerInvariant()); } /// @@ -851,10 +851,10 @@ public virtual async Task CloseMarginPosition /// Callback /// /// Web socket, call Dispose to close - public virtual IWebSocket GetTickersWebSocket(Action>> callback, params string[] symbols) + public virtual Task GetTickersWebSocketAsync(Action>> callback, params string[] symbols) { callback.ThrowIfNull(nameof(callback), "Callback must not be null"); - return OnGetTickersWebSocket(callback, symbols); + return OnGetTickersWebSocketAsync(callback, symbols); } /// @@ -863,10 +863,10 @@ public virtual IWebSocket GetTickersWebSocket(ActionCallback (symbol and trade) /// Market Symbols /// Web socket, call Dispose to close - public virtual IWebSocket GetTradesWebSocket(Func, Task> callback, params string[] marketSymbols) + public virtual Task GetTradesWebSocketAsync(Func, Task> callback, params string[] marketSymbols) { callback.ThrowIfNull(nameof(callback), "Callback must not be null"); - return OnGetTradesWebSocket(callback, marketSymbols); + return OnGetTradesWebSocketAsync(callback, marketSymbols); } /// @@ -876,10 +876,10 @@ public virtual IWebSocket GetTradesWebSocket(FuncMax count of bids and asks - not all exchanges will honor this parameter /// Market symbols or null/empty for all of them (if supported) /// Web socket, call Dispose to close - public virtual IWebSocket GetDeltaOrderBookWebSocket(Action callback, int maxCount = 20, params string[] marketSymbols) + public virtual Task GetDeltaOrderBookWebSocketAsync(Action callback, int maxCount = 20, params string[] marketSymbols) { callback.ThrowIfNull(nameof(callback), "Callback must not be null"); - return OnGetDeltaOrderBookWebSocket(callback, maxCount, marketSymbols); + return OnGetDeltaOrderBookWebSocketAsync(callback, maxCount, marketSymbols); } /// @@ -887,10 +887,10 @@ public virtual IWebSocket GetDeltaOrderBookWebSocket(Action c /// /// Callback /// Web socket, call Dispose to close - public virtual IWebSocket GetOrderDetailsWebSocket(Action callback) + public virtual Task GetOrderDetailsWebSocketAsync(Action callback) { callback.ThrowIfNull(nameof(callback), "Callback must not be null"); - return OnGetOrderDetailsWebSocket(callback); + return OnGetOrderDetailsWebSocketAsync(callback); } /// @@ -898,16 +898,22 @@ public virtual IWebSocket GetOrderDetailsWebSocket(Action c /// /// Callback /// Web socket, call Dispose to close - public virtual IWebSocket GetCompletedOrderDetailsWebSocket(Action callback) + public virtual Task GetCompletedOrderDetailsWebSocketAsync(Action callback) { callback.ThrowIfNull(nameof(callback), "Callback must not be null"); - return OnGetCompletedOrderDetailsWebSocket(callback); + return OnGetCompletedOrderDetailsWebSocketAsync(callback); } - public virtual IWebSocket GetUserDataWebSocket(Action callback, string listenKey) + /// + /// Get user detail over web socket + /// + /// Callback + /// Listen key + /// Web socket, call Dispose to close + public virtual Task GetUserDataWebSocketAsync(Action callback, string listenKey) { callback.ThrowIfNull(nameof(callback), "Callback must not be null"); - return OnUserDataWebSocket(callback, listenKey); + return OnUserDataWebSocketAsync(callback, listenKey); } #endregion Web Socket API } diff --git a/ExchangeSharp/API/Exchanges/_Base/ExchangeAPIDefinitions.cs b/ExchangeSharp/API/Exchanges/_Base/ExchangeAPIDefinitions.cs index 649fd7a3..f5eaa0ea 100644 --- a/ExchangeSharp/API/Exchanges/_Base/ExchangeAPIDefinitions.cs +++ b/ExchangeSharp/API/Exchanges/_Base/ExchangeAPIDefinitions.cs @@ -51,11 +51,11 @@ public abstract partial class ExchangeAPI protected virtual Task OnGetOpenPositionAsync(string symbol); protected virtual Task OnCloseMarginPositionAsync(string symbol); - protected virtual IWebSocket OnGetTickersWebSocket(Action>> tickers); - protected virtual IWebSocket OnGetTradesWebSocket(Action> callback, params string[] symbols); - protected virtual IWebSocket OnGetDeltaOrderBookWebSocket(Action callback, int maxCount = 20, params string[] symbols); - protected virtual IWebSocket OnGetOrderDetailsWebSocket(Action callback); - protected virtual IWebSocket OnGetCompletedOrderDetailsWebSocket(Action callback); + protected virtual Task OnGetTickersWebSocket(Action>> tickers); + protected virtual Task OnGetTradesWebSocket(Action> callback, params string[] symbols); + protected virtual Task OnGetDeltaOrderBookWebSocket(Action callback, int maxCount = 20, params string[] symbols); + protected virtual Task OnGetOrderDetailsWebSocket(Action callback); + protected virtual Task OnGetCompletedOrderDetailsWebSocket(Action callback); // these generally do not need to be overriden unless your Exchange does something funny or does not use a symbol separator public virtual string NormalizeSymbol(string symbol); diff --git a/ExchangeSharp/API/Exchanges/_Base/ExchangeAPIExtensions.cs b/ExchangeSharp/API/Exchanges/_Base/ExchangeAPIExtensions.cs index 8c6ab5ed..c395fdaf 100644 --- a/ExchangeSharp/API/Exchanges/_Base/ExchangeAPIExtensions.cs +++ b/ExchangeSharp/API/Exchanges/_Base/ExchangeAPIExtensions.cs @@ -37,7 +37,7 @@ public static class ExchangeAPIExtensions /// parameter /// Order book symbols or null/empty for all of them (if supported) /// Web socket, call Dispose to close - public static IWebSocket GetFullOrderBookWebSocket(this IOrderBookProvider api, Action callback, int maxCount = 20, params string[] symbols) + public static async Task GetFullOrderBookWebSocketAsync(this IOrderBookProvider api, Action callback, int maxCount = 20, params string[] symbols) { if (api.WebSocketOrderBookType == WebSocketOrderBookType.None) { @@ -170,7 +170,7 @@ async Task innerCallback(ExchangeOrderBook newOrderBook) callback(fullOrderBook); } - IWebSocket socket = api.GetDeltaOrderBookWebSocket(async (b) => + IWebSocket socket = await api.GetDeltaOrderBookWebSocketAsync(async (b) => { try { @@ -441,7 +441,7 @@ internal static ExchangeOrderBook ParseOrderBookFromJTokenDictionaries /// Quote currency key /// Id key /// ExchangeTicker - internal static ExchangeTicker ParseTicker(this ExchangeAPI api, JToken token, string marketSymbol, + internal static async Task ParseTickerAsync(this ExchangeAPI api, JToken token, string marketSymbol, object askKey, object bidKey, object lastKey, object baseVolumeKey, object quoteVolumeKey = null, object timestampKey = null, TimestampType timestampType = TimestampType.None, object baseCurrencyKey = null, object quoteCurrencyKey = null, object idKey = null) @@ -472,7 +472,7 @@ internal static ExchangeTicker ParseTicker(this ExchangeAPI api, JToken token, s } else { - (baseCurrency, quoteCurrency) = api.ExchangeMarketSymbolToCurrencies(marketSymbol); + (baseCurrency, quoteCurrency) = await api.ExchangeMarketSymbolToCurrenciesAsync(marketSymbol); } // create the ticker and return it diff --git a/ExchangeSharp/API/Exchanges/_Base/ExchangeLogger.cs b/ExchangeSharp/API/Exchanges/_Base/ExchangeLogger.cs index 8e3b8225..87dac80b 100644 --- a/ExchangeSharp/API/Exchanges/_Base/ExchangeLogger.cs +++ b/ExchangeSharp/API/Exchanges/_Base/ExchangeLogger.cs @@ -34,11 +34,11 @@ public sealed class ExchangeLogger : IDisposable HashSet tradeIds = new HashSet(); HashSet tradeIds2 = new HashSet(); - private void LoggerThread() + private async Task LoggerThread() { while (IsRunningInBackground && !cancelEvent.WaitOne(Interval)) { - Update(); + await UpdateAsync(); } cancelEvent.Set(); IsRunningInBackground = false; @@ -77,7 +77,7 @@ public ExchangeLogger(IExchangeAPI api, string marketSymbol, float intervalSecon /// /// Update the logger - you can call this periodically if you don't want to call Start to run the logger in a background thread. /// - public void Update() + public async Task UpdateAsync() { ExchangeTrade[] newTrades; HashSet tmpTradeIds; @@ -87,7 +87,7 @@ public void Update() if (MarketSymbol == "*") { // get all symbols - Tickers = API.GetTickersAsync().Sync().ToArray(); + Tickers = (await API.GetTickersAsync()).ToArray(); tickerWriter.Write(Tickers.Count); foreach (KeyValuePair ticker in Tickers) { @@ -98,9 +98,9 @@ public void Update() else { // make API calls first, if they fail we will try again later - Tickers = new KeyValuePair[1] { new KeyValuePair(MarketSymbol, API.GetTickerAsync(MarketSymbol).Sync()) }; - OrderBook = API.GetOrderBookAsync(MarketSymbol).Sync(); - Trades = API.GetRecentTradesAsync(MarketSymbol).Sync().OrderBy(t => t.Timestamp).ToArray(); + Tickers = new KeyValuePair[1] { new KeyValuePair(MarketSymbol, await API.GetTickerAsync(MarketSymbol)) }; + OrderBook = await API.GetOrderBookAsync(MarketSymbol); + Trades = (await API.GetRecentTradesAsync(MarketSymbol)).OrderBy(t => t.Timestamp).ToArray(); // all API calls succeeded, we can write to files diff --git a/ExchangeSharp/API/Exchanges/_Base/IExchangeAPI.cs b/ExchangeSharp/API/Exchanges/_Base/IExchangeAPI.cs index d509bc7d..dab28e57 100644 --- a/ExchangeSharp/API/Exchanges/_Base/IExchangeAPI.cs +++ b/ExchangeSharp/API/Exchanges/_Base/IExchangeAPI.cs @@ -39,14 +39,14 @@ public interface IExchangeAPI : IDisposable, IBaseAPI, IOrderBookProvider /// /// Exchange symbol /// Global symbol - string ExchangeMarketSymbolToGlobalMarketSymbol(string marketSymbol); + Task ExchangeMarketSymbolToGlobalMarketSymbolAsync(string marketSymbol); /// /// Convert a global symbol into an exchange symbol, which will potentially be different from other exchanges. /// /// Global symbol /// Exchange symbol - string GlobalMarketSymbolToExchangeMarketSymbol(string marketSymbol); + Task GlobalMarketSymbolToExchangeMarketSymbolAsync(string marketSymbol); /// /// Convert seconds to a period string, or throw exception if seconds invalid. Example: 60 seconds becomes 1m. @@ -225,7 +225,7 @@ public interface IExchangeAPI : IDisposable, IBaseAPI, IOrderBookProvider /// Callback /// Symbols. If no symbols are specified, this will get the tickers for all symbols. NOTE: Some exchanges don't allow you to specify which symbols to return. /// Web socket, call Dispose to close - IWebSocket GetTickersWebSocket(Action>> callback, params string[] symbols); + Task GetTickersWebSocketAsync(Action>> callback, params string[] symbols); /// /// Get information about trades via web socket @@ -233,21 +233,21 @@ public interface IExchangeAPI : IDisposable, IBaseAPI, IOrderBookProvider /// Callback (symbol and trade) /// Market symbols /// Web socket, call Dispose to close - IWebSocket GetTradesWebSocket(Func, Task> callback, params string[] marketSymbols); + Task GetTradesWebSocketAsync(Func, Task> callback, params string[] marketSymbols); /// /// Get the details of all changed orders via web socket /// /// Callback /// Web socket, call Dispose to close - IWebSocket GetOrderDetailsWebSocket(Action callback); + Task GetOrderDetailsWebSocketAsync(Action callback); /// /// Get the details of all completed orders via web socket /// /// Callback /// Web socket, call Dispose to close - IWebSocket GetCompletedOrderDetailsWebSocket(Action callback); + Task GetCompletedOrderDetailsWebSocketAsync(Action callback); #endregion Web Socket } diff --git a/ExchangeSharp/API/Exchanges/_Base/Interfaces/IOrderBookProvider.cs b/ExchangeSharp/API/Exchanges/_Base/Interfaces/IOrderBookProvider.cs index 20914e8f..437e80df 100644 --- a/ExchangeSharp/API/Exchanges/_Base/Interfaces/IOrderBookProvider.cs +++ b/ExchangeSharp/API/Exchanges/_Base/Interfaces/IOrderBookProvider.cs @@ -44,7 +44,7 @@ public interface IOrderBookProvider /// Max count of bids and asks - not all exchanges will honor this parameter /// Market symbols or null/empty for all of them (if supported) /// Web socket, call Dispose to close - IWebSocket GetDeltaOrderBookWebSocket(Action callback, int maxCount = 20, params string[] marketSymbols); + Task GetDeltaOrderBookWebSocketAsync(Action callback, int maxCount = 20, params string[] marketSymbols); /// /// What type of web socket order book is provided diff --git a/ExchangeSharp/Model/ExchangeInfo.cs b/ExchangeSharp/Model/ExchangeInfo.cs index 53110e09..5bfb1b51 100644 --- a/ExchangeSharp/Model/ExchangeInfo.cs +++ b/ExchangeSharp/Model/ExchangeInfo.cs @@ -27,20 +27,21 @@ public sealed class ExchangeInfo /// Constructor /// /// Exchange API + /// Market symbols /// The market symbol to trade by default, can be null - public ExchangeInfo(IExchangeAPI api, string marketSymbol = null) + public ExchangeInfo(IExchangeAPI api, IReadOnlyCollection marketSymbols, string marketSymbol = null) { API = api; - MarketSymbols = api.GetMarketSymbolsAsync().Sync().ToArray(); + MarketSymbols = marketSymbols; TradeInfo = new ExchangeTradeInfo(this, marketSymbol); } /// /// Update the exchange info - get new trade info, etc. /// - public void Update() + public async Task UpdateAsync() { - TradeInfo.Update(); + await TradeInfo.UpdateAsync(); } /// diff --git a/ExchangeSharp/Model/ExchangeTradeInfo.cs b/ExchangeSharp/Model/ExchangeTradeInfo.cs index 89242cef..278bcd7b 100644 --- a/ExchangeSharp/Model/ExchangeTradeInfo.cs +++ b/ExchangeSharp/Model/ExchangeTradeInfo.cs @@ -37,10 +37,10 @@ public ExchangeTradeInfo(ExchangeInfo info, string marketSymbol) /// /// Update the trade info via API /// - public void Update() + public async Task UpdateAsync() { - Ticker = ExchangeInfo.API.GetTickerAsync(MarketSymbol).Sync(); - RecentTrades = ExchangeInfo.API.GetRecentTradesAsync(MarketSymbol).Sync().ToArray(); + Ticker = await ExchangeInfo.API.GetTickerAsync(MarketSymbol); + RecentTrades = (await ExchangeInfo.API.GetRecentTradesAsync(MarketSymbol)).ToArray(); if (RecentTrades.Length == 0) { Trade = new Trade(); @@ -49,7 +49,7 @@ public void Update() { Trade = new Trade { Amount = (float)RecentTrades[RecentTrades.Length - 1].Amount, Price = (float)RecentTrades[RecentTrades.Length - 1].Price, Ticks = (long)CryptoUtility.UnixTimestampFromDateTimeMilliseconds(RecentTrades[RecentTrades.Length - 1].Timestamp) }; } - Orders = ExchangeInfo.API.GetOrderBookAsync(MarketSymbol).Sync(); + Orders = await ExchangeInfo.API.GetOrderBookAsync(MarketSymbol); } /// diff --git a/ExchangeSharp/Traders/SimplePeakValleyTrader.cs b/ExchangeSharp/Traders/SimplePeakValleyTrader.cs index be7f42b5..02b63fef 100644 --- a/ExchangeSharp/Traders/SimplePeakValleyTrader.cs +++ b/ExchangeSharp/Traders/SimplePeakValleyTrader.cs @@ -27,18 +27,18 @@ public class SimplePeakValleyTrader : Trader public bool HitPeak { get; private set; } [MethodImpl(MethodImplOptions.AggressiveInlining)] - protected override void Initialize(ExchangeTradeInfo tradeInfo) + protected override async Task InitializeAsync(ExchangeTradeInfo tradeInfo) { - base.Initialize(tradeInfo); + await base.InitializeAsync(tradeInfo); SetPlotListCount(1); AnchorPrice = TradeInfo.Trade.Price; HitValley = HitPeak = false; - ProcessTrade(); + await ProcessTradeAsync(); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - protected override void ProcessTrade() + protected override async Task ProcessTradeAsync() { double diff = TradeInfo.Trade.Price - AnchorPrice; PlotPoints[0].Add(new KeyValuePair(TradeInfo.Trade.Ticks, TradeInfo.Trade.Price)); @@ -48,7 +48,7 @@ protected override void ProcessTrade() // lower anchor price just a bit in case price drops so we will buy more AnchorPrice -= (BuyFalseReverseThresholdPercent * AnchorPrice); HitPeak = false; - PerformBuy(); + await PerformBuyAsync(); } else if (HitPeak && diff >= ((SellThresholdPercent * AnchorPrice) + (SellReverseThresholdPercent * AnchorPrice)) && BuyPrices.Count != 0 && TradeInfo.Trade.Price > BuyPrices[BuyPrices.Count - 1].Value + (SellReverseThresholdPercent * AnchorPrice)) @@ -56,7 +56,7 @@ protected override void ProcessTrade() // peak reversal, sell AnchorPrice = TradeInfo.Trade.Price; HitPeak = HitValley = false; - PerformSell(); + await PerformSellAsync(); } else if (diff < (BuyThresholdPercent * AnchorPrice)) { diff --git a/ExchangeSharp/Traders/Trader.cs b/ExchangeSharp/Traders/Trader.cs index c625ff25..072b13a0 100644 --- a/ExchangeSharp/Traders/Trader.cs +++ b/ExchangeSharp/Traders/Trader.cs @@ -57,11 +57,11 @@ public abstract class Trader public List> SellPrices { get; } = new List>(); [MethodImpl(MethodImplOptions.AggressiveInlining)] - protected virtual void Initialize(ExchangeTradeInfo info) + protected virtual async Task InitializeAsync(ExchangeTradeInfo info) { TradeInfo = info; LastTradeTimestamp = info.Trade.Ticks; - UpdateAmounts(); + await UpdateAmountsAsync(); StartCashFlow = CashFlow; Profit = (CashFlow - StartCashFlow) + (ItemCount * (decimal)info.Trade.Price); ItemCount = 0.0m; @@ -77,11 +77,11 @@ protected virtual void Initialize(ExchangeTradeInfo info) } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void UpdateAmounts() + private async Task UpdateAmountsAsync() { if (ProductionMode) { - var dict = TradeInfo.ExchangeInfo.API.GetAmountsAvailableToTradeAsync().Sync(); + var dict = await TradeInfo.ExchangeInfo.API.GetAmountsAvailableToTradeAsync(); string[] tradeSymbols = TradeInfo.MarketSymbol.Split('_'); dict.TryGetValue(tradeSymbols[1], out decimal itemCount); dict.TryGetValue(tradeSymbols[0], out decimal cashFlow); @@ -103,7 +103,7 @@ protected void SetPlotListCount(int count) /// Use TradeInfo property to update the trader /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - protected abstract void ProcessTrade(); + protected abstract Task ProcessTradeAsync(); public override string ToString() { @@ -118,7 +118,7 @@ public void Reset() } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Update() + public async Task UpdateAsync() { if (TradeInfo.Trade.Ticks <= 0) { @@ -128,7 +128,7 @@ public void Update() // init else if (LastTradeTimestamp == 0) { - Initialize(TradeInfo); + await InitializeAsync(TradeInfo); return; } else if (TradeInfo.Trade.Ticks - LastTradeTimestamp >= Interval) @@ -142,14 +142,14 @@ public void Update() #endif LastTradeTimestamp = TradeInfo.Trade.Ticks; - ProcessTrade(); + await ProcessTradeAsync(); } - UpdateAmounts(); + await UpdateAmountsAsync(); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public decimal PerformBuy(decimal count = -1) + public async Task PerformBuyAsync(decimal count = -1) { count = (count <= 0m ? BuyUnits : count); if (CashFlow >= ((decimal)TradeInfo.Trade.Price * count)) @@ -159,14 +159,14 @@ public decimal PerformBuy(decimal count = -1) actualBuyPrice += (actualBuyPrice * OrderPriceDifferentialPercentage); if (ProductionMode) { - TradeInfo.ExchangeInfo.API.PlaceOrderAsync(new ExchangeOrderRequest + await TradeInfo.ExchangeInfo.API.PlaceOrderAsync(new ExchangeOrderRequest { Amount = count, IsBuy = true, Price = actualBuyPrice, ShouldRoundAmount = false, MarketSymbol = TradeInfo.MarketSymbol - }).Sync(); + }); } else { @@ -177,14 +177,14 @@ public decimal PerformBuy(decimal count = -1) } Buys++; Spend += actualBuyPrice * count; - UpdateAmounts(); + await UpdateAmountsAsync(); return count; } return 0m; } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public decimal PerformSell(decimal count = -1) + public async Task PerformSellAsync(decimal count = -1) { count = (count <= 0m ? SellUnits : count); if (ItemCount >= count) @@ -193,14 +193,14 @@ public decimal PerformSell(decimal count = -1) actualSellPrice -= (actualSellPrice * OrderPriceDifferentialPercentage); if (ProductionMode) { - TradeInfo.ExchangeInfo.API.PlaceOrderAsync(new ExchangeOrderRequest + await TradeInfo.ExchangeInfo.API.PlaceOrderAsync(new ExchangeOrderRequest { Amount = count, IsBuy = false, Price = actualSellPrice, ShouldRoundAmount = false, MarketSymbol = TradeInfo.MarketSymbol - }).Sync(); + }); } else { @@ -211,7 +211,7 @@ public decimal PerformSell(decimal count = -1) } Sells++; Earned += actualSellPrice * count; - UpdateAmounts(); + await UpdateAmountsAsync(); return count; } return 0m; diff --git a/ExchangeSharp/Traders/TraderExchangeExport.cs b/ExchangeSharp/Traders/TraderExchangeExport.cs index d95ca61a..f164c4b7 100644 --- a/ExchangeSharp/Traders/TraderExchangeExport.cs +++ b/ExchangeSharp/Traders/TraderExchangeExport.cs @@ -29,7 +29,7 @@ public static class TraderExchangeExport /// Base path to export to, should not contain symbol, symbol will be appended /// Start date to begin export at /// Callback if api is not null to notify of progress - public static void ExportExchangeTrades(IExchangeAPI api, string marketSymbol, string basePath, DateTime sinceDateTime, Action callback = null) + public static async Task ExportExchangeTrades(IExchangeAPI api, string marketSymbol, string basePath, DateTime sinceDateTime, Action callback = null) { basePath = Path.Combine(basePath, marketSymbol); Directory.CreateDirectory(basePath); @@ -62,7 +62,7 @@ bool innerCallback(IEnumerable trades) } return true; } - api.GetHistoricalTradesAsync(innerCallback, marketSymbol, sinceDateTime).Sync(); + await api.GetHistoricalTradesAsync(innerCallback, marketSymbol, sinceDateTime); writer.Close(); callback?.Invoke(count); } diff --git a/ExchangeSharp/Utility/CryptoUtility.cs b/ExchangeSharp/Utility/CryptoUtility.cs index 9b53f435..490da8d8 100644 --- a/ExchangeSharp/Utility/CryptoUtility.cs +++ b/ExchangeSharp/Utility/CryptoUtility.cs @@ -1333,6 +1333,7 @@ public static decimal CalculatePrecision(string numberWithDecimals) /// /// Make a task execute synchronously - do not call this from the UI thread or it will lock up the application + /// You should almos always use async / await instead /// /// Task public static void Sync(this Task task) @@ -1342,6 +1343,7 @@ public static void Sync(this Task task) /// /// Make a task execute synchronously - do not call this from the UI thread or it will lock up the application + /// You should almos always use async / await instead /// /// Task /// Result diff --git a/ExchangeSharp/Utility/SignalrManager.cs b/ExchangeSharp/Utility/SignalrManager.cs index 379cfdc5..51c3be8b 100644 --- a/ExchangeSharp/Utility/SignalrManager.cs +++ b/ExchangeSharp/Utility/SignalrManager.cs @@ -115,18 +115,18 @@ public async Task OpenAsync(string functionName, Func callback, in string functionFullName = _manager.GetFunctionFullName(functionName); this.functionFullName = functionFullName; - while (!_manager.disposed) + while (!disposed && !_manager.disposed) { - await _manager.AddListener(functionName, callback, param); - - if (_manager.hubConnection.State != ConnectionState.Connected) - { - await Task.Delay(100); - continue; - } - try { + // performs any needed reconnect + await _manager.AddListener(functionName, callback, param); + + while (!disposed && !_manager.disposed && _manager.hubConnection.State != ConnectionState.Connected) + { + await Task.Delay(100); + } + // ask for proxy after adding the listener, as the listener will force a connection if needed IHubProxy _proxy = _manager.hubProxy; if (_proxy == null) @@ -141,12 +141,12 @@ public async Task OpenAsync(string functionName, Func callback, in { await Task.Delay(delayMilliseconds); } - bool result = await _proxy.Invoke(functionFullName, param[i]).ConfigureAwait(false); - if (!result) + if (!(await _proxy.Invoke(functionFullName, param[i]))) { throw new APIException("Invoke returned success code of false"); } } + ex = null; break; } catch (Exception _ex) @@ -168,7 +168,7 @@ public async Task OpenAsync(string functionName, Func callback, in } } - if (ex == null) + if (ex == null && !disposed && !_manager.disposed) { this.callback = callback; lock (_manager.sockets) @@ -179,10 +179,13 @@ public async Task OpenAsync(string functionName, Func callback, in { initialConnectFired = true; - // kick off a connect event if this is the first time, the connect even can only get set after the open request is sent - Task.Delay(1000).ContinueWith(async (t) => { await InvokeConnected(); }).ConfigureAwait(false).GetAwaiter(); + // kick off a connect event if this is the first time, the connect event can only get set after the open request is sent + Task.Run(async () => + { + await Task.Delay(1000); // give time for the caller to set a connected event + await InvokeConnected(); + }).ConfigureAwait(false).GetAwaiter(); } - return; } } @@ -222,7 +225,7 @@ public void Dispose() manager.sockets.Remove(this); } manager.RemoveListener(functionFullName, callback); - InvokeDisconnected().Sync(); + InvokeDisconnected().GetAwaiter(); } catch { @@ -244,8 +247,9 @@ public sealed class WebsocketCustomTransport : ClientTransportBase { private IConnection connection; private string connectionData; - TimeSpan connectInterval; - TimeSpan keepAlive; + private readonly TimeSpan connectInterval; + private readonly TimeSpan keepAlive; + public ExchangeSharp.ClientWebSocket WebSocket { get; private set; } public override bool SupportsKeepAlive => true; @@ -591,14 +595,21 @@ public async Task StartAsync() // setup connect event customTransport.WebSocket.Connected += async (ws) => { - SignalrSocketConnection[] socketsCopy; - lock (sockets) + try { - socketsCopy = sockets.ToArray(); + SignalrSocketConnection[] socketsCopy; + lock (sockets) + { + socketsCopy = sockets.ToArray(); + } + foreach (SignalrSocketConnection socket in socketsCopy) + { + await socket.InvokeConnected(); + } } - foreach (SignalrSocketConnection socket in socketsCopy) + catch (Exception ex) { - await socket.InvokeConnected(); + Logger.Info(ex.ToString()); } }; @@ -631,22 +642,33 @@ public async Task StartAsync() Logger.Info(ex.ToString()); } }; - await hubConnection.Start(autoTransport); - // get list of listeners quickly to limit lock - HubListener[] listeners; - lock (this.listeners) + try { - listeners = this.listeners.Values.ToArray(); - } + // it's possible for the hub connection to disconnect during this code if connection is crappy + // so we simply catch the exception and log an info message, the disconnect/reconnect loop will + // catch the close and re-initiate this whole method again + await hubConnection.Start(autoTransport); - // re-call the end point to enable messages - foreach (var listener in listeners) - { - foreach (object[] p in listener.Param) + // get list of listeners quickly to limit lock + HubListener[] listeners; + lock (this.listeners) { - await hubProxy.Invoke(listener.FunctionFullName, p); + listeners = this.listeners.Values.ToArray(); } + + // re-call the end point to enable messages + foreach (var listener in listeners) + { + foreach (object[] p in listener.Param) + { + await hubProxy.Invoke(listener.FunctionFullName, p); + } + } + } + catch (Exception ex) + { + Logger.Info(ex.ToString()); } } diff --git a/ExchangeSharpConsole/Console/ExchangeSharpConsole_Example.cs b/ExchangeSharpConsole/Console/ExchangeSharpConsole_Example.cs index 71eb5620..3e92d1ee 100644 --- a/ExchangeSharpConsole/Console/ExchangeSharpConsole_Example.cs +++ b/ExchangeSharpConsole/Console/ExchangeSharpConsole_Example.cs @@ -21,38 +21,46 @@ namespace ExchangeSharpConsole { public static partial class ExchangeSharpConsoleMain { - public static void RunExample(Dictionary dict) + public static async Task RunExample(Dictionary dict) { ExchangeKrakenAPI api = new ExchangeKrakenAPI(); - ExchangeTicker ticker = api.GetTickerAsync("XXBTZUSD").Sync(); - Console.WriteLine("On the Kraken exchange, 1 bitcoin is worth {0} USD.", ticker.Bid); + ExchangeTicker ticker = await api.GetTickerAsync("XXBTZUSD"); + Logger.Info("On the Kraken exchange, 1 bitcoin is worth {0} USD.", ticker.Bid); // load API keys created from ExchangeSharpConsole.exe keys mode=create path=keys.bin keylist=public_key,private_key api.LoadAPIKeys("keys.bin"); /// place limit order for 0.01 bitcoin at ticker.Ask USD - ExchangeOrderResult result = api.PlaceOrderAsync(new ExchangeOrderRequest + ExchangeOrderResult result = await api.PlaceOrderAsync(new ExchangeOrderRequest { Amount = 0.01m, IsBuy = true, Price = ticker.Ask, MarketSymbol = "XXBTZUSD" - }).Sync(); + }); // Kraken is a bit funny in that they don't return the order details in the initial request, so you have to follow up with an order details request // if you want to know more info about the order - most other exchanges don't return until they have the order details for you. // I've also found that Kraken tends to fail if you follow up too quickly with an order details request, so sleep a bit to give them time to get // their house in order. - System.Threading.Thread.Sleep(500); - result = api.GetOrderDetailsAsync(result.OrderId).Sync(); + await Task.Delay(500); + result = await api.GetOrderDetailsAsync(result.OrderId); - Console.WriteLine("Placed an order on Kraken for 0.01 bitcoin at {0} USD. Status is {1}. Order id is {2}.", ticker.Ask, result.Result, result.OrderId); + Logger.Info("Placed an order on Kraken for 0.01 bitcoin at {0} USD. Status is {1}. Order id is {2}.", ticker.Ask, result.Result, result.OrderId); + } + + private static void WaitForKey() + { + Console.WriteLine("Press any key to quit."); + Console.ReadKey(); } private static string[] GetMarketSymbols(Dictionary dict, bool required = true) { - if(required) - RequireArgs(dict, "marketSymbols"); + if (required) + { + RequireArgs(dict, "marketSymbols"); + } if ((!dict.ContainsKey("marketSymbols") && !required) || dict["marketSymbols"] == "*") { return null; @@ -60,9 +68,9 @@ private static string[] GetMarketSymbols(Dictionary dict, bool r return dict["marketSymbols"].Split(",".ToCharArray(), StringSplitOptions.RemoveEmptyEntries); } - private static string[] ValidateMarketSymbols(IExchangeAPI api, string[] marketSymbols) + private static async Task ValidateMarketSymbolsAsync(IExchangeAPI api, string[] marketSymbols) { - string[] apiSymbols = api.GetMarketSymbolsAsync().Sync().ToArray(); + string[] apiSymbols = (await api.GetMarketSymbolsAsync()).ToArray(); if (marketSymbols == null || marketSymbols.Length == 0) { marketSymbols = apiSymbols; @@ -91,7 +99,7 @@ private static void SetWebSocketEvents(IWebSocket socket) }; } - private static void RunWebSocket(Dictionary dict, Func func) + private static async Task RunWebSocket(Dictionary dict, Func> func) { RequireArgs(dict, "exchangeName"); using (var api = ExchangeAPI.GetExchangeAPI(dict["exchangeName"])) @@ -102,7 +110,8 @@ private static void RunWebSocket(Dictionary dict, Func dict, Func dict) + private static async Task RunWebSocketTickers(Dictionary dict) { string[] symbols = GetMarketSymbols(dict, false); - RunWebSocket(dict, (api) => - { - if(symbols != null) - symbols = ValidateMarketSymbols(api, symbols); - return api.GetTickersWebSocket( - freshTickers => - { - foreach (KeyValuePair kvp in freshTickers) - { - Console.WriteLine($"market {kvp.Key}, ticker {kvp.Value}"); - } - }, symbols - ); - } - ); + await RunWebSocket(dict, async (api) => + { + if (symbols != null) + { + symbols = await ValidateMarketSymbolsAsync(api, symbols); + } + return await api.GetTickersWebSocketAsync(freshTickers => + { + foreach (KeyValuePair kvp in freshTickers) + { + Logger.Info($"market {kvp.Key}, ticker {kvp.Value}"); + } + }, symbols); + }); } - private static void RunTradesWebSocket(Dictionary dict) + private static async Task RunTradesWebSocket(Dictionary dict) { string[] symbols = GetMarketSymbols(dict); - RunWebSocket(dict, (api) => + await RunWebSocket(dict, async (api) => { - symbols = ValidateMarketSymbols(api, symbols); - return api.GetTradesWebSocket(message => + symbols = await ValidateMarketSymbolsAsync(api, symbols); + return await api.GetTradesWebSocketAsync(message => { - Console.WriteLine($"{message.Key}: {message.Value}"); + Logger.Info($"{message.Key}: {message.Value}"); return Task.CompletedTask; }, symbols); }); } - private static void RunOrderBookWebSocket(Dictionary dict) + private static async Task RunOrderBookWebSocket(Dictionary dict) { string[] symbols = GetMarketSymbols(dict); - RunWebSocket(dict, (api) => + await RunWebSocket(dict, async (api) => { - symbols = ValidateMarketSymbols(api, symbols); - return ExchangeAPIExtensions.GetFullOrderBookWebSocket(api, message => + symbols = await ValidateMarketSymbolsAsync(api, symbols); + return await ExchangeAPIExtensions.GetFullOrderBookWebSocketAsync(api, message => { //print the top bid and ask with amount var topBid = message.Bids.FirstOrDefault(); var topAsk = message.Asks.FirstOrDefault(); - Console.WriteLine($"[{message.MarketSymbol}:{message.SequenceId}] {topBid.Value.Price} ({topBid.Value.Amount}) | {topAsk.Value.Price} ({topAsk.Value.Amount})"); + Logger.Info($"[{message.MarketSymbol}:{message.SequenceId}] {topBid.Value.Price} ({topBid.Value.Amount}) | {topAsk.Value.Price} ({topAsk.Value.Amount})"); }, symbols: symbols); }); } @@ -179,7 +187,7 @@ public static void RunProcessEncryptedAPIKeys(Dictionary dict) System.Security.SecureString[] secureStrings = CryptoUtility.LoadProtectedStringsFromFile(dict["path"]); foreach (System.Security.SecureString s in secureStrings) { - Console.WriteLine(CryptoUtility.ToUnsecureString(s)); + Logger.Info(CryptoUtility.ToUnsecureString(s)); } } else @@ -188,7 +196,7 @@ public static void RunProcessEncryptedAPIKeys(Dictionary dict) } } - public static void RunGetSymbolsMetadata(Dictionary dict) + public static async Task RunGetSymbolsMetadata(Dictionary dict) { RequireArgs(dict, "exchangeName"); using (var api = ExchangeAPI.GetExchangeAPI(dict["exchangeName"])) @@ -200,11 +208,11 @@ public static void RunGetSymbolsMetadata(Dictionary dict) try { - var marketSymbols = api.GetMarketSymbolsMetadataAsync().Sync(); + var marketSymbols = await api.GetMarketSymbolsMetadataAsync(); foreach (var marketSymbol in marketSymbols) { - Console.WriteLine(marketSymbol); + Logger.Info(marketSymbol.ToString()); } Console.WriteLine("Press any key to quit."); @@ -217,7 +225,7 @@ public static void RunGetSymbolsMetadata(Dictionary dict) } } - public static void RunGetMarketSymbols(Dictionary dict) + public static async Task RunGetMarketSymbols(Dictionary dict) { RequireArgs(dict, "exchangeName"); using (var api = ExchangeAPI.GetExchangeAPI(dict["exchangeName"])) @@ -229,15 +237,14 @@ public static void RunGetMarketSymbols(Dictionary dict) try { - var marketSymbols = api.GetMarketSymbolsAsync().Sync(); + var marketSymbols = await api.GetMarketSymbolsAsync(); foreach (var marketSymbol in marketSymbols) { - Console.WriteLine(marketSymbol); + Logger.Info(marketSymbol); } - Console.WriteLine("Press any key to quit."); - Console.ReadKey(); + WaitForKey(); } catch (Exception ex) { @@ -246,7 +253,7 @@ public static void RunGetMarketSymbols(Dictionary dict) } } - public static void RunGetTickers(Dictionary dict) + public static async Task RunGetTickers(Dictionary dict) { RequireArgs(dict, "exchangeName"); using (var api = ExchangeAPI.GetExchangeAPI(dict["exchangeName"])) @@ -262,24 +269,23 @@ public static void RunGetTickers(Dictionary dict) if (dict.ContainsKey("marketSymbol")) { var marketSymbol = dict["marketSymbol"]; - var ticker = api.GetTickerAsync(marketSymbol).Sync(); + var ticker = await api.GetTickerAsync(marketSymbol); tickers = new List>() - { - new KeyValuePair(marketSymbol, ticker) - }; + { + new KeyValuePair(marketSymbol, ticker) + }; } else { - tickers = api.GetTickersAsync().Sync(); + tickers = await api.GetTickersAsync(); } foreach (var ticker in tickers) { - Console.WriteLine(ticker); + Logger.Info(ticker.ToString()); } - Console.WriteLine("Press any key to quit."); - Console.ReadKey(); + WaitForKey(); } catch (Exception ex) { @@ -288,7 +294,7 @@ public static void RunGetTickers(Dictionary dict) } } - public static void RunGetCandles(Dictionary dict) + public static async Task RunGetCandles(Dictionary dict) { RequireArgs(dict, "exchangeName", "marketSymbol"); using (var api = ExchangeAPI.GetExchangeAPI(dict["exchangeName"])) @@ -301,15 +307,14 @@ public static void RunGetCandles(Dictionary dict) try { var marketSymbol = dict["marketSymbol"]; - var candles = api.GetCandlesAsync(marketSymbol, 1800, CryptoUtility.UtcNow.AddDays(-12), CryptoUtility.UtcNow).Sync(); + var candles = await api.GetCandlesAsync(marketSymbol, 1800, CryptoUtility.UtcNow.AddDays(-12), CryptoUtility.UtcNow); foreach (var candle in candles) { - Console.WriteLine(candle); + Logger.Info(candle.ToString()); } - Console.WriteLine("Press any key to quit."); - Console.ReadKey(); + WaitForKey(); } catch (Exception ex) { diff --git a/ExchangeSharpConsole/Console/ExchangeSharpConsole_ExchangeTests.cs b/ExchangeSharpConsole/Console/ExchangeSharpConsole_ExchangeTests.cs index 1916edb5..bed2b95b 100644 --- a/ExchangeSharpConsole/Console/ExchangeSharpConsole_ExchangeTests.cs +++ b/ExchangeSharpConsole/Console/ExchangeSharpConsole_ExchangeTests.cs @@ -18,6 +18,7 @@ The above copyright notice and this permission notice shall be included in all c using System.Security; using System.Security.Cryptography; using System.Text.RegularExpressions; +using System.Threading.Tasks; using ExchangeSharp; @@ -33,7 +34,7 @@ private static void Assert(bool expression) } } - private static void TestExchanges(string nameRegex = null, string functionRegex = null) + private static async Task TestExchanges(string nameRegex = null, string functionRegex = null) { string GetSymbol(IExchangeAPI api) { @@ -97,7 +98,7 @@ bool histTradeCallback(IEnumerable tradeEnum) if (functionRegex == null || Regex.IsMatch("symbol", functionRegex, RegexOptions.IgnoreCase)) { Console.Write("Test {0} GetSymbolsAsync... ", api.Name); - IReadOnlyCollection symbols = api.GetMarketSymbolsAsync().Sync().ToArray(); + IReadOnlyCollection symbols = (await api.GetMarketSymbolsAsync()).ToArray(); Assert(symbols != null && symbols.Count != 0 && symbols.Contains(marketSymbol, StringComparer.OrdinalIgnoreCase)); Console.WriteLine($"OK (default: {marketSymbol}; {symbols.Count} symbols)"); } @@ -107,7 +108,7 @@ bool histTradeCallback(IEnumerable tradeEnum) try { Console.Write("Test {0} GetCurrenciesAsync... ", api.Name); - var currencies = api.GetCurrenciesAsync().Sync(); + var currencies = await api.GetCurrenciesAsync(); Assert(currencies.Count != 0); Console.WriteLine($"OK ({currencies.Count} currencies)"); } @@ -122,7 +123,7 @@ bool histTradeCallback(IEnumerable tradeEnum) try { Console.Write("Test {0} GetOrderBookAsync... ", api.Name); - var book = api.GetOrderBookAsync(marketSymbol).Sync(); + var book = await api.GetOrderBookAsync(marketSymbol); Assert(book.Asks.Count != 0 && book.Bids.Count != 0 && book.Asks.First().Value.Amount > 0m && book.Asks.First().Value.Price > 0m && book.Bids.First().Value.Amount > 0m && book.Bids.First().Value.Price > 0m); Console.WriteLine($"OK ({book.Asks.Count} asks, {book.Bids.Count} bids)"); @@ -138,7 +139,7 @@ bool histTradeCallback(IEnumerable tradeEnum) try { Console.Write("Test {0} GetTickerAsync... ", api.Name); - var ticker = api.GetTickerAsync(marketSymbol).Sync(); + var ticker = await api.GetTickerAsync(marketSymbol); Assert(ticker != null && ticker.Ask > 0m && ticker.Bid > 0m && ticker.Last > 0m && ticker.Volume != null && ticker.Volume.QuoteCurrencyVolume > 0m && ticker.Volume.BaseCurrencyVolume > 0m); Console.WriteLine($"OK (ask: {ticker.Ask}, bid: {ticker.Bid}, last: {ticker.Last})"); @@ -154,12 +155,12 @@ bool histTradeCallback(IEnumerable tradeEnum) try { Console.Write("Test {0} GetHistoricalTradesAsync... ", api.Name); - api.GetHistoricalTradesAsync(histTradeCallback, marketSymbol).Sync(); + await api.GetHistoricalTradesAsync(histTradeCallback, marketSymbol); Assert(trades.Length != 0 && trades[0].Price > 0m && trades[0].Amount > 0m); Console.WriteLine($"OK ({trades.Length})"); Console.Write("Test {0} GetRecentTradesAsync... ", api.Name); - trades = api.GetRecentTradesAsync(marketSymbol).Sync().ToArray(); + trades = (await api.GetRecentTradesAsync(marketSymbol)).ToArray(); Assert(trades.Length != 0 && trades[0].Price > 0m && trades[0].Amount > 0m); Console.WriteLine($"OK ({trades.Length} trades)"); } @@ -174,7 +175,7 @@ bool histTradeCallback(IEnumerable tradeEnum) try { Console.Write("Test {0} GetCandlesAsync... ", api.Name); - var candles = api.GetCandlesAsync(marketSymbol, 86400, CryptoUtility.UtcNow.Subtract(TimeSpan.FromDays(7.0)), null).Sync().ToArray(); + var candles = (await api.GetCandlesAsync(marketSymbol, 86400, CryptoUtility.UtcNow.Subtract(TimeSpan.FromDays(7.0)), null)).ToArray(); Assert(candles.Length != 0 && candles[0].ClosePrice > 0m && candles[0].HighPrice > 0m && candles[0].LowPrice > 0m && candles[0].OpenPrice > 0m && candles[0].HighPrice >= candles[0].LowPrice && candles[0].HighPrice >= candles[0].ClosePrice && candles[0].HighPrice >= candles[0].OpenPrice && !string.IsNullOrWhiteSpace(candles[0].Name) && candles[0].ExchangeName == api.Name && candles[0].PeriodSeconds == 86400 && candles[0].BaseCurrencyVolume > 0.0 && @@ -203,11 +204,11 @@ bool histTradeCallback(IEnumerable tradeEnum) } } - public static void RunPerformTests(Dictionary dict) + public static async Task RunPerformTests(Dictionary dict) { dict.TryGetValue("exchangeName", out string exchangeNameRegex); dict.TryGetValue("function", out string functionRegex); - TestExchanges(exchangeNameRegex, functionRegex); + await TestExchanges(exchangeNameRegex, functionRegex); } } } \ No newline at end of file diff --git a/ExchangeSharpConsole/Console/ExchangeSharpConsole_Export.cs b/ExchangeSharpConsole/Console/ExchangeSharpConsole_Export.cs index 905957e0..ee3e0811 100644 --- a/ExchangeSharpConsole/Console/ExchangeSharpConsole_Export.cs +++ b/ExchangeSharpConsole/Console/ExchangeSharpConsole_Export.cs @@ -12,13 +12,15 @@ The above copyright notice and this permission notice shall be included in all c using System; using System.Collections.Generic; +using System.Threading.Tasks; + using ExchangeSharp; namespace ExchangeSharpConsole { public static partial class ExchangeSharpConsoleMain { - public static void RunGetHistoricalTrades(Dictionary dict) + public static async Task RunGetHistoricalTrades(Dictionary dict) { RequireArgs(dict, "exchangeName", "marketSymbol"); @@ -36,14 +38,14 @@ public static void RunGetHistoricalTrades(Dictionary dict) { endDate = DateTime.Parse(dict["endDate"]).ToUniversalTime(); } - api.GetHistoricalTradesAsync((IEnumerable trades) => + await api.GetHistoricalTradesAsync((IEnumerable trades) => { foreach (ExchangeTrade trade in trades) { Console.WriteLine("Trade at timestamp {0}: {1}/{2}/{3}", trade.Timestamp.ToLocalTime(), trade.Id, trade.Price, trade.Amount); } return true; - }, marketSymbol, startDate, endDate).Sync(); + }, marketSymbol, startDate, endDate); } public static void RunExportData(Dictionary dict) diff --git a/ExchangeSharpConsole/Console/ExchangeSharpConsole_Orders.cs b/ExchangeSharpConsole/Console/ExchangeSharpConsole_Orders.cs index 9ff17d51..7903f69d 100644 --- a/ExchangeSharpConsole/Console/ExchangeSharpConsole_Orders.cs +++ b/ExchangeSharpConsole/Console/ExchangeSharpConsole_Orders.cs @@ -10,7 +10,7 @@ namespace ExchangeSharpConsole { public static partial class ExchangeSharpConsoleMain { - public static void RunGetOrderHistory(Dictionary dict) + public static async Task RunGetOrderHistoryAsync(Dictionary dict) { RequireArgs(dict, "exchangeName", "marketSymbol"); @@ -26,7 +26,7 @@ public static void RunGetOrderHistory(Dictionary dict) startDate = DateTime.Parse(dict["startDate"]).ToUniversalTime(); } - var completedOrders = api.GetCompletedOrderDetailsAsync(marketSymbol, startDate).Sync(); + var completedOrders = await api.GetCompletedOrderDetailsAsync(marketSymbol, startDate); foreach (var completedOrder in completedOrders) { Console.WriteLine(completedOrder); @@ -36,7 +36,7 @@ public static void RunGetOrderHistory(Dictionary dict) Console.ReadLine(); } - public static void RunGetOrderDetails(Dictionary dict) + public static async Task RunGetOrderDetailsAsync(Dictionary dict) { RequireArgs(dict, "exchangeName", "orderId"); @@ -52,7 +52,7 @@ public static void RunGetOrderDetails(Dictionary dict) marketSymbol = dict["marketSymbol"]; } - var orderDetails = api.GetOrderDetailsAsync(orderId, marketSymbol).Sync(); + var orderDetails = await api.GetOrderDetailsAsync(orderId, marketSymbol); Console.WriteLine(orderDetails); Console.Write("Press enter to exit.."); diff --git a/ExchangeSharpConsole/Console/ExchangeSharpConsole_Stats.cs b/ExchangeSharpConsole/Console/ExchangeSharpConsole_Stats.cs index 1dd1782e..da192860 100644 --- a/ExchangeSharpConsole/Console/ExchangeSharpConsole_Stats.cs +++ b/ExchangeSharpConsole/Console/ExchangeSharpConsole_Stats.cs @@ -14,13 +14,15 @@ The above copyright notice and this permission notice shall be included in all c using System.Collections.Generic; using System.Linq; using System.Threading; +using System.Threading.Tasks; + using ExchangeSharp; namespace ExchangeSharpConsole { public static partial class ExchangeSharpConsoleMain { - public static void RunShowExchangeStats(Dictionary dict) + public static async Task RunShowExchangeStats(Dictionary dict) { string marketSymbol = "BTC-USD"; string marketSymbol2 = "XXBTZUSD"; @@ -31,29 +33,29 @@ public static void RunShowExchangeStats(Dictionary dict) while (true) { - ExchangeTicker ticker = apiCoinbase.GetTickerAsync(marketSymbol).Sync(); - ExchangeOrderBook orders = apiCoinbase.GetOrderBookAsync(marketSymbol).Sync(); + ExchangeTicker ticker = await apiCoinbase.GetTickerAsync(marketSymbol); + ExchangeOrderBook orders = await apiCoinbase.GetOrderBookAsync(marketSymbol); decimal askAmountSum = orders.Asks.Values.Sum(o => o.Amount); decimal askPriceSum = orders.Asks.Values.Sum(o => o.Price); decimal bidAmountSum = orders.Bids.Values.Sum(o => o.Amount); decimal bidPriceSum = orders.Bids.Values.Sum(o => o.Price); - ExchangeTicker ticker2 = apiGemini.GetTickerAsync(marketSymbol).Sync(); - ExchangeOrderBook orders2 = apiGemini.GetOrderBookAsync(marketSymbol).Sync(); + ExchangeTicker ticker2 = await apiGemini.GetTickerAsync(marketSymbol); + ExchangeOrderBook orders2 = await apiGemini.GetOrderBookAsync(marketSymbol); decimal askAmountSum2 = orders2.Asks.Values.Sum(o => o.Amount); decimal askPriceSum2 = orders2.Asks.Values.Sum(o => o.Price); decimal bidAmountSum2 = orders2.Bids.Values.Sum(o => o.Amount); decimal bidPriceSum2 = orders2.Bids.Values.Sum(o => o.Price); - ExchangeTicker ticker3 = apiKraken.GetTickerAsync(marketSymbol2).Sync(); - ExchangeOrderBook orders3 = apiKraken.GetOrderBookAsync(marketSymbol2).Sync(); + ExchangeTicker ticker3 = await apiKraken.GetTickerAsync(marketSymbol2); + ExchangeOrderBook orders3 = await apiKraken.GetOrderBookAsync(marketSymbol2); decimal askAmountSum3 = orders3.Asks.Values.Sum(o => o.Amount); decimal askPriceSum3 = orders3.Asks.Values.Sum(o => o.Price); decimal bidAmountSum3 = orders3.Bids.Values.Sum(o => o.Amount); decimal bidPriceSum3 = orders3.Bids.Values.Sum(o => o.Price); - ExchangeTicker ticker4 = apiBitfinex.GetTickerAsync(marketSymbol).Sync(); - ExchangeOrderBook orders4 = apiBitfinex.GetOrderBookAsync(marketSymbol).Sync(); + ExchangeTicker ticker4 = await apiBitfinex.GetTickerAsync(marketSymbol); + ExchangeOrderBook orders4 = await apiBitfinex.GetOrderBookAsync(marketSymbol); decimal askAmountSum4 = orders4.Asks.Values.Sum(o => o.Amount); decimal askPriceSum4 = orders4.Asks.Values.Sum(o => o.Price); decimal bidAmountSum4 = orders4.Bids.Values.Sum(o => o.Amount); diff --git a/ExchangeSharpConsole/ExchangeSharpConsole.csproj b/ExchangeSharpConsole/ExchangeSharpConsole.csproj index 60647251..91462aed 100644 --- a/ExchangeSharpConsole/ExchangeSharpConsole.csproj +++ b/ExchangeSharpConsole/ExchangeSharpConsole.csproj @@ -7,6 +7,7 @@ 0.6.0.3 en true + latest diff --git a/ExchangeSharpConsole/ExchangeSharpConsole_Main.cs b/ExchangeSharpConsole/ExchangeSharpConsole_Main.cs index 0dd0ccbb..47149928 100644 --- a/ExchangeSharpConsole/ExchangeSharpConsole_Main.cs +++ b/ExchangeSharpConsole/ExchangeSharpConsole_Main.cs @@ -19,18 +19,13 @@ The above copyright notice and this permission notice shall be included in all c using System.Security.Cryptography; using System.Text; using System.Threading; - +using System.Threading.Tasks; using ExchangeSharp; namespace ExchangeSharpConsole { public static partial class ExchangeSharpConsoleMain { - public static int Main(string[] args) - { - return ExchangeSharpConsoleMain.ConsoleMain(args); - } - private static void RequireArgs(Dictionary dict, params string[] args) { bool fail = false; @@ -65,7 +60,22 @@ private static void TestMethod() { } - public static int ConsoleMain(string[] args) + /// + /// Console app main method + /// + /// Args + /// Task + public static Task Main(string[] args) + { + return ExchangeSharpConsoleMain.ConsoleMain(args); + } + + /// + /// Console sub-main entry method + /// + /// Args + /// Task + public static async Task ConsoleMain(string[] args) { try { @@ -80,7 +90,7 @@ public static int ConsoleMain(string[] args) } else if (argsDictionary.Count >= 1 && argsDictionary.ContainsKey("test")) { - RunPerformTests(argsDictionary); + await RunPerformTests(argsDictionary); } else if (argsDictionary.Count >= 1 && argsDictionary.ContainsKey("export")) { @@ -92,11 +102,11 @@ public static int ConsoleMain(string[] args) } else if (argsDictionary.Count >= 1 && argsDictionary.ContainsKey("stats")) { - RunShowExchangeStats(argsDictionary); + await RunShowExchangeStats(argsDictionary); } else if (argsDictionary.ContainsKey("example")) { - RunExample(argsDictionary); + await RunExample(argsDictionary); } else if (argsDictionary.ContainsKey("keys")) { @@ -104,15 +114,15 @@ public static int ConsoleMain(string[] args) } else if (argsDictionary.ContainsKey("websocket-ticker")) { - RunWebSocketTickers(argsDictionary); + await RunWebSocketTickers(argsDictionary); } else if (argsDictionary.ContainsKey("websocket-trades")) { - RunTradesWebSocket(argsDictionary); + await RunTradesWebSocket(argsDictionary); } else if (argsDictionary.ContainsKey("websocket-orderbook")) { - RunOrderBookWebSocket(argsDictionary); + await RunOrderBookWebSocket(argsDictionary); } else if (argsDictionary.ContainsKey("getExchangeNames")) { @@ -120,31 +130,31 @@ public static int ConsoleMain(string[] args) } else if (argsDictionary.ContainsKey("showHistoricalTrades")) { - RunGetHistoricalTrades(argsDictionary); + await RunGetHistoricalTrades(argsDictionary); } else if (argsDictionary.ContainsKey("getOrderHistory")) { - RunGetOrderHistory(argsDictionary); + await RunGetOrderHistoryAsync(argsDictionary); } else if (argsDictionary.ContainsKey("getOrderDetails")) { - RunGetOrderDetails(argsDictionary); + await RunGetOrderDetailsAsync(argsDictionary); } else if (argsDictionary.ContainsKey("symbols-metadata")) { - RunGetSymbolsMetadata(argsDictionary); + await RunGetSymbolsMetadata(argsDictionary); } else if (argsDictionary.ContainsKey("symbols")) { - RunGetMarketSymbols(argsDictionary); + await RunGetMarketSymbols(argsDictionary); } else if (argsDictionary.ContainsKey("tickers")) { - RunGetTickers(argsDictionary); + await RunGetTickers(argsDictionary); } else if (argsDictionary.ContainsKey("candles")) { - RunGetCandles(argsDictionary); + await RunGetCandles(argsDictionary); } else { diff --git a/ExchangeSharpConsole/nlog.config b/ExchangeSharpConsole/nlog.config index a8c92404..f9e6b2b4 100644 --- a/ExchangeSharpConsole/nlog.config +++ b/ExchangeSharpConsole/nlog.config @@ -6,6 +6,6 @@ - + \ No newline at end of file diff --git a/ExchangeSharpTests/CryptoUtilityTests.cs b/ExchangeSharpTests/CryptoUtilityTests.cs index 49e3f7a4..d53577f6 100644 --- a/ExchangeSharpTests/CryptoUtilityTests.cs +++ b/ExchangeSharpTests/CryptoUtilityTests.cs @@ -28,6 +28,7 @@ namespace ExchangeSharpTests using System.Globalization; using System.Security; using System.Security.Cryptography; + using System.Threading.Tasks; [TestClass] public class CryptoUtilityTests @@ -226,7 +227,7 @@ public void ConvertInvariantTest() } [TestMethod] - public void RateGate() + public async Task RateGate() { const int timesPerPeriod = 1; const int ms = 100; @@ -234,14 +235,14 @@ public void RateGate() double msMax = (double)ms * 1.5; double msMin = (double)ms * (1.0 / 1.5); RateGate gate = new RateGate(timesPerPeriod, TimeSpan.FromMilliseconds(ms)); - if (!gate.WaitToProceedAsync(0).Sync()) + if (!(await gate.WaitToProceedAsync(0))) { throw new APIException("Rate gate should have allowed immediate access to first attempt"); } for (int i = 0; i < loops; i++) { Stopwatch timer = Stopwatch.StartNew(); - gate.WaitToProceedAsync().Sync(); + await gate.WaitToProceedAsync(); timer.Stop(); if (i > 0) diff --git a/ExchangeSharpTests/ExchangeBinanceAPITests.cs b/ExchangeSharpTests/ExchangeBinanceAPITests.cs index dc59ccbc..07509795 100644 --- a/ExchangeSharpTests/ExchangeBinanceAPITests.cs +++ b/ExchangeSharpTests/ExchangeBinanceAPITests.cs @@ -10,21 +10,21 @@ The above copyright notice and this permission notice shall be included in all c THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -namespace ExchangeSharpTests -{ - using System.Collections.Generic; - - using ExchangeSharp; - using ExchangeSharp.Binance; +using System.Collections.Generic; +using System.Threading.Tasks; +using ExchangeSharp; +using ExchangeSharp.Binance; - using FluentAssertions; +using FluentAssertions; - using Microsoft.VisualStudio.TestTools.UnitTesting; +using Microsoft.VisualStudio.TestTools.UnitTesting; - using Newtonsoft.Json; +using Newtonsoft.Json; - using NSubstitute; +using NSubstitute; +namespace ExchangeSharpTests +{ [TestClass] public class ExchangeBinanceAPITests { @@ -97,12 +97,12 @@ public void DeserializeRealData() } [TestMethod] - public void CurrenciesParsedCorrectly() + public async Task CurrenciesParsedCorrectly() { var requestMaker = Substitute.For(); requestMaker.MakeRequestAsync(ExchangeBinanceAPI.GetCurrenciesUrl, ExchangeBinanceAPI.BaseWebUrl).Returns(Resources.BinanceGetAllAssets); var binance = new ExchangeBinanceAPI { RequestMaker = requestMaker }; - IReadOnlyDictionary currencies = binance.GetCurrenciesAsync().Sync(); + IReadOnlyDictionary currencies = await binance.GetCurrenciesAsync(); currencies.Should().HaveCount(3); currencies.TryGetValue("bnb", out ExchangeCurrency bnb).Should().BeTrue(); bnb.DepositEnabled.Should().BeFalse(); diff --git a/ExchangeSharpTests/ExchangePoloniexAPITests.cs b/ExchangeSharpTests/ExchangePoloniexAPITests.cs index 02fa6c1b..68a500e8 100644 --- a/ExchangeSharpTests/ExchangePoloniexAPITests.cs +++ b/ExchangeSharpTests/ExchangePoloniexAPITests.cs @@ -13,6 +13,7 @@ The above copyright notice and this permission notice shall be included in all c using System; using System.Collections.Generic; using System.Linq; +using System.Threading.Tasks; using ExchangeSharp; @@ -196,11 +197,11 @@ public void ReturnOpenOrders_Unfilled_IsCorrect() } [TestMethod] - public void GetOpenOrderDetails_Unfilled_IsCorrect() + public async Task GetOpenOrderDetails_Unfilled_IsCorrect() { var polo = CreatePoloniexAPI(Unfilled); - IEnumerable orders = polo.GetOpenOrderDetailsAsync("ETH_BCH").Sync(); + IEnumerable orders = await polo.GetOpenOrderDetailsAsync("ETH_BCH"); ExchangeOrderResult order = orders.Single(); order.OrderId.Should().Be("35329211614"); order.IsBuy.Should().BeTrue(); @@ -213,11 +214,11 @@ public void GetOpenOrderDetails_Unfilled_IsCorrect() } [TestMethod] - public void GetOpenOrderDetails_AllUnfilled_IsCorrect() + public async Task GetOpenOrderDetails_AllUnfilled_IsCorrect() { var polo = CreatePoloniexAPI(AllUnfilledOrders); - IEnumerable orders = polo.GetOpenOrderDetailsAsync().Sync(); // all + IEnumerable orders = await polo.GetOpenOrderDetailsAsync(); // all ExchangeOrderResult order = orders.Single(); order.OrderId.Should().Be("35329211614"); order.IsBuy.Should().BeTrue(); @@ -230,10 +231,10 @@ public void GetOpenOrderDetails_AllUnfilled_IsCorrect() } [TestMethod] - public void GetOrderDetails_HappyPath() + public async Task GetOrderDetails_HappyPath() { var polo = CreatePoloniexAPI(ReturnOrderTrades_SimpleBuy); - ExchangeOrderResult order = polo.GetOrderDetailsAsync("1").Sync(); + ExchangeOrderResult order = await polo.GetOrderDetailsAsync("1"); order.OrderId.Should().Be("1"); order.Amount.Should().Be(19096.46996880m); @@ -252,7 +253,7 @@ public void GetOrderDetails_OrderNotFound_DoesNotThrow() { const string response = @"{""error"":""Order not found, or you are not the person who placed it.""}"; var polo = CreatePoloniexAPI(response); - void a() => polo.GetOrderDetailsAsync("1").Sync(); + async Task a() => await polo.GetOrderDetailsAsync("1"); Invoking(a).Should().Throw(); } @@ -262,15 +263,15 @@ public void GetOrderDetails_OtherErrors_ThrowAPIException() const string response = @"{""error"":""Big scary error.""}"; var polo = CreatePoloniexAPI(response); - void a() => polo.GetOrderDetailsAsync("1").Sync(); + async Task a() => await polo.GetOrderDetailsAsync("1"); Invoking(a).Should().Throw(); } [TestMethod] - public void GetCompletedOrderDetails_MultipleOrders() + public async Task GetCompletedOrderDetails_MultipleOrders() { var polo = CreatePoloniexAPI(ReturnOrderTrades_AllGas); - IEnumerable orders = polo.GetCompletedOrderDetailsAsync("ETH_GAS").Sync(); + IEnumerable orders = await polo.GetCompletedOrderDetailsAsync("ETH_GAS"); orders.Should().HaveCount(2); ExchangeOrderResult sellorder = orders.Single(x => !x.IsBuy); sellorder.AveragePrice.Should().Be(0.04123m); @@ -288,11 +289,11 @@ public void GetCompletedOrderDetails_MultipleOrders() } [TestMethod] - public void GetCompletedOrderDetails_AllSymbols() + public async Task GetCompletedOrderDetails_AllSymbols() { // {"BTC_MAID": [ { "globalTradeID": 29251512, "tradeID": "1385888", "date": "2016-05-03 01:29:55", "rate": "0.00014243", "amount": "353.74692925", "total": "0.05038417", "fee": "0.00200000", "orderNumber": "12603322113", "type": "buy", "category": "settlement" }, { "globalTradeID": 29251511, "tradeID": "1385887", "date": "2016-05-03 01:29:55", "rate": "0.00014111", "amount": "311.24262497", "total": "0.04391944", "fee": "0.00200000", "orderNumber": "12603319116", "type": "sell", "category": "marginTrade" } var polo = CreatePoloniexAPI(GetCompletedOrderDetails_AllSymbolsOrders); - ExchangeOrderResult order = polo.GetCompletedOrderDetailsAsync().Sync().First(); + ExchangeOrderResult order = (await polo.GetCompletedOrderDetailsAsync()).First(); order.MarketSymbol.Should().Be("BTC_MAID"); order.OrderId.Should().Be("12603322113"); order.OrderDate.Should().Be(new DateTime(2016, 5, 3, 1, 29, 55)); @@ -304,14 +305,23 @@ public void GetCompletedOrderDetails_AllSymbols() } [TestMethod] - public void OnGetDepositHistory_DoesNotFailOnMinTimestamp() + public async Task OnGetDepositHistory_DoesNotFailOnMinTimestamp() { var polo = CreatePoloniexAPI(null); - Invoking(() => polo.GetDepositHistoryAsync("doesntmatter").Sync()).Should().Throw().And.Message.Should().Contain("No result"); + try + { + await polo.GetDepositHistoryAsync("doesntmatter"); + } + catch (APIException ex) + { + Assert.IsTrue(ex.Message.Contains("No result")); + return; + } + Assert.Fail("Expected APIException with message containing 'No result'"); } [TestMethod] - public void GetExchangeMarketFromCache_SymbolsMetadataCacheRefreshesWhenSymbolNotFound() + public async Task GetExchangeMarketFromCache_SymbolsMetadataCacheRefreshesWhenSymbolNotFound() { var polo = CreatePoloniexAPI(Resources.PoloniexGetSymbolsMetadata1); int requestCount = 0; @@ -324,24 +334,24 @@ public void GetExchangeMarketFromCache_SymbolsMetadataCacheRefreshesWhenSymbolNo }; // retrieve without BTC_BCH in the result - polo.GetExchangeMarketFromCacheAsync("XMR_LTC").Sync().Should().NotBeNull(); + (await polo.GetExchangeMarketFromCacheAsync("XMR_LTC")).Should().NotBeNull(); requestCount.Should().Be(1); - polo.GetExchangeMarketFromCacheAsync("BTC_BCH").Sync().Should().BeNull(); + (await polo.GetExchangeMarketFromCacheAsync("BTC_BCH")).Should().BeNull(); requestCount.Should().Be(2); // now many moons later we request BTC_BCH, which wasn't in the first request but is in the latest exchange result (polo.RequestMaker as MockAPIRequestMaker).GlobalResponse = Resources.PoloniexGetSymbolsMetadata2; - polo.GetExchangeMarketFromCacheAsync("BTC_BCH").Sync().Should().NotBeNull(); + (await polo.GetExchangeMarketFromCacheAsync("BTC_BCH")).Should().NotBeNull(); requestCount.Should().Be(3); // and lets make sure it doesn't return something for null and garbage symbols - polo.GetExchangeMarketFromCacheAsync(null).Sync().Should().BeNull(); - polo.GetExchangeMarketFromCacheAsync(string.Empty).Sync().Should().BeNull(); - polo.GetExchangeMarketFromCacheAsync("324235!@^%Q@#%^").Sync().Should().BeNull(); - polo.GetExchangeMarketFromCacheAsync("NOCOIN_NORESULT").Sync().Should().BeNull(); + (await polo.GetExchangeMarketFromCacheAsync(null)).Should().BeNull(); + (await polo.GetExchangeMarketFromCacheAsync(string.Empty)).Should().BeNull(); + (await polo.GetExchangeMarketFromCacheAsync("324235!@^%Q@#%^")).Should().BeNull(); + (await polo.GetExchangeMarketFromCacheAsync("NOCOIN_NORESULT")).Should().BeNull(); } - private static Action Invoking(Action action) => action; + private static Func Invoking(Func action) => action; #region RealResponseJSON private const string SingleMarketTradeHistory = @"[{ diff --git a/ExchangeSharpTests/ExchangeTests.cs b/ExchangeSharpTests/ExchangeTests.cs index b56c8034..db894d14 100644 --- a/ExchangeSharpTests/ExchangeTests.cs +++ b/ExchangeSharpTests/ExchangeTests.cs @@ -33,28 +33,27 @@ public class ExchangeTests /// Loop through all exchanges, get a json string for all symbols /// /// - private string GetAllSymbolsJson() + private async Task GetAllSymbolsJsonAsync() { Dictionary allSymbols = new Dictionary(); - Parallel.ForEach(ExchangeAPI.GetExchangeAPIs(), (api) => + List tasks = new List(); + foreach (ExchangeAPI api in ExchangeAPI.GetExchangeAPIs()) { - try + tasks.Add(Task.Run(async () => { + string[] symbols = (await api.GetMarketSymbolsAsync()).ToArray(); lock (allSymbols) { - allSymbols[api.Name] = api.GetMarketSymbolsAsync().Sync().ToArray(); + allSymbols[api.Name] = symbols; } - } - catch (Exception ex) - { - Logger.Error(ex); - } - }); + })); + } + await Task.WhenAll(tasks); return JsonConvert.SerializeObject(allSymbols); } [TestMethod] - public void GlobalSymbolTest() + public async Task GlobalSymbolTest() { // if tests fail, uncomment this and add replace Resources.AllSymbolsJson // string allSymbolsJson = GetAllSymbolsJson(); System.IO.File.WriteAllText("TestData/AllSymbols.json", allSymbolsJson); @@ -76,8 +75,8 @@ public void GlobalSymbolTest() } bool isBithumb = (api.Name == ExchangeName.Bithumb); - string exchangeMarketSymbol = api.GlobalMarketSymbolToExchangeMarketSymbol(isBithumb ? globalMarketSymbolAlt : globalMarketSymbol); - string globalMarketSymbol2 = api.ExchangeMarketSymbolToGlobalMarketSymbol(exchangeMarketSymbol); + string exchangeMarketSymbol = await api.GlobalMarketSymbolToExchangeMarketSymbolAsync(isBithumb ? globalMarketSymbolAlt : globalMarketSymbol); + string globalMarketSymbol2 = await api.ExchangeMarketSymbolToGlobalMarketSymbolAsync(exchangeMarketSymbol); if ((!isBithumb && globalMarketSymbol2.EndsWith("-BTC")) || globalMarketSymbol2.EndsWith("-USD") || globalMarketSymbol2.EndsWith("-USDT")) diff --git a/README.md b/README.md index 05c850df..6f2ad675 100644 --- a/README.md +++ b/README.md @@ -72,42 +72,45 @@ You can also publish from Visual Studio (right click project, select publish), w ``` PM> Install-Package DigitalRuby.ExchangeSharp -Version 0.6.0 ``` -### Simple Example -``` -ExchangeKrakenAPI api = new ExchangeKrakenAPI(); -ExchangeTicker ticker = api.GetTickerAsync("XXBTZUSD").Sync(); -Console.WriteLine("On the Kraken exchange, 1 bitcoin is worth {0} USD.", ticker.Bid); +### Order Example +```csharp +public static async Task OrderExample() +{ + ExchangeKrakenAPI api = new ExchangeKrakenAPI(); + ExchangeTicker ticker = await api.GetTickerAsync("XXBTZUSD"); + Logger.Info("On the Kraken exchange, 1 bitcoin is worth {0} USD.", ticker.Bid); -// load API keys created from ExchangeSharpConsole.exe keys mode=create path=keys.bin keylist=public_key,private_key -api.LoadAPIKeys("keys.bin"); + // load API keys created from ExchangeSharpConsole.exe keys mode=create path=keys.bin keylist=public_key,private_key + api.LoadAPIKeys("keys.bin"); -/// place limit order for 0.01 bitcoin at ticker.Ask USD -ExchangeOrderResult result = api.PlaceOrderAsync(new ExchangeOrderRequest -{ - Amount = 0.01m, - IsBuy = true, - Price = ticker.Ask, - MarketSymbol = "XXBTZUSD" -}).Sync(); - -// Kraken is a bit funny in that they don't return the order details in the initial request, so you have to follow up with an order details request -// if you want to know more info about the order - most other exchanges don't return until they have the order details for you. -// I've also found that Kraken tends to fail if you follow up too quickly with an order details request, so sleep a bit to give them time to get -// their house in order. -System.Threading.Thread.Sleep(500); -result = api.GetOrderDetailsAsync(result.OrderId).Sync(); - -Console.WriteLine("Placed an order on Kraken for 0.01 bitcoin at {0} USD. Status is {1}. Order id is {2}.", ticker.Ask, result.Result, result.OrderId); + /// place limit order for 0.01 bitcoin at ticker.Ask USD + ExchangeOrderResult result = await api.PlaceOrderAsync(new ExchangeOrderRequest + { + Amount = 0.01m, + IsBuy = true, + Price = ticker.Ask, + MarketSymbol = "XXBTZUSD" + }); + + // Kraken is a bit funny in that they don't return the order details in the initial request, so you have to follow up with an order details request + // if you want to know more info about the order - most other exchanges don't return until they have the order details for you. + // I've also found that Kraken tends to fail if you follow up too quickly with an order details request, so sleep a bit to give them time to get + // their house in order. + await Task.Delay(500); + result = await api.GetOrderDetailsAsync(result.OrderId); + + Logger.Info("Placed an order on Kraken for 0.01 bitcoin at {0} USD. Status is {1}. Order id is {2}.", ticker.Ask, result.Result, result.OrderId); +} ``` ### Web Socket Example -``` -public static void Main(string[] args) +```csharp +public static async Task Main(string[] args) { // create a web socket connection to Binance. Note you can Dispose the socket anytime to shut it down. // the web socket will handle disconnects and attempt to re-connect automatically. ExchangeBinanceAPI b = new ExchangeBinanceAPI(); - using (var socket = b.GetTickersWebSocket((tickers) => + using (var socket = await b.GetTickersWebSocket((tickers) => { Console.WriteLine("{0} tickers, first: {1}", tickers.Count, tickers.First()); }))