From 9e1655dd7e7b22acfb6c870d1bcef2c7cecbb6e5 Mon Sep 17 00:00:00 2001 From: BZ-CO <30245815+BZ-CO@users.noreply.github.com> Date: Thu, 14 Oct 2021 22:48:50 +0300 Subject: [PATCH 1/8] Update OnGetAmountsAsync Migration to V5 API --- .../API/Exchanges/OKGroup/ExchangeOKExAPI.cs | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/src/ExchangeSharp/API/Exchanges/OKGroup/ExchangeOKExAPI.cs b/src/ExchangeSharp/API/Exchanges/OKGroup/ExchangeOKExAPI.cs index 14dd6c2a..2527099b 100644 --- a/src/ExchangeSharp/API/Exchanges/OKGroup/ExchangeOKExAPI.cs +++ b/src/ExchangeSharp/API/Exchanges/OKGroup/ExchangeOKExAPI.cs @@ -10,11 +10,15 @@ 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. */ +#nullable enable using System; using System.Collections.Generic; using System.Linq; +using System.Security.Cryptography; +using System.Text; using System.Threading.Tasks; using ExchangeSharp.OKGroup; +using Newtonsoft.Json; using Newtonsoft.Json.Linq; namespace ExchangeSharp @@ -207,6 +211,49 @@ protected override async Task> OnGetCandlesAsync(strin return candles; } + protected override async Task> OnGetAmountsAsync() + { + var payload = await GetNoncePayloadAsync(); + JToken token = await MakeJsonRequestAsync("/account/balance", BaseUrlV5, payload); + return token[0]["details"] + .Select(x => new { Currency = x["ccy"].Value(), TotalBalance = x["cashBal"].Value() }) + .ToDictionary(k => k.Currency, v => v.TotalBalance);; + } + + protected override Task ProcessRequestAsync(IHttpWebRequest request, Dictionary payload) + { + if (!CanMakeAuthenticatedRequest(payload)) return Task.CompletedTask; + // We don't need nonce in the request. Using it only to not break CanMakeAuthenticatedRequest. + payload.Remove("nonce"); + + var method = request.Method; + var now = DateTime.Now; + var timeStamp = TimeZoneInfo.ConvertTimeToUtc(now).ToString("yyyy-MM-ddTHH:mm:ss.fffZ"); + var requestUrl = request.RequestUri.PathAndQuery; + var body = payload.Any() ? JsonConvert.SerializeObject(payload) : string.Empty; + + var sign = string.IsNullOrEmpty(body) + ? HmacSHA256($"{timeStamp}{method}{requestUrl}", PrivateApiKey!.ToUnsecureString()) + : HmacSHA256($"{timeStamp}{method}{requestUrl}{body}", PrivateApiKey!.ToUnsecureString()); + + request.AddHeader("OK-ACCESS-KEY", PublicApiKey!.ToUnsecureString()); + request.AddHeader("OK-ACCESS-SIGN", sign); + request.AddHeader("OK-ACCESS-TIMESTAMP", timeStamp.ToString()); + request.AddHeader("OK-ACCESS-PASSPHRASE", Passphrase!.ToUnsecureString()); + request.AddHeader("x-simulated-trading", "0"); + + string HmacSHA256(string infoStr, string secret) + { + var sha256Data = Encoding.UTF8.GetBytes(infoStr); + var secretData = Encoding.UTF8.GetBytes(secret); + using var hmacsha256 = new HMACSHA256(secretData); + var buffer = hmacsha256.ComputeHash(sha256Data); + return Convert.ToBase64String(buffer); + } + + return Task.CompletedTask; + } + private async Task ParseTickerV5Async(JToken t, string symbol) { return await this.ParseTickerAsync( From 44beed0f010da633869a620a52c5e8081f4a7916 Mon Sep 17 00:00:00 2001 From: BZ-CO <30245815+BZ-CO@users.noreply.github.com> Date: Thu, 14 Oct 2021 22:59:30 +0300 Subject: [PATCH 2/8] Update OnGetAmountsAvailableToTradeAsync Migration to V5 API --- .../API/Exchanges/OKGroup/ExchangeOKExAPI.cs | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/ExchangeSharp/API/Exchanges/OKGroup/ExchangeOKExAPI.cs b/src/ExchangeSharp/API/Exchanges/OKGroup/ExchangeOKExAPI.cs index 2527099b..a60d7cbe 100644 --- a/src/ExchangeSharp/API/Exchanges/OKGroup/ExchangeOKExAPI.cs +++ b/src/ExchangeSharp/API/Exchanges/OKGroup/ExchangeOKExAPI.cs @@ -213,13 +213,21 @@ protected override async Task> OnGetCandlesAsync(strin protected override async Task> OnGetAmountsAsync() { - var payload = await GetNoncePayloadAsync(); - JToken token = await MakeJsonRequestAsync("/account/balance", BaseUrlV5, payload); + var token = await GetBalance(); return token[0]["details"] .Select(x => new { Currency = x["ccy"].Value(), TotalBalance = x["cashBal"].Value() }) .ToDictionary(k => k.Currency, v => v.TotalBalance);; } + protected override async Task> OnGetAmountsAvailableToTradeAsync() + { + var token = await GetBalance(); + return token[0]["details"] + .Select(x => new + { Currency = x["ccy"].Value(), AvailableBalance = x["availBal"].Value() }) + .ToDictionary(k => k.Currency, v => v.AvailableBalance); + } + protected override Task ProcessRequestAsync(IHttpWebRequest request, Dictionary payload) { if (!CanMakeAuthenticatedRequest(payload)) return Task.CompletedTask; @@ -254,6 +262,11 @@ string HmacSHA256(string infoStr, string secret) return Task.CompletedTask; } + private async Task GetBalance() + { + return await MakeJsonRequestAsync("/account/balance", BaseUrlV5, await GetNoncePayloadAsync()); + } + private async Task ParseTickerV5Async(JToken t, string symbol) { return await this.ParseTickerAsync( From 5b41fbfde4118c617bae205dd553dc16dd42e58e Mon Sep 17 00:00:00 2001 From: BZ-CO <30245815+BZ-CO@users.noreply.github.com> Date: Thu, 14 Oct 2021 23:46:10 +0300 Subject: [PATCH 3/8] Update OnGetMarginAmountsAvailableToTradeAsync Migration to V5 API --- .../API/Exchanges/OKGroup/ExchangeOKExAPI.cs | 65 ++++++++++++++----- 1 file changed, 47 insertions(+), 18 deletions(-) diff --git a/src/ExchangeSharp/API/Exchanges/OKGroup/ExchangeOKExAPI.cs b/src/ExchangeSharp/API/Exchanges/OKGroup/ExchangeOKExAPI.cs index a60d7cbe..c104df11 100644 --- a/src/ExchangeSharp/API/Exchanges/OKGroup/ExchangeOKExAPI.cs +++ b/src/ExchangeSharp/API/Exchanges/OKGroup/ExchangeOKExAPI.cs @@ -85,20 +85,26 @@ protected internal override async Task> OnGetMarketS void parseMarketSymbolTokens(JToken allMarketSymbolTokens) { 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() - }); + 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< + decimal>(), // 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() + }); } } @@ -171,11 +177,13 @@ protected override async Task> OnGetRecentTradesAsync protected override async Task OnGetOrderBookAsync(string marketSymbol, int maxCount = 100) { - var token = await MakeJsonRequestAsync($"/market/books?instId={marketSymbol}&sz={maxCount}", BaseUrlV5); + 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) + protected override async Task> OnGetCandlesAsync(string marketSymbol, + int periodSeconds, DateTime? startDate = null, DateTime? endDate = null, int? limit = null) { /* { @@ -207,7 +215,8 @@ protected override async Task> OnGetCandlesAsync(strin 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)); + candles.Add(this.ParseCandle(token, marketSymbol, periodSeconds, 1, 2, 3, 4, 0, + TimestampType.UnixMilliseconds, 5, 6)); return candles; } @@ -216,7 +225,8 @@ protected override async Task> OnGetAmountsAsync() var token = await GetBalance(); return token[0]["details"] .Select(x => new { Currency = x["ccy"].Value(), TotalBalance = x["cashBal"].Value() }) - .ToDictionary(k => k.Currency, v => v.TotalBalance);; + .ToDictionary(k => k.Currency, v => v.TotalBalance); + ; } protected override async Task> OnGetAmountsAvailableToTradeAsync() @@ -228,6 +238,25 @@ protected override async Task> OnGetAmountsAvailable .ToDictionary(k => k.Currency, v => v.AvailableBalance); } + protected override async Task> OnGetMarginAmountsAvailableToTradeAsync( + bool includeZeroBalances) + { + var token = await GetBalance(); + var availableEquity = token[0]["details"] + .Select(x => new + { + Currency = x["ccy"].Value(), + AvailableEquity = x["availEq"].Value() == string.Empty ? 0 : x["availEq"].Value() + }) + .ToDictionary(k => k.Currency, v => v.AvailableEquity); + + return includeZeroBalances + ? availableEquity + : availableEquity + .Where(x => x.Value > 0) + .ToDictionary(k => k.Key, v => v.Value); + } + protected override Task ProcessRequestAsync(IHttpWebRequest request, Dictionary payload) { if (!CanMakeAuthenticatedRequest(payload)) return Task.CompletedTask; From ea2598ed0493dc63f6de08bff8e7dee33612451f Mon Sep 17 00:00:00 2001 From: BZ-CO <30245815+BZ-CO@users.noreply.github.com> Date: Fri, 15 Oct 2021 02:15:49 +0300 Subject: [PATCH 4/8] Update OnGetOpenOrderDetailsAsync Migration to V5 API --- .../API/Exchanges/OKGroup/ExchangeOKExAPI.cs | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/ExchangeSharp/API/Exchanges/OKGroup/ExchangeOKExAPI.cs b/src/ExchangeSharp/API/Exchanges/OKGroup/ExchangeOKExAPI.cs index c104df11..4ed0b1f3 100644 --- a/src/ExchangeSharp/API/Exchanges/OKGroup/ExchangeOKExAPI.cs +++ b/src/ExchangeSharp/API/Exchanges/OKGroup/ExchangeOKExAPI.cs @@ -257,6 +257,28 @@ protected override async Task> OnGetMarginAmountsAva .ToDictionary(k => k.Key, v => v.Value); } + protected override async Task> OnGetOpenOrderDetailsAsync(string marketSymbol) + { + var token = await MakeJsonRequestAsync("/trade/orders-pending", BaseUrlV5, + await GetNoncePayloadAsync()); + return token + .Select(x => new ExchangeOrderResult() + { + OrderId = x["ordId"].Value(), + OrderDate = DateTimeOffset.FromUnixTimeMilliseconds(x["cTime"].Value()).DateTime, + Result = x["state"].Value() == "live" ? ExchangeAPIOrderResult.Open : ExchangeAPIOrderResult.FilledPartially, + IsBuy = x["side"].Value() == "buy" ? true : false, + IsAmountFilledReversed = false, + Amount = x["sz"].Value(), + AmountFilled = x["accFillSz"].Value(), + AveragePrice = x["avgPx"].Value() == string.Empty ? default : x["avgPx"].Value(), + Price = x["px"].Value(), + ClientOrderId = x["clOrdId"].Value(), + FeesCurrency = x["feeCcy"].Value(), + MarketSymbol = x["instId"].Value() + }); + } + protected override Task ProcessRequestAsync(IHttpWebRequest request, Dictionary payload) { if (!CanMakeAuthenticatedRequest(payload)) return Task.CompletedTask; From e62b7df0b97940368b202cd994a96f11f2ec8db2 Mon Sep 17 00:00:00 2001 From: BZ-CO <30245815+BZ-CO@users.noreply.github.com> Date: Fri, 15 Oct 2021 21:49:35 +0300 Subject: [PATCH 5/8] Update OnGetOrderDetailsAsync Migration to V5 API --- .../API/Exchanges/OKGroup/ExchangeOKExAPI.cs | 59 ++++++++++++++----- 1 file changed, 43 insertions(+), 16 deletions(-) diff --git a/src/ExchangeSharp/API/Exchanges/OKGroup/ExchangeOKExAPI.cs b/src/ExchangeSharp/API/Exchanges/OKGroup/ExchangeOKExAPI.cs index 4ed0b1f3..d278e26d 100644 --- a/src/ExchangeSharp/API/Exchanges/OKGroup/ExchangeOKExAPI.cs +++ b/src/ExchangeSharp/API/Exchanges/OKGroup/ExchangeOKExAPI.cs @@ -261,22 +261,29 @@ protected override async Task> OnGetOpenOrderDe { var token = await MakeJsonRequestAsync("/trade/orders-pending", BaseUrlV5, await GetNoncePayloadAsync()); - return token - .Select(x => new ExchangeOrderResult() - { - OrderId = x["ordId"].Value(), - OrderDate = DateTimeOffset.FromUnixTimeMilliseconds(x["cTime"].Value()).DateTime, - Result = x["state"].Value() == "live" ? ExchangeAPIOrderResult.Open : ExchangeAPIOrderResult.FilledPartially, - IsBuy = x["side"].Value() == "buy" ? true : false, - IsAmountFilledReversed = false, - Amount = x["sz"].Value(), - AmountFilled = x["accFillSz"].Value(), - AveragePrice = x["avgPx"].Value() == string.Empty ? default : x["avgPx"].Value(), - Price = x["px"].Value(), - ClientOrderId = x["clOrdId"].Value(), - FeesCurrency = x["feeCcy"].Value(), - MarketSymbol = x["instId"].Value() - }); + return ParseOrders(token); + } + + protected override async Task OnGetOrderDetailsAsync(string orderId, + string marketSymbol = null, bool isClientOrderId = false) + { + if (string.IsNullOrEmpty(marketSymbol)) + { + throw new ArgumentNullException(nameof(marketSymbol), + "Okex single order details request requires symbol"); + } + + if (string.IsNullOrEmpty(orderId)) + { + throw new ArgumentNullException(nameof(orderId), + "Okex single order details request requires order ID or client-supplied order ID"); + } + + var param = isClientOrderId ? $"clOrdId={orderId}" : $"ordId={orderId}"; + var token = await MakeJsonRequestAsync($"/trade/order?{param}&instId={marketSymbol}", BaseUrlV5, + await GetNoncePayloadAsync()); + + return ParseOrders(token).First(); } protected override Task ProcessRequestAsync(IHttpWebRequest request, Dictionary payload) @@ -318,6 +325,26 @@ private async Task GetBalance() return await MakeJsonRequestAsync("/account/balance", BaseUrlV5, await GetNoncePayloadAsync()); } + private IEnumerable ParseOrders(JToken token) + => token.Select(x => + new ExchangeOrderResult() + { + OrderId = x["ordId"].Value(), + OrderDate = DateTimeOffset.FromUnixTimeMilliseconds(x["cTime"].Value()).DateTime, + Result = x["state"].Value() == "live" + ? ExchangeAPIOrderResult.Open + : ExchangeAPIOrderResult.FilledPartially, + IsBuy = x["side"].Value() == "buy" ? true : false, + IsAmountFilledReversed = false, + Amount = x["sz"].Value(), + AmountFilled = x["accFillSz"].Value(), + AveragePrice = x["avgPx"].Value() == string.Empty ? default : x["avgPx"].Value(), + Price = x["px"].Value(), + ClientOrderId = x["clOrdId"].Value(), + FeesCurrency = x["feeCcy"].Value(), + MarketSymbol = x["instId"].Value() + }); + private async Task ParseTickerV5Async(JToken t, string symbol) { return await this.ParseTickerAsync( From d5aa708979924ae3ccb49616190ba1079446cdc3 Mon Sep 17 00:00:00 2001 From: BZ-CO <30245815+BZ-CO@users.noreply.github.com> Date: Sat, 16 Oct 2021 02:11:25 +0300 Subject: [PATCH 6/8] Update OnCancelOrderAsync Migration to V5 API --- .../API/Exchanges/OKGroup/ExchangeOKExAPI.cs | 33 ++++++++++++++++--- 1 file changed, 28 insertions(+), 5 deletions(-) diff --git a/src/ExchangeSharp/API/Exchanges/OKGroup/ExchangeOKExAPI.cs b/src/ExchangeSharp/API/Exchanges/OKGroup/ExchangeOKExAPI.cs index d278e26d..72cb0ca3 100644 --- a/src/ExchangeSharp/API/Exchanges/OKGroup/ExchangeOKExAPI.cs +++ b/src/ExchangeSharp/API/Exchanges/OKGroup/ExchangeOKExAPI.cs @@ -29,7 +29,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:8443/ws/v3"; - public string BaseUrlV5 { get; set; } = "https://okex.com/api/v5"; + public string BaseUrlV5 { get; set; } = "https://www.okex.com/api/v5"; protected override bool IsFuturesAndSwapEnabled { get; } = true; public override string PeriodSecondsToString(int seconds) @@ -286,9 +286,27 @@ protected override async Task OnGetOrderDetailsAsync(string return ParseOrders(token).First(); } - protected override Task ProcessRequestAsync(IHttpWebRequest request, Dictionary payload) + protected override async Task OnCancelOrderAsync(string orderId, string marketSymbol) { - if (!CanMakeAuthenticatedRequest(payload)) return Task.CompletedTask; + if (string.IsNullOrEmpty(orderId)) + { + throw new ArgumentNullException(nameof(orderId), "Okex cancel order request requires order ID"); + } + + if (string.IsNullOrEmpty(marketSymbol)) + { + throw new ArgumentNullException(nameof(marketSymbol), "Okex cancel order request requires symbol"); + } + + var payload = await GetNoncePayloadAsync(); + payload["ordId"] = orderId; + payload["instId"] = marketSymbol; + var token = await MakeJsonRequestAsync("/trade/cancel-order", BaseUrlV5, payload, "POST"); + } + + protected override async Task ProcessRequestAsync(IHttpWebRequest request, Dictionary payload) + { + if (!CanMakeAuthenticatedRequest(payload)) return; // We don't need nonce in the request. Using it only to not break CanMakeAuthenticatedRequest. payload.Remove("nonce"); @@ -307,7 +325,14 @@ protected override Task ProcessRequestAsync(IHttpWebRequest request, Dictionary< request.AddHeader("OK-ACCESS-TIMESTAMP", timeStamp.ToString()); request.AddHeader("OK-ACCESS-PASSPHRASE", Passphrase!.ToUnsecureString()); request.AddHeader("x-simulated-trading", "0"); + request.AddHeader("content-type", "application/json"); + if (request.Method == "POST") + { + await request.WritePayloadJsonToRequestAsync(payload); + } + + // TODO Check why it doesn't return the same result as CryptoUtility.SHA256Sign() string HmacSHA256(string infoStr, string secret) { var sha256Data = Encoding.UTF8.GetBytes(infoStr); @@ -316,8 +341,6 @@ string HmacSHA256(string infoStr, string secret) var buffer = hmacsha256.ComputeHash(sha256Data); return Convert.ToBase64String(buffer); } - - return Task.CompletedTask; } private async Task GetBalance() From 5361dcf57fb96279435056d46d6db6f8c6288896 Mon Sep 17 00:00:00 2001 From: BZ-CO <30245815+BZ-CO@users.noreply.github.com> Date: Sat, 16 Oct 2021 02:21:02 +0300 Subject: [PATCH 7/8] Cleanup --- .../API/Exchanges/OKGroup/ExchangeOKExAPI.cs | 25 +++++++++---------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/src/ExchangeSharp/API/Exchanges/OKGroup/ExchangeOKExAPI.cs b/src/ExchangeSharp/API/Exchanges/OKGroup/ExchangeOKExAPI.cs index 72cb0ca3..d48f13ca 100644 --- a/src/ExchangeSharp/API/Exchanges/OKGroup/ExchangeOKExAPI.cs +++ b/src/ExchangeSharp/API/Exchanges/OKGroup/ExchangeOKExAPI.cs @@ -72,17 +72,17 @@ protected internal override async Task> OnGetMarketS } */ var markets = new List(); - parseMarketSymbolTokens(await MakeJsonRequestAsync( + ParseMarketSymbolTokens(await MakeJsonRequestAsync( "/public/instruments?instType=SPOT", BaseUrlV5)); if (!IsFuturesAndSwapEnabled) return markets; - parseMarketSymbolTokens(await MakeJsonRequestAsync( + ParseMarketSymbolTokens(await MakeJsonRequestAsync( "/public/instruments?instType=FUTURES", BaseUrlV5)); - parseMarketSymbolTokens(await MakeJsonRequestAsync( + ParseMarketSymbolTokens(await MakeJsonRequestAsync( "/public/instruments?instType=SWAP", BaseUrlV5)); return markets; - void parseMarketSymbolTokens(JToken allMarketSymbolTokens) + void ParseMarketSymbolTokens(JToken allMarketSymbolTokens) { markets.AddRange(from marketSymbolToken in allMarketSymbolTokens let isSpot = marketSymbolToken["instType"].Value() == "SPOT" @@ -118,14 +118,14 @@ protected override async Task OnGetTickerAsync(string marketSymb protected override async Task>> OnGetTickersAsync() { var tickers = new List>(); - await parseData(await MakeJsonRequestAsync("/market/tickers?instType=SPOT", BaseUrlV5)); + await ParseData(await MakeJsonRequestAsync("/market/tickers?instType=SPOT", BaseUrlV5)); if (!IsFuturesAndSwapEnabled) return tickers; - await parseData(await MakeJsonRequestAsync("/market/tickers?instType=FUTURES", BaseUrlV5)); - await parseData(await MakeJsonRequestAsync("/market/tickers?instType=SWAP", BaseUrlV5)); + 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) + async Task ParseData(JToken tickerResponse) { /*{ "code":"0", @@ -226,7 +226,6 @@ protected override async Task> OnGetAmountsAsync() return token[0]["details"] .Select(x => new { Currency = x["ccy"].Value(), TotalBalance = x["cashBal"].Value() }) .ToDictionary(k => k.Currency, v => v.TotalBalance); - ; } protected override async Task> OnGetAmountsAvailableToTradeAsync() @@ -265,7 +264,7 @@ protected override async Task> OnGetOpenOrderDe } protected override async Task OnGetOrderDetailsAsync(string orderId, - string marketSymbol = null, bool isClientOrderId = false) + string marketSymbol, bool isClientOrderId = false) { if (string.IsNullOrEmpty(marketSymbol)) { @@ -301,7 +300,7 @@ protected override async Task OnCancelOrderAsync(string orderId, string marketSy var payload = await GetNoncePayloadAsync(); payload["ordId"] = orderId; payload["instId"] = marketSymbol; - var token = await MakeJsonRequestAsync("/trade/cancel-order", BaseUrlV5, payload, "POST"); + await MakeJsonRequestAsync("/trade/cancel-order", BaseUrlV5, payload, "POST"); } protected override async Task ProcessRequestAsync(IHttpWebRequest request, Dictionary payload) @@ -322,7 +321,7 @@ protected override async Task ProcessRequestAsync(IHttpWebRequest request, Dicti request.AddHeader("OK-ACCESS-KEY", PublicApiKey!.ToUnsecureString()); request.AddHeader("OK-ACCESS-SIGN", sign); - request.AddHeader("OK-ACCESS-TIMESTAMP", timeStamp.ToString()); + request.AddHeader("OK-ACCESS-TIMESTAMP", timeStamp); request.AddHeader("OK-ACCESS-PASSPHRASE", Passphrase!.ToUnsecureString()); request.AddHeader("x-simulated-trading", "0"); request.AddHeader("content-type", "application/json"); @@ -357,7 +356,7 @@ private IEnumerable ParseOrders(JToken token) Result = x["state"].Value() == "live" ? ExchangeAPIOrderResult.Open : ExchangeAPIOrderResult.FilledPartially, - IsBuy = x["side"].Value() == "buy" ? true : false, + IsBuy = x["side"].Value() == "buy", IsAmountFilledReversed = false, Amount = x["sz"].Value(), AmountFilled = x["accFillSz"].Value(), From d51b66d3da14ba0d677b221078fdb3034732c71a Mon Sep 17 00:00:00 2001 From: BZ-CO <30245815+BZ-CO@users.noreply.github.com> Date: Sat, 16 Oct 2021 22:33:11 +0300 Subject: [PATCH 8/8] Update OnPlaceOrderAsync Migration to V5 API --- .../API/Exchanges/OKGroup/ExchangeOKExAPI.cs | 61 +++++++++++++++---- 1 file changed, 49 insertions(+), 12 deletions(-) diff --git a/src/ExchangeSharp/API/Exchanges/OKGroup/ExchangeOKExAPI.cs b/src/ExchangeSharp/API/Exchanges/OKGroup/ExchangeOKExAPI.cs index d48f13ca..208b18c3 100644 --- a/src/ExchangeSharp/API/Exchanges/OKGroup/ExchangeOKExAPI.cs +++ b/src/ExchangeSharp/API/Exchanges/OKGroup/ExchangeOKExAPI.cs @@ -303,6 +303,51 @@ protected override async Task OnCancelOrderAsync(string orderId, string marketSy await MakeJsonRequestAsync("/trade/cancel-order", BaseUrlV5, payload, "POST"); } + protected override async Task OnPlaceOrderAsync(ExchangeOrderRequest order) + { + if (string.IsNullOrEmpty(order.MarketSymbol)) + { + throw new ArgumentNullException(nameof(order.MarketSymbol), "Okex place order request requires symbol"); + } + + var payload = await GetNoncePayloadAsync(); + payload["instId"] = order.MarketSymbol; + payload["tdMode"] = order.IsMargin ? "isolated" : "cash"; + if (!string.IsNullOrEmpty(order.ClientOrderId)) + { + payload["clOrdId"] = order.ClientOrderId; + } + payload["side"] = order.IsBuy ? "buy" : "sell"; + payload["posSide"] = "net"; + payload["ordType"] = order.OrderType switch + { + OrderType.Limit => "limit", + OrderType.Market => "market", + OrderType.Stop => throw new ArgumentException("Okex does not support stop order", + nameof(order.OrderType)), + _ => throw new ArgumentOutOfRangeException(nameof(order.OrderType), "Invalid order type.") + }; + payload["sz"] = order.Amount.ToStringInvariant(); + if (order.OrderType != OrderType.Market) + { + if (!order.Price.HasValue) throw new ArgumentNullException(nameof(order.Price), "Okex place order request requires price"); + payload["px"] = order.Price.ToStringInvariant(); + } + + var token = await MakeJsonRequestAsync("/trade/order", BaseUrlV5, payload, "POST"); + return new ExchangeOrderResult() + { + MarketSymbol = order.MarketSymbol, + Amount = order.Amount, + Price = order.Price, + OrderDate = DateTime.UtcNow, + OrderId = token[0]["ordId"].Value(), + ClientOrderId = token[0]["clOrdId"].Value(), + Result = ExchangeAPIOrderResult.Open, + IsBuy = order.IsBuy + }; + } + protected override async Task ProcessRequestAsync(IHttpWebRequest request, Dictionary payload) { if (!CanMakeAuthenticatedRequest(payload)) return; @@ -316,8 +361,10 @@ protected override async Task ProcessRequestAsync(IHttpWebRequest request, Dicti var body = payload.Any() ? JsonConvert.SerializeObject(payload) : string.Empty; var sign = string.IsNullOrEmpty(body) - ? HmacSHA256($"{timeStamp}{method}{requestUrl}", PrivateApiKey!.ToUnsecureString()) - : HmacSHA256($"{timeStamp}{method}{requestUrl}{body}", PrivateApiKey!.ToUnsecureString()); + ? CryptoUtility.SHA256SignBase64($"{timeStamp}{method}{requestUrl}", + PrivateApiKey!.ToUnsecureString().ToBytesUTF8()) + : CryptoUtility.SHA256SignBase64($"{timeStamp}{method}{requestUrl}{body}", + PrivateApiKey!.ToUnsecureString().ToBytesUTF8()); request.AddHeader("OK-ACCESS-KEY", PublicApiKey!.ToUnsecureString()); request.AddHeader("OK-ACCESS-SIGN", sign); @@ -330,16 +377,6 @@ protected override async Task ProcessRequestAsync(IHttpWebRequest request, Dicti { await request.WritePayloadJsonToRequestAsync(payload); } - - // TODO Check why it doesn't return the same result as CryptoUtility.SHA256Sign() - string HmacSHA256(string infoStr, string secret) - { - var sha256Data = Encoding.UTF8.GetBytes(infoStr); - var secretData = Encoding.UTF8.GetBytes(secret); - using var hmacsha256 = new HMACSHA256(secretData); - var buffer = hmacsha256.ComputeHash(sha256Data); - return Convert.ToBase64String(buffer); - } } private async Task GetBalance()