From b99d7df2b5ee0536b6d62348323fc30d9f02ad63 Mon Sep 17 00:00:00 2001 From: BZ-CO <30245815+BZ-CO@users.noreply.github.com> Date: Tue, 5 Oct 2021 23:16:02 +0300 Subject: [PATCH 1/4] Code cleanup --- .../API/Exchanges/OKGroup/ExchangeOKExAPI.cs | 123 ++++++++---------- 1 file changed, 54 insertions(+), 69 deletions(-) diff --git a/src/ExchangeSharp/API/Exchanges/OKGroup/ExchangeOKExAPI.cs b/src/ExchangeSharp/API/Exchanges/OKGroup/ExchangeOKExAPI.cs index 80528a5c..bcf01fbc 100644 --- a/src/ExchangeSharp/API/Exchanges/OKGroup/ExchangeOKExAPI.cs +++ b/src/ExchangeSharp/API/Exchanges/OKGroup/ExchangeOKExAPI.cs @@ -12,8 +12,8 @@ The above copyright notice and this permission notice shall be included in all c using ExchangeSharp.OKGroup; using Newtonsoft.Json.Linq; -using System; using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; namespace ExchangeSharp @@ -61,43 +61,35 @@ protected internal override async Task> OnGetMarketS ] } */ - List markets = new List(); + var markets = new List(); parseMarketSymbolTokens(await MakeJsonRequestAsync( "/public/instruments?instType=SPOT", BaseUrlV5)); - if (IsFuturesAndSwapEnabled) - { - parseMarketSymbolTokens(await MakeJsonRequestAsync( - "/public/instruments?instType=FUTURES", BaseUrlV5)); - parseMarketSymbolTokens(await MakeJsonRequestAsync( - "/public/instruments?instType=SWAP", BaseUrlV5)); - } + if (!IsFuturesAndSwapEnabled) + return markets; + parseMarketSymbolTokens(await MakeJsonRequestAsync( + "/public/instruments?instType=FUTURES", BaseUrlV5)); + parseMarketSymbolTokens(await MakeJsonRequestAsync( + "/public/instruments?instType=SWAP", BaseUrlV5)); + return markets; + void parseMarketSymbolTokens(JToken allMarketSymbolTokens) { - foreach (JToken marketSymbolToken in allMarketSymbolTokens) - { - var isSpot = marketSymbolToken["instType"].Value() == "SPOT"; - var baseCurrency = isSpot - ? marketSymbolToken["baseCcy"].Value() - : marketSymbolToken["settleCcy"].Value(); - var quoteCurrency = isSpot - ? marketSymbolToken["quoteCcy"].Value() - : marketSymbolToken["ctValCcy"].Value(); - var market = new ExchangeMarket - { - MarketSymbol = marketSymbolToken["instId"].Value(), - IsActive = marketSymbolToken["state"].Value() == "live", - QuoteCurrency = quoteCurrency, - BaseCurrency = baseCurrency, - PriceStepSize = marketSymbolToken["tickSz"].ConvertInvariant(), - MinPrice = marketSymbolToken["tickSz"].ConvertInvariant(), // assuming that this is also the min price since it isn't provided explicitly by the exchange - MinTradeSize = marketSymbolToken["minSz"].ConvertInvariant(), - QuantityStepSize = marketSymbolToken["lotSz"].ConvertInvariant(), - }; - markets.Add(market); - } + markets.AddRange(from marketSymbolToken in allMarketSymbolTokens + let isSpot = marketSymbolToken["instType"].Value() == "SPOT" + let baseCurrency = isSpot ? marketSymbolToken["baseCcy"].Value() : marketSymbolToken["settleCcy"].Value() + let quoteCurrency = isSpot ? marketSymbolToken["quoteCcy"].Value() : marketSymbolToken["ctValCcy"].Value() + select new ExchangeMarket + { + MarketSymbol = marketSymbolToken["instId"].Value(), + IsActive = marketSymbolToken["state"].Value() == "live", + QuoteCurrency = quoteCurrency, + BaseCurrency = baseCurrency, + PriceStepSize = marketSymbolToken["tickSz"].ConvertInvariant(), + MinPrice = marketSymbolToken["tickSz"].ConvertInvariant(), // assuming that this is also the min price since it isn't provided explicitly by the exchange + MinTradeSize = marketSymbolToken["minSz"].ConvertInvariant(), + QuantityStepSize = marketSymbolToken["lotSz"].ConvertInvariant() + }); } - - return markets; } protected override async Task OnGetTickerAsync(string marketSymbol) @@ -109,13 +101,14 @@ protected override async Task OnGetTickerAsync(string marketSymb protected override async Task>> OnGetTickersAsync() { - List> tickers = new List>(); + var tickers = new List>(); await parseData(await MakeJsonRequestAsync("/market/tickers?instType=SPOT", BaseUrlV5)); - if (IsFuturesAndSwapEnabled) - { - await parseData(await MakeJsonRequestAsync("/market/tickers?instType=FUTURES", BaseUrlV5)); - await parseData(await MakeJsonRequestAsync("/market/tickers?instType=SWAP", BaseUrlV5)); - } + if (!IsFuturesAndSwapEnabled) + return tickers; + await parseData(await MakeJsonRequestAsync("/market/tickers?instType=FUTURES", BaseUrlV5)); + await parseData(await MakeJsonRequestAsync("/market/tickers?instType=SWAP", BaseUrlV5)); + return tickers; + async Task parseData(JToken tickerResponse) { /*{ @@ -148,49 +141,41 @@ async Task parseData(JToken tickerResponse) foreach (JToken t in tickerResponse) { var symbol = t["instId"].Value(); - ExchangeTicker ticker = await ParseTickerV5Async(t, symbol); + var ticker = await ParseTickerV5Async(t, symbol); tickers.Add(new KeyValuePair(symbol, ticker)); } } - - return tickers; } - protected override async Task> OnGetRecentTradesAsync(string marketSymbol, int? limit) + protected override async Task> OnGetRecentTradesAsync(string marketSymbol, + int? limit = null) { - limit = limit ?? 500; + limit ??= 500; marketSymbol = NormalizeMarketSymbol(marketSymbol); - List trades = new List(); - var recentTradesResponse = await MakeJsonRequestAsync($"/market/trades?instId={marketSymbol}&limit={limit}", BaseUrlV5); - foreach (var t in recentTradesResponse) - { - trades.Add( - t.ParseTrade( - amountKey: "sz", - priceKey: "px", - typeKey: "side", - timestampKey: "ts", - timestampType: TimestampType.UnixMilliseconds, - idKey: "tradeId")); - } - - return trades; + var recentTradesResponse = + await MakeJsonRequestAsync($"/market/trades?instId={marketSymbol}&limit={limit}", BaseUrlV5); + return recentTradesResponse.Select(t => t.ParseTrade( + "sz", "px", "side", "ts", TimestampType.UnixMilliseconds, "tradeId")) + .ToList(); } private async Task ParseTickerV5Async(JToken t, string symbol) { return await this.ParseTickerAsync( - t, - symbol, - askKey: "askPx", - bidKey: "bidPx", - lastKey: "last", - baseVolumeKey: "vol24h", - quoteVolumeKey: "volCcy24h", - timestampKey: "ts", - timestampType: TimestampType.UnixMilliseconds); + token: t, + marketSymbol: symbol, + askKey: "askPx", + bidKey: "bidPx", + lastKey: "last", + baseVolumeKey: "vol24h", + quoteVolumeKey: "volCcy24h", + timestampKey: "ts", + timestampType: TimestampType.UnixMilliseconds); } } - public partial class ExchangeName { public const string OKEx = "OKEx"; } + public partial class ExchangeName + { + public const string OKEx = "OKEx"; + } } From b410ade3ab64b6828211ecafcfe456c5b94ff186 Mon Sep 17 00:00:00 2001 From: BZ-CO <30245815+BZ-CO@users.noreply.github.com> Date: Tue, 5 Oct 2021 23:49:24 +0300 Subject: [PATCH 2/4] Update OnGetOrderBookAsync Migration to V5 API --- .../API/Exchanges/OKGroup/ExchangeOKExAPI.cs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/ExchangeSharp/API/Exchanges/OKGroup/ExchangeOKExAPI.cs b/src/ExchangeSharp/API/Exchanges/OKGroup/ExchangeOKExAPI.cs index bcf01fbc..13208be3 100644 --- a/src/ExchangeSharp/API/Exchanges/OKGroup/ExchangeOKExAPI.cs +++ b/src/ExchangeSharp/API/Exchanges/OKGroup/ExchangeOKExAPI.cs @@ -10,11 +10,11 @@ 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. */ -using ExchangeSharp.OKGroup; -using Newtonsoft.Json.Linq; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using ExchangeSharp.OKGroup; +using Newtonsoft.Json.Linq; namespace ExchangeSharp { @@ -159,6 +159,12 @@ protected override async Task> OnGetRecentTradesAsync .ToList(); } + protected override async Task OnGetOrderBookAsync(string marketSymbol, int maxCount = 100) + { + var token = await MakeJsonRequestAsync($"/market/books?instId={marketSymbol}&sz={maxCount}", BaseUrlV5); + return token[0].ParseOrderBookFromJTokenArrays(maxCount: maxCount); + } + private async Task ParseTickerV5Async(JToken t, string symbol) { return await this.ParseTickerAsync( From 960c5b9e244157bc8cee3992dfb110388dd764fc Mon Sep 17 00:00:00 2001 From: BZ-CO <30245815+BZ-CO@users.noreply.github.com> Date: Wed, 6 Oct 2021 01:02:03 +0300 Subject: [PATCH 3/4] Update CryptoUtility SecondsToPeriodString Added new parameter `capitalAfterMinute`, because OKEx requires capital letter after minute. Probably some other exchanges needs that as well. --- src/ExchangeSharp/Utility/CryptoUtility.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/ExchangeSharp/Utility/CryptoUtility.cs b/src/ExchangeSharp/Utility/CryptoUtility.cs index 788a6171..b6e6f9ed 100644 --- a/src/ExchangeSharp/Utility/CryptoUtility.cs +++ b/src/ExchangeSharp/Utility/CryptoUtility.cs @@ -1137,8 +1137,9 @@ public static byte[] AesEncryption(byte[] input, byte[] password, byte[] salt) /// Convert seconds to a period string, i.e. 5s, 1m, 2h, 3d, 1w, 1M, etc. /// /// Seconds. Use 60 for minute, 3600 for hour, 3600*24 for day, 3600*24*30 for month. + /// Capitalize all letters after m, i.e. 5s, 1m, 30m, 1H, 2H, 3D, 1W, 1M, etc. /// Period string - public static string SecondsToPeriodString(int seconds) + public static string SecondsToPeriodString(int seconds, bool capitalAfterMinute = false) { const int minuteThreshold = 60; const int hourThreshold = 60 * 60; @@ -1152,15 +1153,15 @@ public static string SecondsToPeriodString(int seconds) } else if (seconds >= weekThreshold) { - return seconds / weekThreshold + "w"; + return seconds / weekThreshold + (capitalAfterMinute ? "W" : "w"); } else if (seconds >= dayThreshold) { - return seconds / dayThreshold + "d"; + return seconds / dayThreshold + (capitalAfterMinute ? "D" : "d"); } else if (seconds >= hourThreshold) { - return seconds / hourThreshold + "h"; + return seconds / hourThreshold + (capitalAfterMinute ? "H" : "h"); } else if (seconds >= minuteThreshold) { From ac401082e9d9478ccac6fe8352a26c8fa6d1c9c8 Mon Sep 17 00:00:00 2001 From: BZ-CO <30245815+BZ-CO@users.noreply.github.com> Date: Wed, 6 Oct 2021 01:03:01 +0300 Subject: [PATCH 4/4] Update OnGetCandlesAsync Migration to V5 API --- .../API/Exchanges/OKGroup/ExchangeOKExAPI.cs | 46 ++++++++++++++++++- 1 file changed, 44 insertions(+), 2 deletions(-) diff --git a/src/ExchangeSharp/API/Exchanges/OKGroup/ExchangeOKExAPI.cs b/src/ExchangeSharp/API/Exchanges/OKGroup/ExchangeOKExAPI.cs index 13208be3..14dd6c2a 100644 --- a/src/ExchangeSharp/API/Exchanges/OKGroup/ExchangeOKExAPI.cs +++ b/src/ExchangeSharp/API/Exchanges/OKGroup/ExchangeOKExAPI.cs @@ -10,6 +10,7 @@ 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. */ +using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; @@ -27,6 +28,11 @@ public sealed partial class ExchangeOKExAPI : OKGroupCommon public string BaseUrlV5 { get; set; } = "https://okex.com/api/v5"; protected override bool IsFuturesAndSwapEnabled { get; } = true; + public override string PeriodSecondsToString(int seconds) + { + return CryptoUtility.SecondsToPeriodString(seconds, true); + } + protected internal override async Task> OnGetMarketSymbolsMetadataAsync() { /* @@ -95,8 +101,8 @@ void parseMarketSymbolTokens(JToken allMarketSymbolTokens) protected override async Task OnGetTickerAsync(string marketSymbol) { var tickerResponse = await MakeJsonRequestAsync($"/market/ticker?instId={marketSymbol}", BaseUrlV5); - var symbol = tickerResponse["instId"].Value(); - return await ParseTickerV5Async(tickerResponse, symbol); + var symbol = tickerResponse[0]["instId"].Value(); + return await ParseTickerV5Async(tickerResponse[0], symbol); } protected override async Task>> OnGetTickersAsync() @@ -165,6 +171,42 @@ protected override async Task OnGetOrderBookAsync(string mark return token[0].ParseOrderBookFromJTokenArrays(maxCount: maxCount); } + protected override async Task> OnGetCandlesAsync(string marketSymbol, int periodSeconds, DateTime? startDate = null, DateTime? endDate = null, int? limit = null) + { + /* + { + "code":"0", + "msg":"", + "data":[ + [ + "1597026383085", timestamp + "3.721", open + "3.743", high + "3.677", low + "3.708", close + "8422410", volume + "22698348.04828491" volCcy (Quote) + ],.. + ] + } + */ + + var candles = new List(); + var url = $"/market/history-candles?instId={marketSymbol}"; + if (startDate.HasValue) + url += "&after=" + (long)startDate.Value.UnixTimestampFromDateTimeMilliseconds(); + if (endDate.HasValue) + url += "&before=" + (long)endDate.Value.UnixTimestampFromDateTimeMilliseconds(); + if (limit.HasValue) + url += "&limit=" + limit.Value.ToStringInvariant(); + var periodString = PeriodSecondsToString(periodSeconds); + url += $"&bar={periodString}"; + var obj = await MakeJsonRequestAsync(url, BaseUrlV5); + foreach (JArray token in obj) + candles.Add(this.ParseCandle(token, marketSymbol, periodSeconds, 1, 2, 3, 4, 0, TimestampType.UnixMilliseconds, 5, 6)); + return candles; + } + private async Task ParseTickerV5Async(JToken t, string symbol) { return await this.ParseTickerAsync(