diff --git a/src/ExchangeSharp/API/Exchanges/OKGroup/ExchangeOKExAPI.cs b/src/ExchangeSharp/API/Exchanges/OKGroup/ExchangeOKExAPI.cs index 80528a5c..14dd6c2a 100644 --- a/src/ExchangeSharp/API/Exchanges/OKGroup/ExchangeOKExAPI.cs +++ b/src/ExchangeSharp/API/Exchanges/OKGroup/ExchangeOKExAPI.cs @@ -10,11 +10,12 @@ 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; using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; +using ExchangeSharp.OKGroup; +using Newtonsoft.Json.Linq; namespace ExchangeSharp { @@ -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() { /* @@ -61,61 +67,54 @@ 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) { 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() { - 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 +147,83 @@ 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")); + 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(); + } + + 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); + } + + 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) + ],.. + ] } + */ - return trades; + 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( - 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"; + } } 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) {