From b6ab89bd6aaa2f282a55ad2b657b41be04ce4a68 Mon Sep 17 00:00:00 2001 From: Josh Plumbly Date: Wed, 11 Aug 2021 23:46:36 +0100 Subject: [PATCH 01/29] Added parsing of fees for Kraken exchange ParseOrder method in the ExchangeKrakenAPI class will now parse the fee from the response and set it in the order result. --- src/ExchangeSharp/API/Exchanges/Kraken/ExchangeKrakenAPI.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ExchangeSharp/API/Exchanges/Kraken/ExchangeKrakenAPI.cs b/src/ExchangeSharp/API/Exchanges/Kraken/ExchangeKrakenAPI.cs index f5d915f5..dd828a50 100644 --- a/src/ExchangeSharp/API/Exchanges/Kraken/ExchangeKrakenAPI.cs +++ b/src/ExchangeSharp/API/Exchanges/Kraken/ExchangeKrakenAPI.cs @@ -206,6 +206,7 @@ private ExchangeOrderResult ParseOrder(string orderId, JToken order) orderResult.AmountFilled = order["vol_exec"].ConvertInvariant(); orderResult.Price = order["descr"]["price"].ConvertInvariant(); orderResult.AveragePrice = order["price"].ConvertInvariant(); + orderResult.Fees = order["fee"].ConvertInvariant(); return orderResult; } From 161bbba908a1a8ce4f6b6b815b802b3496628aef Mon Sep 17 00:00:00 2001 From: Josh Plumbly Date: Thu, 12 Aug 2021 01:06:02 +0100 Subject: [PATCH 02/29] Added margin properties --- .../API/Exchanges/Kraken/ExchangeKrakenAPI.cs | 29 ++++++++++++++++++- src/ExchangeSharp/Model/ExchangeMarket.cs | 24 ++++++++++++++- 2 files changed, 51 insertions(+), 2 deletions(-) diff --git a/src/ExchangeSharp/API/Exchanges/Kraken/ExchangeKrakenAPI.cs b/src/ExchangeSharp/API/Exchanges/Kraken/ExchangeKrakenAPI.cs index b3ff7788..79ddddc8 100644 --- a/src/ExchangeSharp/API/Exchanges/Kraken/ExchangeKrakenAPI.cs +++ b/src/ExchangeSharp/API/Exchanges/Kraken/ExchangeKrakenAPI.cs @@ -537,8 +537,35 @@ protected internal override async Task> OnGetMarketS BaseCurrency = pair["base"].ToStringInvariant(), QuoteCurrency = pair["quote"].ToStringInvariant(), QuantityStepSize = quantityStepSize, - PriceStepSize = Math.Pow(0.1, pair["pair_decimals"].ConvertInvariant()).ConvertInvariant() + PriceStepSize = Math.Pow(0.1, pair["pair_decimals"].ConvertInvariant()).ConvertInvariant(), + MarginCall = pair["margin_call"].ConvertInvariant(), + MarginStop = pair["margin_stop"].ConvertInvariant() }; + + if (pair["leverage_sell"].Children().Any()) + { + var marginSell = new List(); + + foreach (var margin in pair["leverage_sell"].Children()) + { + marginSell.Add(margin.ConvertInvariant()); + } + + market.MarginSell = marginSell; + } + + if (pair["leverage_buy"].Children().Any()) + { + var marginBuy = new List(); + + foreach (var margin in pair["leverage_buy"].Children()) + { + marginBuy.Add(margin.ConvertInvariant()); + } + + market.MarginBuy = marginBuy; + } + markets.Add(market); } diff --git a/src/ExchangeSharp/Model/ExchangeMarket.cs b/src/ExchangeSharp/Model/ExchangeMarket.cs index d6fa4aee..a98b0a03 100644 --- a/src/ExchangeSharp/Model/ExchangeMarket.cs +++ b/src/ExchangeSharp/Model/ExchangeMarket.cs @@ -10,6 +10,8 @@ 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.Collections.Generic; + namespace ExchangeSharp { /// Representation of a market on an exchange. @@ -73,7 +75,27 @@ public sealed class ExchangeMarket /// public bool MarginEnabled { get; set; } - public override string ToString() + /// + /// Amount of leverage allowed on a buy. + /// + public IEnumerable MarginBuy { get; set; } + + /// + /// Amount of leverage allowed on a sell. + /// + public IEnumerable MarginSell { get; set; } + + /// + /// The threshold at which a warning will be issued on a postion + /// + public long MarginStop { get; set; } + + /// + /// The threshold at which a position will be liquidated. + /// + public long MarginCall { get; set; } + + public override string ToString() { return $"{MarketSymbol}, {BaseCurrency}-{QuoteCurrency}"; } From 93ffb7fb060066eacd225698a8f79e8390d3a5f6 Mon Sep 17 00:00:00 2001 From: Josh Plumbly Date: Fri, 27 Aug 2021 09:17:15 +0100 Subject: [PATCH 03/29] Exchange Identifier Added a exchange property to the ticker model to be able to identify what exchange it came from. --- src/ExchangeSharp/API/Exchanges/Kraken/ExchangeKrakenAPI.cs | 1 + .../API/Exchanges/_Base/ExchangeAPIExtensions.cs | 1 + src/ExchangeSharp/Model/ExchangeTicker.cs | 5 +++++ 3 files changed, 7 insertions(+) diff --git a/src/ExchangeSharp/API/Exchanges/Kraken/ExchangeKrakenAPI.cs b/src/ExchangeSharp/API/Exchanges/Kraken/ExchangeKrakenAPI.cs index 79ddddc8..90fe84b6 100644 --- a/src/ExchangeSharp/API/Exchanges/Kraken/ExchangeKrakenAPI.cs +++ b/src/ExchangeSharp/API/Exchanges/Kraken/ExchangeKrakenAPI.cs @@ -631,6 +631,7 @@ private async Task ConvertToExchangeTickerAsync(string symbol, J var (baseCurrency, quoteCurrency) = await ExchangeMarketSymbolToCurrenciesAsync(symbol); return new ExchangeTicker { + Exchange = Name, MarketSymbol = symbol, ApiResponse = ticker, Ask = ticker["a"][0].ConvertInvariant(), diff --git a/src/ExchangeSharp/API/Exchanges/_Base/ExchangeAPIExtensions.cs b/src/ExchangeSharp/API/Exchanges/_Base/ExchangeAPIExtensions.cs index 252dd0b9..6da3544f 100644 --- a/src/ExchangeSharp/API/Exchanges/_Base/ExchangeAPIExtensions.cs +++ b/src/ExchangeSharp/API/Exchanges/_Base/ExchangeAPIExtensions.cs @@ -507,6 +507,7 @@ internal static async Task ParseTickerAsync(this ExchangeAPI api } ExchangeTicker ticker = new ExchangeTicker { + Exchange = api.Name, MarketSymbol = marketSymbol, ApiResponse = token, Ask = ask, diff --git a/src/ExchangeSharp/Model/ExchangeTicker.cs b/src/ExchangeSharp/Model/ExchangeTicker.cs index 308b5895..282b9e57 100644 --- a/src/ExchangeSharp/Model/ExchangeTicker.cs +++ b/src/ExchangeSharp/Model/ExchangeTicker.cs @@ -32,6 +32,11 @@ public sealed class ExchangeTicker /// public string Id { get; set; } + /// + /// The name of the exchange the tick was sent from. + /// + public string Exchange { get; set; } + /// /// The currency pair symbol that this ticker is in reference to /// From ce07e9ef153f61018d6148f1ee66d8cb4912721b Mon Sep 17 00:00:00 2001 From: Josh Plumbly Date: Sun, 29 Aug 2021 15:27:24 +0100 Subject: [PATCH 04/29] Base FTX Implementation Added the base implementation for the FTX exchange. --- .../API/Exchanges/FTX/Models/ExchangeFTXAPI.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 src/ExchangeSharp/API/Exchanges/FTX/Models/ExchangeFTXAPI.cs diff --git a/src/ExchangeSharp/API/Exchanges/FTX/Models/ExchangeFTXAPI.cs b/src/ExchangeSharp/API/Exchanges/FTX/Models/ExchangeFTXAPI.cs new file mode 100644 index 00000000..d510c792 --- /dev/null +++ b/src/ExchangeSharp/API/Exchanges/FTX/Models/ExchangeFTXAPI.cs @@ -0,0 +1,11 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace ExchangeSharp.API.Exchanges.FTX.Models +{ + public sealed partial class ExchangeFTXAPI : ExchangeAPI + { + public override string BaseUrl { get; set; } = "https://ftx.com/api"; + } +} From d15453410e395cf4703c010a0973d07ac44c05f8 Mon Sep 17 00:00:00 2001 From: Josh Plumbly Date: Sun, 29 Aug 2021 15:45:29 +0100 Subject: [PATCH 05/29] Added onGetMarketSymbolsAsync Implemented onGetMarketSynbolsAsync method for FTX exchange --- .../API/Exchanges/FTX/Models/ExchangeFTXAPI.cs | 14 ++++++++++++++ .../ExchangeSharpTests/ExchangeFTXAPITests.cs | 18 ++++++++++++++++++ 2 files changed, 32 insertions(+) create mode 100644 tests/ExchangeSharpTests/ExchangeFTXAPITests.cs diff --git a/src/ExchangeSharp/API/Exchanges/FTX/Models/ExchangeFTXAPI.cs b/src/ExchangeSharp/API/Exchanges/FTX/Models/ExchangeFTXAPI.cs index d510c792..068c1455 100644 --- a/src/ExchangeSharp/API/Exchanges/FTX/Models/ExchangeFTXAPI.cs +++ b/src/ExchangeSharp/API/Exchanges/FTX/Models/ExchangeFTXAPI.cs @@ -1,11 +1,25 @@ +using Newtonsoft.Json.Linq; using System; using System.Collections.Generic; +using System.Linq; using System.Text; +using System.Threading.Tasks; namespace ExchangeSharp.API.Exchanges.FTX.Models { public sealed partial class ExchangeFTXAPI : ExchangeAPI { public override string BaseUrl { get; set; } = "https://ftx.com/api"; + + protected async override Task> OnGetMarketSymbolsAsync(bool isWebSocket = false) + { + JToken result = await MakeJsonRequestAsync("/markets"); + + var names = result.Children().Select(x => x["name"].ToString()).ToList(); + + names.Sort(); + + return names; + } } } diff --git a/tests/ExchangeSharpTests/ExchangeFTXAPITests.cs b/tests/ExchangeSharpTests/ExchangeFTXAPITests.cs new file mode 100644 index 00000000..6ac49b0c --- /dev/null +++ b/tests/ExchangeSharpTests/ExchangeFTXAPITests.cs @@ -0,0 +1,18 @@ +using ExchangeSharp.API.Exchanges.FTX.Models; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System.Threading.Tasks; + +namespace ExchangeSharpTests +{ + [TestClass] + public class ExchangeFTXAPITests + { + [TestMethod] + public async Task TempTest() + { + var exchange = new ExchangeFTXAPI(); + + await exchange.GetMarketSymbolsAsync(); + } + } +} From e8460480d4ba135d58a33d646b910129ddf21edf Mon Sep 17 00:00:00 2001 From: Josh Plumbly Date: Sun, 29 Aug 2021 16:33:00 +0100 Subject: [PATCH 06/29] Added OnGetMarketSymbolsMetadataAsync Implemented OnGetMarketSymbolsMetadataAsync for FTX exchange Modified ,methods to exclude to futures symbols --- .../Exchanges/FTX/Models/ExchangeFTXAPI.cs | 59 ++++++++++++++++++- .../ExchangeSharpTests/ExchangeFTXAPITests.cs | 2 +- 2 files changed, 59 insertions(+), 2 deletions(-) diff --git a/src/ExchangeSharp/API/Exchanges/FTX/Models/ExchangeFTXAPI.cs b/src/ExchangeSharp/API/Exchanges/FTX/Models/ExchangeFTXAPI.cs index 068c1455..5959dc99 100644 --- a/src/ExchangeSharp/API/Exchanges/FTX/Models/ExchangeFTXAPI.cs +++ b/src/ExchangeSharp/API/Exchanges/FTX/Models/ExchangeFTXAPI.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using System.Text; +using System.Text.RegularExpressions; using System.Threading.Tasks; namespace ExchangeSharp.API.Exchanges.FTX.Models @@ -15,11 +16,67 @@ protected async override Task> OnGetMarketSymbolsAsync(bool { JToken result = await MakeJsonRequestAsync("/markets"); - var names = result.Children().Select(x => x["name"].ToString()).ToList(); + var names = result.Children().Select(x => x["name"].ToStringInvariant()).Where(x => Regex.Match(x, @"[\w\d]*\/[[\w\d]]*").Success).ToList(); names.Sort(); return names; } + + protected async internal override Task> OnGetMarketSymbolsMetadataAsync() + { + //{ + // "name": "BTC-0628", + // "baseCurrency": null, + // "quoteCurrency": null, + // "quoteVolume24h": 28914.76, + // "change1h": 0.012, + // "change24h": 0.0299, + // "changeBod": 0.0156, + // "highLeverageFeeExempt": false, + // "minProvideSize": 0.001, + // "type": "future", + // "underlying": "BTC", + // "enabled": true, + // "ask": 3949.25, + // "bid": 3949, + // "last": 10579.52, + // "postOnly": false, + // "price": 10579.52, + // "priceIncrement": 0.25, + // "sizeIncrement": 0.0001, + // "restricted": false, + // "volumeUsd24h": 28914.76 + //} + + var markets = new List(); + + JToken result = await MakeJsonRequestAsync("/markets"); + + foreach (JToken token in result.Children()) + { + var symbol = token["name"].ToStringInvariant(); + + if (!Regex.Match(symbol, @"[\w\d]*\/[[\w\d]]*").Success) + { + continue; + } + + var market = new ExchangeMarket() + { + MarketSymbol = symbol, + BaseCurrency = token["baseCurrency"].ToStringInvariant(), + QuoteCurrency = token["quoteCurrency"].ToStringInvariant(), + PriceStepSize = token["priceIncrement"].ConvertInvariant(), + QuantityStepSize = token["sizeIncrement"].ConvertInvariant(), + MinTradeSize = token["minProvideSize"].ConvertInvariant(), + IsActive = token["enabled"].ConvertInvariant(), + }; + + markets.Add(market); + } + + return markets; + } } } diff --git a/tests/ExchangeSharpTests/ExchangeFTXAPITests.cs b/tests/ExchangeSharpTests/ExchangeFTXAPITests.cs index 6ac49b0c..b4afbcd5 100644 --- a/tests/ExchangeSharpTests/ExchangeFTXAPITests.cs +++ b/tests/ExchangeSharpTests/ExchangeFTXAPITests.cs @@ -12,7 +12,7 @@ public async Task TempTest() { var exchange = new ExchangeFTXAPI(); - await exchange.GetMarketSymbolsAsync(); + await exchange.GetMarketSymbolsMetadataAsync(); } } } From 39b4a60211a38e2eba13cca8f1b3d5866d8cbb6b Mon Sep 17 00:00:00 2001 From: Josh Plumbly Date: Sun, 29 Aug 2021 16:35:54 +0100 Subject: [PATCH 07/29] Added Websocket base URL --- src/ExchangeSharp/API/Exchanges/FTX/Models/ExchangeFTXAPI.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ExchangeSharp/API/Exchanges/FTX/Models/ExchangeFTXAPI.cs b/src/ExchangeSharp/API/Exchanges/FTX/Models/ExchangeFTXAPI.cs index 5959dc99..fff9d5f0 100644 --- a/src/ExchangeSharp/API/Exchanges/FTX/Models/ExchangeFTXAPI.cs +++ b/src/ExchangeSharp/API/Exchanges/FTX/Models/ExchangeFTXAPI.cs @@ -11,6 +11,7 @@ namespace ExchangeSharp.API.Exchanges.FTX.Models public sealed partial class ExchangeFTXAPI : ExchangeAPI { public override string BaseUrl { get; set; } = "https://ftx.com/api"; + public override string BaseUrlWebSocket { get; set; } = "wss://ftx.com/ws/"; protected async override Task> OnGetMarketSymbolsAsync(bool isWebSocket = false) { From af17f284351d8ba767877e22027a974d4f4817ab Mon Sep 17 00:00:00 2001 From: Josh Plumbly Date: Mon, 6 Sep 2021 01:58:44 +0100 Subject: [PATCH 08/29] Added signing of requests in FTX exchange Added method for creating the appropriate signature on the request so that it can authenticate against the private API for the FTX exchange Implemented the OnGetAmountsAsync method for the FTX exchange. --- .../Exchanges/FTX/Models/ExchangeFTXAPI.cs | 50 +++++++++++++++++++ .../ExchangeSharpTests/ExchangeFTXAPITests.cs | 4 +- 2 files changed, 53 insertions(+), 1 deletion(-) diff --git a/src/ExchangeSharp/API/Exchanges/FTX/Models/ExchangeFTXAPI.cs b/src/ExchangeSharp/API/Exchanges/FTX/Models/ExchangeFTXAPI.cs index fff9d5f0..16a77192 100644 --- a/src/ExchangeSharp/API/Exchanges/FTX/Models/ExchangeFTXAPI.cs +++ b/src/ExchangeSharp/API/Exchanges/FTX/Models/ExchangeFTXAPI.cs @@ -13,6 +13,27 @@ public sealed partial class ExchangeFTXAPI : ExchangeAPI public override string BaseUrl { get; set; } = "https://ftx.com/api"; public override string BaseUrlWebSocket { get; set; } = "wss://ftx.com/ws/"; + public ExchangeFTXAPI() + { + NonceStyle = NonceStyle.UnixMillisecondsString; + } + + protected async override Task> OnGetAmountsAsync() + { + var balances = new Dictionary(); + + JToken result = await MakeJsonRequestAsync("/wallet/balances", null, await GetNoncePayloadAsync()); + + foreach (JObject obj in result) + { + decimal amount = obj["total"].ConvertInvariant(); + + balances[obj["coin"].ToStringInvariant()] = amount; + } + + return balances; + } + protected async override Task> OnGetMarketSymbolsAsync(bool isWebSocket = false) { JToken result = await MakeJsonRequestAsync("/markets"); @@ -79,5 +100,34 @@ protected async internal override Task> OnGetMarketS return markets; } + + protected override async Task ProcessRequestAsync(IHttpWebRequest request, Dictionary payload) + { + if (CanMakeAuthenticatedRequest(payload)) + { + // Coinbase is funny and wants a seconds double for the nonce, weird... we convert it to double and back to string invariantly to ensure decimal dot is used and not comma + string timestamp = payload["nonce"].ToStringInvariant(); + + string form = CryptoUtility.GetJsonForPayload(payload); + + //Create the signature payload + string toHash = $"{timestamp}{request.Method.ToUpperInvariant()}{request.RequestUri.PathAndQuery}"; + + if (request.Method == "POST") + { + toHash += form; + + await CryptoUtility.WriteToRequestAsync(request, form); + } + + byte[] secret = CryptoUtility.ToUnsecureBytesUTF8(PrivateApiKey); + + string signatureHexString = CryptoUtility.SHA256Sign(toHash, secret); + + request.AddHeader("FTX-KEY", PublicApiKey.ToUnsecureString()); + request.AddHeader("FTX-SIGN", signatureHexString); + request.AddHeader("FTX-TS", timestamp); + } + } } } diff --git a/tests/ExchangeSharpTests/ExchangeFTXAPITests.cs b/tests/ExchangeSharpTests/ExchangeFTXAPITests.cs index b4afbcd5..15ef4485 100644 --- a/tests/ExchangeSharpTests/ExchangeFTXAPITests.cs +++ b/tests/ExchangeSharpTests/ExchangeFTXAPITests.cs @@ -12,7 +12,9 @@ public async Task TempTest() { var exchange = new ExchangeFTXAPI(); - await exchange.GetMarketSymbolsMetadataAsync(); + exchange.LoadAPIKeysUnsecure("qq0Y6JKAbefgU4GBllT_iGwrhhhJnM7YExkP8q_F", "BvdkXp4vJHxigx5W5d97YmCVEv9TEr944dDQBpLR"); + + var result = await exchange.GetAmountsAsync(); } } } From 33fc44473019a9890705378c088c8e9711d98629 Mon Sep 17 00:00:00 2001 From: nlv-hack Date: Mon, 6 Sep 2021 18:24:36 +0100 Subject: [PATCH 09/29] added OnGetOpenOrderDetailsAsync --- .../FTX/{Models => }/ExchangeFTXAPI.cs | 35 +++++++++++++++++-- src/ExchangeSharp/ExchangeSharp.csproj | 4 +++ 2 files changed, 37 insertions(+), 2 deletions(-) rename src/ExchangeSharp/API/Exchanges/FTX/{Models => }/ExchangeFTXAPI.cs (77%) diff --git a/src/ExchangeSharp/API/Exchanges/FTX/Models/ExchangeFTXAPI.cs b/src/ExchangeSharp/API/Exchanges/FTX/ExchangeFTXAPI.cs similarity index 77% rename from src/ExchangeSharp/API/Exchanges/FTX/Models/ExchangeFTXAPI.cs rename to src/ExchangeSharp/API/Exchanges/FTX/ExchangeFTXAPI.cs index 16a77192..e521c01f 100644 --- a/src/ExchangeSharp/API/Exchanges/FTX/Models/ExchangeFTXAPI.cs +++ b/src/ExchangeSharp/API/Exchanges/FTX/ExchangeFTXAPI.cs @@ -2,11 +2,10 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Text; using System.Text.RegularExpressions; using System.Threading.Tasks; -namespace ExchangeSharp.API.Exchanges.FTX.Models +namespace ExchangeSharp.API.Exchanges.FTX { public sealed partial class ExchangeFTXAPI : ExchangeAPI { @@ -101,6 +100,38 @@ protected async internal override Task> OnGetMarketS return markets; } + protected async override Task> OnGetOpenOrderDetailsAsync(string marketSymbol = null) + { + var markets = new List(); + + JToken result = await MakeJsonRequestAsync($"/orders?market={marketSymbol}"); + + foreach (JToken token in result.Children()) + { + //var symbol = token["name"].ToStringInvariant(); + + //if (!Regex.Match(symbol, @"[\w\d]*\/[[\w\d]]*").Success) + //{ + // continue; + //} + + markets.Add(new ExchangeOrderResult() + { + MarketSymbol = token["market"].ToStringInvariant(), + Price = token["price"].ConvertInvariant(), + AveragePrice = token["avgFillPrice"].ConvertInvariant(), + OrderDate = token["createdAt"].ConvertInvariant(), + IsBuy = token["side"].ToStringInvariant().Equals("buy"), + OrderId = token["id"].ToStringInvariant(), + Amount = token["size"].ConvertInvariant(), + AmountFilled = token["filledSize"].ConvertInvariant(), // ? + ClientOrderId = token["clientId"].ToStringInvariant() + }); + } + + return markets; + } + protected override async Task ProcessRequestAsync(IHttpWebRequest request, Dictionary payload) { if (CanMakeAuthenticatedRequest(payload)) diff --git a/src/ExchangeSharp/ExchangeSharp.csproj b/src/ExchangeSharp/ExchangeSharp.csproj index b8ce8a88..5edadb42 100644 --- a/src/ExchangeSharp/ExchangeSharp.csproj +++ b/src/ExchangeSharp/ExchangeSharp.csproj @@ -43,4 +43,8 @@ + + + + From 1efc65e4f6e10605f8c96f54bb84b2fb376ec2e0 Mon Sep 17 00:00:00 2001 From: Josh Plumbly Date: Mon, 6 Sep 2021 18:44:09 +0100 Subject: [PATCH 10/29] Implemented onGetHistoricalTrades FTX Implemented ongetHistoricaltrades for the FTX exchange. --- .../Exchanges/FTX/Models/ExchangeFTXAPI.cs | 31 ++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/src/ExchangeSharp/API/Exchanges/FTX/Models/ExchangeFTXAPI.cs b/src/ExchangeSharp/API/Exchanges/FTX/Models/ExchangeFTXAPI.cs index 16a77192..2a19ffe8 100644 --- a/src/ExchangeSharp/API/Exchanges/FTX/Models/ExchangeFTXAPI.cs +++ b/src/ExchangeSharp/API/Exchanges/FTX/Models/ExchangeFTXAPI.cs @@ -16,6 +16,7 @@ public sealed partial class ExchangeFTXAPI : ExchangeAPI public ExchangeFTXAPI() { NonceStyle = NonceStyle.UnixMillisecondsString; + MarketSymbolSeparator = "/"; } protected async override Task> OnGetAmountsAsync() @@ -101,11 +102,39 @@ protected async internal override Task> OnGetMarketS return markets; } + protected async override Task OnGetHistoricalTradesAsync(Func, bool> callback, string marketSymbol, DateTime? startDate = null, DateTime? endDate = null, int? limit = null) + { + string baseUrl = $"/markets/{marketSymbol}/trades?"; + + if (startDate != null) + { + baseUrl += $"start_time={startDate?.UnixTimestampFromDateTimeMilliseconds()}"; + } + + if (endDate != null) + { + baseUrl += $"start_time={endDate?.UnixTimestampFromDateTimeMilliseconds()}"; + } + + List trades = new List(); + + while (true) + { + JToken result = await MakeJsonRequestAsync(baseUrl); + + if (!callback(trades)) + { + break; + } + + Task.Delay(1000).Wait(); + } + } + protected override async Task ProcessRequestAsync(IHttpWebRequest request, Dictionary payload) { if (CanMakeAuthenticatedRequest(payload)) { - // Coinbase is funny and wants a seconds double for the nonce, weird... we convert it to double and back to string invariantly to ensure decimal dot is used and not comma string timestamp = payload["nonce"].ToStringInvariant(); string form = CryptoUtility.GetJsonForPayload(payload); From ce9a0c399c0d8be45aa2c7a3fc7a2da70b70463a Mon Sep 17 00:00:00 2001 From: Josh Plumbly Date: Mon, 6 Sep 2021 18:55:52 +0100 Subject: [PATCH 11/29] onGetHistoricalTrade changes Modified onGetHistoricalTrade to use utility method for parsing. --- src/ExchangeSharp/API/Exchanges/FTX/Models/ExchangeFTXAPI.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/ExchangeSharp/API/Exchanges/FTX/Models/ExchangeFTXAPI.cs b/src/ExchangeSharp/API/Exchanges/FTX/Models/ExchangeFTXAPI.cs index 2a19ffe8..fa585b4f 100644 --- a/src/ExchangeSharp/API/Exchanges/FTX/Models/ExchangeFTXAPI.cs +++ b/src/ExchangeSharp/API/Exchanges/FTX/Models/ExchangeFTXAPI.cs @@ -122,6 +122,11 @@ protected async override Task OnGetHistoricalTradesAsync(Func(baseUrl); + foreach (JToken trade in result.Children()) + { + trades.Add(trade.ParseTrade("size", "price", "side", "time", TimestampType.Iso8601, "id", "buy")); + } + if (!callback(trades)) { break; From b42988072fa1d5d0605f4647a08690a06148cef7 Mon Sep 17 00:00:00 2001 From: Josh Plumbly Date: Mon, 6 Sep 2021 19:01:22 +0100 Subject: [PATCH 12/29] Update ExchangeFTXAPI.cs Moved methods into alphabetical order --- .../Exchanges/FTX/Models/ExchangeFTXAPI.cs | 68 +++++++++---------- 1 file changed, 34 insertions(+), 34 deletions(-) diff --git a/src/ExchangeSharp/API/Exchanges/FTX/Models/ExchangeFTXAPI.cs b/src/ExchangeSharp/API/Exchanges/FTX/Models/ExchangeFTXAPI.cs index fa585b4f..2935f75d 100644 --- a/src/ExchangeSharp/API/Exchanges/FTX/Models/ExchangeFTXAPI.cs +++ b/src/ExchangeSharp/API/Exchanges/FTX/Models/ExchangeFTXAPI.cs @@ -35,6 +35,40 @@ protected async override Task> OnGetAmountsAsync() return balances; } + protected async override Task OnGetHistoricalTradesAsync(Func, bool> callback, string marketSymbol, DateTime? startDate = null, DateTime? endDate = null, int? limit = null) + { + string baseUrl = $"/markets/{marketSymbol}/trades?"; + + if (startDate != null) + { + baseUrl += $"start_time={startDate?.UnixTimestampFromDateTimeMilliseconds()}"; + } + + if (endDate != null) + { + baseUrl += $"start_time={endDate?.UnixTimestampFromDateTimeMilliseconds()}"; + } + + List trades = new List(); + + while (true) + { + JToken result = await MakeJsonRequestAsync(baseUrl); + + foreach (JToken trade in result.Children()) + { + trades.Add(trade.ParseTrade("size", "price", "side", "time", TimestampType.Iso8601, "id", "buy")); + } + + if (!callback(trades)) + { + break; + } + + Task.Delay(1000).Wait(); + } + } + protected async override Task> OnGetMarketSymbolsAsync(bool isWebSocket = false) { JToken result = await MakeJsonRequestAsync("/markets"); @@ -102,40 +136,6 @@ protected async internal override Task> OnGetMarketS return markets; } - protected async override Task OnGetHistoricalTradesAsync(Func, bool> callback, string marketSymbol, DateTime? startDate = null, DateTime? endDate = null, int? limit = null) - { - string baseUrl = $"/markets/{marketSymbol}/trades?"; - - if (startDate != null) - { - baseUrl += $"start_time={startDate?.UnixTimestampFromDateTimeMilliseconds()}"; - } - - if (endDate != null) - { - baseUrl += $"start_time={endDate?.UnixTimestampFromDateTimeMilliseconds()}"; - } - - List trades = new List(); - - while (true) - { - JToken result = await MakeJsonRequestAsync(baseUrl); - - foreach (JToken trade in result.Children()) - { - trades.Add(trade.ParseTrade("size", "price", "side", "time", TimestampType.Iso8601, "id", "buy")); - } - - if (!callback(trades)) - { - break; - } - - Task.Delay(1000).Wait(); - } - } - protected override async Task ProcessRequestAsync(IHttpWebRequest request, Dictionary payload) { if (CanMakeAuthenticatedRequest(payload)) From 7a07be62e58012fdab057b19a00719c228888093 Mon Sep 17 00:00:00 2001 From: nlv-hack Date: Tue, 7 Sep 2021 15:33:34 +0100 Subject: [PATCH 13/29] working progress on OnGetOrderDetailsAsync --- .../API/Exchanges/FTX/ExchangeFTXAPI.cs | 16 +++++++++++++ .../API/Exchanges/FTX/Extensions.cs | 23 +++++++++++++++++++ 2 files changed, 39 insertions(+) create mode 100644 src/ExchangeSharp/API/Exchanges/FTX/Extensions.cs diff --git a/src/ExchangeSharp/API/Exchanges/FTX/ExchangeFTXAPI.cs b/src/ExchangeSharp/API/Exchanges/FTX/ExchangeFTXAPI.cs index 36bd92ba..da76efc7 100644 --- a/src/ExchangeSharp/API/Exchanges/FTX/ExchangeFTXAPI.cs +++ b/src/ExchangeSharp/API/Exchanges/FTX/ExchangeFTXAPI.cs @@ -167,6 +167,22 @@ protected async override Task> OnGetOpenOrderDe return markets; } + protected async override Task OnGetOrderDetailsAsync(string orderId, string marketSymbol = null) + { + // https://docs.ftx.com/#get-order-status + + JToken result = await MakeJsonRequestAsync($"/orders?{orderId}"); + + var resp = result.First(); + + return new ExchangeOrderResult() + { + OrderId = resp["id"].ToStringInvariant(), + OrderDate = resp["createdAt"].ConvertInvariant(), + Result = resp["id"].ToStringLowerInvariant().ToExchangeAPIOrderResult() + }; + } + protected override async Task ProcessRequestAsync(IHttpWebRequest request, Dictionary payload) { if (CanMakeAuthenticatedRequest(payload)) diff --git a/src/ExchangeSharp/API/Exchanges/FTX/Extensions.cs b/src/ExchangeSharp/API/Exchanges/FTX/Extensions.cs new file mode 100644 index 00000000..5fbead56 --- /dev/null +++ b/src/ExchangeSharp/API/Exchanges/FTX/Extensions.cs @@ -0,0 +1,23 @@ +namespace ExchangeSharp.API.Exchanges.FTX +{ + /// + /// Extension helper methods. + /// + internal static class Extensions + { + /// + /// Cnvert FTX order status string to . + /// + /// FTX order status string. + /// + internal static ExchangeAPIOrderResult ToExchangeAPIOrderResult(this string status) + { + return status switch + { + "open" => ExchangeAPIOrderResult.Pending, + "closed" => ExchangeAPIOrderResult.Filled, + _ => ExchangeAPIOrderResult.Unknown, + }; + } + } +} From 36c11a41d15fc2cc4436de76300b7f670e423706 Mon Sep 17 00:00:00 2001 From: Josh Plumbly Date: Wed, 8 Sep 2021 00:09:21 +0100 Subject: [PATCH 14/29] Update ExchangeFTXAPI.cs Implemented websocket connection for retrieving ticker information. --- .../API/Exchanges/FTX/ExchangeFTXAPI.cs | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/src/ExchangeSharp/API/Exchanges/FTX/ExchangeFTXAPI.cs b/src/ExchangeSharp/API/Exchanges/FTX/ExchangeFTXAPI.cs index da76efc7..5ec19f36 100644 --- a/src/ExchangeSharp/API/Exchanges/FTX/ExchangeFTXAPI.cs +++ b/src/ExchangeSharp/API/Exchanges/FTX/ExchangeFTXAPI.cs @@ -16,6 +16,7 @@ public ExchangeFTXAPI() { NonceStyle = NonceStyle.UnixMillisecondsString; MarketSymbolSeparator = "/"; + //WebSocketOrderBookType = WebSocketOrderBookType. } protected async override Task> OnGetAmountsAsync() @@ -183,6 +184,50 @@ protected async override Task OnGetOrderDetailsAsync(string }; } + protected override async Task OnGetTickersWebSocketAsync(Action>> tickers, params string[] marketSymbols) + { + if (marketSymbols == null || marketSymbols.Length == 0) + { + marketSymbols = (await GetMarketSymbolsAsync(true)).ToArray(); + } + return await ConnectPublicWebSocketAsync(null, messageCallback: async (_socket, msg) => + { + JToken parsedMsg = JToken.Parse(msg.ToStringFromUTF8()); + + if (parsedMsg["channel"].ToStringInvariant().Equals("ticker")) + { + JToken data = parsedMsg["data"]; + + var exchangeTicker = new ExchangeTicker() + { + Exchange = Name, + MarketSymbol = parsedMsg["market"].ToStringInvariant(), + Ask = CryptoUtility.ConvertInvariant(data["ask"]), + Bid = CryptoUtility.ConvertInvariant(data["bid"]), + Last = CryptoUtility.ConvertInvariant(data["last"]) + }; + + var kv = new KeyValuePair(exchangeTicker.MarketSymbol, exchangeTicker); + tickers(new List> { kv }); + } + }, connectCallback: async (_socket) => + { + List marketSymbolList = marketSymbols.ToList(); + + //{'op': 'subscribe', 'channel': 'trades', 'market': 'BTC-PERP'} + + for (int i = 0; i < marketSymbolList.Count; i++) + { + await _socket.SendMessageAsync(new + { + op = "subscribe", + market = marketSymbolList[i], + channel = "ticker" + }); + } + }); + } + protected override async Task ProcessRequestAsync(IHttpWebRequest request, Dictionary payload) { if (CanMakeAuthenticatedRequest(payload)) From 7a866d651a812c664b6b35b66cd44f41f36ba2b5 Mon Sep 17 00:00:00 2001 From: Josh Plumbly Date: Wed, 8 Sep 2021 00:23:46 +0100 Subject: [PATCH 15/29] Update ExchangeFTXAPI.cs Refined websocket ticker implementation to make use of utility functions for parsing. --- src/ExchangeSharp/API/Exchanges/FTX/ExchangeFTXAPI.cs | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/ExchangeSharp/API/Exchanges/FTX/ExchangeFTXAPI.cs b/src/ExchangeSharp/API/Exchanges/FTX/ExchangeFTXAPI.cs index 5ec19f36..0a7b5243 100644 --- a/src/ExchangeSharp/API/Exchanges/FTX/ExchangeFTXAPI.cs +++ b/src/ExchangeSharp/API/Exchanges/FTX/ExchangeFTXAPI.cs @@ -198,16 +198,10 @@ protected override async Task OnGetTickersWebSocketAsync(Action(data["ask"]), - Bid = CryptoUtility.ConvertInvariant(data["bid"]), - Last = CryptoUtility.ConvertInvariant(data["last"]) - }; + var exchangeTicker = await this.ParseTickerAsync(data, parsedMsg["market"].ToStringInvariant(), "ask", "bid", "last", null, null, "time", TimestampType.UnixSecondsDouble); var kv = new KeyValuePair(exchangeTicker.MarketSymbol, exchangeTicker); + tickers(new List> { kv }); } }, connectCallback: async (_socket) => From d0948c23e5e0d7c120a319af6cd7ef4919a3c8d0 Mon Sep 17 00:00:00 2001 From: nlv-hack Date: Wed, 8 Sep 2021 16:46:51 +0100 Subject: [PATCH 16/29] added OnGetAmountsAvailableToTradeAsync Using https://docs.ftx.com/#get-coins --- .../API/Exchanges/FTX/ExchangeFTXAPI.cs | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/src/ExchangeSharp/API/Exchanges/FTX/ExchangeFTXAPI.cs b/src/ExchangeSharp/API/Exchanges/FTX/ExchangeFTXAPI.cs index 0a7b5243..e19172da 100644 --- a/src/ExchangeSharp/API/Exchanges/FTX/ExchangeFTXAPI.cs +++ b/src/ExchangeSharp/API/Exchanges/FTX/ExchangeFTXAPI.cs @@ -184,6 +184,30 @@ protected async override Task OnGetOrderDetailsAsync(string }; } + protected async override Task> OnGetAmountsAvailableToTradeAsync() + { + // https://docs.ftx.com/#get-balances + // NOTE there is also is "Get balances of all accounts"? + //"coin": "USDTBEAR", + // "free": 2320.2, + // "spotBorrow": 0.0, + // "total": 2340.2, + // "usdValue": 2340.2, + // "availableWithoutBorrow": 2320.2 + + var balances = new Dictionary(); + + JToken result = await MakeJsonRequestAsync($"/wallet/balances"); + + foreach (JToken token in result.Children()) + { + balances.Add(token["coin"].ToStringInvariant(), + token["availableWithoutBorrow"].ConvertInvariant()); + } + + return balances; + } + protected override async Task OnGetTickersWebSocketAsync(Action>> tickers, params string[] marketSymbols) { if (marketSymbols == null || marketSymbols.Length == 0) @@ -249,5 +273,6 @@ protected override async Task ProcessRequestAsync(IHttpWebRequest request, Dicti request.AddHeader("FTX-TS", timestamp); } } + } } From b852bac71966aacac022dd0ae0773f3cdee44c99 Mon Sep 17 00:00:00 2001 From: nlv-hack Date: Fri, 10 Sep 2021 17:53:04 +0100 Subject: [PATCH 17/29] Update ExchangeFTXAPI.cs * checks for future coins --- .../API/Exchanges/FTX/ExchangeFTXAPI.cs | 49 ++++++++++++++----- 1 file changed, 36 insertions(+), 13 deletions(-) diff --git a/src/ExchangeSharp/API/Exchanges/FTX/ExchangeFTXAPI.cs b/src/ExchangeSharp/API/Exchanges/FTX/ExchangeFTXAPI.cs index e19172da..ae1da3ad 100644 --- a/src/ExchangeSharp/API/Exchanges/FTX/ExchangeFTXAPI.cs +++ b/src/ExchangeSharp/API/Exchanges/FTX/ExchangeFTXAPI.cs @@ -12,6 +12,8 @@ public sealed partial class ExchangeFTXAPI : ExchangeAPI public override string BaseUrl { get; set; } = "https://ftx.com/api"; public override string BaseUrlWebSocket { get; set; } = "wss://ftx.com/ws/"; + #region [ Constructor(s) ] + public ExchangeFTXAPI() { NonceStyle = NonceStyle.UnixMillisecondsString; @@ -19,6 +21,11 @@ public ExchangeFTXAPI() //WebSocketOrderBookType = WebSocketOrderBookType. } + #endregion + + #region [ Implementation ] + + /// protected async override Task> OnGetAmountsAsync() { var balances = new Dictionary(); @@ -35,6 +42,7 @@ protected async override Task> OnGetAmountsAsync() return balances; } + /// protected async override Task OnGetHistoricalTradesAsync(Func, bool> callback, string marketSymbol, DateTime? startDate = null, DateTime? endDate = null, int? limit = null) { string baseUrl = $"/markets/{marketSymbol}/trades?"; @@ -69,6 +77,7 @@ protected async override Task OnGetHistoricalTradesAsync(Func protected async override Task> OnGetMarketSymbolsAsync(bool isWebSocket = false) { JToken result = await MakeJsonRequestAsync("/markets"); @@ -80,6 +89,7 @@ protected async override Task> OnGetMarketSymbolsAsync(bool return names; } + /// protected async internal override Task> OnGetMarketSymbolsMetadataAsync() { //{ @@ -136,21 +146,25 @@ protected async internal override Task> OnGetMarketS return markets; } + /// protected async override Task> OnGetOpenOrderDetailsAsync(string marketSymbol = null) { + // https://docs.ftx.com/#get-open-orders + + var markets = new List(); JToken result = await MakeJsonRequestAsync($"/orders?market={marketSymbol}"); foreach (JToken token in result.Children()) { - //var symbol = token["name"].ToStringInvariant(); - - //if (!Regex.Match(symbol, @"[\w\d]*\/[[\w\d]]*").Success) - //{ - // continue; - //} + var symbol = token["name"].ToStringInvariant(); + if (!Regex.Match(symbol, @"[\w\d]*\/[[\w\d]]*").Success) + { + continue; + } + markets.Add(new ExchangeOrderResult() { MarketSymbol = token["market"].ToStringInvariant(), @@ -160,7 +174,7 @@ protected async override Task> OnGetOpenOrderDe IsBuy = token["side"].ToStringInvariant().Equals("buy"), OrderId = token["id"].ToStringInvariant(), Amount = token["size"].ConvertInvariant(), - AmountFilled = token["filledSize"].ConvertInvariant(), // ? + AmountFilled = token["filledSize"].ConvertInvariant(), ClientOrderId = token["clientId"].ToStringInvariant() }); } @@ -168,6 +182,7 @@ protected async override Task> OnGetOpenOrderDe return markets; } + /// protected async override Task OnGetOrderDetailsAsync(string orderId, string marketSymbol = null) { // https://docs.ftx.com/#get-order-status @@ -184,16 +199,17 @@ protected async override Task OnGetOrderDetailsAsync(string }; } + /// protected async override Task> OnGetAmountsAvailableToTradeAsync() { // https://docs.ftx.com/#get-balances // NOTE there is also is "Get balances of all accounts"? - //"coin": "USDTBEAR", - // "free": 2320.2, - // "spotBorrow": 0.0, - // "total": 2340.2, - // "usdValue": 2340.2, - // "availableWithoutBorrow": 2320.2 + // "coin": "USDTBEAR", + // "free": 2320.2, + // "spotBorrow": 0.0, + // "total": 2340.2, + // "usdValue": 2340.2, + // "availableWithoutBorrow": 2320.2 var balances = new Dictionary(); @@ -201,6 +217,11 @@ protected async override Task> OnGetAmountsAvailable foreach (JToken token in result.Children()) { + if (!Regex.Match(token["coin"].ToStringInvariant(), @"[\w\d]*\/[[\w\d]]*").Success) + { + continue; + } + balances.Add(token["coin"].ToStringInvariant(), token["availableWithoutBorrow"].ConvertInvariant()); } @@ -208,6 +229,7 @@ protected async override Task> OnGetAmountsAvailable return balances; } + /// protected override async Task OnGetTickersWebSocketAsync(Action>> tickers, params string[] marketSymbols) { if (marketSymbols == null || marketSymbols.Length == 0) @@ -274,5 +296,6 @@ protected override async Task ProcessRequestAsync(IHttpWebRequest request, Dicti } } + #endregion } } From d98b19ae67c73666aa6073cab0fc73b4f1632fa7 Mon Sep 17 00:00:00 2001 From: Josh Plumbly Date: Tue, 14 Sep 2021 23:48:19 +0100 Subject: [PATCH 18/29] Update ExchangeFTXAPI.cs Implemented onPlaceOrderAsync for FTX exchange. --- .../API/Exchanges/FTX/ExchangeFTXAPI.cs | 66 +++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/src/ExchangeSharp/API/Exchanges/FTX/ExchangeFTXAPI.cs b/src/ExchangeSharp/API/Exchanges/FTX/ExchangeFTXAPI.cs index ae1da3ad..2d3956be 100644 --- a/src/ExchangeSharp/API/Exchanges/FTX/ExchangeFTXAPI.cs +++ b/src/ExchangeSharp/API/Exchanges/FTX/ExchangeFTXAPI.cs @@ -18,6 +18,7 @@ public ExchangeFTXAPI() { NonceStyle = NonceStyle.UnixMillisecondsString; MarketSymbolSeparator = "/"; + RequestContentType = "application/json"; //WebSocketOrderBookType = WebSocketOrderBookType. } @@ -229,6 +230,69 @@ protected async override Task> OnGetAmountsAvailable return balances; } + protected async override Task OnPlaceOrderAsync(ExchangeOrderRequest order) + { + //{ + // "market": "XRP-PERP", + // "side": "sell", + // "price": 0.306525, + // "type": "limit", + // "size": 31431.0, + // "reduceOnly": false, + // "ioc": false, + // "postOnly": false, + // "clientId": null + //} + + IEnumerable markets = await OnGetMarketSymbolsMetadataAsync(); + ExchangeMarket market = markets.Where(m => m.MarketSymbol == order.MarketSymbol).First(); + + var payload = await GetNoncePayloadAsync(); + + var parameters = new Dictionary(StringComparer.OrdinalIgnoreCase) + { + {"market", market.MarketSymbol}, + {"side", order.IsBuy ? "buy" : "sell" }, + {"type", order.OrderType.ToStringLowerInvariant() }, + {"size", order.RoundAmount() }, + }; + + if (!string.IsNullOrEmpty(order.ClientOrderId)) + { + parameters.Add("clientId", order.ClientOrderId); + } + + if (order.OrderType != OrderType.Market) + { + int precision = BitConverter.GetBytes(decimal.GetBits((decimal)market.PriceStepSize)[3])[2]; + + if (order.Price == null) throw new ArgumentNullException(nameof(order.Price)); + + parameters.Add("price", Math.Round(order.Price.Value, precision)); + } + + parameters.CopyTo(payload); + + order.ExtraParameters.CopyTo(payload); + + var response = await MakeJsonRequestAsync("/orders", null, payload, "POST"); + + ExchangeOrderResult result = new ExchangeOrderResult + { + OrderId = response["id"].ToStringInvariant(), + ClientOrderId = response["clientId"].ToStringInvariant(), + OrderDate = CryptoUtility.ToDateTimeInvariant(response["createdAt"]), + Price = CryptoUtility.ConvertInvariant(response["price"]), + AmountFilled = CryptoUtility.ConvertInvariant(response["filledSize"]), + AveragePrice = CryptoUtility.ConvertInvariant(response["avgFillPrice"]), + Amount = CryptoUtility.ConvertInvariant(response["size"]), + MarketSymbol = response["market"].ToStringInvariant(), + IsBuy = response["side"].ToStringInvariant() == "buy" + }; + + return result; + } + /// protected override async Task OnGetTickersWebSocketAsync(Action>> tickers, params string[] marketSymbols) { @@ -274,6 +338,8 @@ protected override async Task ProcessRequestAsync(IHttpWebRequest request, Dicti { string timestamp = payload["nonce"].ToStringInvariant(); + payload.Remove("nonce"); + string form = CryptoUtility.GetJsonForPayload(payload); //Create the signature payload From bafc58e0adbafed6b40398a7cf064a4f9b86df67 Mon Sep 17 00:00:00 2001 From: Josh Plumbly Date: Thu, 16 Sep 2021 00:49:11 +0100 Subject: [PATCH 19/29] Update ExchangeFTXAPI.cs Added remaining overrides of methods required to be implemented to FTX exchange Added implementation of OnCancelOrderAsync Added implementation of OnGetCandleAsync --- .../API/Exchanges/FTX/ExchangeFTXAPI.cs | 85 ++++++++++++++++++- 1 file changed, 83 insertions(+), 2 deletions(-) diff --git a/src/ExchangeSharp/API/Exchanges/FTX/ExchangeFTXAPI.cs b/src/ExchangeSharp/API/Exchanges/FTX/ExchangeFTXAPI.cs index 2d3956be..893cdd4f 100644 --- a/src/ExchangeSharp/API/Exchanges/FTX/ExchangeFTXAPI.cs +++ b/src/ExchangeSharp/API/Exchanges/FTX/ExchangeFTXAPI.cs @@ -50,12 +50,12 @@ protected async override Task OnGetHistoricalTradesAsync(Func trades = new List(); @@ -362,6 +362,87 @@ protected override async Task ProcessRequestAsync(IHttpWebRequest request, Dicti } } + protected override Task OnInitializeAsync() + { + return base.OnInitializeAsync(); + } + + protected override Task> OnGetRecentTradesAsync(string marketSymbol, int? limit = null) + { + return base.OnGetRecentTradesAsync(marketSymbol, limit); + } + + protected override Task OnGetOrderBookAsync(string marketSymbol, int maxCount = 100) + { + return base.OnGetOrderBookAsync(marketSymbol, maxCount); + } + + protected async override Task> OnGetCandlesAsync(string marketSymbol, int periodSeconds, DateTime? startDate = null, DateTime? endDate = null, int? limit = null) + { + + //period options: 15, 60, 300, 900, 3600, 14400, 86400, or any multiple of 86400 up to 30*86400 + + var queryUrl = $"/markets/{marketSymbol}/candles?resolution={periodSeconds}"; + + if (startDate.HasValue) + { + queryUrl += $"&start_time={startDate?.UnixTimestampFromDateTimeSeconds()}"; + } + + if (endDate.HasValue) + { + queryUrl += $"&end_time={endDate?.UnixTimestampFromDateTimeSeconds()}"; + } + + var candles = new List(); + + var response = await MakeJsonRequestAsync(queryUrl, null, await GetNoncePayloadAsync()); + + foreach (JToken candle in response.Children()) + { + var parsedCandle = this.ParseCandle(candle, marketSymbol, periodSeconds, "open", "high", "low", "close", "startTime", TimestampType.Iso8601, "volume"); + + candles.Add(parsedCandle); + } + + return candles; + } + + protected override Task> OnGetCompletedOrderDetailsAsync(string marketSymbol = null, DateTime? afterDate = null) + { + return base.OnGetCompletedOrderDetailsAsync(marketSymbol, afterDate); + } + + protected async override Task OnCancelOrderAsync(string orderId, string marketSymbol = null) + { + var response = await MakeJsonRequestAsync($"/orders/{orderId}", null, await GetNoncePayloadAsync(), "DELETE"); + } + + public override Task ExchangeMarketSymbolToGlobalMarketSymbolAsync(string marketSymbol) + { + return base.ExchangeMarketSymbolToGlobalMarketSymbolAsync(marketSymbol); + } + + public override Task<(string baseCurrency, string quoteCurrency)> ExchangeMarketSymbolToCurrenciesAsync(string marketSymbol) + { + return base.ExchangeMarketSymbolToCurrenciesAsync(marketSymbol); + } + + public override Task GlobalMarketSymbolToExchangeMarketSymbolAsync(string marketSymbol) + { + return base.GlobalMarketSymbolToExchangeMarketSymbolAsync(marketSymbol); + } + + public override Task> GetCurrenciesAsync() + { + return base.GetCurrenciesAsync(); + } + + public override Task>> GetTickersAsync() + { + return base.GetTickersAsync(); + } + #endregion } } From 3ac7348c48c792d40bb81ebd7eb44c077c6f825f Mon Sep 17 00:00:00 2001 From: Josh Plumbly Date: Tue, 21 Sep 2021 22:12:06 +0100 Subject: [PATCH 20/29] Update ExchangeFTXAPI.cs Implemented getOrderBook for FTX exchange. --- src/ExchangeSharp/API/Exchanges/FTX/ExchangeFTXAPI.cs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/ExchangeSharp/API/Exchanges/FTX/ExchangeFTXAPI.cs b/src/ExchangeSharp/API/Exchanges/FTX/ExchangeFTXAPI.cs index 893cdd4f..0d9e6a4b 100644 --- a/src/ExchangeSharp/API/Exchanges/FTX/ExchangeFTXAPI.cs +++ b/src/ExchangeSharp/API/Exchanges/FTX/ExchangeFTXAPI.cs @@ -367,14 +367,11 @@ protected override Task OnInitializeAsync() return base.OnInitializeAsync(); } - protected override Task> OnGetRecentTradesAsync(string marketSymbol, int? limit = null) + protected async override Task OnGetOrderBookAsync(string marketSymbol, int maxCount = 100) { - return base.OnGetRecentTradesAsync(marketSymbol, limit); - } + JToken response = await MakeJsonRequestAsync($"/markets/{marketSymbol}/orderbook?depth={maxCount}"); - protected override Task OnGetOrderBookAsync(string marketSymbol, int maxCount = 100) - { - return base.OnGetOrderBookAsync(marketSymbol, maxCount); + return ExchangeAPIExtensions.ParseOrderBookFromJTokenArrays(response, maxCount: maxCount); } protected async override Task> OnGetCandlesAsync(string marketSymbol, int periodSeconds, DateTime? startDate = null, DateTime? endDate = null, int? limit = null) From c97051f96a772a08aeb661acc5bd3cdc15dbf21b Mon Sep 17 00:00:00 2001 From: Josh Plumbly Date: Tue, 21 Sep 2021 23:14:03 +0100 Subject: [PATCH 21/29] Update ExchangeFTXAPI.cs Implemented GetCompletedOrders method for FTX exchange --- .../API/Exchanges/FTX/ExchangeFTXAPI.cs | 69 +++++++++++++++---- 1 file changed, 54 insertions(+), 15 deletions(-) diff --git a/src/ExchangeSharp/API/Exchanges/FTX/ExchangeFTXAPI.cs b/src/ExchangeSharp/API/Exchanges/FTX/ExchangeFTXAPI.cs index 0d9e6a4b..2d715b4b 100644 --- a/src/ExchangeSharp/API/Exchanges/FTX/ExchangeFTXAPI.cs +++ b/src/ExchangeSharp/API/Exchanges/FTX/ExchangeFTXAPI.cs @@ -166,23 +166,28 @@ protected async override Task> OnGetOpenOrderDe continue; } - markets.Add(new ExchangeOrderResult() - { - MarketSymbol = token["market"].ToStringInvariant(), - Price = token["price"].ConvertInvariant(), - AveragePrice = token["avgFillPrice"].ConvertInvariant(), - OrderDate = token["createdAt"].ConvertInvariant(), - IsBuy = token["side"].ToStringInvariant().Equals("buy"), - OrderId = token["id"].ToStringInvariant(), - Amount = token["size"].ConvertInvariant(), - AmountFilled = token["filledSize"].ConvertInvariant(), - ClientOrderId = token["clientId"].ToStringInvariant() - }); + markets.Add(ParseOrder(token)); } - + return markets; } + private ExchangeOrderResult ParseOrder(JToken token) + { + return new ExchangeOrderResult() + { + MarketSymbol = token["market"].ToStringInvariant(), + Price = token["price"].ConvertInvariant(), + AveragePrice = token["avgFillPrice"].ConvertInvariant(), + OrderDate = token["createdAt"].ConvertInvariant(), + IsBuy = token["side"].ToStringInvariant().Equals("buy"), + OrderId = token["id"].ToStringInvariant(), + Amount = token["size"].ConvertInvariant(), + AmountFilled = token["filledSize"].ConvertInvariant(), + ClientOrderId = token["clientId"].ToStringInvariant() + }; + } + /// protected async override Task OnGetOrderDetailsAsync(string orderId, string marketSymbol = null) { @@ -270,6 +275,10 @@ protected async override Task OnPlaceOrderAsync(ExchangeOrd parameters.Add("price", Math.Round(order.Price.Value, precision)); } + else + { + parameters.Add("price", null); + } parameters.CopyTo(payload); @@ -405,9 +414,39 @@ protected async override Task> OnGetCandlesAsync(strin return candles; } - protected override Task> OnGetCompletedOrderDetailsAsync(string marketSymbol = null, DateTime? afterDate = null) + protected async override Task> OnGetCompletedOrderDetailsAsync(string marketSymbol = null, DateTime? afterDate = null) { - return base.OnGetCompletedOrderDetailsAsync(marketSymbol, afterDate); + string query = "/orders/history"; + + var param = new Dictionary(); + + if (!string.IsNullOrEmpty(marketSymbol)) + { + query += $"&market={marketSymbol}"; + } + + if (afterDate != null) + { + query += $"&start_time={afterDate?.UnixTimestampFromDateTimeSeconds()}"; + } + + JToken response = await MakeJsonRequestAsync(query, null, await GetNoncePayloadAsync()); + + var orders = new List(); + + foreach (JToken token in response.Children()) + { + var symbol = token["market"].ToStringInvariant(); + + if (!Regex.Match(symbol, @"[\w\d]*\/[[\w\d]]*").Success) + { + continue; + } + + orders.Add(ParseOrder(token)); + } + + return orders; } protected async override Task OnCancelOrderAsync(string orderId, string marketSymbol = null) From 2d385c80afcb1338353194a8f5544b944d85371e Mon Sep 17 00:00:00 2001 From: Josh Plumbly Date: Tue, 21 Sep 2021 23:39:08 +0100 Subject: [PATCH 22/29] Update ExchangeFTXAPI.cs Implemented GetTickersAsync method for FTX --- .../API/Exchanges/FTX/ExchangeFTXAPI.cs | 72 ++++++++++++------- 1 file changed, 46 insertions(+), 26 deletions(-) diff --git a/src/ExchangeSharp/API/Exchanges/FTX/ExchangeFTXAPI.cs b/src/ExchangeSharp/API/Exchanges/FTX/ExchangeFTXAPI.cs index 2d715b4b..efd59888 100644 --- a/src/ExchangeSharp/API/Exchanges/FTX/ExchangeFTXAPI.cs +++ b/src/ExchangeSharp/API/Exchanges/FTX/ExchangeFTXAPI.cs @@ -172,22 +172,6 @@ protected async override Task> OnGetOpenOrderDe return markets; } - private ExchangeOrderResult ParseOrder(JToken token) - { - return new ExchangeOrderResult() - { - MarketSymbol = token["market"].ToStringInvariant(), - Price = token["price"].ConvertInvariant(), - AveragePrice = token["avgFillPrice"].ConvertInvariant(), - OrderDate = token["createdAt"].ConvertInvariant(), - IsBuy = token["side"].ToStringInvariant().Equals("buy"), - OrderId = token["id"].ToStringInvariant(), - Amount = token["size"].ConvertInvariant(), - AmountFilled = token["filledSize"].ConvertInvariant(), - ClientOrderId = token["clientId"].ToStringInvariant() - }; - } - /// protected async override Task OnGetOrderDetailsAsync(string orderId, string marketSymbol = null) { @@ -418,16 +402,21 @@ protected async override Task> OnGetCompletedOr { string query = "/orders/history"; - var param = new Dictionary(); + string parameters = ""; if (!string.IsNullOrEmpty(marketSymbol)) { - query += $"&market={marketSymbol}"; + parameters += $"&market={marketSymbol}"; } if (afterDate != null) { - query += $"&start_time={afterDate?.UnixTimestampFromDateTimeSeconds()}"; + parameters += $"&start_time={afterDate?.UnixTimestampFromDateTimeSeconds()}"; + } + + if (!string.IsNullOrEmpty(parameters)) + { + query += $"?{parameters}"; } JToken response = await MakeJsonRequestAsync(query, null, await GetNoncePayloadAsync()); @@ -451,7 +440,7 @@ protected async override Task> OnGetCompletedOr protected async override Task OnCancelOrderAsync(string orderId, string marketSymbol = null) { - var response = await MakeJsonRequestAsync($"/orders/{orderId}", null, await GetNoncePayloadAsync(), "DELETE"); + await MakeJsonRequestAsync($"/orders/{orderId}", null, await GetNoncePayloadAsync(), "DELETE"); } public override Task ExchangeMarketSymbolToGlobalMarketSymbolAsync(string marketSymbol) @@ -469,16 +458,47 @@ public override Task GlobalMarketSymbolToExchangeMarketSymbolAsync(strin return base.GlobalMarketSymbolToExchangeMarketSymbolAsync(marketSymbol); } - public override Task> GetCurrenciesAsync() + protected async override Task>> OnGetTickersAsync() { - return base.GetCurrenciesAsync(); - } + JToken result = await MakeJsonRequestAsync("/markets"); - public override Task>> GetTickersAsync() - { - return base.GetTickersAsync(); + + var tickers = new Dictionary(); + + foreach (JToken token in result.Children()) + { + var symbol = token["name"].ToStringInvariant(); + + if (!Regex.Match(symbol, @"[\w\d]*\/[[\w\d]]*").Success) + { + continue; + } + + var ticker = await this.ParseTickerAsync(token, symbol, "ask", "bid", "last", null, null, "time", TimestampType.UnixSecondsDouble); + + tickers.Add(symbol, ticker); + } + + return tickers; } #endregion + + + private ExchangeOrderResult ParseOrder(JToken token) + { + return new ExchangeOrderResult() + { + MarketSymbol = token["market"].ToStringInvariant(), + Price = token["price"].ConvertInvariant(), + AveragePrice = token["avgFillPrice"].ConvertInvariant(), + OrderDate = token["createdAt"].ConvertInvariant(), + IsBuy = token["side"].ToStringInvariant().Equals("buy"), + OrderId = token["id"].ToStringInvariant(), + Amount = token["size"].ConvertInvariant(), + AmountFilled = token["filledSize"].ConvertInvariant(), + ClientOrderId = token["clientId"].ToStringInvariant() + }; + } } } From 1a6a5f2c8041ca354ddf7ee405d812897023d536 Mon Sep 17 00:00:00 2001 From: Josh Plumbly Date: Mon, 27 Sep 2021 21:37:45 +0100 Subject: [PATCH 23/29] Updated FTX Implementation Fixed minor issues in some of the methods Modified order of methods so they are alphabetical Removed debug test class. --- .../API/Exchanges/FTX/ExchangeFTXAPI.cs | 376 +++++++++--------- .../ExchangeSharpTests/ExchangeFTXAPITests.cs | 20 - 2 files changed, 181 insertions(+), 215 deletions(-) delete mode 100644 tests/ExchangeSharpTests/ExchangeFTXAPITests.cs diff --git a/src/ExchangeSharp/API/Exchanges/FTX/ExchangeFTXAPI.cs b/src/ExchangeSharp/API/Exchanges/FTX/ExchangeFTXAPI.cs index efd59888..17a64733 100644 --- a/src/ExchangeSharp/API/Exchanges/FTX/ExchangeFTXAPI.cs +++ b/src/ExchangeSharp/API/Exchanges/FTX/ExchangeFTXAPI.cs @@ -19,13 +19,18 @@ public ExchangeFTXAPI() NonceStyle = NonceStyle.UnixMillisecondsString; MarketSymbolSeparator = "/"; RequestContentType = "application/json"; - //WebSocketOrderBookType = WebSocketOrderBookType. } #endregion #region [ Implementation ] + /// + protected async override Task OnCancelOrderAsync(string orderId, string marketSymbol = null) + { + await MakeJsonRequestAsync($"/orders/{orderId}", null, await GetNoncePayloadAsync(), "DELETE"); + } + /// protected async override Task> OnGetAmountsAsync() { @@ -43,6 +48,105 @@ protected async override Task> OnGetAmountsAsync() return balances; } + /// + protected async override Task> OnGetAmountsAvailableToTradeAsync() + { + // https://docs.ftx.com/#get-balances + // NOTE there is also is "Get balances of all accounts"? + // "coin": "USDTBEAR", + // "free": 2320.2, + // "spotBorrow": 0.0, + // "total": 2340.2, + // "usdValue": 2340.2, + // "availableWithoutBorrow": 2320.2 + + var balances = new Dictionary(); + + JToken result = await MakeJsonRequestAsync($"/wallet/balances", null, await GetNoncePayloadAsync()); + + foreach (JToken token in result.Children()) + { + balances.Add(token["coin"].ToStringInvariant(), + token["availableWithoutBorrow"].ConvertInvariant()); + } + + return balances; + } + + + /// + protected async override Task> OnGetCandlesAsync(string marketSymbol, int periodSeconds, DateTime? startDate = null, DateTime? endDate = null, int? limit = null) + { + + //period options: 15, 60, 300, 900, 3600, 14400, 86400, or any multiple of 86400 up to 30*86400 + + var queryUrl = $"/markets/{marketSymbol}/candles?resolution={periodSeconds}"; + + if (startDate.HasValue) + { + queryUrl += $"&start_time={startDate?.UnixTimestampFromDateTimeSeconds()}"; + } + + if (endDate.HasValue) + { + queryUrl += $"&end_time={endDate?.UnixTimestampFromDateTimeSeconds()}"; + } + + var candles = new List(); + + var response = await MakeJsonRequestAsync(queryUrl, null, await GetNoncePayloadAsync()); + + foreach (JToken candle in response.Children()) + { + var parsedCandle = this.ParseCandle(candle, marketSymbol, periodSeconds, "open", "high", "low", "close", "startTime", TimestampType.Iso8601, "volume"); + + candles.Add(parsedCandle); + } + + return candles; + } + + /// + protected async override Task> OnGetCompletedOrderDetailsAsync(string marketSymbol = null, DateTime? afterDate = null) + { + string query = "/orders/history"; + + string parameters = ""; + + if (!string.IsNullOrEmpty(marketSymbol)) + { + parameters += $"&market={marketSymbol}"; + } + + if (afterDate != null) + { + parameters += $"&start_time={afterDate?.UnixTimestampFromDateTimeSeconds()}"; + } + + if (!string.IsNullOrEmpty(parameters)) + { + query += $"?{parameters}"; + } + + JToken response = await MakeJsonRequestAsync(query, null, await GetNoncePayloadAsync()); + + var orders = new List(); + + foreach (JToken token in response.Children()) + { + var symbol = token["market"].ToStringInvariant(); + + if (!Regex.Match(symbol, @"[\w\d]*\/[[\w\d]]*").Success) + { + continue; + } + + orders.Add(ParseOrder(token)); + } + + return orders; + } + /// protected async override Task OnGetHistoricalTradesAsync(Func, bool> callback, string marketSymbol, DateTime? startDate = null, DateTime? endDate = null, int? limit = null) { @@ -83,6 +187,7 @@ protected async override Task> OnGetMarketSymbolsAsync(bool { JToken result = await MakeJsonRequestAsync("/markets"); + //FTX contains futures which we are not interested in so we filter them out. var names = result.Children().Select(x => x["name"].ToStringInvariant()).Where(x => Regex.Match(x, @"[\w\d]*\/[[\w\d]]*").Success).ToList(); names.Sort(); @@ -155,11 +260,11 @@ protected async override Task> OnGetOpenOrderDe var markets = new List(); - JToken result = await MakeJsonRequestAsync($"/orders?market={marketSymbol}"); + JToken result = await MakeJsonRequestAsync($"/orders?market={marketSymbol}", null, await GetNoncePayloadAsync()); foreach (JToken token in result.Children()) { - var symbol = token["name"].ToStringInvariant(); + var symbol = token["market"].ToStringInvariant(); if (!Regex.Match(symbol, @"[\w\d]*\/[[\w\d]]*").Success) { @@ -172,53 +277,88 @@ protected async override Task> OnGetOpenOrderDe return markets; } + /// + protected async override Task OnGetOrderBookAsync(string marketSymbol, int maxCount = 100) + { + JToken response = await MakeJsonRequestAsync($"/markets/{marketSymbol}/orderbook?depth={maxCount}"); + + return ExchangeAPIExtensions.ParseOrderBookFromJTokenArrays(response, maxCount: maxCount); + } + /// protected async override Task OnGetOrderDetailsAsync(string orderId, string marketSymbol = null) { // https://docs.ftx.com/#get-order-status - JToken result = await MakeJsonRequestAsync($"/orders?{orderId}"); + JToken result = await MakeJsonRequestAsync($"/orders/{orderId}", null, await GetNoncePayloadAsync()); - var resp = result.First(); - - return new ExchangeOrderResult() - { - OrderId = resp["id"].ToStringInvariant(), - OrderDate = resp["createdAt"].ConvertInvariant(), - Result = resp["id"].ToStringLowerInvariant().ToExchangeAPIOrderResult() - }; + return ParseOrder(result); } /// - protected async override Task> OnGetAmountsAvailableToTradeAsync() + protected async override Task>> OnGetTickersAsync() { - // https://docs.ftx.com/#get-balances - // NOTE there is also is "Get balances of all accounts"? - // "coin": "USDTBEAR", - // "free": 2320.2, - // "spotBorrow": 0.0, - // "total": 2340.2, - // "usdValue": 2340.2, - // "availableWithoutBorrow": 2320.2 - - var balances = new Dictionary(); + JToken result = await MakeJsonRequestAsync("/markets"); - JToken result = await MakeJsonRequestAsync($"/wallet/balances"); + var tickers = new Dictionary(); foreach (JToken token in result.Children()) { - if (!Regex.Match(token["coin"].ToStringInvariant(), @"[\w\d]*\/[[\w\d]]*").Success) + var symbol = token["name"].ToStringInvariant(); + + if (!Regex.Match(symbol, @"[\w\d]*\/[[\w\d]]*").Success) { continue; } - balances.Add(token["coin"].ToStringInvariant(), - token["availableWithoutBorrow"].ConvertInvariant()); + var ticker = await this.ParseTickerAsync(token, symbol, "ask", "bid", "last", null, null, "time", TimestampType.UnixSecondsDouble); + + tickers.Add(symbol, ticker); } - return balances; + return tickers; + } + + /// + protected override async Task OnGetTickersWebSocketAsync(Action>> tickers, params string[] marketSymbols) + { + if (marketSymbols == null || marketSymbols.Length == 0) + { + marketSymbols = (await GetMarketSymbolsAsync(true)).ToArray(); + } + return await ConnectPublicWebSocketAsync(null, messageCallback: async (_socket, msg) => + { + JToken parsedMsg = JToken.Parse(msg.ToStringFromUTF8()); + + if (parsedMsg["channel"].ToStringInvariant().Equals("ticker") && !parsedMsg["type"].ToStringInvariant().Equals("subscribed")) + { + JToken data = parsedMsg["data"]; + + var exchangeTicker = await this.ParseTickerAsync(data, parsedMsg["market"].ToStringInvariant(), "ask", "bid", "last", null, null, "time", TimestampType.UnixSecondsDouble); + + var kv = new KeyValuePair(exchangeTicker.MarketSymbol, exchangeTicker); + + tickers(new List> { kv }); + } + }, connectCallback: async (_socket) => + { + List marketSymbolList = marketSymbols.ToList(); + + //{'op': 'subscribe', 'channel': 'trades', 'market': 'BTC-PERP'} + + for (int i = 0; i < marketSymbolList.Count; i++) + { + await _socket.SendMessageAsync(new + { + op = "subscribe", + market = marketSymbolList[i], + channel = "ticker" + }); + } + }); } + /// protected async override Task OnPlaceOrderAsync(ExchangeOrderRequest order) { //{ @@ -287,44 +427,6 @@ protected async override Task OnPlaceOrderAsync(ExchangeOrd } /// - protected override async Task OnGetTickersWebSocketAsync(Action>> tickers, params string[] marketSymbols) - { - if (marketSymbols == null || marketSymbols.Length == 0) - { - marketSymbols = (await GetMarketSymbolsAsync(true)).ToArray(); - } - return await ConnectPublicWebSocketAsync(null, messageCallback: async (_socket, msg) => - { - JToken parsedMsg = JToken.Parse(msg.ToStringFromUTF8()); - - if (parsedMsg["channel"].ToStringInvariant().Equals("ticker")) - { - JToken data = parsedMsg["data"]; - - var exchangeTicker = await this.ParseTickerAsync(data, parsedMsg["market"].ToStringInvariant(), "ask", "bid", "last", null, null, "time", TimestampType.UnixSecondsDouble); - - var kv = new KeyValuePair(exchangeTicker.MarketSymbol, exchangeTicker); - - tickers(new List> { kv }); - } - }, connectCallback: async (_socket) => - { - List marketSymbolList = marketSymbols.ToList(); - - //{'op': 'subscribe', 'channel': 'trades', 'market': 'BTC-PERP'} - - for (int i = 0; i < marketSymbolList.Count; i++) - { - await _socket.SendMessageAsync(new - { - op = "subscribe", - market = marketSymbolList[i], - channel = "ticker" - }); - } - }); - } - protected override async Task ProcessRequestAsync(IHttpWebRequest request, Dictionary payload) { if (CanMakeAuthenticatedRequest(payload)) @@ -355,136 +457,15 @@ protected override async Task ProcessRequestAsync(IHttpWebRequest request, Dicti } } - protected override Task OnInitializeAsync() - { - return base.OnInitializeAsync(); - } - - protected async override Task OnGetOrderBookAsync(string marketSymbol, int maxCount = 100) - { - JToken response = await MakeJsonRequestAsync($"/markets/{marketSymbol}/orderbook?depth={maxCount}"); - - return ExchangeAPIExtensions.ParseOrderBookFromJTokenArrays(response, maxCount: maxCount); - } - - protected async override Task> OnGetCandlesAsync(string marketSymbol, int periodSeconds, DateTime? startDate = null, DateTime? endDate = null, int? limit = null) - { - - //period options: 15, 60, 300, 900, 3600, 14400, 86400, or any multiple of 86400 up to 30*86400 - - var queryUrl = $"/markets/{marketSymbol}/candles?resolution={periodSeconds}"; - - if (startDate.HasValue) - { - queryUrl += $"&start_time={startDate?.UnixTimestampFromDateTimeSeconds()}"; - } - - if (endDate.HasValue) - { - queryUrl += $"&end_time={endDate?.UnixTimestampFromDateTimeSeconds()}"; - } - - var candles = new List(); - - var response = await MakeJsonRequestAsync(queryUrl, null, await GetNoncePayloadAsync()); - - foreach (JToken candle in response.Children()) - { - var parsedCandle = this.ParseCandle(candle, marketSymbol, periodSeconds, "open", "high", "low", "close", "startTime", TimestampType.Iso8601, "volume"); - - candles.Add(parsedCandle); - } - - return candles; - } - - protected async override Task> OnGetCompletedOrderDetailsAsync(string marketSymbol = null, DateTime? afterDate = null) - { - string query = "/orders/history"; - - string parameters = ""; - - if (!string.IsNullOrEmpty(marketSymbol)) - { - parameters += $"&market={marketSymbol}"; - } - - if (afterDate != null) - { - parameters += $"&start_time={afterDate?.UnixTimestampFromDateTimeSeconds()}"; - } - - if (!string.IsNullOrEmpty(parameters)) - { - query += $"?{parameters}"; - } - - JToken response = await MakeJsonRequestAsync(query, null, await GetNoncePayloadAsync()); - - var orders = new List(); - - foreach (JToken token in response.Children()) - { - var symbol = token["market"].ToStringInvariant(); - - if (!Regex.Match(symbol, @"[\w\d]*\/[[\w\d]]*").Success) - { - continue; - } - - orders.Add(ParseOrder(token)); - } - - return orders; - } - - protected async override Task OnCancelOrderAsync(string orderId, string marketSymbol = null) - { - await MakeJsonRequestAsync($"/orders/{orderId}", null, await GetNoncePayloadAsync(), "DELETE"); - } - - public override Task ExchangeMarketSymbolToGlobalMarketSymbolAsync(string marketSymbol) - { - return base.ExchangeMarketSymbolToGlobalMarketSymbolAsync(marketSymbol); - } - - public override Task<(string baseCurrency, string quoteCurrency)> ExchangeMarketSymbolToCurrenciesAsync(string marketSymbol) - { - return base.ExchangeMarketSymbolToCurrenciesAsync(marketSymbol); - } - - public override Task GlobalMarketSymbolToExchangeMarketSymbolAsync(string marketSymbol) - { - return base.GlobalMarketSymbolToExchangeMarketSymbolAsync(marketSymbol); - } - - protected async override Task>> OnGetTickersAsync() - { - JToken result = await MakeJsonRequestAsync("/markets"); - - - var tickers = new Dictionary(); - - foreach (JToken token in result.Children()) - { - var symbol = token["name"].ToStringInvariant(); - - if (!Regex.Match(symbol, @"[\w\d]*\/[[\w\d]]*").Success) - { - continue; - } - - var ticker = await this.ParseTickerAsync(token, symbol, "ask", "bid", "last", null, null, "time", TimestampType.UnixSecondsDouble); - - tickers.Add(symbol, ticker); - } - - return tickers; - } - #endregion + #region Private Methods + /// + /// Parses the json of an order. + /// + /// Json token to parse the order from. + /// Parsed exchange order result. private ExchangeOrderResult ParseOrder(JToken token) { return new ExchangeOrderResult() @@ -497,8 +478,13 @@ private ExchangeOrderResult ParseOrder(JToken token) OrderId = token["id"].ToStringInvariant(), Amount = token["size"].ConvertInvariant(), AmountFilled = token["filledSize"].ConvertInvariant(), - ClientOrderId = token["clientId"].ToStringInvariant() - }; + ClientOrderId = token["clientId"].ToStringInvariant(), + Result = token["status"].ToStringInvariant().ToExchangeAPIOrderResult(), + ResultCode = token["status"].ToStringInvariant() + }; } + + #endregion + } } diff --git a/tests/ExchangeSharpTests/ExchangeFTXAPITests.cs b/tests/ExchangeSharpTests/ExchangeFTXAPITests.cs deleted file mode 100644 index 15ef4485..00000000 --- a/tests/ExchangeSharpTests/ExchangeFTXAPITests.cs +++ /dev/null @@ -1,20 +0,0 @@ -using ExchangeSharp.API.Exchanges.FTX.Models; -using Microsoft.VisualStudio.TestTools.UnitTesting; -using System.Threading.Tasks; - -namespace ExchangeSharpTests -{ - [TestClass] - public class ExchangeFTXAPITests - { - [TestMethod] - public async Task TempTest() - { - var exchange = new ExchangeFTXAPI(); - - exchange.LoadAPIKeysUnsecure("qq0Y6JKAbefgU4GBllT_iGwrhhhJnM7YExkP8q_F", "BvdkXp4vJHxigx5W5d97YmCVEv9TEr944dDQBpLR"); - - var result = await exchange.GetAmountsAsync(); - } - } -} From 60ed9e1dbe4a5e4649391f18c25fb0960bd1a4ed Mon Sep 17 00:00:00 2001 From: Josh Plumbly Date: Mon, 27 Sep 2021 22:23:07 +0100 Subject: [PATCH 24/29] Update README.md Added FTX to supported exchanges --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 08ca05cc..6252ed48 100644 --- a/README.md +++ b/README.md @@ -41,6 +41,7 @@ The following cryptocurrency exchanges are supported: | Bybit | x | x | R | Has public method for Websocket Positions | Coinbase | x | x | T R | | Digifinex | x | x | R B | +| FTX | x | x | T | | Gemini | x | x | T R B | | HitBTC | x | x | R | | Huobi | x | x | R B | From d2d38e3a7a4b60e7810b4958b1ebeab5fe264d98 Mon Sep 17 00:00:00 2001 From: Josh Plumbly Date: Mon, 27 Sep 2021 22:32:38 +0100 Subject: [PATCH 25/29] Update ExchangeFTXAPI.cs Modified OnGetOrderDetailsAsync for ftx exchange to include isClientOrderId parameter --- src/ExchangeSharp/API/Exchanges/FTX/ExchangeFTXAPI.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ExchangeSharp/API/Exchanges/FTX/ExchangeFTXAPI.cs b/src/ExchangeSharp/API/Exchanges/FTX/ExchangeFTXAPI.cs index 17a64733..f441c88d 100644 --- a/src/ExchangeSharp/API/Exchanges/FTX/ExchangeFTXAPI.cs +++ b/src/ExchangeSharp/API/Exchanges/FTX/ExchangeFTXAPI.cs @@ -286,7 +286,7 @@ protected async override Task OnGetOrderBookAsync(string mark } /// - protected async override Task OnGetOrderDetailsAsync(string orderId, string marketSymbol = null) + protected async override Task OnGetOrderDetailsAsync(string orderId, string marketSymbol = null, bool isClientOrderId = false) { // https://docs.ftx.com/#get-order-status From 2b43633b7547654dc582a7850e34f70d44d4d7d1 Mon Sep 17 00:00:00 2001 From: Josh Plumbly Date: Mon, 27 Sep 2021 22:34:22 +0100 Subject: [PATCH 26/29] Update ExchangeSharp.csproj Removed unused Models folder --- src/ExchangeSharp/ExchangeSharp.csproj | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/ExchangeSharp/ExchangeSharp.csproj b/src/ExchangeSharp/ExchangeSharp.csproj index f6ae3338..a38ffd88 100644 --- a/src/ExchangeSharp/ExchangeSharp.csproj +++ b/src/ExchangeSharp/ExchangeSharp.csproj @@ -22,6 +22,12 @@ git true + + + + + + @@ -43,8 +49,4 @@ - - - - From ff299360a34df70740786c87421e0b047298a7d4 Mon Sep 17 00:00:00 2001 From: Josh Plumbly Date: Mon, 27 Sep 2021 22:51:58 +0100 Subject: [PATCH 27/29] Revert "Added margin properties" This reverts commit 161bbba908a1a8ce4f6b6b815b802b3496628aef. --- .../API/Exchanges/Kraken/ExchangeKrakenAPI.cs | 29 +------------------ src/ExchangeSharp/Model/ExchangeMarket.cs | 24 +-------------- 2 files changed, 2 insertions(+), 51 deletions(-) diff --git a/src/ExchangeSharp/API/Exchanges/Kraken/ExchangeKrakenAPI.cs b/src/ExchangeSharp/API/Exchanges/Kraken/ExchangeKrakenAPI.cs index aa63f557..6d6743e8 100644 --- a/src/ExchangeSharp/API/Exchanges/Kraken/ExchangeKrakenAPI.cs +++ b/src/ExchangeSharp/API/Exchanges/Kraken/ExchangeKrakenAPI.cs @@ -537,35 +537,8 @@ protected internal override async Task> OnGetMarketS BaseCurrency = pair["base"].ToStringInvariant(), QuoteCurrency = pair["quote"].ToStringInvariant(), QuantityStepSize = quantityStepSize, - PriceStepSize = Math.Pow(0.1, pair["pair_decimals"].ConvertInvariant()).ConvertInvariant(), - MarginCall = pair["margin_call"].ConvertInvariant(), - MarginStop = pair["margin_stop"].ConvertInvariant() + PriceStepSize = Math.Pow(0.1, pair["pair_decimals"].ConvertInvariant()).ConvertInvariant() }; - - if (pair["leverage_sell"].Children().Any()) - { - var marginSell = new List(); - - foreach (var margin in pair["leverage_sell"].Children()) - { - marginSell.Add(margin.ConvertInvariant()); - } - - market.MarginSell = marginSell; - } - - if (pair["leverage_buy"].Children().Any()) - { - var marginBuy = new List(); - - foreach (var margin in pair["leverage_buy"].Children()) - { - marginBuy.Add(margin.ConvertInvariant()); - } - - market.MarginBuy = marginBuy; - } - markets.Add(market); } diff --git a/src/ExchangeSharp/Model/ExchangeMarket.cs b/src/ExchangeSharp/Model/ExchangeMarket.cs index a98b0a03..d6fa4aee 100644 --- a/src/ExchangeSharp/Model/ExchangeMarket.cs +++ b/src/ExchangeSharp/Model/ExchangeMarket.cs @@ -10,8 +10,6 @@ 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.Collections.Generic; - namespace ExchangeSharp { /// Representation of a market on an exchange. @@ -75,27 +73,7 @@ public sealed class ExchangeMarket /// public bool MarginEnabled { get; set; } - /// - /// Amount of leverage allowed on a buy. - /// - public IEnumerable MarginBuy { get; set; } - - /// - /// Amount of leverage allowed on a sell. - /// - public IEnumerable MarginSell { get; set; } - - /// - /// The threshold at which a warning will be issued on a postion - /// - public long MarginStop { get; set; } - - /// - /// The threshold at which a position will be liquidated. - /// - public long MarginCall { get; set; } - - public override string ToString() + public override string ToString() { return $"{MarketSymbol}, {BaseCurrency}-{QuoteCurrency}"; } From 780f7644ad8f9914512bdf57c0a3a52f24e75ae5 Mon Sep 17 00:00:00 2001 From: Josh Plumbly Date: Tue, 28 Sep 2021 20:33:54 +0100 Subject: [PATCH 28/29] Update to Exchange Implementations Modified exchange tickers to include the name of the exchange it came from for all exchange implementations. Added PostOnly parameter to place order method for FTX implementation. Modified GetOrderDetails for FTX implementation so that it handles retrieval via client order ids. Removed unnecessary exclusion in project file --- .../API/Exchanges/Aquanow/ExchangeAquanowAPI.cs | 2 +- .../API/Exchanges/BitBank/ExchangeBitBankAPI.cs | 1 + .../API/Exchanges/Bitfinex/ExchangeBitfinexAPI.cs | 1 + .../Bittrex/ExchangeBittrexAPI_WebSocket.cs | 1 + .../API/Exchanges/Digifinex/ExchangeDigifinexAPI.cs | 1 + .../API/Exchanges/FTX/ExchangeFTXAPI.cs | 12 ++++++++++-- .../API/Exchanges/GateIo/ExchangeGateIoAPI.cs | 1 + .../API/Exchanges/Gemini/ExchangeGeminiAPI.cs | 2 ++ .../API/Exchanges/LBank/ExchangeLBankAPI.cs | 1 + .../API/Exchanges/NDAX/ExchangeNDAXAPI.cs | 4 ++-- .../API/Exchanges/NDAX/Models/Level1Data.cs | 3 ++- .../API/Exchanges/NDAX/Models/NDAXTicker.cs | 3 ++- .../API/Exchanges/UfoDex/ExchangeUfoDexAPI.cs | 1 + .../API/Exchanges/_Base/ExchangeLogger.cs | 2 +- src/ExchangeSharp/ExchangeSharp.csproj | 8 +------- 15 files changed, 28 insertions(+), 15 deletions(-) diff --git a/src/ExchangeSharp/API/Exchanges/Aquanow/ExchangeAquanowAPI.cs b/src/ExchangeSharp/API/Exchanges/Aquanow/ExchangeAquanowAPI.cs index 8eed8c4c..fc774e6c 100644 --- a/src/ExchangeSharp/API/Exchanges/Aquanow/ExchangeAquanowAPI.cs +++ b/src/ExchangeSharp/API/Exchanges/Aquanow/ExchangeAquanowAPI.cs @@ -56,7 +56,7 @@ protected override async Task>> JToken bestPriceSymbol = await MakeJsonRequestAsync($"/bestprice?symbol={symbol}", MarketUrl); decimal bid = bestPriceSymbol["bestBid"].ConvertInvariant(); decimal ask = bestPriceSymbol["bestAsk"].ConvertInvariant(); - ExchangeTicker ticker = new ExchangeTicker { MarketSymbol = symbol, Bid = bid, Ask = ask, ApiResponse = bestPriceSymbol }; + ExchangeTicker ticker = new ExchangeTicker { Exchange = Name, MarketSymbol = symbol, Bid = bid, Ask = ask, ApiResponse = bestPriceSymbol }; tickers.Add(new KeyValuePair(symbol, ticker)); } return tickers; diff --git a/src/ExchangeSharp/API/Exchanges/BitBank/ExchangeBitBankAPI.cs b/src/ExchangeSharp/API/Exchanges/BitBank/ExchangeBitBankAPI.cs index 5cd35e21..75694a64 100644 --- a/src/ExchangeSharp/API/Exchanges/BitBank/ExchangeBitBankAPI.cs +++ b/src/ExchangeSharp/API/Exchanges/BitBank/ExchangeBitBankAPI.cs @@ -49,6 +49,7 @@ protected override async Task>> var data = token[GlobalMarketSymbolToExchangeMarketSymbolAsync(symbol)]; var ticker = new ExchangeTicker() { + Exchange = Name, ApiResponse = token, Ask = data["sell"].ConvertInvariant(), Bid = data["buy"].ConvertInvariant(), diff --git a/src/ExchangeSharp/API/Exchanges/Bitfinex/ExchangeBitfinexAPI.cs b/src/ExchangeSharp/API/Exchanges/Bitfinex/ExchangeBitfinexAPI.cs index 309cd31f..e1bc82f5 100644 --- a/src/ExchangeSharp/API/Exchanges/Bitfinex/ExchangeBitfinexAPI.cs +++ b/src/ExchangeSharp/API/Exchanges/Bitfinex/ExchangeBitfinexAPI.cs @@ -166,6 +166,7 @@ protected override async Task>> var market = marketsBySymbol[marketSymbol.ToLowerInvariant()]; tickers.Add(new KeyValuePair(marketSymbol, new ExchangeTicker { + Exchange = Name, MarketSymbol = marketSymbol, ApiResponse = token, Ask = array[3].ConvertInvariant(), diff --git a/src/ExchangeSharp/API/Exchanges/Bittrex/ExchangeBittrexAPI_WebSocket.cs b/src/ExchangeSharp/API/Exchanges/Bittrex/ExchangeBittrexAPI_WebSocket.cs index 01f5c1ef..fe712c02 100644 --- a/src/ExchangeSharp/API/Exchanges/Bittrex/ExchangeBittrexAPI_WebSocket.cs +++ b/src/ExchangeSharp/API/Exchanges/Bittrex/ExchangeBittrexAPI_WebSocket.cs @@ -141,6 +141,7 @@ async Task innerCallback(string json) DateTime timestamp = CryptoUtility.UnixTimeStampToDateTimeMilliseconds(ticker["T"].ConvertInvariant()); var t = new ExchangeTicker { + Exchange = Name, MarketSymbol = marketName, ApiResponse = ticker, Ask = ask, diff --git a/src/ExchangeSharp/API/Exchanges/Digifinex/ExchangeDigifinexAPI.cs b/src/ExchangeSharp/API/Exchanges/Digifinex/ExchangeDigifinexAPI.cs index eb601ab0..6c4bc20a 100644 --- a/src/ExchangeSharp/API/Exchanges/Digifinex/ExchangeDigifinexAPI.cs +++ b/src/ExchangeSharp/API/Exchanges/Digifinex/ExchangeDigifinexAPI.cs @@ -185,6 +185,7 @@ private async Task ParseTickerAsync(JToken x) return new ExchangeTicker { + Exchange = Name, ApiResponse = t, Ask = t["sell"].ConvertInvariant(), Bid = t["buy"].ConvertInvariant(), diff --git a/src/ExchangeSharp/API/Exchanges/FTX/ExchangeFTXAPI.cs b/src/ExchangeSharp/API/Exchanges/FTX/ExchangeFTXAPI.cs index f441c88d..5db80bcd 100644 --- a/src/ExchangeSharp/API/Exchanges/FTX/ExchangeFTXAPI.cs +++ b/src/ExchangeSharp/API/Exchanges/FTX/ExchangeFTXAPI.cs @@ -290,7 +290,14 @@ protected async override Task OnGetOrderDetailsAsync(string { // https://docs.ftx.com/#get-order-status - JToken result = await MakeJsonRequestAsync($"/orders/{orderId}", null, await GetNoncePayloadAsync()); + var url = "/orders/"; + + if (isClientOrderId) + { + url += "by_client_id/"; + } + + JToken result = await MakeJsonRequestAsync($"{url}{orderId}", null, await GetNoncePayloadAsync()); return ParseOrder(result); } @@ -383,7 +390,8 @@ protected async override Task OnPlaceOrderAsync(ExchangeOrd {"market", market.MarketSymbol}, {"side", order.IsBuy ? "buy" : "sell" }, {"type", order.OrderType.ToStringLowerInvariant() }, - {"size", order.RoundAmount() }, + {"size", order.RoundAmount() }, + {"postOnly", order.IsPostOnly } }; if (!string.IsNullOrEmpty(order.ClientOrderId)) diff --git a/src/ExchangeSharp/API/Exchanges/GateIo/ExchangeGateIoAPI.cs b/src/ExchangeSharp/API/Exchanges/GateIo/ExchangeGateIoAPI.cs index 5783e977..cc3319fd 100644 --- a/src/ExchangeSharp/API/Exchanges/GateIo/ExchangeGateIoAPI.cs +++ b/src/ExchangeSharp/API/Exchanges/GateIo/ExchangeGateIoAPI.cs @@ -166,6 +166,7 @@ private ExchangeTicker ParseTicker(JToken tickerToken) return new ExchangeTicker { + Exchange = Name, MarketSymbol = tickerToken["currency_pair"].ToStringInvariant(), Bid = IsEmptyString(tickerToken["lowest_ask"]) ? default : tickerToken["lowest_ask"].ConvertInvariant(), Ask = IsEmptyString(tickerToken["highest_bid"]) ? default : tickerToken["highest_bid"].ConvertInvariant(), diff --git a/src/ExchangeSharp/API/Exchanges/Gemini/ExchangeGeminiAPI.cs b/src/ExchangeSharp/API/Exchanges/Gemini/ExchangeGeminiAPI.cs index a3d5a2c9..d000e57a 100644 --- a/src/ExchangeSharp/API/Exchanges/Gemini/ExchangeGeminiAPI.cs +++ b/src/ExchangeSharp/API/Exchanges/Gemini/ExchangeGeminiAPI.cs @@ -234,6 +234,7 @@ protected override async Task OnGetTickerAsync(string marketSymb } ExchangeTicker t = new ExchangeTicker { + Exchange = Name, MarketSymbol = marketSymbol, ApiResponse = obj, Ask = obj["ask"].ConvertInvariant(), @@ -375,6 +376,7 @@ static ExchangeTicker GetTicker(ConcurrentDictionary tic (string baseCurrency, string quoteCurrency) = api.ExchangeMarketSymbolToCurrenciesAsync(_marketSymbol).Sync(); return new ExchangeTicker { + Exchange = Name, MarketSymbol = _marketSymbol, Volume = new ExchangeVolume { diff --git a/src/ExchangeSharp/API/Exchanges/LBank/ExchangeLBankAPI.cs b/src/ExchangeSharp/API/Exchanges/LBank/ExchangeLBankAPI.cs index 5936860e..ae82dc70 100644 --- a/src/ExchangeSharp/API/Exchanges/LBank/ExchangeLBankAPI.cs +++ b/src/ExchangeSharp/API/Exchanges/LBank/ExchangeLBankAPI.cs @@ -211,6 +211,7 @@ private ExchangeTicker ParseTicker(JToken resp) ExchangeTicker ticker = new ExchangeTicker { + Exchange = Name, MarketSymbol = symbol, ApiResponse = obj, Ask = obj["high"].ConvertInvariant(), diff --git a/src/ExchangeSharp/API/Exchanges/NDAX/ExchangeNDAXAPI.cs b/src/ExchangeSharp/API/Exchanges/NDAX/ExchangeNDAXAPI.cs index b8547931..b6e7c0a7 100644 --- a/src/ExchangeSharp/API/Exchanges/NDAX/ExchangeNDAXAPI.cs +++ b/src/ExchangeSharp/API/Exchanges/NDAX/ExchangeNDAXAPI.cs @@ -33,7 +33,7 @@ protected override async Task>> await MakeJsonRequestAsync>("ticker", "https://core.ndax.io/v1", null, "GET"); _marketSymbolToInstrumentIdMapping = result.ToDictionary(pair => pair.Key.Replace("_", ""), pair => pair.Value.Id); // remove the _ return result.Select(pair => - new KeyValuePair(pair.Key, pair.Value.ToExchangeTicker(pair.Key))); + new KeyValuePair(pair.Key, pair.Value.ToExchangeTicker(Name, pair.Key))); } protected override async Task OnGetTickerAsync(string symbol) @@ -395,7 +395,7 @@ protected override async Task OnGetTickersWebSocketAsync(Action(symbol, rawPayload.ToExchangeTicker(symbol)), + new KeyValuePair(symbol, rawPayload.ToExchangeTicker(Name, symbol)), }); } else // "{\"result\":false,\"errormsg\":\"Resource Not Found\",\"errorcode\":104,\"detail\":\"Instrument not Found\"}" diff --git a/src/ExchangeSharp/API/Exchanges/NDAX/Models/Level1Data.cs b/src/ExchangeSharp/API/Exchanges/NDAX/Models/Level1Data.cs index 81881afa..747815d7 100644 --- a/src/ExchangeSharp/API/Exchanges/NDAX/Models/Level1Data.cs +++ b/src/ExchangeSharp/API/Exchanges/NDAX/Models/Level1Data.cs @@ -67,11 +67,12 @@ private class Level1Data [JsonProperty("TimeStamp")] public string TimeStamp { get; set; } - public ExchangeTicker ToExchangeTicker(string currencyPair) + public ExchangeTicker ToExchangeTicker(string exchangeName, string currencyPair) { var currencyParts = currencyPair.Split(new[] { "_" }, StringSplitOptions.RemoveEmptyEntries); return new ExchangeTicker() { + Exchange = exchangeName, Bid = BestBid.GetValueOrDefault(), Ask = BestOffer.GetValueOrDefault(), Id = InstrumentId.ToString(), diff --git a/src/ExchangeSharp/API/Exchanges/NDAX/Models/NDAXTicker.cs b/src/ExchangeSharp/API/Exchanges/NDAX/Models/NDAXTicker.cs index 36873883..5fea6a0d 100644 --- a/src/ExchangeSharp/API/Exchanges/NDAX/Models/NDAXTicker.cs +++ b/src/ExchangeSharp/API/Exchanges/NDAX/Models/NDAXTicker.cs @@ -21,11 +21,12 @@ private class NDAXTicker [JsonProperty("baseVolume")] public decimal? BaseVolume { get; set; } [JsonProperty("quoteVolume")] public decimal? QuoteVolume { get; set; } - public ExchangeTicker ToExchangeTicker(string currencyPair) + public ExchangeTicker ToExchangeTicker(string exchangeName, string currencyPair) { var currencyParts = currencyPair.Split(new[] { "_" }, StringSplitOptions.RemoveEmptyEntries); return new ExchangeTicker() { + Exchange = exchangeName, MarketSymbol = currencyPair, Ask = LowestAsk.GetValueOrDefault(), Bid = HighestBid.GetValueOrDefault(), diff --git a/src/ExchangeSharp/API/Exchanges/UfoDex/ExchangeUfoDexAPI.cs b/src/ExchangeSharp/API/Exchanges/UfoDex/ExchangeUfoDexAPI.cs index 0bee1f72..95eef760 100644 --- a/src/ExchangeSharp/API/Exchanges/UfoDex/ExchangeUfoDexAPI.cs +++ b/src/ExchangeSharp/API/Exchanges/UfoDex/ExchangeUfoDexAPI.cs @@ -33,6 +33,7 @@ private ExchangeTicker ParseTicker(JToken token) // TODO: Parse out fields... // Ticker JSON { "GenTime":12345678901234 "Label":"UFO/BTC", "Ask":0.00000005, "Bid":0.00000003, "Open":0.00000006, "High":0.00000007, "Low":0.00000004, "Close":0.00000003, "Volume":3240956.04453450, "BaseVolume":455533325.98457433 } Id = token["GenTime"].ConvertInvariant(), // ???? + Exchange = Name, ApiResponse = token, Ask = token["Ask"].ConvertInvariant(), Bid = token["Bid"].ConvertInvariant(), diff --git a/src/ExchangeSharp/API/Exchanges/_Base/ExchangeLogger.cs b/src/ExchangeSharp/API/Exchanges/_Base/ExchangeLogger.cs index 87dac80b..ab956eb7 100644 --- a/src/ExchangeSharp/API/Exchanges/_Base/ExchangeLogger.cs +++ b/src/ExchangeSharp/API/Exchanges/_Base/ExchangeLogger.cs @@ -1,4 +1,4 @@ -/* +/* MIT LICENSE Copyright 2017 Digital Ruby, LLC - http://www.digitalruby.com diff --git a/src/ExchangeSharp/ExchangeSharp.csproj b/src/ExchangeSharp/ExchangeSharp.csproj index a38ffd88..49354715 100644 --- a/src/ExchangeSharp/ExchangeSharp.csproj +++ b/src/ExchangeSharp/ExchangeSharp.csproj @@ -22,13 +22,7 @@ git true - - - - - - - + From 5b12343b67bbdcf9560b9cb459d4e88f301add69 Mon Sep 17 00:00:00 2001 From: Josh Plumbly Date: Tue, 28 Sep 2021 20:36:06 +0100 Subject: [PATCH 29/29] Update ExchangeGeminiAPI.cs Fixed issue with reference to the exchange name in Gemini when creating a ticker instance. --- src/ExchangeSharp/API/Exchanges/Gemini/ExchangeGeminiAPI.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ExchangeSharp/API/Exchanges/Gemini/ExchangeGeminiAPI.cs b/src/ExchangeSharp/API/Exchanges/Gemini/ExchangeGeminiAPI.cs index d000e57a..6c74f597 100644 --- a/src/ExchangeSharp/API/Exchanges/Gemini/ExchangeGeminiAPI.cs +++ b/src/ExchangeSharp/API/Exchanges/Gemini/ExchangeGeminiAPI.cs @@ -376,7 +376,7 @@ static ExchangeTicker GetTicker(ConcurrentDictionary tic (string baseCurrency, string quoteCurrency) = api.ExchangeMarketSymbolToCurrenciesAsync(_marketSymbol).Sync(); return new ExchangeTicker { - Exchange = Name, + Exchange = api.Name, MarketSymbol = _marketSymbol, Volume = new ExchangeVolume {