From d03e7d5438a016190eb76b2e3f2de837a7f83ebb Mon Sep 17 00:00:00 2001 From: JKorf Date: Sat, 2 Dec 2023 09:41:45 +0100 Subject: [PATCH 01/25] wip --- Bitfinex.Net/Bitfinex.Net.csproj | 4 +- Bitfinex.Net/Bitfinex.Net.xml | 318 +-- .../SpotApi/BitfinexSocketClientSpotApi.cs | 1805 +++++++++-------- .../SpotApi/IBitfinexSocketClientSpotApi.cs | 437 ++-- .../Objects/Sockets/BitfinexRequest.cs | 17 + .../Objects/Sockets/BitfinexResponse.cs | 21 + .../Sockets/BitfinexSocketConverter.cs | 34 + .../Objects/Sockets/BitfinexUpdate.cs | 17 + .../Objects/Sockets/Queries/BitfinexQuery.cs | 21 + .../Subscriptions/BitfinexSubscription.cs | 55 + .../BitfinexSymbolOrderBook.cs | 28 +- 11 files changed, 1312 insertions(+), 1445 deletions(-) create mode 100644 Bitfinex.Net/Objects/Sockets/BitfinexRequest.cs create mode 100644 Bitfinex.Net/Objects/Sockets/BitfinexResponse.cs create mode 100644 Bitfinex.Net/Objects/Sockets/BitfinexSocketConverter.cs create mode 100644 Bitfinex.Net/Objects/Sockets/BitfinexUpdate.cs create mode 100644 Bitfinex.Net/Objects/Sockets/Queries/BitfinexQuery.cs create mode 100644 Bitfinex.Net/Objects/Sockets/Subscriptions/BitfinexSubscription.cs diff --git a/Bitfinex.Net/Bitfinex.Net.csproj b/Bitfinex.Net/Bitfinex.Net.csproj index 395f2bc..fba7ee1 100644 --- a/Bitfinex.Net/Bitfinex.Net.csproj +++ b/Bitfinex.Net/Bitfinex.Net.csproj @@ -51,10 +51,12 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - all runtime; build; native; contentfiles; analyzers; buildtransitive + + + \ No newline at end of file diff --git a/Bitfinex.Net/Bitfinex.Net.xml b/Bitfinex.Net/Bitfinex.Net.xml index 6655e53..a0a11e7 100644 --- a/Bitfinex.Net/Bitfinex.Net.xml +++ b/Bitfinex.Net/Bitfinex.Net.xml @@ -494,88 +494,7 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + @@ -2460,7 +2379,7 @@ Bitfinex spot streams - + Subscribes to ticker updates for a symbol. Use SubscribeToFundingTickerUpdatesAsync for funding symbol ticker updates @@ -2470,237 +2389,6 @@ Cancellation token for closing this subscription - - - Subscribes to funding ticker updates a symbol. Use SubscribeToTickerUpdatesAsync for trade symbol ticker updates - - - The symbol to subscribe to - The handler for the data - Cancellation token for closing this subscription - - - - - Subscribes to order book updates for a symbol. Use SubscribeToFundingOrderBookUpdatesAsync for funding symbol ticker updates - - - The symbol to subscribe to - The precision of the updates - The frequency of updates - The range for the order book updates, either 25 or 100 - The handler for the data - The handler for the checksum, can be used to validate a order book implementation - Cancellation token for closing this subscription - - - - - Subscribes to funding order book updates for a symbol. Use SubscribeToOrderBookUpdatesAsync for trade symbol ticker updates - - - The symbol to subscribe to - The precision of the updates - The frequency of updates - The range for the order book updates, either 25 or 100 - The handler for the data - The handler for the checksum, can be used to validate a order book implementation - Cancellation token for closing this subscription - - - - - Subscribes to raw order book updates for a symbol. Use SubscribeToRawFundingOrderBookUpdatesAsync for funding symbol ticker updates - - - The symbol to subscribe to - The range for the order book updates - The handler for the data - The handler for the checksum, can be used to validate a order book implementation - Cancellation token for closing this subscription - - - - - Subscribes to raw order book updates for a symbol. Use SubscribeToRawOrderBookUpdatesAsync for trade symbol ticker updates - - - The symbol to subscribe to - The range for the order book updates - The handler for the data - The handler for the checksum, can be used to validate a order book implementation - Cancellation token for closing this subscription - - - - - Subscribes to public trade updates for a symbol - - - The symbol to subscribe to - The handler for the data - Cancellation token for closing this subscription - - - - - Subscribes to kline updates for a symbol - - - The symbol to subscribe to. For funding klines use {symbol}:p{period}, for example fUSD:p30 - The interval of the klines - The handler for the data - Cancellation token for closing this subscription - - - - - Subscribe to liquidation updates - - - The handler for the data - Cancellation token for closing this subscription - - - - - Subscribe to derivatives status updates - - - The derivatives symbol, tBTCF0:USTF0 for example - The handler for the data - Cancellation token for closing this subscription - - - - - Subscribe to trading information updates - - - Data handler for order updates. Can be null if not interested - Data handler for trade execution updates. Can be null if not interested - Data handler for position updates. Can be null if not interested - Cancellation token for closing this subscription - - - - - Subscribe to wallet information updates - - - Data handler for wallet updates - Cancellation token for closing this subscription - - - - - Subscribe to funding information updates - - - - - Subscribe to funding offer updates. Can be null if not interested - Subscribe to funding credit updates. Can be null if not interested - Subscribe to funding loan updates. Can be null if not interested - Cancellation token for closing this subscription - - - - - Places a new order - - - The order side - The type of the order - The symbol the order is for - The quantity of the order, positive for buying, negative for selling - Group id to assign to the order - Client order id to assign to the order - Price of the order - Trailing price of the order - Auxiliary limit price of the order - Oco stop price of the order - Additional flags - Leverage - Automatically cancel the order after this time - Affiliate code for the order - - - - - Updates an order - - - The id of the order to update - The new price of the order - The new quantity of the order - The delta to change - the new aux limit price - The new trailing price - The new flags - - - - - Cancels an order - - - The id of the order to cancel - - - - - Cancels multiple orders based on their groupId - - - The group id to cancel - True if successfully committed on server - - - - Cancels multiple orders based on their groupIds - - - The group ids to cancel - True if successfully committed on server - - - - Cancels multiple orders based on their order ids - - - The order ids to cancel - True if successfully committed on server - - - - Cancels multiple orders based on their clientOrderIds - - - The client order ids to cancel, listed as (clientOrderId, Day) pair. ClientOrderIds are unique per day, so timestamp should be provided - True if successfully committed on server - - - - Submit a new funding offer - - - Offer type - Symbol - Amount (more than 0 for offer, less than 0 for bid) - Rate (or offset for FRRDELTA offers) - Time period of offer. Minimum 2 days. Maximum 120 days. - Flags - - - - - Cancel a funding offer - - - Id of the offer to cancel - - Bitfinex order book factory @@ -5453,7 +5141,7 @@ - + Process a received checksum diff --git a/Bitfinex.Net/Clients/SpotApi/BitfinexSocketClientSpotApi.cs b/Bitfinex.Net/Clients/SpotApi/BitfinexSocketClientSpotApi.cs index 4c0f1ba..7595ea4 100644 --- a/Bitfinex.Net/Clients/SpotApi/BitfinexSocketClientSpotApi.cs +++ b/Bitfinex.Net/Clients/SpotApi/BitfinexSocketClientSpotApi.cs @@ -19,6 +19,9 @@ using Bitfinex.Net.Interfaces.Clients.SpotApi; using Bitfinex.Net.Objects.Options; using CryptoExchange.Net.Converters; +using CryptoExchange.Net.Objects.Sockets; +using Bitfinex.Net.Objects.Sockets; +using Bitfinex.Net.Objects.Sockets.Subscriptions; namespace Bitfinex.Net.Clients.SpotApi { @@ -33,6 +36,8 @@ public class BitfinexSocketClientSpotApi : SocketApiClient, IBitfinexSocketClien /// public new BitfinexSocketOptions ClientOptions => (BitfinexSocketOptions)base.ClientOptions; + + public override SocketConverter StreamConverter => new BitfinexSocketConverter(); #endregion #region ctor @@ -42,9 +47,9 @@ internal BitfinexSocketClientSpotApi(ILogger logger, BitfinexSocketOptions optio ContinueOnQueryResponse = true; UnhandledMessageExpected = true; - AddGenericHandler("HB", (messageEvent) => { }); - AddGenericHandler("Info", InfoHandler); - AddGenericHandler("Conf", ConfHandler); + //AddGenericHandler("HB", (messageEvent) => { }); + //AddGenericHandler("Info", InfoHandler); + //AddGenericHandler("Conf", ConfHandler); _affCode = options.AffiliateCode; _bookSerializer.Converters.Add(new OrderBookEntryConverter()); @@ -55,906 +60,910 @@ internal BitfinexSocketClientSpotApi(ILogger logger, BitfinexSocketOptions optio protected override AuthenticationProvider CreateAuthenticationProvider(ApiCredentials credentials) => new BitfinexAuthenticationProvider(credentials, ClientOptions.NonceProvider ?? new BitfinexNonceProvider()); - #region public methods + //#region public methods /// public async Task> SubscribeToTickerUpdatesAsync(string symbol, Action> handler, CancellationToken ct = default) { symbol.ValidateBitfinexTradingSymbol(); - var internalHandler = new Action>(data => - { - HandleData("Ticker", (JArray)data.Data[1]!, symbol, data, handler); - }); - return await SubscribeAsync(BaseAddress.AppendPath("ws/2"), new BitfinexSubscriptionRequest("ticker", symbol), null, false, internalHandler, ct).ConfigureAwait(false); - } - - /// - public async Task> SubscribeToFundingTickerUpdatesAsync(string symbol, Action> handler, CancellationToken ct = default) - { - symbol.ValidateBitfinexFundingSymbol(); - var internalHandler = new Action>(data => - { - HandleData("Ticker", (JArray)data.Data[1]!, symbol, data, handler); - }); - return await SubscribeAsync(BaseAddress.AppendPath("ws/2"), new BitfinexSubscriptionRequest("ticker", symbol), null, false, internalHandler, ct).ConfigureAwait(false); - } - - /// - public async Task> SubscribeToOrderBookUpdatesAsync(string symbol, Precision precision, Frequency frequency, int length, Action>> handler, Action>? checksumHandler = null, CancellationToken ct = default) - { - symbol.ValidateBitfinexTradingSymbol(); - length.ValidateIntValues(nameof(length), 1, 25, 100, 250); - if (precision == Precision.R0) - throw new ArgumentException("Invalid precision R0, use SubscribeToRawBookUpdatesAsync instead"); - - var internalHandler = new Action>(data => - { - if (data.Data[1]?.ToString() == "cs") - { - // Process - checksumHandler?.Invoke(data.As(data.Data[2]!.Value(), symbol)); - } - else - { - var dataArray = (JArray)data.Data[1]!; - if (dataArray.Count == 0) - // Empty array - return; - - if (dataArray[0].Type == JTokenType.Array) - { - HandleData("Book snapshot", dataArray, symbol, data, handler, _bookSerializer); - } - else - { - HandleSingleToArrayData("Book update", dataArray, symbol, data, handler, _bookSerializer); - } - } - }); - - var sub = new BitfinexBookSubscriptionRequest( - symbol, - JsonConvert.SerializeObject(precision, new PrecisionConverter(false)), - JsonConvert.SerializeObject(frequency, new FrequencyConverter(false)), - length); - return await SubscribeAsync(BaseAddress.AppendPath("ws/2"), sub, null, false, internalHandler, ct).ConfigureAwait(false); - } - - /// - public async Task> SubscribeToFundingOrderBookUpdatesAsync(string symbol, Precision precision, Frequency frequency, int length, Action>> handler, Action>? checksumHandler = null, CancellationToken ct = default) - { - symbol.ValidateBitfinexFundingSymbol(); - length.ValidateIntValues(nameof(length), 1, 25, 100, 250); - if (precision == Precision.R0) - throw new ArgumentException("Invalid precision R0, use SubscribeToFundingRawOrderBookUpdatesAsync instead"); - - var internalHandler = new Action>(data => - { - if (data.Data[1]?.ToString() == "cs") - { - // Process - checksumHandler?.Invoke(data.As(data.Data[2]!.Value(), symbol)); - } - else - { - var dataArray = (JArray)data.Data[1]!; - if (dataArray.Count == 0) - // Empty array - return; - - if (dataArray[0].Type == JTokenType.Array) - { - HandleData("Book snapshot", dataArray, symbol, data, handler, _fundingBookSerializer); - } - else - { - HandleSingleToArrayData("Book update", dataArray, symbol, data, handler, _fundingBookSerializer); - } - } - }); - - var sub = new BitfinexBookSubscriptionRequest( - symbol, - JsonConvert.SerializeObject(precision, new PrecisionConverter(false)), - JsonConvert.SerializeObject(frequency, new FrequencyConverter(false)), - length); - return await SubscribeAsync(BaseAddress.AppendPath("ws/2"), sub, null, false, internalHandler, ct).ConfigureAwait(false); - } - - /// - public async Task> SubscribeToRawOrderBookUpdatesAsync(string symbol, int limit, Action>> handler, Action>? checksumHandler = null, CancellationToken ct = default) - { - symbol.ValidateBitfinexTradingSymbol(); - var internalHandler = new Action>(data => - { - if (data.Data[1]?.ToString() == "cs") - { - // Process - checksumHandler?.Invoke(data.As(data.Data[2]!.Value(), symbol)); - } - else - { - var dataArray = (JArray)data.Data[1]!; - if (dataArray[0].Type == JTokenType.Array) - HandleData("Raw book snapshot", dataArray, symbol, data, handler); - else - HandleSingleToArrayData("Raw book update", dataArray, symbol, data, handler); - } - }); - return await SubscribeAsync(BaseAddress.AppendPath("ws/2"), new BitfinexRawBookSubscriptionRequest(symbol, "R0", limit), null, false, internalHandler, ct).ConfigureAwait(false); - } - - /// - public async Task> SubscribeToRawFundingOrderBookUpdatesAsync(string symbol, int limit, Action>> handler, Action>? checksumHandler = null, CancellationToken ct = default) - { - symbol.ValidateBitfinexFundingSymbol(); - var internalHandler = new Action>(data => - { - if (data.Data[1]?.ToString() == "cs") - { - // Process - checksumHandler?.Invoke(data.As(data.Data[2]!.Value(), symbol)); - } - else - { - var dataArray = (JArray)data.Data[1]!; - if (dataArray[0].Type == JTokenType.Array) - HandleData("Raw book snapshot", dataArray, symbol, data, handler); - else - HandleSingleToArrayData("Raw book update", dataArray, symbol, data, handler); - } - }); - return await SubscribeAsync(BaseAddress.AppendPath("ws/2"), new BitfinexRawBookSubscriptionRequest(symbol, "R0", limit), null, false, internalHandler, ct).ConfigureAwait(false); - } - - /// - public async Task> SubscribeToTradeUpdatesAsync(string symbol, Action>> handler, CancellationToken ct = default) - { - var internalHandler = new Action>(data => - { - var arr = (JArray)data.Data; - if (arr[1].Type == JTokenType.Array) - { - HandleData("Trade snapshot", (JArray)arr[1], symbol, data, handler); - } - else - { - var desResult = Deserialize(arr[2]); - if (!desResult) - { - _logger.Log(LogLevel.Warning, "Failed to deserialize trade object: " + desResult.Error); - return; - } - desResult.Data.UpdateType = BitfinexEvents.EventMapping[arr[1].ToString()]; - handler(data.As>(new[] { desResult.Data }, symbol)); - } - }); - return await SubscribeAsync(BaseAddress.AppendPath("ws/2"), new BitfinexSubscriptionRequest("trades", symbol), null, false, internalHandler, ct).ConfigureAwait(false); - } - - /// - public async Task> SubscribeToKlineUpdatesAsync(string symbol, KlineInterval interval, Action>> handler, CancellationToken ct = default) - { - var internalHandler = new Action>(data => - { - var dataArray = (JArray)data.Data[1]!; - if (dataArray.Count == 0) - { - _logger.Log(LogLevel.Warning, "No data in kline update, check if the symbol is correct"); - return; - } - - if (dataArray[0].Type == JTokenType.Array) - HandleData("Kline snapshot", dataArray, symbol, data, handler); - else - HandleSingleToArrayData("Kline update", dataArray, symbol, data, handler); - }); - return await SubscribeAsync(BaseAddress.AppendPath("ws/2"), new BitfinexKlineSubscriptionRequest(symbol, JsonConvert.SerializeObject(interval, new KlineIntervalConverter(false))), null, false, internalHandler, ct).ConfigureAwait(false); - } - - /// - public async Task> SubscribeToLiquidationUpdatesAsync(Action>> handler, CancellationToken ct = default) - { - var internalHandler = new Action>(data => - { - HandleData("Liquidation", (JArray)data.Data[1]!, null, data, handler); - }); - return await SubscribeAsync(BaseAddress.AppendPath("ws/2"), new BitfinexLiquidationSubscriptionRequest(), null, false, internalHandler, ct).ConfigureAwait(false); - } - - /// - public async Task> SubscribeToDerivativesUpdatesAsync(string symbol, Action> handler, CancellationToken ct = default) - { - var internalHandler = new Action>(data => - { - HandleData("DerivativeUpdate", (JArray)data.Data[1]!, symbol, data, handler); - }); - return await SubscribeAsync(BaseAddress.AppendPath("ws/2"), new BitfinexDerivativesStatusSubscriptionRequest(symbol), null, false, internalHandler, ct).ConfigureAwait(false); - } - - /// - public async Task> SubscribeToUserTradeUpdatesAsync( - Action>>> orderHandler, - Action>>> tradeHandler, - Action>>> positionHandler, - CancellationToken ct = default) - { - var tokenHandler = new Action>(tokenData => - { - HandleAuthUpdate(tokenData, orderHandler, "Orders"); - HandleAuthUpdate(tokenData, tradeHandler, "Trades"); - HandleAuthUpdate(tokenData, positionHandler, "Positions"); - }); - - return await SubscribeAsync(BaseAddress.AppendPath("ws/2"), null, "Orders|Trades|Positions", true, tokenHandler, ct).ConfigureAwait(false); - } - - /// - public async Task> SubscribeToBalanceUpdatesAsync(Action>>> walletHandler, CancellationToken ct = default) - { - var tokenHandler = new Action>(tokenData => - { - HandleAuthUpdate(tokenData, walletHandler, "Wallet"); - }); - - return await SubscribeAsync(BaseAddress.AppendPath("ws/2"), null, "Wallet", true, tokenHandler, ct).ConfigureAwait(false); - } - - /// - public async Task> SubscribeToFundingUpdatesAsync( - Action>>> fundingOfferHandler, - Action>>> fundingCreditHandler, - Action>>> fundingLoanHandler, - CancellationToken ct = default) - { - var tokenHandler = new Action>(tokenData => - { - HandleAuthUpdate(tokenData, fundingOfferHandler, "FundingOffers"); - HandleAuthUpdate(tokenData, fundingCreditHandler, "FundingCredits"); - HandleAuthUpdate(tokenData, fundingLoanHandler, "FundingLoans"); - }); - - return await SubscribeAsync(BaseAddress.AppendPath("ws/2"), null, "FundingOffers|FundingCredits|FundingLoans", true, tokenHandler, ct).ConfigureAwait(false); - } - - /// - public async Task> PlaceOrderAsync(OrderSide side, OrderType type, string symbol, decimal quantity, long? groupId = null, long? clientOrderId = null, decimal? price = null, decimal? priceTrailing = null, decimal? priceAuxiliaryLimit = null, decimal? priceOcoStop = null, OrderFlags? flags = null, int? leverage = null, DateTime? cancelTime = null, string? affiliateCode = null) - { - symbol.ValidateBitfinexSymbol(); - _logger.Log(LogLevel.Information, "Going to place order"); - clientOrderId ??= GenerateClientOrderId(); - - var affCode = affiliateCode ?? _affCode; - var query = new BitfinexSocketQuery(clientOrderId.ToString(), BitfinexEventType.OrderNew, new BitfinexNewOrder - { - Amount = side == OrderSide.Buy ? quantity : -quantity, - OrderType = type, - Symbol = symbol, - Price = price, - ClientOrderId = clientOrderId, - Flags = flags, - GroupId = groupId, - PriceAuxiliaryLimit = priceAuxiliaryLimit, - PriceOCOStop = priceOcoStop, - PriceTrailing = priceTrailing, - Leverage = leverage, - CancelAfter = cancelTime, - Meta = affCode == null ? null : new BitfinexMeta() { AffiliateCode = affCode } - }); - - return await QueryAsync(BaseAddress.AppendPath("ws/2"), query, true).ConfigureAwait(false); - } - /// - public async Task> UpdateOrderAsync(long orderId, decimal? price = null, decimal? quantity = null, decimal? delta = null, decimal? priceAuxiliaryLimit = null, decimal? priceTrailing = null, OrderFlags? flags = null) - { - _logger.Log(LogLevel.Information, "Going to update order " + orderId); - var query = new BitfinexSocketQuery(orderId.ToString(CultureInfo.InvariantCulture), BitfinexEventType.OrderUpdate, new BitfinexUpdateOrder - { - OrderId = orderId, - Amount = quantity, - Price = price, - Flags = flags, - PriceAuxiliaryLimit = priceAuxiliaryLimit?.ToString(CultureInfo.InvariantCulture), - PriceTrailing = priceTrailing?.ToString(CultureInfo.InvariantCulture) - }); - - return await QueryAsync(BaseAddress.AppendPath("ws/2"), query, true).ConfigureAwait(false); - } - - ///// - ///// Cancel all open orders - ///// - ///// - //public CallResult CancelAllOrders() => CancelAllOrdersAsync().Result; - ///// - ///// Cancel all open orders - ///// - ///// - //public async Task> CancelAllOrdersAsync() - //{ - // // Doesn't seem to work even though it is implemented as described at https://docs.bitfinex.com/v2/reference#ws-input-order-cancel-multi - // _log.Write(LogLevel.Information, "Going to cancel all orders"); - // var query = new BitfinexSocketQuery(null, BitfinexEventType.OrderCancelMulti, new BitfinexMultiCancel { All = true }); - - // return await Query(query, true).ConfigureAwait(false); - //} - - /// - public async Task> CancelOrderAsync(long orderId) - { - _logger.Log(LogLevel.Information, "Going to cancel order " + orderId); - var query = new BitfinexSocketQuery(orderId.ToString(CultureInfo.InvariantCulture), BitfinexEventType.OrderCancel, new JObject { ["id"] = orderId }); - - return await QueryAsync(BaseAddress.AppendPath("ws/2"), query, true).ConfigureAwait(false); - } - - /// - public async Task> CancelOrdersByGroupIdAsync(long groupOrderId) - { - return await CancelOrdersAsync(null, null, new Dictionary { { groupOrderId, null } }).ConfigureAwait(false); - } - - /// - public async Task> CancelOrdersByGroupIdsAsync(IEnumerable groupOrderIds) - { - groupOrderIds.ValidateNotNull(nameof(groupOrderIds)); - return await CancelOrdersAsync(null, null, groupOrderIds.ToDictionary(v => v, k => (long?)null)).ConfigureAwait(false); - } - - /// - public async Task> CancelOrdersAsync(IEnumerable orderIds) - { - orderIds.ValidateNotNull(nameof(orderIds)); - return await CancelOrdersAsync(orderIds, null).ConfigureAwait(false); - } - - /// - public async Task> CancelOrdersByClientOrderIdsAsync(Dictionary clientOrderIds) - { - return await CancelOrdersAsync(null, clientOrderIds).ConfigureAwait(false); - } - - /// - public async Task> SubmitFundingOfferAsync(FundingOfferType type, string symbol, decimal quantity, decimal price, int period, int? flags = null) - { - var parameters = new Dictionary - { - { "type", EnumConverter.GetString(type) }, - { "symbol", symbol }, - { "amount", quantity }, - { "rate", price }, - { "period", period }, - }; - parameters.AddOptionalParameter("flags", flags); - - var query = new BitfinexSocketQuery(ExchangeHelpers.NextId().ToString(CultureInfo.InvariantCulture), BitfinexEventType.FundingOfferNew, parameters); - return await QueryAsync(BaseAddress.AppendPath("ws/2"), query, true).ConfigureAwait(false); - } - - /// - public async Task> CancelFundingOfferAsync(long id) - { - var parameters = new Dictionary - { - { "id", id } - }; - - var query = new BitfinexSocketQuery(id.ToString(CultureInfo.InvariantCulture), BitfinexEventType.FundingOfferCancel, parameters); - return await QueryAsync(BaseAddress.AppendPath("ws/2"), query, true).ConfigureAwait(false); - } - #endregion - - #region private methods - private void HandleData(string name, JArray dataArray, string? symbol, DataEvent dataEvent, Action> handler, JsonSerializer? serializer = null) - { - var desResult = Deserialize(dataArray, serializer: serializer); - if (!desResult) - { - _logger.Log(LogLevel.Warning, $"Failed to Deserialize {name} object: " + desResult.Error); - return; - } - - handler(dataEvent.As(desResult.Data, symbol)); - } - - private void HandleSingleToArrayData(string name, JArray dataArray, string? symbol, DataEvent dataEvent, Action>> handler, JsonSerializer? serializer = null) - { - var wrapperArray = new JArray { dataArray }; - - var desResult = Deserialize>(wrapperArray, serializer: serializer); - if (!desResult) - { - _logger.Log(LogLevel.Warning, $"Failed to Deserialize {name} object: " + desResult.Error); - return; - } - - handler(dataEvent.As(desResult.Data, symbol)); - } - - private async Task> CancelOrdersAsync(IEnumerable? orderIds = null, Dictionary? clientOrderIds = null, Dictionary? groupOrderIds = null) - { - if (orderIds == null && clientOrderIds == null && groupOrderIds == null) - throw new ArgumentException("Either orderIds, clientOrderIds or groupOrderIds should be provided"); - - _logger.Log(LogLevel.Information, "Going to cancel multiple orders"); - var cancelObject = new BitfinexMultiCancel { OrderIds = orderIds }; - if (clientOrderIds != null) - { - cancelObject.ClientIds = new object[clientOrderIds.Count][]; - for (var i = 0; i < cancelObject.ClientIds.Length; i++) - { - cancelObject.ClientIds[i] = new object[] - { - clientOrderIds.ElementAt(i).Key, - clientOrderIds.ElementAt(i).Value.ToString("yyyy-MM-dd") - }; - } - } - if (groupOrderIds != null) - cancelObject.GroupIds = new[] { groupOrderIds.Select(g => g.Key).ToArray() }; - - var query = new BitfinexSocketQuery(null, BitfinexEventType.OrderCancelMulti, cancelObject); - return await QueryAsync(query, true).ConfigureAwait(false); - } - - private void HandleAuthUpdate(DataEvent token, Action>>> action, string category) - { - var evntStr = token.Data[1]?.ToString(); - if (evntStr == null) - return; - - var evntType = BitfinexEvents.EventMapping[evntStr]; - var evnt = BitfinexEvents.Events.Single(e => e.EventType == evntType); - if (evnt.Category != category) - return; - - if (action == null) - { - _logger.Log(LogLevel.Debug, $"Ignoring {evnt.EventType} event because not subscribed"); - return; - } - - IEnumerable data; - if (evnt.Single) - { - var result = Deserialize(token.Data[2]!); - if (!result) - { - _logger.Log(LogLevel.Warning, "Failed to Deserialize data: " + result.Error); - return; - } - data = new[] { result.Data }; - } - else - { - var result = Deserialize>(token.Data[2]!); - if (!result) - { - _logger.Log(LogLevel.Warning, "Failed to Deserialize data: " + result.Error); - return; - } - data = result.Data; - } - - action(token.As(new BitfinexSocketEvent>(evntType, data))); - } - - private long GenerateClientOrderId() - { - var buffer = new byte[8]; - _random.NextBytes(buffer); - return (long)Math.Round(Math.Abs(BitConverter.ToInt32(buffer, 0)) / 1000m); - } - - - private void ConfHandler(MessageEvent messageEvent) - { - var confEvent = messageEvent.JsonData.Type == JTokenType.Object && messageEvent.JsonData["event"]?.ToString() == "conf"; - if (!confEvent) - return; - - // Could check conf result; - } - - private void InfoHandler(MessageEvent messageEvent) - { - var infoEvent = messageEvent.JsonData.Type == JTokenType.Object && messageEvent.JsonData["event"]?.ToString() == "info"; - if (!infoEvent) - return; - - _logger.Log(LogLevel.Debug, $"Socket {messageEvent.Connection.SocketId} Info event received: {messageEvent.JsonData}"); - if (messageEvent.JsonData["code"] == null) - { - // welcome event, send a config message for receiving checsum updates for order book subscriptions - messageEvent.Connection.Send(ExchangeHelpers.NextId(), new BitfinexSocketConfig { Event = "conf", Flags = 131072 }, 1); - return; - } - - var code = messageEvent.JsonData["code"]?.Value(); - switch (code) - { - case 20051: - _logger.Log(LogLevel.Information, $"Socket {messageEvent.Connection.SocketId} Code {code} received, reconnecting socket"); - messageEvent.Connection.PausedActivity = true; // Prevent new operations to be send - _ = messageEvent.Connection.TriggerReconnectAsync(); - break; - case 20060: - _logger.Log(LogLevel.Information, $"Socket {messageEvent.Connection.SocketId} Code {code} received, entering maintenance mode"); - messageEvent.Connection.PausedActivity = true; - break; - case 20061: - _logger.Log(LogLevel.Information, $"Socket {messageEvent.Connection.SocketId} Code {code} received, leaving maintenance mode. Reconnecting/Resubscribing socket."); - _ = messageEvent.Connection.TriggerReconnectAsync(); // Closing it via socket will automatically reconnect - break; - default: - _logger.Log(LogLevel.Warning, $"Socket {messageEvent.Connection.SocketId} Unknown info code received: {code}"); - break; - } - } - - - /// - protected override async Task UnsubscribeAsync(SocketConnection connection, SocketSubscription subscription) - { - if (subscription.Request == null) - { - // If we don't have a request object we can't unsubscribe it. Probably is an auth subscription which gets pushed regardless - // Just returning true here will remove the handler and close the socket if there are no other handlers left on the socket, which is the best we can do - return true; - } - - var channelId = ((BitfinexSubscriptionRequest)subscription.Request!).ChannelId; - var unsub = new BitfinexUnsubscribeRequest(channelId); - var result = false; - await connection.SendAndWaitAsync(unsub, ClientOptions.RequestTimeout, null, 1, data => - { - if (data.Type != JTokenType.Object) - return false; - - var evnt = data["event"]?.ToString(); - var channel = data["chanId"]?.ToString(); - if (evnt == null || channel == null) - return false; - - if (!int.TryParse(channel, out var chan)) - return false; - - result = evnt == "unsubscribed" && channelId == chan; - return result; - }).ConfigureAwait(false); - return result; - } - - private static BitfinexAuthentication GetAuthObject(SocketApiClient apiClient, params string[] filter) - { - var authProvider = (BitfinexAuthenticationProvider)apiClient.AuthenticationProvider!; - var n = authProvider.GetNonce().ToString(); - var authentication = new BitfinexAuthentication - { - Event = "auth", - ApiKey = authProvider.GetApiKey(), - Nonce = n, - Payload = "AUTH" + n - }; - if (filter.Any()) - authentication.Filter = filter; - authentication.Signature = authProvider.Sign(authentication.Payload).ToLower(CultureInfo.InvariantCulture); - return authentication; - } - - #endregion - - /// - protected override async Task> AuthenticateSocketAsync(SocketConnection s) - { - if (s.ApiClient.AuthenticationProvider == null) - return new CallResult(new NoApiCredentialsError()); - - var authObject = GetAuthObject(s.ApiClient); - var result = new CallResult(new ServerError("No response from server")); - await s.SendAndWaitAsync(authObject, ClientOptions.RequestTimeout, null, 1, tokenData => - { - if (tokenData.Type != JTokenType.Object) - return false; - - if (tokenData["event"]?.ToString() != "auth") - return false; - - var authResponse = Deserialize(tokenData); - if (!authResponse) - { - _logger.Log(LogLevel.Warning, $"Socket {s.SocketId} authentication failed: " + authResponse.Error); - result = new CallResult(authResponse.Error!); - return false; - } - - if (authResponse.Data.Status != "OK") - { - var error = new ServerError(authResponse.Data.ErrorCode, authResponse.Data.ErrorMessage ?? "-"); - result = new CallResult(error); - _logger.Log(LogLevel.Debug, $"Socket {s.SocketId} authentication failed: " + error); - return false; - } - - _logger.Log(LogLevel.Debug, $"Socket {s.SocketId} authentication completed"); - result = new CallResult(true); - return true; - }).ConfigureAwait(false); - - return result; - } - - /// -#pragma warning disable 8765 - protected override bool HandleQueryResponse(SocketConnection s, object request, JToken data, out CallResult? callResult) -#pragma warning restore 8765 - { - callResult = null; - if (data.Type != JTokenType.Array) - return false; - - var array = (JArray)data; - if (array.Count < 3) - return false; - - var bfRequest = (BitfinexSocketQuery)request; - var evntString = data[1]!.ToString(); - if (!BitfinexEvents.EventMapping.TryGetValue(evntString, out var eventType)) - return false; - - if (eventType == BitfinexEventType.Notification) - { - var notificationData = (JArray)data[2]!; - var notificationType = BitfinexEvents.EventMapping[notificationData[1].ToString()]; - if (notificationType != BitfinexEventType.OrderNewRequest - && notificationType != BitfinexEventType.OrderCancelRequest - && notificationType != BitfinexEventType.OrderUpdateRequest - && notificationType != BitfinexEventType.OrderCancelMultiRequest - && notificationType != BitfinexEventType.FundingOfferNewRequest - && notificationType != BitfinexEventType.FundingOfferCancelRequest) - { - return false; - } - - var statusString = (notificationData[6].ToString()).ToLower(CultureInfo.InvariantCulture); - if (statusString == "error") - { - if (bfRequest.QueryType == BitfinexEventType.OrderNew && notificationType == BitfinexEventType.OrderNewRequest) - { - var orderData = notificationData[4]; - if (orderData[2]?.ToString() != bfRequest.Id) - return false; - - callResult = new CallResult(new ServerError(notificationData[7].ToString())); - return true; - } - - if (bfRequest.QueryType == BitfinexEventType.OrderCancel && notificationType == BitfinexEventType.OrderCancelRequest) - { - var orderData = notificationData[4]; - if (orderData[0]?.ToString() != bfRequest.Id) - return false; - - callResult = new CallResult(new ServerError(notificationData[7].ToString())); - return true; - } - - if (bfRequest.QueryType == BitfinexEventType.OrderUpdate && notificationType == BitfinexEventType.OrderUpdateRequest) - { - // OrderUpdateRequest not found notification doesn't carry the order id, where as OrderCancelRequest not found notification does.. - // Anyway, can't check for ids, so just assume its for this one - - callResult = new CallResult(new ServerError(notificationData[7].ToString())); - return true; - } - - if (bfRequest.QueryType == BitfinexEventType.OrderCancelMulti && notificationType == BitfinexEventType.OrderCancelMultiRequest) - { - callResult = new CallResult(new ServerError(notificationData[7].ToString())); - return true; - } - - if (bfRequest.QueryType == BitfinexEventType.FundingOfferNew && notificationType == BitfinexEventType.FundingOfferNewRequest) - { - callResult = new CallResult(new ServerError(notificationData[7].ToString())); - return true; - } - - if (bfRequest.QueryType == BitfinexEventType.FundingOfferCancel && notificationType == BitfinexEventType.FundingOfferCancelRequest) - { - var fundingData = notificationData[4]; - if (fundingData[0]?.ToString() != bfRequest.Id) - return false; - - callResult = new CallResult(new ServerError(notificationData[7].ToString())); - return true; - } - } - - if (notificationType == BitfinexEventType.OrderNewRequest - || notificationType == BitfinexEventType.OrderUpdateRequest - || notificationType == BitfinexEventType.OrderCancelRequest) - { - if (bfRequest.QueryType == BitfinexEventType.OrderNew - || bfRequest.QueryType == BitfinexEventType.OrderUpdate - || bfRequest.QueryType == BitfinexEventType.OrderCancel) - { - var orderData = notificationData[4]; - var dataOrderId = orderData[0]?.ToString(); - var dataOrderClientId = orderData[2]?.ToString(); - if (dataOrderId == bfRequest.Id || dataOrderClientId == bfRequest.Id) - { - var desResult = Deserialize(orderData); - if (!desResult) - { - callResult = new CallResult(desResult.Error!); - return true; - } - - callResult = new CallResult(desResult.Data); - return true; - } - } - } - - if (notificationType == BitfinexEventType.OrderCancelMultiRequest) - { - callResult = new CallResult(Deserialize(JToken.Parse("true")).Data); - return true; - } - - if (notificationType == BitfinexEventType.FundingOfferNewRequest - || notificationType == BitfinexEventType.FundingOfferCancelRequest) - { - if (bfRequest.QueryType == BitfinexEventType.FundingOfferCancelRequest) - { - var fundingData = notificationData[4]; - var dataOrderId = fundingData[0]?.ToString(); - if (dataOrderId == bfRequest.Id) - { - var desResult = Deserialize(fundingData); - if (!desResult) - { - callResult = new CallResult(desResult.Error!); - return true; - } - - callResult = new CallResult(desResult.Data); - return true; - } - } - else if(bfRequest.QueryType == BitfinexEventType.FundingOfferNew) - { - var fundingData = notificationData[4]; - var desResult = Deserialize(fundingData); - if (!desResult) - { - callResult = new CallResult(desResult.Error!); - return true; - } - - callResult = new CallResult(desResult.Data); - return true; - } - } - } - - if (bfRequest.QueryType == BitfinexEventType.OrderCancelMulti && eventType == BitfinexEventType.OrderCancel) - { - callResult = new CallResult(Deserialize(JToken.Parse("true")).Data); - return true; - } - - return false; - } - - /// - protected override bool HandleSubscriptionResponse(SocketConnection s, SocketSubscription subscription, object request, JToken data, out CallResult? callResult) - { - callResult = null; - if (data.Type != JTokenType.Object) - return false; - - var infoEvent = data["event"]?.ToString() == "subscribed"; - var errorEvent = data["event"]?.ToString() == "error"; - if (!infoEvent && !errorEvent) - return false; - - if (infoEvent) - { - var subResponse = Deserialize(data); - if (!subResponse) - { - callResult = new CallResult(subResponse.Error!); - _logger.Log(LogLevel.Warning, $"Socket {s.SocketId} subscription failed: " + subResponse.Error); - return false; - } - - var bRequest = (BitfinexSubscriptionRequest)request; - if (!bRequest.CheckResponse(data)) - return false; - - bRequest.ChannelId = subResponse.Data.ChannelId; - callResult = subResponse.As(subResponse.Data); - return true; - } - else - { - var subResponse = Deserialize(data); - if (!subResponse) - { - callResult = new CallResult(subResponse.Error!); - _logger.Log(LogLevel.Warning, $"Socket {s.SocketId} subscription failed: " + subResponse.Error); - return false; - } - - var error = new ServerError(subResponse.Data.Code, subResponse.Data.Message); - callResult = new CallResult(error); - _logger.Log(LogLevel.Debug, $"Socket {s.SocketId} subscription failed: " + error); - return true; - } - } - - /// - protected override bool MessageMatchesHandler(SocketConnection socketConnection, JToken message, object request) - { - if (message.Type != JTokenType.Array) - return false; - - var array = (JArray)message; - if (array.Count < 2) - return false; - - if (!int.TryParse(array[0].ToString(), out var channelId)) - return false; - - if (channelId == 0) - return false; - - var subId = ((BitfinexSubscriptionRequest)request).ChannelId; - return channelId == subId && array[1].ToString() != "hb"; - } - - /// - protected override bool MessageMatchesHandler(SocketConnection socketConnection, JToken message, string identifier) - { - if (message.Type == JTokenType.Object) - { - if (identifier == "Info") - return message["event"]?.ToString() == "info"; - if (identifier == "Conf") - return message["event"]?.ToString() == "conf"; - } - - else if (message.Type == JTokenType.Array) - { - var array = (JArray)message; - if (array.Count < 2) - return false; - - if (identifier == "HB") - return array[1].ToString() == "hb"; - - if (!int.TryParse(array[0].ToString(), out var channelId)) - return false; - - if (channelId != 0) - return false; - - var split = identifier.Split(new[] { "|" }, StringSplitOptions.RemoveEmptyEntries); - foreach (var id in split) - { - var events = BitfinexEvents.GetEventsForCategory(id); - var eventTypeString = array[1].ToString(); - var eventType = BitfinexEvents.EventMapping[eventTypeString]; - var evnt = events.SingleOrDefault(e => e.EventType == eventType); - if (evnt != null) - return true; - } - } - - return false; - } + var subscription = new BitfinexSubscription(_logger, "ticker", symbol, handler, false); + return await SubscribeAsync(BaseAddress.AppendPath("ws/2"), subscription, ct).ConfigureAwait(false); + + //var internalHandler = new Action>(data => + //{ + // HandleData("Ticker", (JArray)data.Data[1]!, symbol, data, handler); + //}); + //return await SubscribeAsync(BaseAddress.AppendPath("ws/2"), new BitfinexSubscriptionRequest("ticker", symbol), null, false, internalHandler, ct).ConfigureAwait(false); + } + +// /// +// public async Task> SubscribeToFundingTickerUpdatesAsync(string symbol, Action> handler, CancellationToken ct = default) +// { +// symbol.ValidateBitfinexFundingSymbol(); +// var internalHandler = new Action>(data => +// { +// HandleData("Ticker", (JArray)data.Data[1]!, symbol, data, handler); +// }); +// return await SubscribeAsync(BaseAddress.AppendPath("ws/2"), new BitfinexSubscriptionRequest("ticker", symbol), null, false, internalHandler, ct).ConfigureAwait(false); +// } + +// /// +// public async Task> SubscribeToOrderBookUpdatesAsync(string symbol, Precision precision, Frequency frequency, int length, Action>> handler, Action>? checksumHandler = null, CancellationToken ct = default) +// { +// symbol.ValidateBitfinexTradingSymbol(); +// length.ValidateIntValues(nameof(length), 1, 25, 100, 250); +// if (precision == Precision.R0) +// throw new ArgumentException("Invalid precision R0, use SubscribeToRawBookUpdatesAsync instead"); + +// var internalHandler = new Action>(data => +// { +// if (data.Data[1]?.ToString() == "cs") +// { +// // Process +// checksumHandler?.Invoke(data.As(data.Data[2]!.Value(), symbol)); +// } +// else +// { +// var dataArray = (JArray)data.Data[1]!; +// if (dataArray.Count == 0) +// // Empty array +// return; + +// if (dataArray[0].Type == JTokenType.Array) +// { +// HandleData("Book snapshot", dataArray, symbol, data, handler, _bookSerializer); +// } +// else +// { +// HandleSingleToArrayData("Book update", dataArray, symbol, data, handler, _bookSerializer); +// } +// } +// }); + +// var sub = new BitfinexBookSubscriptionRequest( +// symbol, +// JsonConvert.SerializeObject(precision, new PrecisionConverter(false)), +// JsonConvert.SerializeObject(frequency, new FrequencyConverter(false)), +// length); +// return await SubscribeAsync(BaseAddress.AppendPath("ws/2"), sub, null, false, internalHandler, ct).ConfigureAwait(false); +// } + +// /// +// public async Task> SubscribeToFundingOrderBookUpdatesAsync(string symbol, Precision precision, Frequency frequency, int length, Action>> handler, Action>? checksumHandler = null, CancellationToken ct = default) +// { +// symbol.ValidateBitfinexFundingSymbol(); +// length.ValidateIntValues(nameof(length), 1, 25, 100, 250); +// if (precision == Precision.R0) +// throw new ArgumentException("Invalid precision R0, use SubscribeToFundingRawOrderBookUpdatesAsync instead"); + +// var internalHandler = new Action>(data => +// { +// if (data.Data[1]?.ToString() == "cs") +// { +// // Process +// checksumHandler?.Invoke(data.As(data.Data[2]!.Value(), symbol)); +// } +// else +// { +// var dataArray = (JArray)data.Data[1]!; +// if (dataArray.Count == 0) +// // Empty array +// return; + +// if (dataArray[0].Type == JTokenType.Array) +// { +// HandleData("Book snapshot", dataArray, symbol, data, handler, _fundingBookSerializer); +// } +// else +// { +// HandleSingleToArrayData("Book update", dataArray, symbol, data, handler, _fundingBookSerializer); +// } +// } +// }); + +// var sub = new BitfinexBookSubscriptionRequest( +// symbol, +// JsonConvert.SerializeObject(precision, new PrecisionConverter(false)), +// JsonConvert.SerializeObject(frequency, new FrequencyConverter(false)), +// length); +// return await SubscribeAsync(BaseAddress.AppendPath("ws/2"), sub, null, false, internalHandler, ct).ConfigureAwait(false); +// } + +// /// +// public async Task> SubscribeToRawOrderBookUpdatesAsync(string symbol, int limit, Action>> handler, Action>? checksumHandler = null, CancellationToken ct = default) +// { +// symbol.ValidateBitfinexTradingSymbol(); +// var internalHandler = new Action>(data => +// { +// if (data.Data[1]?.ToString() == "cs") +// { +// // Process +// checksumHandler?.Invoke(data.As(data.Data[2]!.Value(), symbol)); +// } +// else +// { +// var dataArray = (JArray)data.Data[1]!; +// if (dataArray[0].Type == JTokenType.Array) +// HandleData("Raw book snapshot", dataArray, symbol, data, handler); +// else +// HandleSingleToArrayData("Raw book update", dataArray, symbol, data, handler); +// } +// }); +// return await SubscribeAsync(BaseAddress.AppendPath("ws/2"), new BitfinexRawBookSubscriptionRequest(symbol, "R0", limit), null, false, internalHandler, ct).ConfigureAwait(false); +// } + +// /// +// public async Task> SubscribeToRawFundingOrderBookUpdatesAsync(string symbol, int limit, Action>> handler, Action>? checksumHandler = null, CancellationToken ct = default) +// { +// symbol.ValidateBitfinexFundingSymbol(); +// var internalHandler = new Action>(data => +// { +// if (data.Data[1]?.ToString() == "cs") +// { +// // Process +// checksumHandler?.Invoke(data.As(data.Data[2]!.Value(), symbol)); +// } +// else +// { +// var dataArray = (JArray)data.Data[1]!; +// if (dataArray[0].Type == JTokenType.Array) +// HandleData("Raw book snapshot", dataArray, symbol, data, handler); +// else +// HandleSingleToArrayData("Raw book update", dataArray, symbol, data, handler); +// } +// }); +// return await SubscribeAsync(BaseAddress.AppendPath("ws/2"), new BitfinexRawBookSubscriptionRequest(symbol, "R0", limit), null, false, internalHandler, ct).ConfigureAwait(false); +// } + +// /// +// public async Task> SubscribeToTradeUpdatesAsync(string symbol, Action>> handler, CancellationToken ct = default) +// { +// var internalHandler = new Action>(data => +// { +// var arr = (JArray)data.Data; +// if (arr[1].Type == JTokenType.Array) +// { +// HandleData("Trade snapshot", (JArray)arr[1], symbol, data, handler); +// } +// else +// { +// var desResult = Deserialize(arr[2]); +// if (!desResult) +// { +// _logger.Log(LogLevel.Warning, "Failed to deserialize trade object: " + desResult.Error); +// return; +// } +// desResult.Data.UpdateType = BitfinexEvents.EventMapping[arr[1].ToString()]; +// handler(data.As>(new[] { desResult.Data }, symbol)); +// } +// }); +// return await SubscribeAsync(BaseAddress.AppendPath("ws/2"), new BitfinexSubscriptionRequest("trades", symbol), null, false, internalHandler, ct).ConfigureAwait(false); +// } + +// /// +// public async Task> SubscribeToKlineUpdatesAsync(string symbol, KlineInterval interval, Action>> handler, CancellationToken ct = default) +// { +// var internalHandler = new Action>(data => +// { +// var dataArray = (JArray)data.Data[1]!; +// if (dataArray.Count == 0) +// { +// _logger.Log(LogLevel.Warning, "No data in kline update, check if the symbol is correct"); +// return; +// } + +// if (dataArray[0].Type == JTokenType.Array) +// HandleData("Kline snapshot", dataArray, symbol, data, handler); +// else +// HandleSingleToArrayData("Kline update", dataArray, symbol, data, handler); +// }); +// return await SubscribeAsync(BaseAddress.AppendPath("ws/2"), new BitfinexKlineSubscriptionRequest(symbol, JsonConvert.SerializeObject(interval, new KlineIntervalConverter(false))), null, false, internalHandler, ct).ConfigureAwait(false); +// } + +// /// +// public async Task> SubscribeToLiquidationUpdatesAsync(Action>> handler, CancellationToken ct = default) +// { +// var internalHandler = new Action>(data => +// { +// HandleData("Liquidation", (JArray)data.Data[1]!, null, data, handler); +// }); +// return await SubscribeAsync(BaseAddress.AppendPath("ws/2"), new BitfinexLiquidationSubscriptionRequest(), null, false, internalHandler, ct).ConfigureAwait(false); +// } + +// /// +// public async Task> SubscribeToDerivativesUpdatesAsync(string symbol, Action> handler, CancellationToken ct = default) +// { +// var internalHandler = new Action>(data => +// { +// HandleData("DerivativeUpdate", (JArray)data.Data[1]!, symbol, data, handler); +// }); +// return await SubscribeAsync(BaseAddress.AppendPath("ws/2"), new BitfinexDerivativesStatusSubscriptionRequest(symbol), null, false, internalHandler, ct).ConfigureAwait(false); +// } + +// /// +// public async Task> SubscribeToUserTradeUpdatesAsync( +// Action>>> orderHandler, +// Action>>> tradeHandler, +// Action>>> positionHandler, +// CancellationToken ct = default) +// { +// var tokenHandler = new Action>(tokenData => +// { +// HandleAuthUpdate(tokenData, orderHandler, "Orders"); +// HandleAuthUpdate(tokenData, tradeHandler, "Trades"); +// HandleAuthUpdate(tokenData, positionHandler, "Positions"); +// }); + +// return await SubscribeAsync(BaseAddress.AppendPath("ws/2"), null, "Orders|Trades|Positions", true, tokenHandler, ct).ConfigureAwait(false); +// } + +// /// +// public async Task> SubscribeToBalanceUpdatesAsync(Action>>> walletHandler, CancellationToken ct = default) +// { +// var tokenHandler = new Action>(tokenData => +// { +// HandleAuthUpdate(tokenData, walletHandler, "Wallet"); +// }); + +// return await SubscribeAsync(BaseAddress.AppendPath("ws/2"), null, "Wallet", true, tokenHandler, ct).ConfigureAwait(false); +// } + +// /// +// public async Task> SubscribeToFundingUpdatesAsync( +// Action>>> fundingOfferHandler, +// Action>>> fundingCreditHandler, +// Action>>> fundingLoanHandler, +// CancellationToken ct = default) +// { +// var tokenHandler = new Action>(tokenData => +// { +// HandleAuthUpdate(tokenData, fundingOfferHandler, "FundingOffers"); +// HandleAuthUpdate(tokenData, fundingCreditHandler, "FundingCredits"); +// HandleAuthUpdate(tokenData, fundingLoanHandler, "FundingLoans"); +// }); + +// return await SubscribeAsync(BaseAddress.AppendPath("ws/2"), null, "FundingOffers|FundingCredits|FundingLoans", true, tokenHandler, ct).ConfigureAwait(false); +// } + +// /// +// public async Task> PlaceOrderAsync(OrderSide side, OrderType type, string symbol, decimal quantity, long? groupId = null, long? clientOrderId = null, decimal? price = null, decimal? priceTrailing = null, decimal? priceAuxiliaryLimit = null, decimal? priceOcoStop = null, OrderFlags? flags = null, int? leverage = null, DateTime? cancelTime = null, string? affiliateCode = null) +// { +// symbol.ValidateBitfinexSymbol(); +// _logger.Log(LogLevel.Information, "Going to place order"); +// clientOrderId ??= GenerateClientOrderId(); + +// var affCode = affiliateCode ?? _affCode; +// var query = new BitfinexSocketQuery(clientOrderId.ToString(), BitfinexEventType.OrderNew, new BitfinexNewOrder +// { +// Amount = side == OrderSide.Buy ? quantity : -quantity, +// OrderType = type, +// Symbol = symbol, +// Price = price, +// ClientOrderId = clientOrderId, +// Flags = flags, +// GroupId = groupId, +// PriceAuxiliaryLimit = priceAuxiliaryLimit, +// PriceOCOStop = priceOcoStop, +// PriceTrailing = priceTrailing, +// Leverage = leverage, +// CancelAfter = cancelTime, +// Meta = affCode == null ? null : new BitfinexMeta() { AffiliateCode = affCode } +// }); + +// return await QueryAsync(BaseAddress.AppendPath("ws/2"), query, true).ConfigureAwait(false); +// } + +// /// +// public async Task> UpdateOrderAsync(long orderId, decimal? price = null, decimal? quantity = null, decimal? delta = null, decimal? priceAuxiliaryLimit = null, decimal? priceTrailing = null, OrderFlags? flags = null) +// { +// _logger.Log(LogLevel.Information, "Going to update order " + orderId); +// var query = new BitfinexSocketQuery(orderId.ToString(CultureInfo.InvariantCulture), BitfinexEventType.OrderUpdate, new BitfinexUpdateOrder +// { +// OrderId = orderId, +// Amount = quantity, +// Price = price, +// Flags = flags, +// PriceAuxiliaryLimit = priceAuxiliaryLimit?.ToString(CultureInfo.InvariantCulture), +// PriceTrailing = priceTrailing?.ToString(CultureInfo.InvariantCulture) +// }); + +// return await QueryAsync(BaseAddress.AppendPath("ws/2"), query, true).ConfigureAwait(false); +// } + +// ///// +// ///// Cancel all open orders +// ///// +// ///// +// //public CallResult CancelAllOrders() => CancelAllOrdersAsync().Result; +// ///// +// ///// Cancel all open orders +// ///// +// ///// +// //public async Task> CancelAllOrdersAsync() +// //{ +// // // Doesn't seem to work even though it is implemented as described at https://docs.bitfinex.com/v2/reference#ws-input-order-cancel-multi +// // _log.Write(LogLevel.Information, "Going to cancel all orders"); +// // var query = new BitfinexSocketQuery(null, BitfinexEventType.OrderCancelMulti, new BitfinexMultiCancel { All = true }); + +// // return await Query(query, true).ConfigureAwait(false); +// //} + +// /// +// public async Task> CancelOrderAsync(long orderId) +// { +// _logger.Log(LogLevel.Information, "Going to cancel order " + orderId); +// var query = new BitfinexSocketQuery(orderId.ToString(CultureInfo.InvariantCulture), BitfinexEventType.OrderCancel, new JObject { ["id"] = orderId }); + +// return await QueryAsync(BaseAddress.AppendPath("ws/2"), query, true).ConfigureAwait(false); +// } + +// /// +// public async Task> CancelOrdersByGroupIdAsync(long groupOrderId) +// { +// return await CancelOrdersAsync(null, null, new Dictionary { { groupOrderId, null } }).ConfigureAwait(false); +// } + +// /// +// public async Task> CancelOrdersByGroupIdsAsync(IEnumerable groupOrderIds) +// { +// groupOrderIds.ValidateNotNull(nameof(groupOrderIds)); +// return await CancelOrdersAsync(null, null, groupOrderIds.ToDictionary(v => v, k => (long?)null)).ConfigureAwait(false); +// } + +// /// +// public async Task> CancelOrdersAsync(IEnumerable orderIds) +// { +// orderIds.ValidateNotNull(nameof(orderIds)); +// return await CancelOrdersAsync(orderIds, null).ConfigureAwait(false); +// } + +// /// +// public async Task> CancelOrdersByClientOrderIdsAsync(Dictionary clientOrderIds) +// { +// return await CancelOrdersAsync(null, clientOrderIds).ConfigureAwait(false); +// } + +// /// +// public async Task> SubmitFundingOfferAsync(FundingOfferType type, string symbol, decimal quantity, decimal price, int period, int? flags = null) +// { +// var parameters = new Dictionary +// { +// { "type", EnumConverter.GetString(type) }, +// { "symbol", symbol }, +// { "amount", quantity }, +// { "rate", price }, +// { "period", period }, +// }; +// parameters.AddOptionalParameter("flags", flags); + +// var query = new BitfinexSocketQuery(ExchangeHelpers.NextId().ToString(CultureInfo.InvariantCulture), BitfinexEventType.FundingOfferNew, parameters); +// return await QueryAsync(BaseAddress.AppendPath("ws/2"), query, true).ConfigureAwait(false); +// } + +// /// +// public async Task> CancelFundingOfferAsync(long id) +// { +// var parameters = new Dictionary +// { +// { "id", id } +// }; + +// var query = new BitfinexSocketQuery(id.ToString(CultureInfo.InvariantCulture), BitfinexEventType.FundingOfferCancel, parameters); +// return await QueryAsync(BaseAddress.AppendPath("ws/2"), query, true).ConfigureAwait(false); +// } +// #endregion + +// #region private methods +// private void HandleData(string name, JArray dataArray, string? symbol, DataEvent dataEvent, Action> handler, JsonSerializer? serializer = null) +// { +// var desResult = Deserialize(dataArray, serializer: serializer); +// if (!desResult) +// { +// _logger.Log(LogLevel.Warning, $"Failed to Deserialize {name} object: " + desResult.Error); +// return; +// } + +// handler(dataEvent.As(desResult.Data, symbol)); +// } + +// private void HandleSingleToArrayData(string name, JArray dataArray, string? symbol, DataEvent dataEvent, Action>> handler, JsonSerializer? serializer = null) +// { +// var wrapperArray = new JArray { dataArray }; + +// var desResult = Deserialize>(wrapperArray, serializer: serializer); +// if (!desResult) +// { +// _logger.Log(LogLevel.Warning, $"Failed to Deserialize {name} object: " + desResult.Error); +// return; +// } + +// handler(dataEvent.As(desResult.Data, symbol)); +// } + +// private async Task> CancelOrdersAsync(IEnumerable? orderIds = null, Dictionary? clientOrderIds = null, Dictionary? groupOrderIds = null) +// { +// if (orderIds == null && clientOrderIds == null && groupOrderIds == null) +// throw new ArgumentException("Either orderIds, clientOrderIds or groupOrderIds should be provided"); + +// _logger.Log(LogLevel.Information, "Going to cancel multiple orders"); +// var cancelObject = new BitfinexMultiCancel { OrderIds = orderIds }; +// if (clientOrderIds != null) +// { +// cancelObject.ClientIds = new object[clientOrderIds.Count][]; +// for (var i = 0; i < cancelObject.ClientIds.Length; i++) +// { +// cancelObject.ClientIds[i] = new object[] +// { +// clientOrderIds.ElementAt(i).Key, +// clientOrderIds.ElementAt(i).Value.ToString("yyyy-MM-dd") +// }; +// } +// } +// if (groupOrderIds != null) +// cancelObject.GroupIds = new[] { groupOrderIds.Select(g => g.Key).ToArray() }; + +// var query = new BitfinexSocketQuery(null, BitfinexEventType.OrderCancelMulti, cancelObject); +// return await QueryAsync(query, true).ConfigureAwait(false); +// } + +// private void HandleAuthUpdate(DataEvent token, Action>>> action, string category) +// { +// var evntStr = token.Data[1]?.ToString(); +// if (evntStr == null) +// return; + +// var evntType = BitfinexEvents.EventMapping[evntStr]; +// var evnt = BitfinexEvents.Events.Single(e => e.EventType == evntType); +// if (evnt.Category != category) +// return; + +// if (action == null) +// { +// _logger.Log(LogLevel.Debug, $"Ignoring {evnt.EventType} event because not subscribed"); +// return; +// } + +// IEnumerable data; +// if (evnt.Single) +// { +// var result = Deserialize(token.Data[2]!); +// if (!result) +// { +// _logger.Log(LogLevel.Warning, "Failed to Deserialize data: " + result.Error); +// return; +// } +// data = new[] { result.Data }; +// } +// else +// { +// var result = Deserialize>(token.Data[2]!); +// if (!result) +// { +// _logger.Log(LogLevel.Warning, "Failed to Deserialize data: " + result.Error); +// return; +// } +// data = result.Data; +// } + +// action(token.As(new BitfinexSocketEvent>(evntType, data))); +// } + +// private long GenerateClientOrderId() +// { +// var buffer = new byte[8]; +// _random.NextBytes(buffer); +// return (long)Math.Round(Math.Abs(BitConverter.ToInt32(buffer, 0)) / 1000m); +// } + + +// private void ConfHandler(MessageEvent messageEvent) +// { +// var confEvent = messageEvent.JsonData.Type == JTokenType.Object && messageEvent.JsonData["event"]?.ToString() == "conf"; +// if (!confEvent) +// return; + +// // Could check conf result; +// } + +// private void InfoHandler(MessageEvent messageEvent) +// { +// var infoEvent = messageEvent.JsonData.Type == JTokenType.Object && messageEvent.JsonData["event"]?.ToString() == "info"; +// if (!infoEvent) +// return; + +// _logger.Log(LogLevel.Debug, $"Socket {messageEvent.Connection.SocketId} Info event received: {messageEvent.JsonData}"); +// if (messageEvent.JsonData["code"] == null) +// { +// // welcome event, send a config message for receiving checsum updates for order book subscriptions +// messageEvent.Connection.Send(ExchangeHelpers.NextId(), new BitfinexSocketConfig { Event = "conf", Flags = 131072 }, 1); +// return; +// } + +// var code = messageEvent.JsonData["code"]?.Value(); +// switch (code) +// { +// case 20051: +// _logger.Log(LogLevel.Information, $"Socket {messageEvent.Connection.SocketId} Code {code} received, reconnecting socket"); +// messageEvent.Connection.PausedActivity = true; // Prevent new operations to be send +// _ = messageEvent.Connection.TriggerReconnectAsync(); +// break; +// case 20060: +// _logger.Log(LogLevel.Information, $"Socket {messageEvent.Connection.SocketId} Code {code} received, entering maintenance mode"); +// messageEvent.Connection.PausedActivity = true; +// break; +// case 20061: +// _logger.Log(LogLevel.Information, $"Socket {messageEvent.Connection.SocketId} Code {code} received, leaving maintenance mode. Reconnecting/Resubscribing socket."); +// _ = messageEvent.Connection.TriggerReconnectAsync(); // Closing it via socket will automatically reconnect +// break; +// default: +// _logger.Log(LogLevel.Warning, $"Socket {messageEvent.Connection.SocketId} Unknown info code received: {code}"); +// break; +// } +// } + + +// /// +// protected override async Task UnsubscribeAsync(SocketConnection connection, SocketSubscription subscription) +// { +// if (subscription.Request == null) +// { +// // If we don't have a request object we can't unsubscribe it. Probably is an auth subscription which gets pushed regardless +// // Just returning true here will remove the handler and close the socket if there are no other handlers left on the socket, which is the best we can do +// return true; +// } + +// var channelId = ((BitfinexSubscriptionRequest)subscription.Request!).ChannelId; +// var unsub = new BitfinexUnsubscribeRequest(channelId); +// var result = false; +// await connection.SendAndWaitAsync(unsub, ClientOptions.RequestTimeout, null, 1, data => +// { +// if (data.Type != JTokenType.Object) +// return false; + +// var evnt = data["event"]?.ToString(); +// var channel = data["chanId"]?.ToString(); +// if (evnt == null || channel == null) +// return false; + +// if (!int.TryParse(channel, out var chan)) +// return false; + +// result = evnt == "unsubscribed" && channelId == chan; +// return result; +// }).ConfigureAwait(false); +// return result; +// } + +// private static BitfinexAuthentication GetAuthObject(SocketApiClient apiClient, params string[] filter) +// { +// var authProvider = (BitfinexAuthenticationProvider)apiClient.AuthenticationProvider!; +// var n = authProvider.GetNonce().ToString(); +// var authentication = new BitfinexAuthentication +// { +// Event = "auth", +// ApiKey = authProvider.GetApiKey(), +// Nonce = n, +// Payload = "AUTH" + n +// }; +// if (filter.Any()) +// authentication.Filter = filter; +// authentication.Signature = authProvider.Sign(authentication.Payload).ToLower(CultureInfo.InvariantCulture); +// return authentication; +// } + +// #endregion + +// /// +// protected override async Task> AuthenticateSocketAsync(SocketConnection s) +// { +// if (s.ApiClient.AuthenticationProvider == null) +// return new CallResult(new NoApiCredentialsError()); + +// var authObject = GetAuthObject(s.ApiClient); +// var result = new CallResult(new ServerError("No response from server")); +// await s.SendAndWaitAsync(authObject, ClientOptions.RequestTimeout, null, 1, tokenData => +// { +// if (tokenData.Type != JTokenType.Object) +// return false; + +// if (tokenData["event"]?.ToString() != "auth") +// return false; + +// var authResponse = Deserialize(tokenData); +// if (!authResponse) +// { +// _logger.Log(LogLevel.Warning, $"Socket {s.SocketId} authentication failed: " + authResponse.Error); +// result = new CallResult(authResponse.Error!); +// return false; +// } + +// if (authResponse.Data.Status != "OK") +// { +// var error = new ServerError(authResponse.Data.ErrorCode, authResponse.Data.ErrorMessage ?? "-"); +// result = new CallResult(error); +// _logger.Log(LogLevel.Debug, $"Socket {s.SocketId} authentication failed: " + error); +// return false; +// } + +// _logger.Log(LogLevel.Debug, $"Socket {s.SocketId} authentication completed"); +// result = new CallResult(true); +// return true; +// }).ConfigureAwait(false); + +// return result; +// } + +// /// +//#pragma warning disable 8765 +// protected override bool HandleQueryResponse(SocketConnection s, object request, JToken data, out CallResult? callResult) +//#pragma warning restore 8765 +// { +// callResult = null; +// if (data.Type != JTokenType.Array) +// return false; + +// var array = (JArray)data; +// if (array.Count < 3) +// return false; + +// var bfRequest = (BitfinexSocketQuery)request; +// var evntString = data[1]!.ToString(); +// if (!BitfinexEvents.EventMapping.TryGetValue(evntString, out var eventType)) +// return false; + +// if (eventType == BitfinexEventType.Notification) +// { +// var notificationData = (JArray)data[2]!; +// var notificationType = BitfinexEvents.EventMapping[notificationData[1].ToString()]; +// if (notificationType != BitfinexEventType.OrderNewRequest +// && notificationType != BitfinexEventType.OrderCancelRequest +// && notificationType != BitfinexEventType.OrderUpdateRequest +// && notificationType != BitfinexEventType.OrderCancelMultiRequest +// && notificationType != BitfinexEventType.FundingOfferNewRequest +// && notificationType != BitfinexEventType.FundingOfferCancelRequest) +// { +// return false; +// } + +// var statusString = (notificationData[6].ToString()).ToLower(CultureInfo.InvariantCulture); +// if (statusString == "error") +// { +// if (bfRequest.QueryType == BitfinexEventType.OrderNew && notificationType == BitfinexEventType.OrderNewRequest) +// { +// var orderData = notificationData[4]; +// if (orderData[2]?.ToString() != bfRequest.Id) +// return false; + +// callResult = new CallResult(new ServerError(notificationData[7].ToString())); +// return true; +// } + +// if (bfRequest.QueryType == BitfinexEventType.OrderCancel && notificationType == BitfinexEventType.OrderCancelRequest) +// { +// var orderData = notificationData[4]; +// if (orderData[0]?.ToString() != bfRequest.Id) +// return false; + +// callResult = new CallResult(new ServerError(notificationData[7].ToString())); +// return true; +// } + +// if (bfRequest.QueryType == BitfinexEventType.OrderUpdate && notificationType == BitfinexEventType.OrderUpdateRequest) +// { +// // OrderUpdateRequest not found notification doesn't carry the order id, where as OrderCancelRequest not found notification does.. +// // Anyway, can't check for ids, so just assume its for this one + +// callResult = new CallResult(new ServerError(notificationData[7].ToString())); +// return true; +// } + +// if (bfRequest.QueryType == BitfinexEventType.OrderCancelMulti && notificationType == BitfinexEventType.OrderCancelMultiRequest) +// { +// callResult = new CallResult(new ServerError(notificationData[7].ToString())); +// return true; +// } + +// if (bfRequest.QueryType == BitfinexEventType.FundingOfferNew && notificationType == BitfinexEventType.FundingOfferNewRequest) +// { +// callResult = new CallResult(new ServerError(notificationData[7].ToString())); +// return true; +// } + +// if (bfRequest.QueryType == BitfinexEventType.FundingOfferCancel && notificationType == BitfinexEventType.FundingOfferCancelRequest) +// { +// var fundingData = notificationData[4]; +// if (fundingData[0]?.ToString() != bfRequest.Id) +// return false; + +// callResult = new CallResult(new ServerError(notificationData[7].ToString())); +// return true; +// } +// } + +// if (notificationType == BitfinexEventType.OrderNewRequest +// || notificationType == BitfinexEventType.OrderUpdateRequest +// || notificationType == BitfinexEventType.OrderCancelRequest) +// { +// if (bfRequest.QueryType == BitfinexEventType.OrderNew +// || bfRequest.QueryType == BitfinexEventType.OrderUpdate +// || bfRequest.QueryType == BitfinexEventType.OrderCancel) +// { +// var orderData = notificationData[4]; +// var dataOrderId = orderData[0]?.ToString(); +// var dataOrderClientId = orderData[2]?.ToString(); +// if (dataOrderId == bfRequest.Id || dataOrderClientId == bfRequest.Id) +// { +// var desResult = Deserialize(orderData); +// if (!desResult) +// { +// callResult = new CallResult(desResult.Error!); +// return true; +// } + +// callResult = new CallResult(desResult.Data); +// return true; +// } +// } +// } + +// if (notificationType == BitfinexEventType.OrderCancelMultiRequest) +// { +// callResult = new CallResult(Deserialize(JToken.Parse("true")).Data); +// return true; +// } + +// if (notificationType == BitfinexEventType.FundingOfferNewRequest +// || notificationType == BitfinexEventType.FundingOfferCancelRequest) +// { +// if (bfRequest.QueryType == BitfinexEventType.FundingOfferCancelRequest) +// { +// var fundingData = notificationData[4]; +// var dataOrderId = fundingData[0]?.ToString(); +// if (dataOrderId == bfRequest.Id) +// { +// var desResult = Deserialize(fundingData); +// if (!desResult) +// { +// callResult = new CallResult(desResult.Error!); +// return true; +// } + +// callResult = new CallResult(desResult.Data); +// return true; +// } +// } +// else if(bfRequest.QueryType == BitfinexEventType.FundingOfferNew) +// { +// var fundingData = notificationData[4]; +// var desResult = Deserialize(fundingData); +// if (!desResult) +// { +// callResult = new CallResult(desResult.Error!); +// return true; +// } + +// callResult = new CallResult(desResult.Data); +// return true; +// } +// } +// } + +// if (bfRequest.QueryType == BitfinexEventType.OrderCancelMulti && eventType == BitfinexEventType.OrderCancel) +// { +// callResult = new CallResult(Deserialize(JToken.Parse("true")).Data); +// return true; +// } + +// return false; +// } + +// /// +// protected override bool HandleSubscriptionResponse(SocketConnection s, SocketSubscription subscription, object request, JToken data, out CallResult? callResult) +// { +// callResult = null; +// if (data.Type != JTokenType.Object) +// return false; + +// var infoEvent = data["event"]?.ToString() == "subscribed"; +// var errorEvent = data["event"]?.ToString() == "error"; +// if (!infoEvent && !errorEvent) +// return false; + +// if (infoEvent) +// { +// var subResponse = Deserialize(data); +// if (!subResponse) +// { +// callResult = new CallResult(subResponse.Error!); +// _logger.Log(LogLevel.Warning, $"Socket {s.SocketId} subscription failed: " + subResponse.Error); +// return false; +// } + +// var bRequest = (BitfinexSubscriptionRequest)request; +// if (!bRequest.CheckResponse(data)) +// return false; + +// bRequest.ChannelId = subResponse.Data.ChannelId; +// callResult = subResponse.As(subResponse.Data); +// return true; +// } +// else +// { +// var subResponse = Deserialize(data); +// if (!subResponse) +// { +// callResult = new CallResult(subResponse.Error!); +// _logger.Log(LogLevel.Warning, $"Socket {s.SocketId} subscription failed: " + subResponse.Error); +// return false; +// } + +// var error = new ServerError(subResponse.Data.Code, subResponse.Data.Message); +// callResult = new CallResult(error); +// _logger.Log(LogLevel.Debug, $"Socket {s.SocketId} subscription failed: " + error); +// return true; +// } +// } + +// /// +// protected override bool MessageMatchesHandler(SocketConnection socketConnection, JToken message, object request) +// { +// if (message.Type != JTokenType.Array) +// return false; + +// var array = (JArray)message; +// if (array.Count < 2) +// return false; + +// if (!int.TryParse(array[0].ToString(), out var channelId)) +// return false; + +// if (channelId == 0) +// return false; + +// var subId = ((BitfinexSubscriptionRequest)request).ChannelId; +// return channelId == subId && array[1].ToString() != "hb"; +// } + +// /// +// protected override bool MessageMatchesHandler(SocketConnection socketConnection, JToken message, string identifier) +// { +// if (message.Type == JTokenType.Object) +// { +// if (identifier == "Info") +// return message["event"]?.ToString() == "info"; +// if (identifier == "Conf") +// return message["event"]?.ToString() == "conf"; +// } + +// else if (message.Type == JTokenType.Array) +// { +// var array = (JArray)message; +// if (array.Count < 2) +// return false; + +// if (identifier == "HB") +// return array[1].ToString() == "hb"; + +// if (!int.TryParse(array[0].ToString(), out var channelId)) +// return false; + +// if (channelId != 0) +// return false; + +// var split = identifier.Split(new[] { "|" }, StringSplitOptions.RemoveEmptyEntries); +// foreach (var id in split) +// { +// var events = BitfinexEvents.GetEventsForCategory(id); +// var eventTypeString = array[1].ToString(); +// var eventType = BitfinexEvents.EventMapping[eventTypeString]; +// var evnt = events.SingleOrDefault(e => e.EventType == eventType); +// if (evnt != null) +// return true; +// } +// } + +// return false; +// } } } diff --git a/Bitfinex.Net/Interfaces/Clients/SpotApi/IBitfinexSocketClientSpotApi.cs b/Bitfinex.Net/Interfaces/Clients/SpotApi/IBitfinexSocketClientSpotApi.cs index ec0998d..9b32980 100644 --- a/Bitfinex.Net/Interfaces/Clients/SpotApi/IBitfinexSocketClientSpotApi.cs +++ b/Bitfinex.Net/Interfaces/Clients/SpotApi/IBitfinexSocketClientSpotApi.cs @@ -7,6 +7,7 @@ using Bitfinex.Net.Objects.Models.Socket; using CryptoExchange.Net.Interfaces; using CryptoExchange.Net.Objects; +using CryptoExchange.Net.Objects.Sockets; using CryptoExchange.Net.Sockets; namespace Bitfinex.Net.Interfaces.Clients.SpotApi @@ -26,243 +27,243 @@ public interface IBitfinexSocketClientSpotApi : ISocketApiClient, IDisposable /// Task> SubscribeToTickerUpdatesAsync(string symbol, Action> handler, CancellationToken ct = default); - /// - /// Subscribes to funding ticker updates a symbol. Use SubscribeToTickerUpdatesAsync for trade symbol ticker updates - /// - /// - /// The symbol to subscribe to - /// The handler for the data - /// Cancellation token for closing this subscription - /// - Task> SubscribeToFundingTickerUpdatesAsync(string symbol, Action> handler, CancellationToken ct = default); + ///// + ///// Subscribes to funding ticker updates a symbol. Use SubscribeToTickerUpdatesAsync for trade symbol ticker updates + ///// + ///// + ///// The symbol to subscribe to + ///// The handler for the data + ///// Cancellation token for closing this subscription + ///// + //Task> SubscribeToFundingTickerUpdatesAsync(string symbol, Action> handler, CancellationToken ct = default); - /// - /// Subscribes to order book updates for a symbol. Use SubscribeToFundingOrderBookUpdatesAsync for funding symbol ticker updates - /// - /// - /// The symbol to subscribe to - /// The precision of the updates - /// The frequency of updates - /// The range for the order book updates, either 25 or 100 - /// The handler for the data - /// The handler for the checksum, can be used to validate a order book implementation - /// Cancellation token for closing this subscription - /// - Task> SubscribeToOrderBookUpdatesAsync(string symbol, Precision precision, Frequency frequency, int length, Action>> handler, Action>? checksumHandler = null, CancellationToken ct = default); + ///// + ///// Subscribes to order book updates for a symbol. Use SubscribeToFundingOrderBookUpdatesAsync for funding symbol ticker updates + ///// + ///// + ///// The symbol to subscribe to + ///// The precision of the updates + ///// The frequency of updates + ///// The range for the order book updates, either 25 or 100 + ///// The handler for the data + ///// The handler for the checksum, can be used to validate a order book implementation + ///// Cancellation token for closing this subscription + ///// + //Task> SubscribeToOrderBookUpdatesAsync(string symbol, Precision precision, Frequency frequency, int length, Action>> handler, Action>? checksumHandler = null, CancellationToken ct = default); - /// - /// Subscribes to funding order book updates for a symbol. Use SubscribeToOrderBookUpdatesAsync for trade symbol ticker updates - /// - /// - /// The symbol to subscribe to - /// The precision of the updates - /// The frequency of updates - /// The range for the order book updates, either 25 or 100 - /// The handler for the data - /// The handler for the checksum, can be used to validate a order book implementation - /// Cancellation token for closing this subscription - /// - Task> SubscribeToFundingOrderBookUpdatesAsync(string symbol, Precision precision, Frequency frequency, int length, Action>> handler, Action>? checksumHandler = null, CancellationToken ct = default); + ///// + ///// Subscribes to funding order book updates for a symbol. Use SubscribeToOrderBookUpdatesAsync for trade symbol ticker updates + ///// + ///// + ///// The symbol to subscribe to + ///// The precision of the updates + ///// The frequency of updates + ///// The range for the order book updates, either 25 or 100 + ///// The handler for the data + ///// The handler for the checksum, can be used to validate a order book implementation + ///// Cancellation token for closing this subscription + ///// + //Task> SubscribeToFundingOrderBookUpdatesAsync(string symbol, Precision precision, Frequency frequency, int length, Action>> handler, Action>? checksumHandler = null, CancellationToken ct = default); - /// - /// Subscribes to raw order book updates for a symbol. Use SubscribeToRawFundingOrderBookUpdatesAsync for funding symbol ticker updates - /// - /// - /// The symbol to subscribe to - /// The range for the order book updates - /// The handler for the data - /// The handler for the checksum, can be used to validate a order book implementation - /// Cancellation token for closing this subscription - /// - Task> SubscribeToRawOrderBookUpdatesAsync(string symbol, int limit, Action>> handler, Action>? checksumHandler = null, CancellationToken ct = default); + ///// + ///// Subscribes to raw order book updates for a symbol. Use SubscribeToRawFundingOrderBookUpdatesAsync for funding symbol ticker updates + ///// + ///// + ///// The symbol to subscribe to + ///// The range for the order book updates + ///// The handler for the data + ///// The handler for the checksum, can be used to validate a order book implementation + ///// Cancellation token for closing this subscription + ///// + //Task> SubscribeToRawOrderBookUpdatesAsync(string symbol, int limit, Action>> handler, Action>? checksumHandler = null, CancellationToken ct = default); - /// - /// Subscribes to raw order book updates for a symbol. Use SubscribeToRawOrderBookUpdatesAsync for trade symbol ticker updates - /// - /// - /// The symbol to subscribe to - /// The range for the order book updates - /// The handler for the data - /// The handler for the checksum, can be used to validate a order book implementation - /// Cancellation token for closing this subscription - /// - Task> SubscribeToRawFundingOrderBookUpdatesAsync(string symbol, int limit, Action>> handler, Action>? checksumHandler = null, CancellationToken ct = default); + ///// + ///// Subscribes to raw order book updates for a symbol. Use SubscribeToRawOrderBookUpdatesAsync for trade symbol ticker updates + ///// + ///// + ///// The symbol to subscribe to + ///// The range for the order book updates + ///// The handler for the data + ///// The handler for the checksum, can be used to validate a order book implementation + ///// Cancellation token for closing this subscription + ///// + //Task> SubscribeToRawFundingOrderBookUpdatesAsync(string symbol, int limit, Action>> handler, Action>? checksumHandler = null, CancellationToken ct = default); - /// - /// Subscribes to public trade updates for a symbol - /// - /// - /// The symbol to subscribe to - /// The handler for the data - /// Cancellation token for closing this subscription - /// - Task> SubscribeToTradeUpdatesAsync(string symbol, Action>> handler, CancellationToken ct = default); + ///// + ///// Subscribes to public trade updates for a symbol + ///// + ///// + ///// The symbol to subscribe to + ///// The handler for the data + ///// Cancellation token for closing this subscription + ///// + //Task> SubscribeToTradeUpdatesAsync(string symbol, Action>> handler, CancellationToken ct = default); - /// - /// Subscribes to kline updates for a symbol - /// - /// - /// The symbol to subscribe to. For funding klines use {symbol}:p{period}, for example fUSD:p30 - /// The interval of the klines - /// The handler for the data - /// Cancellation token for closing this subscription - /// - Task> SubscribeToKlineUpdatesAsync(string symbol, KlineInterval interval, Action>> handler, CancellationToken ct = default); + ///// + ///// Subscribes to kline updates for a symbol + ///// + ///// + ///// The symbol to subscribe to. For funding klines use {symbol}:p{period}, for example fUSD:p30 + ///// The interval of the klines + ///// The handler for the data + ///// Cancellation token for closing this subscription + ///// + //Task> SubscribeToKlineUpdatesAsync(string symbol, KlineInterval interval, Action>> handler, CancellationToken ct = default); - /// - /// Subscribe to liquidation updates - /// - /// - /// The handler for the data - /// Cancellation token for closing this subscription - /// - Task> SubscribeToLiquidationUpdatesAsync(Action>> handler, CancellationToken ct = default); + ///// + ///// Subscribe to liquidation updates + ///// + ///// + ///// The handler for the data + ///// Cancellation token for closing this subscription + ///// + //Task> SubscribeToLiquidationUpdatesAsync(Action>> handler, CancellationToken ct = default); - /// - /// Subscribe to derivatives status updates - /// - /// - /// The derivatives symbol, tBTCF0:USTF0 for example - /// The handler for the data - /// Cancellation token for closing this subscription - /// - Task> SubscribeToDerivativesUpdatesAsync(string symbol, Action> handler, CancellationToken ct = default); + ///// + ///// Subscribe to derivatives status updates + ///// + ///// + ///// The derivatives symbol, tBTCF0:USTF0 for example + ///// The handler for the data + ///// Cancellation token for closing this subscription + ///// + //Task> SubscribeToDerivativesUpdatesAsync(string symbol, Action> handler, CancellationToken ct = default); - /// - /// Subscribe to trading information updates - /// - /// - /// Data handler for order updates. Can be null if not interested - /// Data handler for trade execution updates. Can be null if not interested - /// Data handler for position updates. Can be null if not interested - /// Cancellation token for closing this subscription - /// - Task> SubscribeToUserTradeUpdatesAsync( - Action>>> orderHandler, - Action>>> tradeHandler, - Action>>> positionHandler, - CancellationToken ct = default); + ///// + ///// Subscribe to trading information updates + ///// + ///// + ///// Data handler for order updates. Can be null if not interested + ///// Data handler for trade execution updates. Can be null if not interested + ///// Data handler for position updates. Can be null if not interested + ///// Cancellation token for closing this subscription + ///// + //Task> SubscribeToUserTradeUpdatesAsync( + // Action>>> orderHandler, + // Action>>> tradeHandler, + // Action>>> positionHandler, + // CancellationToken ct = default); - /// - /// Subscribe to wallet information updates - /// - /// - /// Data handler for wallet updates - /// Cancellation token for closing this subscription - /// - Task> SubscribeToBalanceUpdatesAsync(Action>>> walletHandler, CancellationToken ct = default); + ///// + ///// Subscribe to wallet information updates + ///// + ///// + ///// Data handler for wallet updates + ///// Cancellation token for closing this subscription + ///// + //Task> SubscribeToBalanceUpdatesAsync(Action>>> walletHandler, CancellationToken ct = default); - /// - /// Subscribe to funding information updates - /// - /// - /// - /// - /// Subscribe to funding offer updates. Can be null if not interested - /// Subscribe to funding credit updates. Can be null if not interested - /// Subscribe to funding loan updates. Can be null if not interested - /// Cancellation token for closing this subscription - /// - Task> SubscribeToFundingUpdatesAsync( - Action>>> fundingOfferHandler, - Action>>> fundingCreditHandler, - Action>>> fundingLoanHandler, - CancellationToken ct = default); + ///// + ///// Subscribe to funding information updates + ///// + ///// + ///// + ///// + ///// Subscribe to funding offer updates. Can be null if not interested + ///// Subscribe to funding credit updates. Can be null if not interested + ///// Subscribe to funding loan updates. Can be null if not interested + ///// Cancellation token for closing this subscription + ///// + //Task> SubscribeToFundingUpdatesAsync( + // Action>>> fundingOfferHandler, + // Action>>> fundingCreditHandler, + // Action>>> fundingLoanHandler, + // CancellationToken ct = default); - /// - /// Places a new order - /// - /// - /// The order side - /// The type of the order - /// The symbol the order is for - /// The quantity of the order, positive for buying, negative for selling - /// Group id to assign to the order - /// Client order id to assign to the order - /// Price of the order - /// Trailing price of the order - /// Auxiliary limit price of the order - /// Oco stop price of the order - /// Additional flags - /// Leverage - /// Automatically cancel the order after this time - /// Affiliate code for the order - /// - Task> PlaceOrderAsync(OrderSide side, OrderType type, string symbol, decimal quantity, long? groupId = null, long? clientOrderId = null, decimal? price = null, decimal? priceTrailing = null, decimal? priceAuxiliaryLimit = null, decimal? priceOcoStop = null, OrderFlags? flags = null, int? leverage = null, DateTime? cancelTime = null, string? affiliateCode = null); + ///// + ///// Places a new order + ///// + ///// + ///// The order side + ///// The type of the order + ///// The symbol the order is for + ///// The quantity of the order, positive for buying, negative for selling + ///// Group id to assign to the order + ///// Client order id to assign to the order + ///// Price of the order + ///// Trailing price of the order + ///// Auxiliary limit price of the order + ///// Oco stop price of the order + ///// Additional flags + ///// Leverage + ///// Automatically cancel the order after this time + ///// Affiliate code for the order + ///// + //Task> PlaceOrderAsync(OrderSide side, OrderType type, string symbol, decimal quantity, long? groupId = null, long? clientOrderId = null, decimal? price = null, decimal? priceTrailing = null, decimal? priceAuxiliaryLimit = null, decimal? priceOcoStop = null, OrderFlags? flags = null, int? leverage = null, DateTime? cancelTime = null, string? affiliateCode = null); - /// - /// Updates an order - /// - /// - /// The id of the order to update - /// The new price of the order - /// The new quantity of the order - /// The delta to change - /// the new aux limit price - /// The new trailing price - /// The new flags - /// - Task> UpdateOrderAsync(long orderId, decimal? price = null, decimal? quantity = null, decimal? delta = null, decimal? priceAuxiliaryLimit = null, decimal? priceTrailing = null, OrderFlags? flags = null); + ///// + ///// Updates an order + ///// + ///// + ///// The id of the order to update + ///// The new price of the order + ///// The new quantity of the order + ///// The delta to change + ///// the new aux limit price + ///// The new trailing price + ///// The new flags + ///// + //Task> UpdateOrderAsync(long orderId, decimal? price = null, decimal? quantity = null, decimal? delta = null, decimal? priceAuxiliaryLimit = null, decimal? priceTrailing = null, OrderFlags? flags = null); - /// - /// Cancels an order - /// - /// - /// The id of the order to cancel - /// - Task> CancelOrderAsync(long orderId); + ///// + ///// Cancels an order + ///// + ///// + ///// The id of the order to cancel + ///// + //Task> CancelOrderAsync(long orderId); - /// - /// Cancels multiple orders based on their groupId - /// - /// - /// The group id to cancel - /// True if successfully committed on server - Task> CancelOrdersByGroupIdAsync(long groupOrderId); + ///// + ///// Cancels multiple orders based on their groupId + ///// + ///// + ///// The group id to cancel + ///// True if successfully committed on server + //Task> CancelOrdersByGroupIdAsync(long groupOrderId); - /// - /// Cancels multiple orders based on their groupIds - /// - /// - /// The group ids to cancel - /// True if successfully committed on server - Task> CancelOrdersByGroupIdsAsync(IEnumerable groupOrderIds); + ///// + ///// Cancels multiple orders based on their groupIds + ///// + ///// + ///// The group ids to cancel + ///// True if successfully committed on server + //Task> CancelOrdersByGroupIdsAsync(IEnumerable groupOrderIds); - /// - /// Cancels multiple orders based on their order ids - /// - /// - /// The order ids to cancel - /// True if successfully committed on server - Task> CancelOrdersAsync(IEnumerable orderIds); + ///// + ///// Cancels multiple orders based on their order ids + ///// + ///// + ///// The order ids to cancel + ///// True if successfully committed on server + //Task> CancelOrdersAsync(IEnumerable orderIds); - /// - /// Cancels multiple orders based on their clientOrderIds - /// - /// - /// The client order ids to cancel, listed as (clientOrderId, Day) pair. ClientOrderIds are unique per day, so timestamp should be provided - /// True if successfully committed on server - Task> CancelOrdersByClientOrderIdsAsync(Dictionary clientOrderIds); + ///// + ///// Cancels multiple orders based on their clientOrderIds + ///// + ///// + ///// The client order ids to cancel, listed as (clientOrderId, Day) pair. ClientOrderIds are unique per day, so timestamp should be provided + ///// True if successfully committed on server + //Task> CancelOrdersByClientOrderIdsAsync(Dictionary clientOrderIds); - /// - /// Submit a new funding offer - /// - /// - /// Offer type - /// Symbol - /// Amount (more than 0 for offer, less than 0 for bid) - /// Rate (or offset for FRRDELTA offers) - /// Time period of offer. Minimum 2 days. Maximum 120 days. - /// Flags - /// - Task> SubmitFundingOfferAsync(FundingOfferType type, string symbol, decimal quantity, decimal price, int period, int? flags = null); + ///// + ///// Submit a new funding offer + ///// + ///// + ///// Offer type + ///// Symbol + ///// Amount (more than 0 for offer, less than 0 for bid) + ///// Rate (or offset for FRRDELTA offers) + ///// Time period of offer. Minimum 2 days. Maximum 120 days. + ///// Flags + ///// + //Task> SubmitFundingOfferAsync(FundingOfferType type, string symbol, decimal quantity, decimal price, int period, int? flags = null); - /// - /// Cancel a funding offer - /// - /// - /// Id of the offer to cancel - /// - Task> CancelFundingOfferAsync(long id); + ///// + ///// Cancel a funding offer + ///// + ///// + ///// Id of the offer to cancel + ///// + //Task> CancelFundingOfferAsync(long id); } } \ No newline at end of file diff --git a/Bitfinex.Net/Objects/Sockets/BitfinexRequest.cs b/Bitfinex.Net/Objects/Sockets/BitfinexRequest.cs new file mode 100644 index 0000000..c88d2dc --- /dev/null +++ b/Bitfinex.Net/Objects/Sockets/BitfinexRequest.cs @@ -0,0 +1,17 @@ +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Text; + +namespace Bitfinex.Net.Objects.Sockets +{ + internal class BitfinexRequest + { + [JsonProperty("event")] + public string Event { get; set; } + [JsonProperty("channel")] + public string Channel { get; set; } + [JsonProperty("symbol")] + public string? Symbol { get; set; } + } +} diff --git a/Bitfinex.Net/Objects/Sockets/BitfinexResponse.cs b/Bitfinex.Net/Objects/Sockets/BitfinexResponse.cs new file mode 100644 index 0000000..8f50c68 --- /dev/null +++ b/Bitfinex.Net/Objects/Sockets/BitfinexResponse.cs @@ -0,0 +1,21 @@ +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Text; + +namespace Bitfinex.Net.Objects.Sockets +{ + internal class BitfinexResponse + { + [JsonProperty("event")] + public string Event { get; set; } + [JsonProperty("channel")] + public string Channel { get; set; } + [JsonProperty("symbol")] + public string? Symbol { get; set; } + [JsonProperty("pair")] + public string? Pair { get; set; } + [JsonProperty("chanId")] + public int? ChannelId { get; set; } + } +} diff --git a/Bitfinex.Net/Objects/Sockets/BitfinexSocketConverter.cs b/Bitfinex.Net/Objects/Sockets/BitfinexSocketConverter.cs new file mode 100644 index 0000000..d578e21 --- /dev/null +++ b/Bitfinex.Net/Objects/Sockets/BitfinexSocketConverter.cs @@ -0,0 +1,34 @@ +using CryptoExchange.Net.Converters; +using CryptoExchange.Net.Interfaces; +using CryptoExchange.Net.Objects.Sockets; +using System; +using System.Collections.Generic; +using System.Text; + +namespace Bitfinex.Net.Objects.Sockets +{ + internal class BitfinexSocketConverter : SocketConverter + { + public override MessageInterpreterPipeline InterpreterPipeline { get; } = new MessageInterpreterPipeline + { + GetIdentity = GetIdentity + }; + + private static string GetIdentity(IMessageAccessor accessor) + { + if (!accessor.IsObject(null)) + { + var channelId = accessor.GetArrayIntValue(null, 0); + if (accessor.GetArrayStringValue(null, 1) == "hb") + return "hb"; + + return channelId.ToString(); + } + + var evnt = accessor.GetStringValue("event"); + var channel = accessor.GetStringValue("channel"); + var symbol = accessor.GetStringValue("symbol"); + return evnt + channel + symbol; + } + } +} diff --git a/Bitfinex.Net/Objects/Sockets/BitfinexUpdate.cs b/Bitfinex.Net/Objects/Sockets/BitfinexUpdate.cs new file mode 100644 index 0000000..1f0fcb6 --- /dev/null +++ b/Bitfinex.Net/Objects/Sockets/BitfinexUpdate.cs @@ -0,0 +1,17 @@ +using CryptoExchange.Net.Converters; +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Text; + +namespace Bitfinex.Net.Objects.Sockets +{ + [JsonConverter(typeof(ArrayConverter))] + internal class BitfinexUpdate + { + [ArrayProperty(0)] + public int ChannelId { get; set; } + [ArrayProperty(1)] + public T Data { get; set; } + } +} diff --git a/Bitfinex.Net/Objects/Sockets/Queries/BitfinexQuery.cs b/Bitfinex.Net/Objects/Sockets/Queries/BitfinexQuery.cs new file mode 100644 index 0000000..f68a46c --- /dev/null +++ b/Bitfinex.Net/Objects/Sockets/Queries/BitfinexQuery.cs @@ -0,0 +1,21 @@ +using CryptoExchange.Net.Sockets; +using System; +using System.Collections.Generic; +using System.Text; + +namespace Bitfinex.Net.Objects.Sockets.Queries +{ + internal class BitfinexQuery : Query + { + public override List Identifiers { get; } + + public BitfinexQuery(string evnt, string channel, string? symbol, bool authenticated, int weight = 1) : base(new BitfinexRequest { Channel = channel, Event = evnt, Symbol = symbol }, authenticated, weight) + { + if (evnt == "subscribe" || evnt == "unsubscribe") + evnt += "d"; + + Identifiers = new List { evnt + channel + symbol }; + } + + } +} diff --git a/Bitfinex.Net/Objects/Sockets/Subscriptions/BitfinexSubscription.cs b/Bitfinex.Net/Objects/Sockets/Subscriptions/BitfinexSubscription.cs new file mode 100644 index 0000000..c68af6c --- /dev/null +++ b/Bitfinex.Net/Objects/Sockets/Subscriptions/BitfinexSubscription.cs @@ -0,0 +1,55 @@ +using Bitfinex.Net.Objects.Sockets.Queries; +using CryptoExchange.Net.Objects; +using CryptoExchange.Net.Objects.Sockets; +using CryptoExchange.Net.Sockets; +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; + +namespace Bitfinex.Net.Objects.Sockets.Subscriptions +{ + internal class BitfinexSubscription : Subscription> + { + private string _channel; + private string _symbol; + private int _channelId; + private Action> _handler; + + private List _identifiers; + + public override List Identifiers => _identifiers; + + public BitfinexSubscription(ILogger logger, string channel, string symbol, Action> handler, bool authenticated) : base(logger, authenticated) + { + _handler = handler; + _channel = channel; + _symbol = symbol; + } + + public override void HandleSubQueryResponse(ParsedMessage message) + { + _channelId = message.TypedData.ChannelId.Value; + + // Doesn't work, as the subscription is immediately added along with the identifiers + // Would maybe be better to wait with adding the subscription untill it is confirmed? + _identifiers = new List { _channelId.ToString() }; + } + + public override BaseQuery? GetSubQuery(SocketConnection connection) + { + return new BitfinexQuery("subscribe", _channel, _symbol, Authenticated); + } + public override BaseQuery? GetUnsubQuery() + { + return new BitfinexQuery("unsubscribe", _channel, _symbol, Authenticated); + } + + public override Task HandleEventAsync(SocketConnection connection, DataEvent>> message) + { + _handler.Invoke(message.As(message.Data.TypedData.Data)); // TODo + return Task.FromResult(new CallResult(null)); + } + } +} diff --git a/Bitfinex.Net/SymbolOrderBooks/BitfinexSymbolOrderBook.cs b/Bitfinex.Net/SymbolOrderBooks/BitfinexSymbolOrderBook.cs index e55400d..0c95cf2 100644 --- a/Bitfinex.Net/SymbolOrderBooks/BitfinexSymbolOrderBook.cs +++ b/Bitfinex.Net/SymbolOrderBooks/BitfinexSymbolOrderBook.cs @@ -12,6 +12,7 @@ using Bitfinex.Net.Objects.Options; using CryptoExchange.Net.Interfaces; using CryptoExchange.Net.Objects; +using CryptoExchange.Net.Objects.Sockets; using CryptoExchange.Net.OrderBook; using CryptoExchange.Net.Sockets; using Force.Crc32; @@ -71,23 +72,24 @@ public BitfinexSymbolOrderBook(string symbol, /// protected override async Task> DoStartAsync(CancellationToken ct) { - if(_precision == Precision.R0) - throw new ArgumentException("Invalid precision: R0"); + return new CallResult(new ServerError(null)); + //if(_precision == Precision.R0) + // throw new ArgumentException("Invalid precision: R0"); - var result = await _socketClient.SpotApi.SubscribeToOrderBookUpdatesAsync(Symbol, _precision, Frequency.Realtime, Levels!.Value, ProcessUpdate, ProcessChecksum).ConfigureAwait(false); - if (!result) - return result; + //var result = await _socketClient.SpotApi.SubscribeToOrderBookUpdatesAsync(Symbol, _precision, Frequency.Realtime, Levels!.Value, ProcessUpdate, ProcessChecksum).ConfigureAwait(false); + //if (!result) + // return result; - if (ct.IsCancellationRequested) - { - await result.Data.CloseAsync().ConfigureAwait(false); - return result.AsError(new CancellationRequestedError()); - } + //if (ct.IsCancellationRequested) + //{ + // await result.Data.CloseAsync().ConfigureAwait(false); + // return result.AsError(new CancellationRequestedError()); + //} - Status = OrderBookStatus.Syncing; + //Status = OrderBookStatus.Syncing; - var setResult = await WaitForSetOrderBookAsync(_initialDataTimeout, ct).ConfigureAwait(false); - return setResult ? result : new CallResult(setResult.Error!); + //var setResult = await WaitForSetOrderBookAsync(_initialDataTimeout, ct).ConfigureAwait(false); + //return setResult ? result : new CallResult(setResult.Error!); } /// From d69fdc8ce7b0d41421d79bcc1b34cf3999f9d706 Mon Sep 17 00:00:00 2001 From: JKorf Date: Tue, 12 Dec 2023 21:37:54 +0100 Subject: [PATCH 02/25] wip --- Bitfinex.Net/Bitfinex.Net.xml | 13 + Bitfinex.Net/Clients/BitfinexSocketClient.cs | 13 +- .../SpotApi/BitfinexSocketClientSpotApi.cs | 1787 ++++++++--------- .../SpotApi/IBitfinexSocketClientSpotApi.cs | 18 +- .../Models/Socket/BitfinexSocketInfo.cs | 25 + .../Objects/Sockets/BitfinexRequest.cs | 3 - .../Objects/Sockets/BitfinexResponse.cs | 3 - .../Sockets/BitfinexSocketConverter.cs | 6 +- .../Objects/Sockets/BitfinexUpdate.cs | 3 - .../Objects/Sockets/Queries/BitfinexQuery.cs | 2 - .../BitfinexHeartbeatSubscription.cs | 23 + .../Subscriptions/BitfinexInfoSubscription.cs | 24 + .../Subscriptions/BitfinexSubscription.cs | 1 - 13 files changed, 988 insertions(+), 933 deletions(-) create mode 100644 Bitfinex.Net/Objects/Models/Socket/BitfinexSocketInfo.cs create mode 100644 Bitfinex.Net/Objects/Sockets/Subscriptions/BitfinexHeartbeatSubscription.cs create mode 100644 Bitfinex.Net/Objects/Sockets/Subscriptions/BitfinexInfoSubscription.cs diff --git a/Bitfinex.Net/Bitfinex.Net.xml b/Bitfinex.Net/Bitfinex.Net.xml index a0a11e7..21e30c9 100644 --- a/Bitfinex.Net/Bitfinex.Net.xml +++ b/Bitfinex.Net/Bitfinex.Net.xml @@ -497,6 +497,9 @@ + + + Socket event types @@ -2389,6 +2392,16 @@ Cancellation token for closing this subscription + + + Subscribes to funding ticker updates a symbol. Use SubscribeToTickerUpdatesAsync for trade symbol ticker updates + + + The symbol to subscribe to + The handler for the data + Cancellation token for closing this subscription + + Bitfinex order book factory diff --git a/Bitfinex.Net/Clients/BitfinexSocketClient.cs b/Bitfinex.Net/Clients/BitfinexSocketClient.cs index ae267a0..547e82a 100644 --- a/Bitfinex.Net/Clients/BitfinexSocketClient.cs +++ b/Bitfinex.Net/Clients/BitfinexSocketClient.cs @@ -1,17 +1,6 @@ -using Bitfinex.Net.Objects; -using CryptoExchange.Net; -using CryptoExchange.Net.Objects; -using CryptoExchange.Net.Sockets; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; +using CryptoExchange.Net; using System; -using System.Globalization; -using System.Linq; -using System.Threading.Tasks; using Microsoft.Extensions.Logging; -using Bitfinex.Net.Enums; -using System.Threading; -using Bitfinex.Net.Objects.Internal; using Bitfinex.Net.Interfaces.Clients; using Bitfinex.Net.Interfaces.Clients.SpotApi; using Bitfinex.Net.Clients.SpotApi; diff --git a/Bitfinex.Net/Clients/SpotApi/BitfinexSocketClientSpotApi.cs b/Bitfinex.Net/Clients/SpotApi/BitfinexSocketClientSpotApi.cs index 7595ea4..7a7fb58 100644 --- a/Bitfinex.Net/Clients/SpotApi/BitfinexSocketClientSpotApi.cs +++ b/Bitfinex.Net/Clients/SpotApi/BitfinexSocketClientSpotApi.cs @@ -1,20 +1,13 @@ using Bitfinex.Net.Converters; using CryptoExchange.Net; using CryptoExchange.Net.Objects; -using CryptoExchange.Net.Sockets; using Newtonsoft.Json; -using Newtonsoft.Json.Linq; using System; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; using System.Threading.Tasks; using Microsoft.Extensions.Logging; using CryptoExchange.Net.Authentication; -using Bitfinex.Net.Enums; using System.Threading; using Bitfinex.Net.Objects.Internal; -using Bitfinex.Net.Objects.Models; using Bitfinex.Net.Objects.Models.Socket; using Bitfinex.Net.Interfaces.Clients.SpotApi; using Bitfinex.Net.Objects.Options; @@ -22,6 +15,7 @@ using CryptoExchange.Net.Objects.Sockets; using Bitfinex.Net.Objects.Sockets; using Bitfinex.Net.Objects.Sockets.Subscriptions; +using Bitfinex.Net.Objects.Models; namespace Bitfinex.Net.Clients.SpotApi { @@ -47,10 +41,11 @@ internal BitfinexSocketClientSpotApi(ILogger logger, BitfinexSocketOptions optio ContinueOnQueryResponse = true; UnhandledMessageExpected = true; - //AddGenericHandler("HB", (messageEvent) => { }); - //AddGenericHandler("Info", InfoHandler); //AddGenericHandler("Conf", ConfHandler); + AddSystemSubscription(new BitfinexInfoSubscription(_logger)); + AddSystemSubscription(new BitfinexHeartbeatSubscription(_logger)); + _affCode = options.AffiliateCode; _bookSerializer.Converters.Add(new OrderBookEntryConverter()); _fundingBookSerializer.Converters.Add(new OrderBookFundingEntryConverter()); @@ -69,6 +64,15 @@ public async Task> SubscribeToTickerUpdatesAsync( var subscription = new BitfinexSubscription(_logger, "ticker", symbol, handler, false); return await SubscribeAsync(BaseAddress.AppendPath("ws/2"), subscription, ct).ConfigureAwait(false); + } + + /// + public async Task> SubscribeToFundingTickerUpdatesAsync(string symbol, Action> handler, CancellationToken ct = default) + { + symbol.ValidateBitfinexFundingSymbol(); + + var subscription = new BitfinexSubscription(_logger, "ticker", symbol, handler, false); + return await SubscribeAsync(BaseAddress.AppendPath("ws/2"), subscription, ct).ConfigureAwait(false); //var internalHandler = new Action>(data => //{ @@ -77,893 +81,882 @@ public async Task> SubscribeToTickerUpdatesAsync( //return await SubscribeAsync(BaseAddress.AppendPath("ws/2"), new BitfinexSubscriptionRequest("ticker", symbol), null, false, internalHandler, ct).ConfigureAwait(false); } -// /// -// public async Task> SubscribeToFundingTickerUpdatesAsync(string symbol, Action> handler, CancellationToken ct = default) -// { -// symbol.ValidateBitfinexFundingSymbol(); -// var internalHandler = new Action>(data => -// { -// HandleData("Ticker", (JArray)data.Data[1]!, symbol, data, handler); -// }); -// return await SubscribeAsync(BaseAddress.AppendPath("ws/2"), new BitfinexSubscriptionRequest("ticker", symbol), null, false, internalHandler, ct).ConfigureAwait(false); -// } - -// /// -// public async Task> SubscribeToOrderBookUpdatesAsync(string symbol, Precision precision, Frequency frequency, int length, Action>> handler, Action>? checksumHandler = null, CancellationToken ct = default) -// { -// symbol.ValidateBitfinexTradingSymbol(); -// length.ValidateIntValues(nameof(length), 1, 25, 100, 250); -// if (precision == Precision.R0) -// throw new ArgumentException("Invalid precision R0, use SubscribeToRawBookUpdatesAsync instead"); - -// var internalHandler = new Action>(data => -// { -// if (data.Data[1]?.ToString() == "cs") -// { -// // Process -// checksumHandler?.Invoke(data.As(data.Data[2]!.Value(), symbol)); -// } -// else -// { -// var dataArray = (JArray)data.Data[1]!; -// if (dataArray.Count == 0) -// // Empty array -// return; - -// if (dataArray[0].Type == JTokenType.Array) -// { -// HandleData("Book snapshot", dataArray, symbol, data, handler, _bookSerializer); -// } -// else -// { -// HandleSingleToArrayData("Book update", dataArray, symbol, data, handler, _bookSerializer); -// } -// } -// }); - -// var sub = new BitfinexBookSubscriptionRequest( -// symbol, -// JsonConvert.SerializeObject(precision, new PrecisionConverter(false)), -// JsonConvert.SerializeObject(frequency, new FrequencyConverter(false)), -// length); -// return await SubscribeAsync(BaseAddress.AppendPath("ws/2"), sub, null, false, internalHandler, ct).ConfigureAwait(false); -// } - -// /// -// public async Task> SubscribeToFundingOrderBookUpdatesAsync(string symbol, Precision precision, Frequency frequency, int length, Action>> handler, Action>? checksumHandler = null, CancellationToken ct = default) -// { -// symbol.ValidateBitfinexFundingSymbol(); -// length.ValidateIntValues(nameof(length), 1, 25, 100, 250); -// if (precision == Precision.R0) -// throw new ArgumentException("Invalid precision R0, use SubscribeToFundingRawOrderBookUpdatesAsync instead"); - -// var internalHandler = new Action>(data => -// { -// if (data.Data[1]?.ToString() == "cs") -// { -// // Process -// checksumHandler?.Invoke(data.As(data.Data[2]!.Value(), symbol)); -// } -// else -// { -// var dataArray = (JArray)data.Data[1]!; -// if (dataArray.Count == 0) -// // Empty array -// return; - -// if (dataArray[0].Type == JTokenType.Array) -// { -// HandleData("Book snapshot", dataArray, symbol, data, handler, _fundingBookSerializer); -// } -// else -// { -// HandleSingleToArrayData("Book update", dataArray, symbol, data, handler, _fundingBookSerializer); -// } -// } -// }); - -// var sub = new BitfinexBookSubscriptionRequest( -// symbol, -// JsonConvert.SerializeObject(precision, new PrecisionConverter(false)), -// JsonConvert.SerializeObject(frequency, new FrequencyConverter(false)), -// length); -// return await SubscribeAsync(BaseAddress.AppendPath("ws/2"), sub, null, false, internalHandler, ct).ConfigureAwait(false); -// } - -// /// -// public async Task> SubscribeToRawOrderBookUpdatesAsync(string symbol, int limit, Action>> handler, Action>? checksumHandler = null, CancellationToken ct = default) -// { -// symbol.ValidateBitfinexTradingSymbol(); -// var internalHandler = new Action>(data => -// { -// if (data.Data[1]?.ToString() == "cs") -// { -// // Process -// checksumHandler?.Invoke(data.As(data.Data[2]!.Value(), symbol)); -// } -// else -// { -// var dataArray = (JArray)data.Data[1]!; -// if (dataArray[0].Type == JTokenType.Array) -// HandleData("Raw book snapshot", dataArray, symbol, data, handler); -// else -// HandleSingleToArrayData("Raw book update", dataArray, symbol, data, handler); -// } -// }); -// return await SubscribeAsync(BaseAddress.AppendPath("ws/2"), new BitfinexRawBookSubscriptionRequest(symbol, "R0", limit), null, false, internalHandler, ct).ConfigureAwait(false); -// } - -// /// -// public async Task> SubscribeToRawFundingOrderBookUpdatesAsync(string symbol, int limit, Action>> handler, Action>? checksumHandler = null, CancellationToken ct = default) -// { -// symbol.ValidateBitfinexFundingSymbol(); -// var internalHandler = new Action>(data => -// { -// if (data.Data[1]?.ToString() == "cs") -// { -// // Process -// checksumHandler?.Invoke(data.As(data.Data[2]!.Value(), symbol)); -// } -// else -// { -// var dataArray = (JArray)data.Data[1]!; -// if (dataArray[0].Type == JTokenType.Array) -// HandleData("Raw book snapshot", dataArray, symbol, data, handler); -// else -// HandleSingleToArrayData("Raw book update", dataArray, symbol, data, handler); -// } -// }); -// return await SubscribeAsync(BaseAddress.AppendPath("ws/2"), new BitfinexRawBookSubscriptionRequest(symbol, "R0", limit), null, false, internalHandler, ct).ConfigureAwait(false); -// } - -// /// -// public async Task> SubscribeToTradeUpdatesAsync(string symbol, Action>> handler, CancellationToken ct = default) -// { -// var internalHandler = new Action>(data => -// { -// var arr = (JArray)data.Data; -// if (arr[1].Type == JTokenType.Array) -// { -// HandleData("Trade snapshot", (JArray)arr[1], symbol, data, handler); -// } -// else -// { -// var desResult = Deserialize(arr[2]); -// if (!desResult) -// { -// _logger.Log(LogLevel.Warning, "Failed to deserialize trade object: " + desResult.Error); -// return; -// } -// desResult.Data.UpdateType = BitfinexEvents.EventMapping[arr[1].ToString()]; -// handler(data.As>(new[] { desResult.Data }, symbol)); -// } -// }); -// return await SubscribeAsync(BaseAddress.AppendPath("ws/2"), new BitfinexSubscriptionRequest("trades", symbol), null, false, internalHandler, ct).ConfigureAwait(false); -// } - -// /// -// public async Task> SubscribeToKlineUpdatesAsync(string symbol, KlineInterval interval, Action>> handler, CancellationToken ct = default) -// { -// var internalHandler = new Action>(data => -// { -// var dataArray = (JArray)data.Data[1]!; -// if (dataArray.Count == 0) -// { -// _logger.Log(LogLevel.Warning, "No data in kline update, check if the symbol is correct"); -// return; -// } - -// if (dataArray[0].Type == JTokenType.Array) -// HandleData("Kline snapshot", dataArray, symbol, data, handler); -// else -// HandleSingleToArrayData("Kline update", dataArray, symbol, data, handler); -// }); -// return await SubscribeAsync(BaseAddress.AppendPath("ws/2"), new BitfinexKlineSubscriptionRequest(symbol, JsonConvert.SerializeObject(interval, new KlineIntervalConverter(false))), null, false, internalHandler, ct).ConfigureAwait(false); -// } - -// /// -// public async Task> SubscribeToLiquidationUpdatesAsync(Action>> handler, CancellationToken ct = default) -// { -// var internalHandler = new Action>(data => -// { -// HandleData("Liquidation", (JArray)data.Data[1]!, null, data, handler); -// }); -// return await SubscribeAsync(BaseAddress.AppendPath("ws/2"), new BitfinexLiquidationSubscriptionRequest(), null, false, internalHandler, ct).ConfigureAwait(false); -// } - -// /// -// public async Task> SubscribeToDerivativesUpdatesAsync(string symbol, Action> handler, CancellationToken ct = default) -// { -// var internalHandler = new Action>(data => -// { -// HandleData("DerivativeUpdate", (JArray)data.Data[1]!, symbol, data, handler); -// }); -// return await SubscribeAsync(BaseAddress.AppendPath("ws/2"), new BitfinexDerivativesStatusSubscriptionRequest(symbol), null, false, internalHandler, ct).ConfigureAwait(false); -// } - -// /// -// public async Task> SubscribeToUserTradeUpdatesAsync( -// Action>>> orderHandler, -// Action>>> tradeHandler, -// Action>>> positionHandler, -// CancellationToken ct = default) -// { -// var tokenHandler = new Action>(tokenData => -// { -// HandleAuthUpdate(tokenData, orderHandler, "Orders"); -// HandleAuthUpdate(tokenData, tradeHandler, "Trades"); -// HandleAuthUpdate(tokenData, positionHandler, "Positions"); -// }); - -// return await SubscribeAsync(BaseAddress.AppendPath("ws/2"), null, "Orders|Trades|Positions", true, tokenHandler, ct).ConfigureAwait(false); -// } - -// /// -// public async Task> SubscribeToBalanceUpdatesAsync(Action>>> walletHandler, CancellationToken ct = default) -// { -// var tokenHandler = new Action>(tokenData => -// { -// HandleAuthUpdate(tokenData, walletHandler, "Wallet"); -// }); - -// return await SubscribeAsync(BaseAddress.AppendPath("ws/2"), null, "Wallet", true, tokenHandler, ct).ConfigureAwait(false); -// } - -// /// -// public async Task> SubscribeToFundingUpdatesAsync( -// Action>>> fundingOfferHandler, -// Action>>> fundingCreditHandler, -// Action>>> fundingLoanHandler, -// CancellationToken ct = default) -// { -// var tokenHandler = new Action>(tokenData => -// { -// HandleAuthUpdate(tokenData, fundingOfferHandler, "FundingOffers"); -// HandleAuthUpdate(tokenData, fundingCreditHandler, "FundingCredits"); -// HandleAuthUpdate(tokenData, fundingLoanHandler, "FundingLoans"); -// }); - -// return await SubscribeAsync(BaseAddress.AppendPath("ws/2"), null, "FundingOffers|FundingCredits|FundingLoans", true, tokenHandler, ct).ConfigureAwait(false); -// } - -// /// -// public async Task> PlaceOrderAsync(OrderSide side, OrderType type, string symbol, decimal quantity, long? groupId = null, long? clientOrderId = null, decimal? price = null, decimal? priceTrailing = null, decimal? priceAuxiliaryLimit = null, decimal? priceOcoStop = null, OrderFlags? flags = null, int? leverage = null, DateTime? cancelTime = null, string? affiliateCode = null) -// { -// symbol.ValidateBitfinexSymbol(); -// _logger.Log(LogLevel.Information, "Going to place order"); -// clientOrderId ??= GenerateClientOrderId(); - -// var affCode = affiliateCode ?? _affCode; -// var query = new BitfinexSocketQuery(clientOrderId.ToString(), BitfinexEventType.OrderNew, new BitfinexNewOrder -// { -// Amount = side == OrderSide.Buy ? quantity : -quantity, -// OrderType = type, -// Symbol = symbol, -// Price = price, -// ClientOrderId = clientOrderId, -// Flags = flags, -// GroupId = groupId, -// PriceAuxiliaryLimit = priceAuxiliaryLimit, -// PriceOCOStop = priceOcoStop, -// PriceTrailing = priceTrailing, -// Leverage = leverage, -// CancelAfter = cancelTime, -// Meta = affCode == null ? null : new BitfinexMeta() { AffiliateCode = affCode } -// }); - -// return await QueryAsync(BaseAddress.AppendPath("ws/2"), query, true).ConfigureAwait(false); -// } - -// /// -// public async Task> UpdateOrderAsync(long orderId, decimal? price = null, decimal? quantity = null, decimal? delta = null, decimal? priceAuxiliaryLimit = null, decimal? priceTrailing = null, OrderFlags? flags = null) -// { -// _logger.Log(LogLevel.Information, "Going to update order " + orderId); -// var query = new BitfinexSocketQuery(orderId.ToString(CultureInfo.InvariantCulture), BitfinexEventType.OrderUpdate, new BitfinexUpdateOrder -// { -// OrderId = orderId, -// Amount = quantity, -// Price = price, -// Flags = flags, -// PriceAuxiliaryLimit = priceAuxiliaryLimit?.ToString(CultureInfo.InvariantCulture), -// PriceTrailing = priceTrailing?.ToString(CultureInfo.InvariantCulture) -// }); - -// return await QueryAsync(BaseAddress.AppendPath("ws/2"), query, true).ConfigureAwait(false); -// } - -// ///// -// ///// Cancel all open orders -// ///// -// ///// -// //public CallResult CancelAllOrders() => CancelAllOrdersAsync().Result; -// ///// -// ///// Cancel all open orders -// ///// -// ///// -// //public async Task> CancelAllOrdersAsync() -// //{ -// // // Doesn't seem to work even though it is implemented as described at https://docs.bitfinex.com/v2/reference#ws-input-order-cancel-multi -// // _log.Write(LogLevel.Information, "Going to cancel all orders"); -// // var query = new BitfinexSocketQuery(null, BitfinexEventType.OrderCancelMulti, new BitfinexMultiCancel { All = true }); - -// // return await Query(query, true).ConfigureAwait(false); -// //} - -// /// -// public async Task> CancelOrderAsync(long orderId) -// { -// _logger.Log(LogLevel.Information, "Going to cancel order " + orderId); -// var query = new BitfinexSocketQuery(orderId.ToString(CultureInfo.InvariantCulture), BitfinexEventType.OrderCancel, new JObject { ["id"] = orderId }); - -// return await QueryAsync(BaseAddress.AppendPath("ws/2"), query, true).ConfigureAwait(false); -// } - -// /// -// public async Task> CancelOrdersByGroupIdAsync(long groupOrderId) -// { -// return await CancelOrdersAsync(null, null, new Dictionary { { groupOrderId, null } }).ConfigureAwait(false); -// } - -// /// -// public async Task> CancelOrdersByGroupIdsAsync(IEnumerable groupOrderIds) -// { -// groupOrderIds.ValidateNotNull(nameof(groupOrderIds)); -// return await CancelOrdersAsync(null, null, groupOrderIds.ToDictionary(v => v, k => (long?)null)).ConfigureAwait(false); -// } - -// /// -// public async Task> CancelOrdersAsync(IEnumerable orderIds) -// { -// orderIds.ValidateNotNull(nameof(orderIds)); -// return await CancelOrdersAsync(orderIds, null).ConfigureAwait(false); -// } - -// /// -// public async Task> CancelOrdersByClientOrderIdsAsync(Dictionary clientOrderIds) -// { -// return await CancelOrdersAsync(null, clientOrderIds).ConfigureAwait(false); -// } - -// /// -// public async Task> SubmitFundingOfferAsync(FundingOfferType type, string symbol, decimal quantity, decimal price, int period, int? flags = null) -// { -// var parameters = new Dictionary -// { -// { "type", EnumConverter.GetString(type) }, -// { "symbol", symbol }, -// { "amount", quantity }, -// { "rate", price }, -// { "period", period }, -// }; -// parameters.AddOptionalParameter("flags", flags); - -// var query = new BitfinexSocketQuery(ExchangeHelpers.NextId().ToString(CultureInfo.InvariantCulture), BitfinexEventType.FundingOfferNew, parameters); -// return await QueryAsync(BaseAddress.AppendPath("ws/2"), query, true).ConfigureAwait(false); -// } - -// /// -// public async Task> CancelFundingOfferAsync(long id) -// { -// var parameters = new Dictionary -// { -// { "id", id } -// }; - -// var query = new BitfinexSocketQuery(id.ToString(CultureInfo.InvariantCulture), BitfinexEventType.FundingOfferCancel, parameters); -// return await QueryAsync(BaseAddress.AppendPath("ws/2"), query, true).ConfigureAwait(false); -// } -// #endregion - -// #region private methods -// private void HandleData(string name, JArray dataArray, string? symbol, DataEvent dataEvent, Action> handler, JsonSerializer? serializer = null) -// { -// var desResult = Deserialize(dataArray, serializer: serializer); -// if (!desResult) -// { -// _logger.Log(LogLevel.Warning, $"Failed to Deserialize {name} object: " + desResult.Error); -// return; -// } - -// handler(dataEvent.As(desResult.Data, symbol)); -// } - -// private void HandleSingleToArrayData(string name, JArray dataArray, string? symbol, DataEvent dataEvent, Action>> handler, JsonSerializer? serializer = null) -// { -// var wrapperArray = new JArray { dataArray }; - -// var desResult = Deserialize>(wrapperArray, serializer: serializer); -// if (!desResult) -// { -// _logger.Log(LogLevel.Warning, $"Failed to Deserialize {name} object: " + desResult.Error); -// return; -// } - -// handler(dataEvent.As(desResult.Data, symbol)); -// } - -// private async Task> CancelOrdersAsync(IEnumerable? orderIds = null, Dictionary? clientOrderIds = null, Dictionary? groupOrderIds = null) -// { -// if (orderIds == null && clientOrderIds == null && groupOrderIds == null) -// throw new ArgumentException("Either orderIds, clientOrderIds or groupOrderIds should be provided"); - -// _logger.Log(LogLevel.Information, "Going to cancel multiple orders"); -// var cancelObject = new BitfinexMultiCancel { OrderIds = orderIds }; -// if (clientOrderIds != null) -// { -// cancelObject.ClientIds = new object[clientOrderIds.Count][]; -// for (var i = 0; i < cancelObject.ClientIds.Length; i++) -// { -// cancelObject.ClientIds[i] = new object[] -// { -// clientOrderIds.ElementAt(i).Key, -// clientOrderIds.ElementAt(i).Value.ToString("yyyy-MM-dd") -// }; -// } -// } -// if (groupOrderIds != null) -// cancelObject.GroupIds = new[] { groupOrderIds.Select(g => g.Key).ToArray() }; - -// var query = new BitfinexSocketQuery(null, BitfinexEventType.OrderCancelMulti, cancelObject); -// return await QueryAsync(query, true).ConfigureAwait(false); -// } - -// private void HandleAuthUpdate(DataEvent token, Action>>> action, string category) -// { -// var evntStr = token.Data[1]?.ToString(); -// if (evntStr == null) -// return; - -// var evntType = BitfinexEvents.EventMapping[evntStr]; -// var evnt = BitfinexEvents.Events.Single(e => e.EventType == evntType); -// if (evnt.Category != category) -// return; - -// if (action == null) -// { -// _logger.Log(LogLevel.Debug, $"Ignoring {evnt.EventType} event because not subscribed"); -// return; -// } - -// IEnumerable data; -// if (evnt.Single) -// { -// var result = Deserialize(token.Data[2]!); -// if (!result) -// { -// _logger.Log(LogLevel.Warning, "Failed to Deserialize data: " + result.Error); -// return; -// } -// data = new[] { result.Data }; -// } -// else -// { -// var result = Deserialize>(token.Data[2]!); -// if (!result) -// { -// _logger.Log(LogLevel.Warning, "Failed to Deserialize data: " + result.Error); -// return; -// } -// data = result.Data; -// } - -// action(token.As(new BitfinexSocketEvent>(evntType, data))); -// } - -// private long GenerateClientOrderId() -// { -// var buffer = new byte[8]; -// _random.NextBytes(buffer); -// return (long)Math.Round(Math.Abs(BitConverter.ToInt32(buffer, 0)) / 1000m); -// } - - -// private void ConfHandler(MessageEvent messageEvent) -// { -// var confEvent = messageEvent.JsonData.Type == JTokenType.Object && messageEvent.JsonData["event"]?.ToString() == "conf"; -// if (!confEvent) -// return; - -// // Could check conf result; -// } - -// private void InfoHandler(MessageEvent messageEvent) -// { -// var infoEvent = messageEvent.JsonData.Type == JTokenType.Object && messageEvent.JsonData["event"]?.ToString() == "info"; -// if (!infoEvent) -// return; - -// _logger.Log(LogLevel.Debug, $"Socket {messageEvent.Connection.SocketId} Info event received: {messageEvent.JsonData}"); -// if (messageEvent.JsonData["code"] == null) -// { -// // welcome event, send a config message for receiving checsum updates for order book subscriptions -// messageEvent.Connection.Send(ExchangeHelpers.NextId(), new BitfinexSocketConfig { Event = "conf", Flags = 131072 }, 1); -// return; -// } - -// var code = messageEvent.JsonData["code"]?.Value(); -// switch (code) -// { -// case 20051: -// _logger.Log(LogLevel.Information, $"Socket {messageEvent.Connection.SocketId} Code {code} received, reconnecting socket"); -// messageEvent.Connection.PausedActivity = true; // Prevent new operations to be send -// _ = messageEvent.Connection.TriggerReconnectAsync(); -// break; -// case 20060: -// _logger.Log(LogLevel.Information, $"Socket {messageEvent.Connection.SocketId} Code {code} received, entering maintenance mode"); -// messageEvent.Connection.PausedActivity = true; -// break; -// case 20061: -// _logger.Log(LogLevel.Information, $"Socket {messageEvent.Connection.SocketId} Code {code} received, leaving maintenance mode. Reconnecting/Resubscribing socket."); -// _ = messageEvent.Connection.TriggerReconnectAsync(); // Closing it via socket will automatically reconnect -// break; -// default: -// _logger.Log(LogLevel.Warning, $"Socket {messageEvent.Connection.SocketId} Unknown info code received: {code}"); -// break; -// } -// } - - -// /// -// protected override async Task UnsubscribeAsync(SocketConnection connection, SocketSubscription subscription) -// { -// if (subscription.Request == null) -// { -// // If we don't have a request object we can't unsubscribe it. Probably is an auth subscription which gets pushed regardless -// // Just returning true here will remove the handler and close the socket if there are no other handlers left on the socket, which is the best we can do -// return true; -// } - -// var channelId = ((BitfinexSubscriptionRequest)subscription.Request!).ChannelId; -// var unsub = new BitfinexUnsubscribeRequest(channelId); -// var result = false; -// await connection.SendAndWaitAsync(unsub, ClientOptions.RequestTimeout, null, 1, data => -// { -// if (data.Type != JTokenType.Object) -// return false; - -// var evnt = data["event"]?.ToString(); -// var channel = data["chanId"]?.ToString(); -// if (evnt == null || channel == null) -// return false; - -// if (!int.TryParse(channel, out var chan)) -// return false; - -// result = evnt == "unsubscribed" && channelId == chan; -// return result; -// }).ConfigureAwait(false); -// return result; -// } - -// private static BitfinexAuthentication GetAuthObject(SocketApiClient apiClient, params string[] filter) -// { -// var authProvider = (BitfinexAuthenticationProvider)apiClient.AuthenticationProvider!; -// var n = authProvider.GetNonce().ToString(); -// var authentication = new BitfinexAuthentication -// { -// Event = "auth", -// ApiKey = authProvider.GetApiKey(), -// Nonce = n, -// Payload = "AUTH" + n -// }; -// if (filter.Any()) -// authentication.Filter = filter; -// authentication.Signature = authProvider.Sign(authentication.Payload).ToLower(CultureInfo.InvariantCulture); -// return authentication; -// } - -// #endregion - -// /// -// protected override async Task> AuthenticateSocketAsync(SocketConnection s) -// { -// if (s.ApiClient.AuthenticationProvider == null) -// return new CallResult(new NoApiCredentialsError()); - -// var authObject = GetAuthObject(s.ApiClient); -// var result = new CallResult(new ServerError("No response from server")); -// await s.SendAndWaitAsync(authObject, ClientOptions.RequestTimeout, null, 1, tokenData => -// { -// if (tokenData.Type != JTokenType.Object) -// return false; - -// if (tokenData["event"]?.ToString() != "auth") -// return false; - -// var authResponse = Deserialize(tokenData); -// if (!authResponse) -// { -// _logger.Log(LogLevel.Warning, $"Socket {s.SocketId} authentication failed: " + authResponse.Error); -// result = new CallResult(authResponse.Error!); -// return false; -// } - -// if (authResponse.Data.Status != "OK") -// { -// var error = new ServerError(authResponse.Data.ErrorCode, authResponse.Data.ErrorMessage ?? "-"); -// result = new CallResult(error); -// _logger.Log(LogLevel.Debug, $"Socket {s.SocketId} authentication failed: " + error); -// return false; -// } - -// _logger.Log(LogLevel.Debug, $"Socket {s.SocketId} authentication completed"); -// result = new CallResult(true); -// return true; -// }).ConfigureAwait(false); - -// return result; -// } - -// /// -//#pragma warning disable 8765 -// protected override bool HandleQueryResponse(SocketConnection s, object request, JToken data, out CallResult? callResult) -//#pragma warning restore 8765 -// { -// callResult = null; -// if (data.Type != JTokenType.Array) -// return false; - -// var array = (JArray)data; -// if (array.Count < 3) -// return false; - -// var bfRequest = (BitfinexSocketQuery)request; -// var evntString = data[1]!.ToString(); -// if (!BitfinexEvents.EventMapping.TryGetValue(evntString, out var eventType)) -// return false; - -// if (eventType == BitfinexEventType.Notification) -// { -// var notificationData = (JArray)data[2]!; -// var notificationType = BitfinexEvents.EventMapping[notificationData[1].ToString()]; -// if (notificationType != BitfinexEventType.OrderNewRequest -// && notificationType != BitfinexEventType.OrderCancelRequest -// && notificationType != BitfinexEventType.OrderUpdateRequest -// && notificationType != BitfinexEventType.OrderCancelMultiRequest -// && notificationType != BitfinexEventType.FundingOfferNewRequest -// && notificationType != BitfinexEventType.FundingOfferCancelRequest) -// { -// return false; -// } - -// var statusString = (notificationData[6].ToString()).ToLower(CultureInfo.InvariantCulture); -// if (statusString == "error") -// { -// if (bfRequest.QueryType == BitfinexEventType.OrderNew && notificationType == BitfinexEventType.OrderNewRequest) -// { -// var orderData = notificationData[4]; -// if (orderData[2]?.ToString() != bfRequest.Id) -// return false; - -// callResult = new CallResult(new ServerError(notificationData[7].ToString())); -// return true; -// } - -// if (bfRequest.QueryType == BitfinexEventType.OrderCancel && notificationType == BitfinexEventType.OrderCancelRequest) -// { -// var orderData = notificationData[4]; -// if (orderData[0]?.ToString() != bfRequest.Id) -// return false; - -// callResult = new CallResult(new ServerError(notificationData[7].ToString())); -// return true; -// } - -// if (bfRequest.QueryType == BitfinexEventType.OrderUpdate && notificationType == BitfinexEventType.OrderUpdateRequest) -// { -// // OrderUpdateRequest not found notification doesn't carry the order id, where as OrderCancelRequest not found notification does.. -// // Anyway, can't check for ids, so just assume its for this one - -// callResult = new CallResult(new ServerError(notificationData[7].ToString())); -// return true; -// } - -// if (bfRequest.QueryType == BitfinexEventType.OrderCancelMulti && notificationType == BitfinexEventType.OrderCancelMultiRequest) -// { -// callResult = new CallResult(new ServerError(notificationData[7].ToString())); -// return true; -// } - -// if (bfRequest.QueryType == BitfinexEventType.FundingOfferNew && notificationType == BitfinexEventType.FundingOfferNewRequest) -// { -// callResult = new CallResult(new ServerError(notificationData[7].ToString())); -// return true; -// } - -// if (bfRequest.QueryType == BitfinexEventType.FundingOfferCancel && notificationType == BitfinexEventType.FundingOfferCancelRequest) -// { -// var fundingData = notificationData[4]; -// if (fundingData[0]?.ToString() != bfRequest.Id) -// return false; - -// callResult = new CallResult(new ServerError(notificationData[7].ToString())); -// return true; -// } -// } - -// if (notificationType == BitfinexEventType.OrderNewRequest -// || notificationType == BitfinexEventType.OrderUpdateRequest -// || notificationType == BitfinexEventType.OrderCancelRequest) -// { -// if (bfRequest.QueryType == BitfinexEventType.OrderNew -// || bfRequest.QueryType == BitfinexEventType.OrderUpdate -// || bfRequest.QueryType == BitfinexEventType.OrderCancel) -// { -// var orderData = notificationData[4]; -// var dataOrderId = orderData[0]?.ToString(); -// var dataOrderClientId = orderData[2]?.ToString(); -// if (dataOrderId == bfRequest.Id || dataOrderClientId == bfRequest.Id) -// { -// var desResult = Deserialize(orderData); -// if (!desResult) -// { -// callResult = new CallResult(desResult.Error!); -// return true; -// } - -// callResult = new CallResult(desResult.Data); -// return true; -// } -// } -// } - -// if (notificationType == BitfinexEventType.OrderCancelMultiRequest) -// { -// callResult = new CallResult(Deserialize(JToken.Parse("true")).Data); -// return true; -// } - -// if (notificationType == BitfinexEventType.FundingOfferNewRequest -// || notificationType == BitfinexEventType.FundingOfferCancelRequest) -// { -// if (bfRequest.QueryType == BitfinexEventType.FundingOfferCancelRequest) -// { -// var fundingData = notificationData[4]; -// var dataOrderId = fundingData[0]?.ToString(); -// if (dataOrderId == bfRequest.Id) -// { -// var desResult = Deserialize(fundingData); -// if (!desResult) -// { -// callResult = new CallResult(desResult.Error!); -// return true; -// } - -// callResult = new CallResult(desResult.Data); -// return true; -// } -// } -// else if(bfRequest.QueryType == BitfinexEventType.FundingOfferNew) -// { -// var fundingData = notificationData[4]; -// var desResult = Deserialize(fundingData); -// if (!desResult) -// { -// callResult = new CallResult(desResult.Error!); -// return true; -// } - -// callResult = new CallResult(desResult.Data); -// return true; -// } -// } -// } - -// if (bfRequest.QueryType == BitfinexEventType.OrderCancelMulti && eventType == BitfinexEventType.OrderCancel) -// { -// callResult = new CallResult(Deserialize(JToken.Parse("true")).Data); -// return true; -// } - -// return false; -// } - -// /// -// protected override bool HandleSubscriptionResponse(SocketConnection s, SocketSubscription subscription, object request, JToken data, out CallResult? callResult) -// { -// callResult = null; -// if (data.Type != JTokenType.Object) -// return false; - -// var infoEvent = data["event"]?.ToString() == "subscribed"; -// var errorEvent = data["event"]?.ToString() == "error"; -// if (!infoEvent && !errorEvent) -// return false; - -// if (infoEvent) -// { -// var subResponse = Deserialize(data); -// if (!subResponse) -// { -// callResult = new CallResult(subResponse.Error!); -// _logger.Log(LogLevel.Warning, $"Socket {s.SocketId} subscription failed: " + subResponse.Error); -// return false; -// } - -// var bRequest = (BitfinexSubscriptionRequest)request; -// if (!bRequest.CheckResponse(data)) -// return false; - -// bRequest.ChannelId = subResponse.Data.ChannelId; -// callResult = subResponse.As(subResponse.Data); -// return true; -// } -// else -// { -// var subResponse = Deserialize(data); -// if (!subResponse) -// { -// callResult = new CallResult(subResponse.Error!); -// _logger.Log(LogLevel.Warning, $"Socket {s.SocketId} subscription failed: " + subResponse.Error); -// return false; -// } - -// var error = new ServerError(subResponse.Data.Code, subResponse.Data.Message); -// callResult = new CallResult(error); -// _logger.Log(LogLevel.Debug, $"Socket {s.SocketId} subscription failed: " + error); -// return true; -// } -// } - -// /// -// protected override bool MessageMatchesHandler(SocketConnection socketConnection, JToken message, object request) -// { -// if (message.Type != JTokenType.Array) -// return false; - -// var array = (JArray)message; -// if (array.Count < 2) -// return false; - -// if (!int.TryParse(array[0].ToString(), out var channelId)) -// return false; - -// if (channelId == 0) -// return false; - -// var subId = ((BitfinexSubscriptionRequest)request).ChannelId; -// return channelId == subId && array[1].ToString() != "hb"; -// } - -// /// -// protected override bool MessageMatchesHandler(SocketConnection socketConnection, JToken message, string identifier) -// { -// if (message.Type == JTokenType.Object) -// { -// if (identifier == "Info") -// return message["event"]?.ToString() == "info"; -// if (identifier == "Conf") -// return message["event"]?.ToString() == "conf"; -// } - -// else if (message.Type == JTokenType.Array) -// { -// var array = (JArray)message; -// if (array.Count < 2) -// return false; - -// if (identifier == "HB") -// return array[1].ToString() == "hb"; - -// if (!int.TryParse(array[0].ToString(), out var channelId)) -// return false; - -// if (channelId != 0) -// return false; - -// var split = identifier.Split(new[] { "|" }, StringSplitOptions.RemoveEmptyEntries); -// foreach (var id in split) -// { -// var events = BitfinexEvents.GetEventsForCategory(id); -// var eventTypeString = array[1].ToString(); -// var eventType = BitfinexEvents.EventMapping[eventTypeString]; -// var evnt = events.SingleOrDefault(e => e.EventType == eventType); -// if (evnt != null) -// return true; -// } -// } - -// return false; -// } + // /// + // public async Task> SubscribeToOrderBookUpdatesAsync(string symbol, Precision precision, Frequency frequency, int length, Action>> handler, Action>? checksumHandler = null, CancellationToken ct = default) + // { + // symbol.ValidateBitfinexTradingSymbol(); + // length.ValidateIntValues(nameof(length), 1, 25, 100, 250); + // if (precision == Precision.R0) + // throw new ArgumentException("Invalid precision R0, use SubscribeToRawBookUpdatesAsync instead"); + + // var internalHandler = new Action>(data => + // { + // if (data.Data[1]?.ToString() == "cs") + // { + // // Process + // checksumHandler?.Invoke(data.As(data.Data[2]!.Value(), symbol)); + // } + // else + // { + // var dataArray = (JArray)data.Data[1]!; + // if (dataArray.Count == 0) + // // Empty array + // return; + + // if (dataArray[0].Type == JTokenType.Array) + // { + // HandleData("Book snapshot", dataArray, symbol, data, handler, _bookSerializer); + // } + // else + // { + // HandleSingleToArrayData("Book update", dataArray, symbol, data, handler, _bookSerializer); + // } + // } + // }); + + // var sub = new BitfinexBookSubscriptionRequest( + // symbol, + // JsonConvert.SerializeObject(precision, new PrecisionConverter(false)), + // JsonConvert.SerializeObject(frequency, new FrequencyConverter(false)), + // length); + // return await SubscribeAsync(BaseAddress.AppendPath("ws/2"), sub, null, false, internalHandler, ct).ConfigureAwait(false); + // } + + // /// + // public async Task> SubscribeToFundingOrderBookUpdatesAsync(string symbol, Precision precision, Frequency frequency, int length, Action>> handler, Action>? checksumHandler = null, CancellationToken ct = default) + // { + // symbol.ValidateBitfinexFundingSymbol(); + // length.ValidateIntValues(nameof(length), 1, 25, 100, 250); + // if (precision == Precision.R0) + // throw new ArgumentException("Invalid precision R0, use SubscribeToFundingRawOrderBookUpdatesAsync instead"); + + // var internalHandler = new Action>(data => + // { + // if (data.Data[1]?.ToString() == "cs") + // { + // // Process + // checksumHandler?.Invoke(data.As(data.Data[2]!.Value(), symbol)); + // } + // else + // { + // var dataArray = (JArray)data.Data[1]!; + // if (dataArray.Count == 0) + // // Empty array + // return; + + // if (dataArray[0].Type == JTokenType.Array) + // { + // HandleData("Book snapshot", dataArray, symbol, data, handler, _fundingBookSerializer); + // } + // else + // { + // HandleSingleToArrayData("Book update", dataArray, symbol, data, handler, _fundingBookSerializer); + // } + // } + // }); + + // var sub = new BitfinexBookSubscriptionRequest( + // symbol, + // JsonConvert.SerializeObject(precision, new PrecisionConverter(false)), + // JsonConvert.SerializeObject(frequency, new FrequencyConverter(false)), + // length); + // return await SubscribeAsync(BaseAddress.AppendPath("ws/2"), sub, null, false, internalHandler, ct).ConfigureAwait(false); + // } + + // /// + // public async Task> SubscribeToRawOrderBookUpdatesAsync(string symbol, int limit, Action>> handler, Action>? checksumHandler = null, CancellationToken ct = default) + // { + // symbol.ValidateBitfinexTradingSymbol(); + // var internalHandler = new Action>(data => + // { + // if (data.Data[1]?.ToString() == "cs") + // { + // // Process + // checksumHandler?.Invoke(data.As(data.Data[2]!.Value(), symbol)); + // } + // else + // { + // var dataArray = (JArray)data.Data[1]!; + // if (dataArray[0].Type == JTokenType.Array) + // HandleData("Raw book snapshot", dataArray, symbol, data, handler); + // else + // HandleSingleToArrayData("Raw book update", dataArray, symbol, data, handler); + // } + // }); + // return await SubscribeAsync(BaseAddress.AppendPath("ws/2"), new BitfinexRawBookSubscriptionRequest(symbol, "R0", limit), null, false, internalHandler, ct).ConfigureAwait(false); + // } + + // /// + // public async Task> SubscribeToRawFundingOrderBookUpdatesAsync(string symbol, int limit, Action>> handler, Action>? checksumHandler = null, CancellationToken ct = default) + // { + // symbol.ValidateBitfinexFundingSymbol(); + // var internalHandler = new Action>(data => + // { + // if (data.Data[1]?.ToString() == "cs") + // { + // // Process + // checksumHandler?.Invoke(data.As(data.Data[2]!.Value(), symbol)); + // } + // else + // { + // var dataArray = (JArray)data.Data[1]!; + // if (dataArray[0].Type == JTokenType.Array) + // HandleData("Raw book snapshot", dataArray, symbol, data, handler); + // else + // HandleSingleToArrayData("Raw book update", dataArray, symbol, data, handler); + // } + // }); + // return await SubscribeAsync(BaseAddress.AppendPath("ws/2"), new BitfinexRawBookSubscriptionRequest(symbol, "R0", limit), null, false, internalHandler, ct).ConfigureAwait(false); + // } + + // /// + // public async Task> SubscribeToTradeUpdatesAsync(string symbol, Action>> handler, CancellationToken ct = default) + // { + // var internalHandler = new Action>(data => + // { + // var arr = (JArray)data.Data; + // if (arr[1].Type == JTokenType.Array) + // { + // HandleData("Trade snapshot", (JArray)arr[1], symbol, data, handler); + // } + // else + // { + // var desResult = Deserialize(arr[2]); + // if (!desResult) + // { + // _logger.Log(LogLevel.Warning, "Failed to deserialize trade object: " + desResult.Error); + // return; + // } + // desResult.Data.UpdateType = BitfinexEvents.EventMapping[arr[1].ToString()]; + // handler(data.As>(new[] { desResult.Data }, symbol)); + // } + // }); + // return await SubscribeAsync(BaseAddress.AppendPath("ws/2"), new BitfinexSubscriptionRequest("trades", symbol), null, false, internalHandler, ct).ConfigureAwait(false); + // } + + // /// + // public async Task> SubscribeToKlineUpdatesAsync(string symbol, KlineInterval interval, Action>> handler, CancellationToken ct = default) + // { + // var internalHandler = new Action>(data => + // { + // var dataArray = (JArray)data.Data[1]!; + // if (dataArray.Count == 0) + // { + // _logger.Log(LogLevel.Warning, "No data in kline update, check if the symbol is correct"); + // return; + // } + + // if (dataArray[0].Type == JTokenType.Array) + // HandleData("Kline snapshot", dataArray, symbol, data, handler); + // else + // HandleSingleToArrayData("Kline update", dataArray, symbol, data, handler); + // }); + // return await SubscribeAsync(BaseAddress.AppendPath("ws/2"), new BitfinexKlineSubscriptionRequest(symbol, JsonConvert.SerializeObject(interval, new KlineIntervalConverter(false))), null, false, internalHandler, ct).ConfigureAwait(false); + // } + + // /// + // public async Task> SubscribeToLiquidationUpdatesAsync(Action>> handler, CancellationToken ct = default) + // { + // var internalHandler = new Action>(data => + // { + // HandleData("Liquidation", (JArray)data.Data[1]!, null, data, handler); + // }); + // return await SubscribeAsync(BaseAddress.AppendPath("ws/2"), new BitfinexLiquidationSubscriptionRequest(), null, false, internalHandler, ct).ConfigureAwait(false); + // } + + // /// + // public async Task> SubscribeToDerivativesUpdatesAsync(string symbol, Action> handler, CancellationToken ct = default) + // { + // var internalHandler = new Action>(data => + // { + // HandleData("DerivativeUpdate", (JArray)data.Data[1]!, symbol, data, handler); + // }); + // return await SubscribeAsync(BaseAddress.AppendPath("ws/2"), new BitfinexDerivativesStatusSubscriptionRequest(symbol), null, false, internalHandler, ct).ConfigureAwait(false); + // } + + // /// + // public async Task> SubscribeToUserTradeUpdatesAsync( + // Action>>> orderHandler, + // Action>>> tradeHandler, + // Action>>> positionHandler, + // CancellationToken ct = default) + // { + // var tokenHandler = new Action>(tokenData => + // { + // HandleAuthUpdate(tokenData, orderHandler, "Orders"); + // HandleAuthUpdate(tokenData, tradeHandler, "Trades"); + // HandleAuthUpdate(tokenData, positionHandler, "Positions"); + // }); + + // return await SubscribeAsync(BaseAddress.AppendPath("ws/2"), null, "Orders|Trades|Positions", true, tokenHandler, ct).ConfigureAwait(false); + // } + + // /// + // public async Task> SubscribeToBalanceUpdatesAsync(Action>>> walletHandler, CancellationToken ct = default) + // { + // var tokenHandler = new Action>(tokenData => + // { + // HandleAuthUpdate(tokenData, walletHandler, "Wallet"); + // }); + + // return await SubscribeAsync(BaseAddress.AppendPath("ws/2"), null, "Wallet", true, tokenHandler, ct).ConfigureAwait(false); + // } + + // /// + // public async Task> SubscribeToFundingUpdatesAsync( + // Action>>> fundingOfferHandler, + // Action>>> fundingCreditHandler, + // Action>>> fundingLoanHandler, + // CancellationToken ct = default) + // { + // var tokenHandler = new Action>(tokenData => + // { + // HandleAuthUpdate(tokenData, fundingOfferHandler, "FundingOffers"); + // HandleAuthUpdate(tokenData, fundingCreditHandler, "FundingCredits"); + // HandleAuthUpdate(tokenData, fundingLoanHandler, "FundingLoans"); + // }); + + // return await SubscribeAsync(BaseAddress.AppendPath("ws/2"), null, "FundingOffers|FundingCredits|FundingLoans", true, tokenHandler, ct).ConfigureAwait(false); + // } + + // /// + // public async Task> PlaceOrderAsync(OrderSide side, OrderType type, string symbol, decimal quantity, long? groupId = null, long? clientOrderId = null, decimal? price = null, decimal? priceTrailing = null, decimal? priceAuxiliaryLimit = null, decimal? priceOcoStop = null, OrderFlags? flags = null, int? leverage = null, DateTime? cancelTime = null, string? affiliateCode = null) + // { + // symbol.ValidateBitfinexSymbol(); + // _logger.Log(LogLevel.Information, "Going to place order"); + // clientOrderId ??= GenerateClientOrderId(); + + // var affCode = affiliateCode ?? _affCode; + // var query = new BitfinexSocketQuery(clientOrderId.ToString(), BitfinexEventType.OrderNew, new BitfinexNewOrder + // { + // Amount = side == OrderSide.Buy ? quantity : -quantity, + // OrderType = type, + // Symbol = symbol, + // Price = price, + // ClientOrderId = clientOrderId, + // Flags = flags, + // GroupId = groupId, + // PriceAuxiliaryLimit = priceAuxiliaryLimit, + // PriceOCOStop = priceOcoStop, + // PriceTrailing = priceTrailing, + // Leverage = leverage, + // CancelAfter = cancelTime, + // Meta = affCode == null ? null : new BitfinexMeta() { AffiliateCode = affCode } + // }); + + // return await QueryAsync(BaseAddress.AppendPath("ws/2"), query, true).ConfigureAwait(false); + // } + + // /// + // public async Task> UpdateOrderAsync(long orderId, decimal? price = null, decimal? quantity = null, decimal? delta = null, decimal? priceAuxiliaryLimit = null, decimal? priceTrailing = null, OrderFlags? flags = null) + // { + // _logger.Log(LogLevel.Information, "Going to update order " + orderId); + // var query = new BitfinexSocketQuery(orderId.ToString(CultureInfo.InvariantCulture), BitfinexEventType.OrderUpdate, new BitfinexUpdateOrder + // { + // OrderId = orderId, + // Amount = quantity, + // Price = price, + // Flags = flags, + // PriceAuxiliaryLimit = priceAuxiliaryLimit?.ToString(CultureInfo.InvariantCulture), + // PriceTrailing = priceTrailing?.ToString(CultureInfo.InvariantCulture) + // }); + + // return await QueryAsync(BaseAddress.AppendPath("ws/2"), query, true).ConfigureAwait(false); + // } + + // ///// + // ///// Cancel all open orders + // ///// + // ///// + // //public CallResult CancelAllOrders() => CancelAllOrdersAsync().Result; + // ///// + // ///// Cancel all open orders + // ///// + // ///// + // //public async Task> CancelAllOrdersAsync() + // //{ + // // // Doesn't seem to work even though it is implemented as described at https://docs.bitfinex.com/v2/reference#ws-input-order-cancel-multi + // // _log.Write(LogLevel.Information, "Going to cancel all orders"); + // // var query = new BitfinexSocketQuery(null, BitfinexEventType.OrderCancelMulti, new BitfinexMultiCancel { All = true }); + + // // return await Query(query, true).ConfigureAwait(false); + // //} + + // /// + // public async Task> CancelOrderAsync(long orderId) + // { + // _logger.Log(LogLevel.Information, "Going to cancel order " + orderId); + // var query = new BitfinexSocketQuery(orderId.ToString(CultureInfo.InvariantCulture), BitfinexEventType.OrderCancel, new JObject { ["id"] = orderId }); + + // return await QueryAsync(BaseAddress.AppendPath("ws/2"), query, true).ConfigureAwait(false); + // } + + // /// + // public async Task> CancelOrdersByGroupIdAsync(long groupOrderId) + // { + // return await CancelOrdersAsync(null, null, new Dictionary { { groupOrderId, null } }).ConfigureAwait(false); + // } + + // /// + // public async Task> CancelOrdersByGroupIdsAsync(IEnumerable groupOrderIds) + // { + // groupOrderIds.ValidateNotNull(nameof(groupOrderIds)); + // return await CancelOrdersAsync(null, null, groupOrderIds.ToDictionary(v => v, k => (long?)null)).ConfigureAwait(false); + // } + + // /// + // public async Task> CancelOrdersAsync(IEnumerable orderIds) + // { + // orderIds.ValidateNotNull(nameof(orderIds)); + // return await CancelOrdersAsync(orderIds, null).ConfigureAwait(false); + // } + + // /// + // public async Task> CancelOrdersByClientOrderIdsAsync(Dictionary clientOrderIds) + // { + // return await CancelOrdersAsync(null, clientOrderIds).ConfigureAwait(false); + // } + + // /// + // public async Task> SubmitFundingOfferAsync(FundingOfferType type, string symbol, decimal quantity, decimal price, int period, int? flags = null) + // { + // var parameters = new Dictionary + // { + // { "type", EnumConverter.GetString(type) }, + // { "symbol", symbol }, + // { "amount", quantity }, + // { "rate", price }, + // { "period", period }, + // }; + // parameters.AddOptionalParameter("flags", flags); + + // var query = new BitfinexSocketQuery(ExchangeHelpers.NextId().ToString(CultureInfo.InvariantCulture), BitfinexEventType.FundingOfferNew, parameters); + // return await QueryAsync(BaseAddress.AppendPath("ws/2"), query, true).ConfigureAwait(false); + // } + + // /// + // public async Task> CancelFundingOfferAsync(long id) + // { + // var parameters = new Dictionary + // { + // { "id", id } + // }; + + // var query = new BitfinexSocketQuery(id.ToString(CultureInfo.InvariantCulture), BitfinexEventType.FundingOfferCancel, parameters); + // return await QueryAsync(BaseAddress.AppendPath("ws/2"), query, true).ConfigureAwait(false); + // } + // #endregion + + // #region private methods + // private void HandleData(string name, JArray dataArray, string? symbol, DataEvent dataEvent, Action> handler, JsonSerializer? serializer = null) + // { + // var desResult = Deserialize(dataArray, serializer: serializer); + // if (!desResult) + // { + // _logger.Log(LogLevel.Warning, $"Failed to Deserialize {name} object: " + desResult.Error); + // return; + // } + + // handler(dataEvent.As(desResult.Data, symbol)); + // } + + // private void HandleSingleToArrayData(string name, JArray dataArray, string? symbol, DataEvent dataEvent, Action>> handler, JsonSerializer? serializer = null) + // { + // var wrapperArray = new JArray { dataArray }; + + // var desResult = Deserialize>(wrapperArray, serializer: serializer); + // if (!desResult) + // { + // _logger.Log(LogLevel.Warning, $"Failed to Deserialize {name} object: " + desResult.Error); + // return; + // } + + // handler(dataEvent.As(desResult.Data, symbol)); + // } + + // private async Task> CancelOrdersAsync(IEnumerable? orderIds = null, Dictionary? clientOrderIds = null, Dictionary? groupOrderIds = null) + // { + // if (orderIds == null && clientOrderIds == null && groupOrderIds == null) + // throw new ArgumentException("Either orderIds, clientOrderIds or groupOrderIds should be provided"); + + // _logger.Log(LogLevel.Information, "Going to cancel multiple orders"); + // var cancelObject = new BitfinexMultiCancel { OrderIds = orderIds }; + // if (clientOrderIds != null) + // { + // cancelObject.ClientIds = new object[clientOrderIds.Count][]; + // for (var i = 0; i < cancelObject.ClientIds.Length; i++) + // { + // cancelObject.ClientIds[i] = new object[] + // { + // clientOrderIds.ElementAt(i).Key, + // clientOrderIds.ElementAt(i).Value.ToString("yyyy-MM-dd") + // }; + // } + // } + // if (groupOrderIds != null) + // cancelObject.GroupIds = new[] { groupOrderIds.Select(g => g.Key).ToArray() }; + + // var query = new BitfinexSocketQuery(null, BitfinexEventType.OrderCancelMulti, cancelObject); + // return await QueryAsync(query, true).ConfigureAwait(false); + // } + + // private void HandleAuthUpdate(DataEvent token, Action>>> action, string category) + // { + // var evntStr = token.Data[1]?.ToString(); + // if (evntStr == null) + // return; + + // var evntType = BitfinexEvents.EventMapping[evntStr]; + // var evnt = BitfinexEvents.Events.Single(e => e.EventType == evntType); + // if (evnt.Category != category) + // return; + + // if (action == null) + // { + // _logger.Log(LogLevel.Debug, $"Ignoring {evnt.EventType} event because not subscribed"); + // return; + // } + + // IEnumerable data; + // if (evnt.Single) + // { + // var result = Deserialize(token.Data[2]!); + // if (!result) + // { + // _logger.Log(LogLevel.Warning, "Failed to Deserialize data: " + result.Error); + // return; + // } + // data = new[] { result.Data }; + // } + // else + // { + // var result = Deserialize>(token.Data[2]!); + // if (!result) + // { + // _logger.Log(LogLevel.Warning, "Failed to Deserialize data: " + result.Error); + // return; + // } + // data = result.Data; + // } + + // action(token.As(new BitfinexSocketEvent>(evntType, data))); + // } + + // private long GenerateClientOrderId() + // { + // var buffer = new byte[8]; + // _random.NextBytes(buffer); + // return (long)Math.Round(Math.Abs(BitConverter.ToInt32(buffer, 0)) / 1000m); + // } + + + // private void ConfHandler(MessageEvent messageEvent) + // { + // var confEvent = messageEvent.JsonData.Type == JTokenType.Object && messageEvent.JsonData["event"]?.ToString() == "conf"; + // if (!confEvent) + // return; + + // // Could check conf result; + // } + + // private void InfoHandler(MessageEvent messageEvent) + // { + // var infoEvent = messageEvent.JsonData.Type == JTokenType.Object && messageEvent.JsonData["event"]?.ToString() == "info"; + // if (!infoEvent) + // return; + + // _logger.Log(LogLevel.Debug, $"Socket {messageEvent.Connection.SocketId} Info event received: {messageEvent.JsonData}"); + // if (messageEvent.JsonData["code"] == null) + // { + // // welcome event, send a config message for receiving checsum updates for order book subscriptions + // messageEvent.Connection.Send(ExchangeHelpers.NextId(), new BitfinexSocketConfig { Event = "conf", Flags = 131072 }, 1); + // return; + // } + + // var code = messageEvent.JsonData["code"]?.Value(); + // switch (code) + // { + // case 20051: + // _logger.Log(LogLevel.Information, $"Socket {messageEvent.Connection.SocketId} Code {code} received, reconnecting socket"); + // messageEvent.Connection.PausedActivity = true; // Prevent new operations to be send + // _ = messageEvent.Connection.TriggerReconnectAsync(); + // break; + // case 20060: + // _logger.Log(LogLevel.Information, $"Socket {messageEvent.Connection.SocketId} Code {code} received, entering maintenance mode"); + // messageEvent.Connection.PausedActivity = true; + // break; + // case 20061: + // _logger.Log(LogLevel.Information, $"Socket {messageEvent.Connection.SocketId} Code {code} received, leaving maintenance mode. Reconnecting/Resubscribing socket."); + // _ = messageEvent.Connection.TriggerReconnectAsync(); // Closing it via socket will automatically reconnect + // break; + // default: + // _logger.Log(LogLevel.Warning, $"Socket {messageEvent.Connection.SocketId} Unknown info code received: {code}"); + // break; + // } + // } + + + // /// + // protected override async Task UnsubscribeAsync(SocketConnection connection, SocketSubscription subscription) + // { + // if (subscription.Request == null) + // { + // // If we don't have a request object we can't unsubscribe it. Probably is an auth subscription which gets pushed regardless + // // Just returning true here will remove the handler and close the socket if there are no other handlers left on the socket, which is the best we can do + // return true; + // } + + // var channelId = ((BitfinexSubscriptionRequest)subscription.Request!).ChannelId; + // var unsub = new BitfinexUnsubscribeRequest(channelId); + // var result = false; + // await connection.SendAndWaitAsync(unsub, ClientOptions.RequestTimeout, null, 1, data => + // { + // if (data.Type != JTokenType.Object) + // return false; + + // var evnt = data["event"]?.ToString(); + // var channel = data["chanId"]?.ToString(); + // if (evnt == null || channel == null) + // return false; + + // if (!int.TryParse(channel, out var chan)) + // return false; + + // result = evnt == "unsubscribed" && channelId == chan; + // return result; + // }).ConfigureAwait(false); + // return result; + // } + + // private static BitfinexAuthentication GetAuthObject(SocketApiClient apiClient, params string[] filter) + // { + // var authProvider = (BitfinexAuthenticationProvider)apiClient.AuthenticationProvider!; + // var n = authProvider.GetNonce().ToString(); + // var authentication = new BitfinexAuthentication + // { + // Event = "auth", + // ApiKey = authProvider.GetApiKey(), + // Nonce = n, + // Payload = "AUTH" + n + // }; + // if (filter.Any()) + // authentication.Filter = filter; + // authentication.Signature = authProvider.Sign(authentication.Payload).ToLower(CultureInfo.InvariantCulture); + // return authentication; + // } + + // #endregion + + // /// + // protected override async Task> AuthenticateSocketAsync(SocketConnection s) + // { + // if (s.ApiClient.AuthenticationProvider == null) + // return new CallResult(new NoApiCredentialsError()); + + // var authObject = GetAuthObject(s.ApiClient); + // var result = new CallResult(new ServerError("No response from server")); + // await s.SendAndWaitAsync(authObject, ClientOptions.RequestTimeout, null, 1, tokenData => + // { + // if (tokenData.Type != JTokenType.Object) + // return false; + + // if (tokenData["event"]?.ToString() != "auth") + // return false; + + // var authResponse = Deserialize(tokenData); + // if (!authResponse) + // { + // _logger.Log(LogLevel.Warning, $"Socket {s.SocketId} authentication failed: " + authResponse.Error); + // result = new CallResult(authResponse.Error!); + // return false; + // } + + // if (authResponse.Data.Status != "OK") + // { + // var error = new ServerError(authResponse.Data.ErrorCode, authResponse.Data.ErrorMessage ?? "-"); + // result = new CallResult(error); + // _logger.Log(LogLevel.Debug, $"Socket {s.SocketId} authentication failed: " + error); + // return false; + // } + + // _logger.Log(LogLevel.Debug, $"Socket {s.SocketId} authentication completed"); + // result = new CallResult(true); + // return true; + // }).ConfigureAwait(false); + + // return result; + // } + + // /// + //#pragma warning disable 8765 + // protected override bool HandleQueryResponse(SocketConnection s, object request, JToken data, out CallResult? callResult) + //#pragma warning restore 8765 + // { + // callResult = null; + // if (data.Type != JTokenType.Array) + // return false; + + // var array = (JArray)data; + // if (array.Count < 3) + // return false; + + // var bfRequest = (BitfinexSocketQuery)request; + // var evntString = data[1]!.ToString(); + // if (!BitfinexEvents.EventMapping.TryGetValue(evntString, out var eventType)) + // return false; + + // if (eventType == BitfinexEventType.Notification) + // { + // var notificationData = (JArray)data[2]!; + // var notificationType = BitfinexEvents.EventMapping[notificationData[1].ToString()]; + // if (notificationType != BitfinexEventType.OrderNewRequest + // && notificationType != BitfinexEventType.OrderCancelRequest + // && notificationType != BitfinexEventType.OrderUpdateRequest + // && notificationType != BitfinexEventType.OrderCancelMultiRequest + // && notificationType != BitfinexEventType.FundingOfferNewRequest + // && notificationType != BitfinexEventType.FundingOfferCancelRequest) + // { + // return false; + // } + + // var statusString = (notificationData[6].ToString()).ToLower(CultureInfo.InvariantCulture); + // if (statusString == "error") + // { + // if (bfRequest.QueryType == BitfinexEventType.OrderNew && notificationType == BitfinexEventType.OrderNewRequest) + // { + // var orderData = notificationData[4]; + // if (orderData[2]?.ToString() != bfRequest.Id) + // return false; + + // callResult = new CallResult(new ServerError(notificationData[7].ToString())); + // return true; + // } + + // if (bfRequest.QueryType == BitfinexEventType.OrderCancel && notificationType == BitfinexEventType.OrderCancelRequest) + // { + // var orderData = notificationData[4]; + // if (orderData[0]?.ToString() != bfRequest.Id) + // return false; + + // callResult = new CallResult(new ServerError(notificationData[7].ToString())); + // return true; + // } + + // if (bfRequest.QueryType == BitfinexEventType.OrderUpdate && notificationType == BitfinexEventType.OrderUpdateRequest) + // { + // // OrderUpdateRequest not found notification doesn't carry the order id, where as OrderCancelRequest not found notification does.. + // // Anyway, can't check for ids, so just assume its for this one + + // callResult = new CallResult(new ServerError(notificationData[7].ToString())); + // return true; + // } + + // if (bfRequest.QueryType == BitfinexEventType.OrderCancelMulti && notificationType == BitfinexEventType.OrderCancelMultiRequest) + // { + // callResult = new CallResult(new ServerError(notificationData[7].ToString())); + // return true; + // } + + // if (bfRequest.QueryType == BitfinexEventType.FundingOfferNew && notificationType == BitfinexEventType.FundingOfferNewRequest) + // { + // callResult = new CallResult(new ServerError(notificationData[7].ToString())); + // return true; + // } + + // if (bfRequest.QueryType == BitfinexEventType.FundingOfferCancel && notificationType == BitfinexEventType.FundingOfferCancelRequest) + // { + // var fundingData = notificationData[4]; + // if (fundingData[0]?.ToString() != bfRequest.Id) + // return false; + + // callResult = new CallResult(new ServerError(notificationData[7].ToString())); + // return true; + // } + // } + + // if (notificationType == BitfinexEventType.OrderNewRequest + // || notificationType == BitfinexEventType.OrderUpdateRequest + // || notificationType == BitfinexEventType.OrderCancelRequest) + // { + // if (bfRequest.QueryType == BitfinexEventType.OrderNew + // || bfRequest.QueryType == BitfinexEventType.OrderUpdate + // || bfRequest.QueryType == BitfinexEventType.OrderCancel) + // { + // var orderData = notificationData[4]; + // var dataOrderId = orderData[0]?.ToString(); + // var dataOrderClientId = orderData[2]?.ToString(); + // if (dataOrderId == bfRequest.Id || dataOrderClientId == bfRequest.Id) + // { + // var desResult = Deserialize(orderData); + // if (!desResult) + // { + // callResult = new CallResult(desResult.Error!); + // return true; + // } + + // callResult = new CallResult(desResult.Data); + // return true; + // } + // } + // } + + // if (notificationType == BitfinexEventType.OrderCancelMultiRequest) + // { + // callResult = new CallResult(Deserialize(JToken.Parse("true")).Data); + // return true; + // } + + // if (notificationType == BitfinexEventType.FundingOfferNewRequest + // || notificationType == BitfinexEventType.FundingOfferCancelRequest) + // { + // if (bfRequest.QueryType == BitfinexEventType.FundingOfferCancelRequest) + // { + // var fundingData = notificationData[4]; + // var dataOrderId = fundingData[0]?.ToString(); + // if (dataOrderId == bfRequest.Id) + // { + // var desResult = Deserialize(fundingData); + // if (!desResult) + // { + // callResult = new CallResult(desResult.Error!); + // return true; + // } + + // callResult = new CallResult(desResult.Data); + // return true; + // } + // } + // else if(bfRequest.QueryType == BitfinexEventType.FundingOfferNew) + // { + // var fundingData = notificationData[4]; + // var desResult = Deserialize(fundingData); + // if (!desResult) + // { + // callResult = new CallResult(desResult.Error!); + // return true; + // } + + // callResult = new CallResult(desResult.Data); + // return true; + // } + // } + // } + + // if (bfRequest.QueryType == BitfinexEventType.OrderCancelMulti && eventType == BitfinexEventType.OrderCancel) + // { + // callResult = new CallResult(Deserialize(JToken.Parse("true")).Data); + // return true; + // } + + // return false; + // } + + // /// + // protected override bool HandleSubscriptionResponse(SocketConnection s, SocketSubscription subscription, object request, JToken data, out CallResult? callResult) + // { + // callResult = null; + // if (data.Type != JTokenType.Object) + // return false; + + // var infoEvent = data["event"]?.ToString() == "subscribed"; + // var errorEvent = data["event"]?.ToString() == "error"; + // if (!infoEvent && !errorEvent) + // return false; + + // if (infoEvent) + // { + // var subResponse = Deserialize(data); + // if (!subResponse) + // { + // callResult = new CallResult(subResponse.Error!); + // _logger.Log(LogLevel.Warning, $"Socket {s.SocketId} subscription failed: " + subResponse.Error); + // return false; + // } + + // var bRequest = (BitfinexSubscriptionRequest)request; + // if (!bRequest.CheckResponse(data)) + // return false; + + // bRequest.ChannelId = subResponse.Data.ChannelId; + // callResult = subResponse.As(subResponse.Data); + // return true; + // } + // else + // { + // var subResponse = Deserialize(data); + // if (!subResponse) + // { + // callResult = new CallResult(subResponse.Error!); + // _logger.Log(LogLevel.Warning, $"Socket {s.SocketId} subscription failed: " + subResponse.Error); + // return false; + // } + + // var error = new ServerError(subResponse.Data.Code, subResponse.Data.Message); + // callResult = new CallResult(error); + // _logger.Log(LogLevel.Debug, $"Socket {s.SocketId} subscription failed: " + error); + // return true; + // } + // } + + // /// + // protected override bool MessageMatchesHandler(SocketConnection socketConnection, JToken message, object request) + // { + // if (message.Type != JTokenType.Array) + // return false; + + // var array = (JArray)message; + // if (array.Count < 2) + // return false; + + // if (!int.TryParse(array[0].ToString(), out var channelId)) + // return false; + + // if (channelId == 0) + // return false; + + // var subId = ((BitfinexSubscriptionRequest)request).ChannelId; + // return channelId == subId && array[1].ToString() != "hb"; + // } + + // /// + // protected override bool MessageMatchesHandler(SocketConnection socketConnection, JToken message, string identifier) + // { + // if (message.Type == JTokenType.Object) + // { + // if (identifier == "Info") + // return message["event"]?.ToString() == "info"; + // if (identifier == "Conf") + // return message["event"]?.ToString() == "conf"; + // } + + // else if (message.Type == JTokenType.Array) + // { + // var array = (JArray)message; + // if (array.Count < 2) + // return false; + + // if (identifier == "HB") + // return array[1].ToString() == "hb"; + + // if (!int.TryParse(array[0].ToString(), out var channelId)) + // return false; + + // if (channelId != 0) + // return false; + + // var split = identifier.Split(new[] { "|" }, StringSplitOptions.RemoveEmptyEntries); + // foreach (var id in split) + // { + // var events = BitfinexEvents.GetEventsForCategory(id); + // var eventTypeString = array[1].ToString(); + // var eventType = BitfinexEvents.EventMapping[eventTypeString]; + // var evnt = events.SingleOrDefault(e => e.EventType == eventType); + // if (evnt != null) + // return true; + // } + // } + + // return false; + // } } } diff --git a/Bitfinex.Net/Interfaces/Clients/SpotApi/IBitfinexSocketClientSpotApi.cs b/Bitfinex.Net/Interfaces/Clients/SpotApi/IBitfinexSocketClientSpotApi.cs index 9b32980..9bf36eb 100644 --- a/Bitfinex.Net/Interfaces/Clients/SpotApi/IBitfinexSocketClientSpotApi.cs +++ b/Bitfinex.Net/Interfaces/Clients/SpotApi/IBitfinexSocketClientSpotApi.cs @@ -27,15 +27,15 @@ public interface IBitfinexSocketClientSpotApi : ISocketApiClient, IDisposable /// Task> SubscribeToTickerUpdatesAsync(string symbol, Action> handler, CancellationToken ct = default); - ///// - ///// Subscribes to funding ticker updates a symbol. Use SubscribeToTickerUpdatesAsync for trade symbol ticker updates - ///// - ///// - ///// The symbol to subscribe to - ///// The handler for the data - ///// Cancellation token for closing this subscription - ///// - //Task> SubscribeToFundingTickerUpdatesAsync(string symbol, Action> handler, CancellationToken ct = default); + /// + /// Subscribes to funding ticker updates a symbol. Use SubscribeToTickerUpdatesAsync for trade symbol ticker updates + /// + /// + /// The symbol to subscribe to + /// The handler for the data + /// Cancellation token for closing this subscription + /// + Task> SubscribeToFundingTickerUpdatesAsync(string symbol, Action> handler, CancellationToken ct = default); ///// ///// Subscribes to order book updates for a symbol. Use SubscribeToFundingOrderBookUpdatesAsync for funding symbol ticker updates diff --git a/Bitfinex.Net/Objects/Models/Socket/BitfinexSocketInfo.cs b/Bitfinex.Net/Objects/Models/Socket/BitfinexSocketInfo.cs new file mode 100644 index 0000000..41efff1 --- /dev/null +++ b/Bitfinex.Net/Objects/Models/Socket/BitfinexSocketInfo.cs @@ -0,0 +1,25 @@ +using Bitfinex.Net.Converters; +using Bitfinex.Net.Enums; +using Newtonsoft.Json; + +namespace Bitfinex.Net.Objects.Models.Socket +{ + internal class BitfinexSocketInfo + { + [JsonProperty("event")] + public string Event { get; set; } = string.Empty; + [JsonProperty("version")] + public int Version { get; set; } + [JsonProperty("serverId")] + public string ServerId { get; set; } = string.Empty; + [JsonProperty("platform")] + public BitfinexSocketInfoDetails Platform { get; set; } = null!; + } + + internal class BitfinexSocketInfoDetails + { + [JsonProperty("status")] + [JsonConverter(typeof(PlatformStatusConverter))] + public PlatformStatus Status { get; set; } + } +} diff --git a/Bitfinex.Net/Objects/Sockets/BitfinexRequest.cs b/Bitfinex.Net/Objects/Sockets/BitfinexRequest.cs index c88d2dc..ea5acc7 100644 --- a/Bitfinex.Net/Objects/Sockets/BitfinexRequest.cs +++ b/Bitfinex.Net/Objects/Sockets/BitfinexRequest.cs @@ -1,7 +1,4 @@ using Newtonsoft.Json; -using System; -using System.Collections.Generic; -using System.Text; namespace Bitfinex.Net.Objects.Sockets { diff --git a/Bitfinex.Net/Objects/Sockets/BitfinexResponse.cs b/Bitfinex.Net/Objects/Sockets/BitfinexResponse.cs index 8f50c68..e7e9fc4 100644 --- a/Bitfinex.Net/Objects/Sockets/BitfinexResponse.cs +++ b/Bitfinex.Net/Objects/Sockets/BitfinexResponse.cs @@ -1,7 +1,4 @@ using Newtonsoft.Json; -using System; -using System.Collections.Generic; -using System.Text; namespace Bitfinex.Net.Objects.Sockets { diff --git a/Bitfinex.Net/Objects/Sockets/BitfinexSocketConverter.cs b/Bitfinex.Net/Objects/Sockets/BitfinexSocketConverter.cs index d578e21..70164ac 100644 --- a/Bitfinex.Net/Objects/Sockets/BitfinexSocketConverter.cs +++ b/Bitfinex.Net/Objects/Sockets/BitfinexSocketConverter.cs @@ -1,9 +1,6 @@ using CryptoExchange.Net.Converters; using CryptoExchange.Net.Interfaces; using CryptoExchange.Net.Objects.Sockets; -using System; -using System.Collections.Generic; -using System.Text; namespace Bitfinex.Net.Objects.Sockets { @@ -26,6 +23,9 @@ private static string GetIdentity(IMessageAccessor accessor) } var evnt = accessor.GetStringValue("event"); + if (evnt == "info") + return "info"; + var channel = accessor.GetStringValue("channel"); var symbol = accessor.GetStringValue("symbol"); return evnt + channel + symbol; diff --git a/Bitfinex.Net/Objects/Sockets/BitfinexUpdate.cs b/Bitfinex.Net/Objects/Sockets/BitfinexUpdate.cs index 1f0fcb6..105bc63 100644 --- a/Bitfinex.Net/Objects/Sockets/BitfinexUpdate.cs +++ b/Bitfinex.Net/Objects/Sockets/BitfinexUpdate.cs @@ -1,8 +1,5 @@ using CryptoExchange.Net.Converters; using Newtonsoft.Json; -using System; -using System.Collections.Generic; -using System.Text; namespace Bitfinex.Net.Objects.Sockets { diff --git a/Bitfinex.Net/Objects/Sockets/Queries/BitfinexQuery.cs b/Bitfinex.Net/Objects/Sockets/Queries/BitfinexQuery.cs index f68a46c..f0e8d95 100644 --- a/Bitfinex.Net/Objects/Sockets/Queries/BitfinexQuery.cs +++ b/Bitfinex.Net/Objects/Sockets/Queries/BitfinexQuery.cs @@ -1,7 +1,5 @@ using CryptoExchange.Net.Sockets; -using System; using System.Collections.Generic; -using System.Text; namespace Bitfinex.Net.Objects.Sockets.Queries { diff --git a/Bitfinex.Net/Objects/Sockets/Subscriptions/BitfinexHeartbeatSubscription.cs b/Bitfinex.Net/Objects/Sockets/Subscriptions/BitfinexHeartbeatSubscription.cs new file mode 100644 index 0000000..23cc4f0 --- /dev/null +++ b/Bitfinex.Net/Objects/Sockets/Subscriptions/BitfinexHeartbeatSubscription.cs @@ -0,0 +1,23 @@ +using CryptoExchange.Net.Objects; +using CryptoExchange.Net.Objects.Sockets; +using CryptoExchange.Net.Sockets; +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Bitfinex.Net.Objects.Sockets.Subscriptions +{ + internal class BitfinexHeartbeatSubscription : SystemSubscription + { + public override List Identifiers { get; } = new List { "hb" }; + + public override Type ExpectedMessageType => typeof(BitfinexUpdate); + + public BitfinexHeartbeatSubscription(ILogger logger) : base(logger, false) + { + } + + public override Task DoHandleMessageAsync(SocketConnection connection, DataEvent message) => Task.FromResult(new CallResult(null)); + } +} diff --git a/Bitfinex.Net/Objects/Sockets/Subscriptions/BitfinexInfoSubscription.cs b/Bitfinex.Net/Objects/Sockets/Subscriptions/BitfinexInfoSubscription.cs new file mode 100644 index 0000000..e2b4b0d --- /dev/null +++ b/Bitfinex.Net/Objects/Sockets/Subscriptions/BitfinexInfoSubscription.cs @@ -0,0 +1,24 @@ +using Bitfinex.Net.Objects.Models.Socket; +using CryptoExchange.Net.Objects; +using CryptoExchange.Net.Objects.Sockets; +using CryptoExchange.Net.Sockets; +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Bitfinex.Net.Objects.Sockets.Subscriptions +{ + internal class BitfinexInfoSubscription : SystemSubscription + { + public override List Identifiers { get; } = new List { "info" }; + + public override Type ExpectedMessageType => typeof(BitfinexSocketInfo); + + public BitfinexInfoSubscription(ILogger logger) : base(logger, false) + { + } + + public override Task DoHandleMessageAsync(SocketConnection connection, DataEvent message) => Task.FromResult(new CallResult(null)); + } +} diff --git a/Bitfinex.Net/Objects/Sockets/Subscriptions/BitfinexSubscription.cs b/Bitfinex.Net/Objects/Sockets/Subscriptions/BitfinexSubscription.cs index c68af6c..86a798e 100644 --- a/Bitfinex.Net/Objects/Sockets/Subscriptions/BitfinexSubscription.cs +++ b/Bitfinex.Net/Objects/Sockets/Subscriptions/BitfinexSubscription.cs @@ -5,7 +5,6 @@ using Microsoft.Extensions.Logging; using System; using System.Collections.Generic; -using System.Text; using System.Threading.Tasks; namespace Bitfinex.Net.Objects.Sockets.Subscriptions From 153fae7d16e2d240a4697d25200fc9284ab25006 Mon Sep 17 00:00:00 2001 From: JKorf Date: Sun, 31 Dec 2023 11:29:22 +0100 Subject: [PATCH 03/25] wip --- Bitfinex.Net/Bitfinex.Net.xml | 133 +++++++ .../SpotApi/BitfinexSocketClientSpotApi.cs | 346 +++++++----------- .../SpotApi/IBitfinexSocketClientSpotApi.cs | 196 +++++----- .../Objects/Internal/BitfinexChecksum.cs | 19 + .../Objects/Models/BitfinexOrderBookEntry.cs | 1 + .../Objects/Sockets/BitfinexBookRequest.cs | 16 + .../Sockets/BitfinexSocketConverter.cs | 90 +++-- .../Objects/Sockets/BitfinexUpdate.cs | 17 +- .../Sockets/Queries/BitfinexAuthQuery.cs | 16 + .../Objects/Sockets/Queries/BitfinexQuery.cs | 18 +- .../BitfinexHeartbeatSubscription.cs | 23 -- .../Subscriptions/BitfinexInfoSubscription.cs | 8 +- .../Subscriptions/BitfinexSubscription.cs | 81 +++- .../Subscriptions/BitfinexUserSubscription.cs | 93 +++++ 14 files changed, 673 insertions(+), 384 deletions(-) create mode 100644 Bitfinex.Net/Objects/Internal/BitfinexChecksum.cs create mode 100644 Bitfinex.Net/Objects/Sockets/BitfinexBookRequest.cs create mode 100644 Bitfinex.Net/Objects/Sockets/Queries/BitfinexAuthQuery.cs delete mode 100644 Bitfinex.Net/Objects/Sockets/Subscriptions/BitfinexHeartbeatSubscription.cs create mode 100644 Bitfinex.Net/Objects/Sockets/Subscriptions/BitfinexUserSubscription.cs diff --git a/Bitfinex.Net/Bitfinex.Net.xml b/Bitfinex.Net/Bitfinex.Net.xml index 21e30c9..0d8c5f1 100644 --- a/Bitfinex.Net/Bitfinex.Net.xml +++ b/Bitfinex.Net/Bitfinex.Net.xml @@ -491,6 +491,9 @@ + + + @@ -500,6 +503,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + Socket event types @@ -2402,6 +2432,109 @@ Cancellation token for closing this subscription + + + Subscribes to order book updates for a symbol. Use SubscribeToFundingOrderBookUpdatesAsync for funding symbol ticker updates + + + The symbol to subscribe to + The precision of the updates + The frequency of updates + The range for the order book updates, either 25 or 100 + The handler for the data + The handler for the checksum, can be used to validate a order book implementation + Cancellation token for closing this subscription + + + + + Subscribes to funding order book updates for a symbol. Use SubscribeToOrderBookUpdatesAsync for trade symbol ticker updates + + + The symbol to subscribe to + The precision of the updates + The frequency of updates + The range for the order book updates, either 25 or 100 + The handler for the data + The handler for the checksum, can be used to validate a order book implementation + Cancellation token for closing this subscription + + + + + Subscribes to raw order book updates for a symbol. Use SubscribeToRawFundingOrderBookUpdatesAsync for funding symbol ticker updates + + + The symbol to subscribe to + The range for the order book updates + The handler for the data + The handler for the checksum, can be used to validate a order book implementation + Cancellation token for closing this subscription + + + + + Subscribes to raw order book updates for a symbol. Use SubscribeToRawOrderBookUpdatesAsync for trade symbol ticker updates + + + The symbol to subscribe to + The range for the order book updates + The handler for the data + The handler for the checksum, can be used to validate a order book implementation + Cancellation token for closing this subscription + + + + + Subscribes to public trade updates for a symbol + + + The symbol to subscribe to + The handler for the data + Cancellation token for closing this subscription + + + + + Subscribes to kline updates for a symbol + + + The symbol to subscribe to. For funding klines use {symbol}:p{period}, for example fUSD:p30 + The interval of the klines + The handler for the data + Cancellation token for closing this subscription + + + + + Subscribe to liquidation updates + + + The handler for the data + Cancellation token for closing this subscription + + + + + Subscribe to derivatives status updates + + + The derivatives symbol, tBTCF0:USTF0 for example + The handler for the data + Cancellation token for closing this subscription + + + + + Subscribe to trading information updates + + + Data handler for order updates. Can be null if not interested + Data handler for trade execution updates. Can be null if not interested + Data handler for position updates. Can be null if not interested + Cancellation token for closing this subscription + + Bitfinex order book factory diff --git a/Bitfinex.Net/Clients/SpotApi/BitfinexSocketClientSpotApi.cs b/Bitfinex.Net/Clients/SpotApi/BitfinexSocketClientSpotApi.cs index 7a7fb58..1c04c13 100644 --- a/Bitfinex.Net/Clients/SpotApi/BitfinexSocketClientSpotApi.cs +++ b/Bitfinex.Net/Clients/SpotApi/BitfinexSocketClientSpotApi.cs @@ -16,6 +16,13 @@ using Bitfinex.Net.Objects.Sockets; using Bitfinex.Net.Objects.Sockets.Subscriptions; using Bitfinex.Net.Objects.Models; +using Bitfinex.Net.Enums; +using System.Collections.Generic; +using System.Linq; +using CryptoExchange.Net.Interfaces; +using CryptoExchange.Net.Sockets; +using System.Globalization; +using Bitfinex.Net.Objects.Sockets.Queries; namespace Bitfinex.Net.Clients.SpotApi { @@ -31,7 +38,12 @@ public class BitfinexSocketClientSpotApi : SocketApiClient, IBitfinexSocketClien /// public new BitfinexSocketOptions ClientOptions => (BitfinexSocketOptions)base.ClientOptions; - public override SocketConverter StreamConverter => new BitfinexSocketConverter(); + /// + public override MessageInterpreterPipeline Pipeline { get; } = new MessageInterpreterPipeline + { + GetStreamIdentifier = GetStreamIdentifier, + GetTypeIdentifier = GetTypeIdentifier + }; #endregion #region ctor @@ -44,7 +56,6 @@ internal BitfinexSocketClientSpotApi(ILogger logger, BitfinexSocketOptions optio //AddGenericHandler("Conf", ConfHandler); AddSystemSubscription(new BitfinexInfoSubscription(_logger)); - AddSystemSubscription(new BitfinexHeartbeatSubscription(_logger)); _affCode = options.AffiliateCode; _bookSerializer.Converters.Add(new OrderBookEntryConverter()); @@ -55,6 +66,23 @@ internal BitfinexSocketClientSpotApi(ILogger logger, BitfinexSocketOptions optio protected override AuthenticationProvider CreateAuthenticationProvider(ApiCredentials credentials) => new BitfinexAuthenticationProvider(credentials, ClientOptions.NonceProvider ?? new BitfinexNonceProvider()); + protected override BaseQuery GetAuthenticationRequest() + { + var authProvider = (BitfinexAuthenticationProvider)AuthenticationProvider!; + var n = authProvider.GetNonce().ToString(); + var authentication = new BitfinexAuthentication + { + Event = "auth", + ApiKey = authProvider.GetApiKey(), + Nonce = n, + Payload = "AUTH" + n + }; + //if (filter.Any()) + // authentication.Filter = filter; + authentication.Signature = authProvider.Sign(authentication.Payload).ToLower(CultureInfo.InvariantCulture); + return new BitfinexAuthQuery(authentication); + } + //#region public methods /// @@ -62,7 +90,7 @@ public async Task> SubscribeToTickerUpdatesAsync( { symbol.ValidateBitfinexTradingSymbol(); - var subscription = new BitfinexSubscription(_logger, "ticker", symbol, handler, false); + var subscription = new BitfinexSubscription(_logger, "ticker", symbol, x => handler(x.As(x.Data.First()))); return await SubscribeAsync(BaseAddress.AppendPath("ws/2"), subscription, ct).ConfigureAwait(false); } @@ -71,225 +99,99 @@ public async Task> SubscribeToFundingTickerUpdate { symbol.ValidateBitfinexFundingSymbol(); - var subscription = new BitfinexSubscription(_logger, "ticker", symbol, handler, false); + var subscription = new BitfinexSubscription(_logger, "ticker", symbol, x => handler(x.As(x.Data.First()))); return await SubscribeAsync(BaseAddress.AppendPath("ws/2"), subscription, ct).ConfigureAwait(false); - - //var internalHandler = new Action>(data => - //{ - // HandleData("Ticker", (JArray)data.Data[1]!, symbol, data, handler); - //}); - //return await SubscribeAsync(BaseAddress.AppendPath("ws/2"), new BitfinexSubscriptionRequest("ticker", symbol), null, false, internalHandler, ct).ConfigureAwait(false); } - // /// - // public async Task> SubscribeToOrderBookUpdatesAsync(string symbol, Precision precision, Frequency frequency, int length, Action>> handler, Action>? checksumHandler = null, CancellationToken ct = default) - // { - // symbol.ValidateBitfinexTradingSymbol(); - // length.ValidateIntValues(nameof(length), 1, 25, 100, 250); - // if (precision == Precision.R0) - // throw new ArgumentException("Invalid precision R0, use SubscribeToRawBookUpdatesAsync instead"); - - // var internalHandler = new Action>(data => - // { - // if (data.Data[1]?.ToString() == "cs") - // { - // // Process - // checksumHandler?.Invoke(data.As(data.Data[2]!.Value(), symbol)); - // } - // else - // { - // var dataArray = (JArray)data.Data[1]!; - // if (dataArray.Count == 0) - // // Empty array - // return; - - // if (dataArray[0].Type == JTokenType.Array) - // { - // HandleData("Book snapshot", dataArray, symbol, data, handler, _bookSerializer); - // } - // else - // { - // HandleSingleToArrayData("Book update", dataArray, symbol, data, handler, _bookSerializer); - // } - // } - // }); + /// + public async Task> SubscribeToOrderBookUpdatesAsync(string symbol, Precision precision, Frequency frequency, int length, Action>> handler, Action>? checksumHandler = null, CancellationToken ct = default) + { + symbol.ValidateBitfinexTradingSymbol(); + length.ValidateIntValues(nameof(length), 1, 25, 100, 250); + if (precision == Precision.R0) + throw new ArgumentException("Invalid precision R0, use SubscribeToRawBookUpdatesAsync instead"); - // var sub = new BitfinexBookSubscriptionRequest( - // symbol, - // JsonConvert.SerializeObject(precision, new PrecisionConverter(false)), - // JsonConvert.SerializeObject(frequency, new FrequencyConverter(false)), - // length); - // return await SubscribeAsync(BaseAddress.AppendPath("ws/2"), sub, null, false, internalHandler, ct).ConfigureAwait(false); - // } + var subscription = new BitfinexSubscription(_logger, "book", symbol, handler, checksumHandler, false, precision, frequency, length); + return await SubscribeAsync(BaseAddress.AppendPath("ws/2"), subscription, ct).ConfigureAwait(false); + } - // /// - // public async Task> SubscribeToFundingOrderBookUpdatesAsync(string symbol, Precision precision, Frequency frequency, int length, Action>> handler, Action>? checksumHandler = null, CancellationToken ct = default) - // { - // symbol.ValidateBitfinexFundingSymbol(); - // length.ValidateIntValues(nameof(length), 1, 25, 100, 250); - // if (precision == Precision.R0) - // throw new ArgumentException("Invalid precision R0, use SubscribeToFundingRawOrderBookUpdatesAsync instead"); + /// + public async Task> SubscribeToFundingOrderBookUpdatesAsync(string symbol, Precision precision, Frequency frequency, int length, Action>> handler, Action>? checksumHandler = null, CancellationToken ct = default) + { + symbol.ValidateBitfinexFundingSymbol(); + length.ValidateIntValues(nameof(length), 1, 25, 100, 250); + if (precision == Precision.R0) + throw new ArgumentException("Invalid precision R0, use SubscribeToFundingRawOrderBookUpdatesAsync instead"); - // var internalHandler = new Action>(data => - // { - // if (data.Data[1]?.ToString() == "cs") - // { - // // Process - // checksumHandler?.Invoke(data.As(data.Data[2]!.Value(), symbol)); - // } - // else - // { - // var dataArray = (JArray)data.Data[1]!; - // if (dataArray.Count == 0) - // // Empty array - // return; + var subscription = new BitfinexSubscription(_logger, "book", symbol, handler, checksumHandler, false, precision, frequency, length); + return await SubscribeAsync(BaseAddress.AppendPath("ws/2"), subscription, ct).ConfigureAwait(false); + } - // if (dataArray[0].Type == JTokenType.Array) - // { - // HandleData("Book snapshot", dataArray, symbol, data, handler, _fundingBookSerializer); - // } - // else - // { - // HandleSingleToArrayData("Book update", dataArray, symbol, data, handler, _fundingBookSerializer); - // } - // } - // }); + /// + public async Task> SubscribeToRawOrderBookUpdatesAsync(string symbol, int limit, Action>> handler, Action>? checksumHandler = null, CancellationToken ct = default) + { + symbol.ValidateBitfinexTradingSymbol(); - // var sub = new BitfinexBookSubscriptionRequest( - // symbol, - // JsonConvert.SerializeObject(precision, new PrecisionConverter(false)), - // JsonConvert.SerializeObject(frequency, new FrequencyConverter(false)), - // length); - // return await SubscribeAsync(BaseAddress.AppendPath("ws/2"), sub, null, false, internalHandler, ct).ConfigureAwait(false); - // } + var subscription = new BitfinexSubscription(_logger, "book", symbol, handler, checksumHandler, false, Precision.R0, Frequency.Realtime, limit); + return await SubscribeAsync(BaseAddress.AppendPath("ws/2"), subscription, ct).ConfigureAwait(false); + } - // /// - // public async Task> SubscribeToRawOrderBookUpdatesAsync(string symbol, int limit, Action>> handler, Action>? checksumHandler = null, CancellationToken ct = default) - // { - // symbol.ValidateBitfinexTradingSymbol(); - // var internalHandler = new Action>(data => - // { - // if (data.Data[1]?.ToString() == "cs") - // { - // // Process - // checksumHandler?.Invoke(data.As(data.Data[2]!.Value(), symbol)); - // } - // else - // { - // var dataArray = (JArray)data.Data[1]!; - // if (dataArray[0].Type == JTokenType.Array) - // HandleData("Raw book snapshot", dataArray, symbol, data, handler); - // else - // HandleSingleToArrayData("Raw book update", dataArray, symbol, data, handler); - // } - // }); - // return await SubscribeAsync(BaseAddress.AppendPath("ws/2"), new BitfinexRawBookSubscriptionRequest(symbol, "R0", limit), null, false, internalHandler, ct).ConfigureAwait(false); - // } + /// + public async Task> SubscribeToRawFundingOrderBookUpdatesAsync(string symbol, int limit, Action>> handler, Action>? checksumHandler = null, CancellationToken ct = default) + { + symbol.ValidateBitfinexFundingSymbol(); - // /// - // public async Task> SubscribeToRawFundingOrderBookUpdatesAsync(string symbol, int limit, Action>> handler, Action>? checksumHandler = null, CancellationToken ct = default) - // { - // symbol.ValidateBitfinexFundingSymbol(); - // var internalHandler = new Action>(data => - // { - // if (data.Data[1]?.ToString() == "cs") - // { - // // Process - // checksumHandler?.Invoke(data.As(data.Data[2]!.Value(), symbol)); - // } - // else - // { - // var dataArray = (JArray)data.Data[1]!; - // if (dataArray[0].Type == JTokenType.Array) - // HandleData("Raw book snapshot", dataArray, symbol, data, handler); - // else - // HandleSingleToArrayData("Raw book update", dataArray, symbol, data, handler); - // } - // }); - // return await SubscribeAsync(BaseAddress.AppendPath("ws/2"), new BitfinexRawBookSubscriptionRequest(symbol, "R0", limit), null, false, internalHandler, ct).ConfigureAwait(false); - // } + var subscription = new BitfinexSubscription(_logger, "book", symbol, handler, checksumHandler, false, Precision.R0, Frequency.Realtime, limit); + return await SubscribeAsync(BaseAddress.AppendPath("ws/2"), subscription, ct).ConfigureAwait(false); + } - // /// - // public async Task> SubscribeToTradeUpdatesAsync(string symbol, Action>> handler, CancellationToken ct = default) - // { - // var internalHandler = new Action>(data => - // { - // var arr = (JArray)data.Data; - // if (arr[1].Type == JTokenType.Array) - // { - // HandleData("Trade snapshot", (JArray)arr[1], symbol, data, handler); - // } - // else - // { - // var desResult = Deserialize(arr[2]); - // if (!desResult) - // { - // _logger.Log(LogLevel.Warning, "Failed to deserialize trade object: " + desResult.Error); - // return; - // } - // desResult.Data.UpdateType = BitfinexEvents.EventMapping[arr[1].ToString()]; - // handler(data.As>(new[] { desResult.Data }, symbol)); - // } - // }); - // return await SubscribeAsync(BaseAddress.AppendPath("ws/2"), new BitfinexSubscriptionRequest("trades", symbol), null, false, internalHandler, ct).ConfigureAwait(false); - // } + /// + public async Task> SubscribeToTradeUpdatesAsync(string symbol, Action>> handler, CancellationToken ct = default) + { + var subscription = new BitfinexSubscription(_logger, "trades", symbol, handler); + return await SubscribeAsync(BaseAddress.AppendPath("ws/2"), subscription, ct).ConfigureAwait(false); + } - // /// - // public async Task> SubscribeToKlineUpdatesAsync(string symbol, KlineInterval interval, Action>> handler, CancellationToken ct = default) - // { - // var internalHandler = new Action>(data => - // { - // var dataArray = (JArray)data.Data[1]!; - // if (dataArray.Count == 0) - // { - // _logger.Log(LogLevel.Warning, "No data in kline update, check if the symbol is correct"); - // return; - // } + /// + public async Task> SubscribeToKlineUpdatesAsync(string symbol, KlineInterval interval, Action>> handler, CancellationToken ct = default) + { + var subscription = new BitfinexSubscription(_logger, "candles", null, handler, key: $"trade:{JsonConvert.SerializeObject(interval, new KlineIntervalConverter(false))}:" + symbol); + return await SubscribeAsync(BaseAddress.AppendPath("ws/2"), subscription, ct).ConfigureAwait(false); + } - // if (dataArray[0].Type == JTokenType.Array) - // HandleData("Kline snapshot", dataArray, symbol, data, handler); - // else - // HandleSingleToArrayData("Kline update", dataArray, symbol, data, handler); - // }); - // return await SubscribeAsync(BaseAddress.AppendPath("ws/2"), new BitfinexKlineSubscriptionRequest(symbol, JsonConvert.SerializeObject(interval, new KlineIntervalConverter(false))), null, false, internalHandler, ct).ConfigureAwait(false); - // } + /// + public async Task> SubscribeToLiquidationUpdatesAsync(Action>> handler, CancellationToken ct = default) + { + var subscription = new BitfinexSubscription(_logger, "status", null, handler, key: $"liq:global"); + return await SubscribeAsync(BaseAddress.AppendPath("ws/2"), subscription, ct).ConfigureAwait(false); + } - // /// - // public async Task> SubscribeToLiquidationUpdatesAsync(Action>> handler, CancellationToken ct = default) - // { - // var internalHandler = new Action>(data => - // { - // HandleData("Liquidation", (JArray)data.Data[1]!, null, data, handler); - // }); - // return await SubscribeAsync(BaseAddress.AppendPath("ws/2"), new BitfinexLiquidationSubscriptionRequest(), null, false, internalHandler, ct).ConfigureAwait(false); - // } + /// + public async Task> SubscribeToDerivativesUpdatesAsync(string symbol, Action> handler, CancellationToken ct = default) + { + var subscription = new BitfinexSubscription(_logger, "candles", null, x => handler(x.As(x.Data.Single())), key: $"deriv:" + symbol); + return await SubscribeAsync(BaseAddress.AppendPath("ws/2"), subscription, ct).ConfigureAwait(false); + } - // /// - // public async Task> SubscribeToDerivativesUpdatesAsync(string symbol, Action> handler, CancellationToken ct = default) - // { - // var internalHandler = new Action>(data => - // { - // HandleData("DerivativeUpdate", (JArray)data.Data[1]!, symbol, data, handler); - // }); - // return await SubscribeAsync(BaseAddress.AppendPath("ws/2"), new BitfinexDerivativesStatusSubscriptionRequest(symbol), null, false, internalHandler, ct).ConfigureAwait(false); - // } + /// + public async Task> SubscribeToUserTradeUpdatesAsync( + Action>>> orderHandler, + Action>>> tradeHandler, + Action>>> positionHandler, + CancellationToken ct = default) + { + var subscription = new BitfinexUserSubscription(_logger); + return await SubscribeAsync(BaseAddress.AppendPath("ws/2"), subscription, ct).ConfigureAwait(false); - // /// - // public async Task> SubscribeToUserTradeUpdatesAsync( - // Action>>> orderHandler, - // Action>>> tradeHandler, - // Action>>> positionHandler, - // CancellationToken ct = default) - // { - // var tokenHandler = new Action>(tokenData => - // { - // HandleAuthUpdate(tokenData, orderHandler, "Orders"); - // HandleAuthUpdate(tokenData, tradeHandler, "Trades"); - // HandleAuthUpdate(tokenData, positionHandler, "Positions"); - // }); + //var tokenHandler = new Action>(tokenData => + //{ + // HandleAuthUpdate(tokenData, orderHandler, "Orders"); + // HandleAuthUpdate(tokenData, tradeHandler, "Trades"); + // HandleAuthUpdate(tokenData, positionHandler, "Positions"); + //}); - // return await SubscribeAsync(BaseAddress.AppendPath("ws/2"), null, "Orders|Trades|Positions", true, tokenHandler, ct).ConfigureAwait(false); - // } + //return await SubscribeAsync(BaseAddress.AppendPath("ws/2"), null, "Orders|Trades|Positions", true, tokenHandler, ct).ConfigureAwait(false); + } // /// // public async Task> SubscribeToBalanceUpdatesAsync(Action>>> walletHandler, CancellationToken ct = default) @@ -447,6 +349,40 @@ public async Task> SubscribeToFundingTickerUpdate // } // #endregion + private static string? GetStreamIdentifier(IMessageAccessor accessor) + { + if (!accessor.IsObject(null)) + { + return accessor.GetArrayIntValue(null, 0).ToString(); + } + + var evnt = accessor.GetStringValue("event"); + if (evnt == "info") + return "info"; + + var channel = accessor.GetStringValue("channel"); + var symbol = accessor.GetStringValue("symbol"); + var prec = accessor.GetStringValue("prec"); + var freq = accessor.GetStringValue("freq"); + var len = accessor.GetStringValue("len"); + var key = accessor.GetStringValue("key"); + return evnt + channel + symbol + prec + freq + len + key; + } + + private static string? GetTypeIdentifier(IMessageAccessor accessor) + { + if (accessor.IsObject(null)) + return null; + + var topic = accessor.GetArrayStringValue(null, 1); + var dataIndex = topic == null ? 1 : 2; + var x = topic + "-single"; + if (accessor.IsArray(new[] { dataIndex, 0 })) + x = topic + "-array"; + + return x; + } + // #region private methods // private void HandleData(string name, JArray dataArray, string? symbol, DataEvent dataEvent, Action> handler, JsonSerializer? serializer = null) // { diff --git a/Bitfinex.Net/Interfaces/Clients/SpotApi/IBitfinexSocketClientSpotApi.cs b/Bitfinex.Net/Interfaces/Clients/SpotApi/IBitfinexSocketClientSpotApi.cs index 9bf36eb..1463073 100644 --- a/Bitfinex.Net/Interfaces/Clients/SpotApi/IBitfinexSocketClientSpotApi.cs +++ b/Bitfinex.Net/Interfaces/Clients/SpotApi/IBitfinexSocketClientSpotApi.cs @@ -37,112 +37,112 @@ public interface IBitfinexSocketClientSpotApi : ISocketApiClient, IDisposable /// Task> SubscribeToFundingTickerUpdatesAsync(string symbol, Action> handler, CancellationToken ct = default); - ///// - ///// Subscribes to order book updates for a symbol. Use SubscribeToFundingOrderBookUpdatesAsync for funding symbol ticker updates - ///// - ///// - ///// The symbol to subscribe to - ///// The precision of the updates - ///// The frequency of updates - ///// The range for the order book updates, either 25 or 100 - ///// The handler for the data - ///// The handler for the checksum, can be used to validate a order book implementation - ///// Cancellation token for closing this subscription - ///// - //Task> SubscribeToOrderBookUpdatesAsync(string symbol, Precision precision, Frequency frequency, int length, Action>> handler, Action>? checksumHandler = null, CancellationToken ct = default); + /// + /// Subscribes to order book updates for a symbol. Use SubscribeToFundingOrderBookUpdatesAsync for funding symbol ticker updates + /// + /// + /// The symbol to subscribe to + /// The precision of the updates + /// The frequency of updates + /// The range for the order book updates, either 25 or 100 + /// The handler for the data + /// The handler for the checksum, can be used to validate a order book implementation + /// Cancellation token for closing this subscription + /// + Task> SubscribeToOrderBookUpdatesAsync(string symbol, Precision precision, Frequency frequency, int length, Action>> handler, Action>? checksumHandler = null, CancellationToken ct = default); - ///// - ///// Subscribes to funding order book updates for a symbol. Use SubscribeToOrderBookUpdatesAsync for trade symbol ticker updates - ///// - ///// - ///// The symbol to subscribe to - ///// The precision of the updates - ///// The frequency of updates - ///// The range for the order book updates, either 25 or 100 - ///// The handler for the data - ///// The handler for the checksum, can be used to validate a order book implementation - ///// Cancellation token for closing this subscription - ///// - //Task> SubscribeToFundingOrderBookUpdatesAsync(string symbol, Precision precision, Frequency frequency, int length, Action>> handler, Action>? checksumHandler = null, CancellationToken ct = default); + /// + /// Subscribes to funding order book updates for a symbol. Use SubscribeToOrderBookUpdatesAsync for trade symbol ticker updates + /// + /// + /// The symbol to subscribe to + /// The precision of the updates + /// The frequency of updates + /// The range for the order book updates, either 25 or 100 + /// The handler for the data + /// The handler for the checksum, can be used to validate a order book implementation + /// Cancellation token for closing this subscription + /// + Task> SubscribeToFundingOrderBookUpdatesAsync(string symbol, Precision precision, Frequency frequency, int length, Action>> handler, Action>? checksumHandler = null, CancellationToken ct = default); - ///// - ///// Subscribes to raw order book updates for a symbol. Use SubscribeToRawFundingOrderBookUpdatesAsync for funding symbol ticker updates - ///// - ///// - ///// The symbol to subscribe to - ///// The range for the order book updates - ///// The handler for the data - ///// The handler for the checksum, can be used to validate a order book implementation - ///// Cancellation token for closing this subscription - ///// - //Task> SubscribeToRawOrderBookUpdatesAsync(string symbol, int limit, Action>> handler, Action>? checksumHandler = null, CancellationToken ct = default); + /// + /// Subscribes to raw order book updates for a symbol. Use SubscribeToRawFundingOrderBookUpdatesAsync for funding symbol ticker updates + /// + /// + /// The symbol to subscribe to + /// The range for the order book updates + /// The handler for the data + /// The handler for the checksum, can be used to validate a order book implementation + /// Cancellation token for closing this subscription + /// + Task> SubscribeToRawOrderBookUpdatesAsync(string symbol, int limit, Action>> handler, Action>? checksumHandler = null, CancellationToken ct = default); - ///// - ///// Subscribes to raw order book updates for a symbol. Use SubscribeToRawOrderBookUpdatesAsync for trade symbol ticker updates - ///// - ///// - ///// The symbol to subscribe to - ///// The range for the order book updates - ///// The handler for the data - ///// The handler for the checksum, can be used to validate a order book implementation - ///// Cancellation token for closing this subscription - ///// - //Task> SubscribeToRawFundingOrderBookUpdatesAsync(string symbol, int limit, Action>> handler, Action>? checksumHandler = null, CancellationToken ct = default); + /// + /// Subscribes to raw order book updates for a symbol. Use SubscribeToRawOrderBookUpdatesAsync for trade symbol ticker updates + /// + /// + /// The symbol to subscribe to + /// The range for the order book updates + /// The handler for the data + /// The handler for the checksum, can be used to validate a order book implementation + /// Cancellation token for closing this subscription + /// + Task> SubscribeToRawFundingOrderBookUpdatesAsync(string symbol, int limit, Action>> handler, Action>? checksumHandler = null, CancellationToken ct = default); - ///// - ///// Subscribes to public trade updates for a symbol - ///// - ///// - ///// The symbol to subscribe to - ///// The handler for the data - ///// Cancellation token for closing this subscription - ///// - //Task> SubscribeToTradeUpdatesAsync(string symbol, Action>> handler, CancellationToken ct = default); + /// + /// Subscribes to public trade updates for a symbol + /// + /// + /// The symbol to subscribe to + /// The handler for the data + /// Cancellation token for closing this subscription + /// + Task> SubscribeToTradeUpdatesAsync(string symbol, Action>> handler, CancellationToken ct = default); - ///// - ///// Subscribes to kline updates for a symbol - ///// - ///// - ///// The symbol to subscribe to. For funding klines use {symbol}:p{period}, for example fUSD:p30 - ///// The interval of the klines - ///// The handler for the data - ///// Cancellation token for closing this subscription - ///// - //Task> SubscribeToKlineUpdatesAsync(string symbol, KlineInterval interval, Action>> handler, CancellationToken ct = default); + /// + /// Subscribes to kline updates for a symbol + /// + /// + /// The symbol to subscribe to. For funding klines use {symbol}:p{period}, for example fUSD:p30 + /// The interval of the klines + /// The handler for the data + /// Cancellation token for closing this subscription + /// + Task> SubscribeToKlineUpdatesAsync(string symbol, KlineInterval interval, Action>> handler, CancellationToken ct = default); - ///// - ///// Subscribe to liquidation updates - ///// - ///// - ///// The handler for the data - ///// Cancellation token for closing this subscription - ///// - //Task> SubscribeToLiquidationUpdatesAsync(Action>> handler, CancellationToken ct = default); + /// + /// Subscribe to liquidation updates + /// + /// + /// The handler for the data + /// Cancellation token for closing this subscription + /// + Task> SubscribeToLiquidationUpdatesAsync(Action>> handler, CancellationToken ct = default); - ///// - ///// Subscribe to derivatives status updates - ///// - ///// - ///// The derivatives symbol, tBTCF0:USTF0 for example - ///// The handler for the data - ///// Cancellation token for closing this subscription - ///// - //Task> SubscribeToDerivativesUpdatesAsync(string symbol, Action> handler, CancellationToken ct = default); + /// + /// Subscribe to derivatives status updates + /// + /// + /// The derivatives symbol, tBTCF0:USTF0 for example + /// The handler for the data + /// Cancellation token for closing this subscription + /// + Task> SubscribeToDerivativesUpdatesAsync(string symbol, Action> handler, CancellationToken ct = default); - ///// - ///// Subscribe to trading information updates - ///// - ///// - ///// Data handler for order updates. Can be null if not interested - ///// Data handler for trade execution updates. Can be null if not interested - ///// Data handler for position updates. Can be null if not interested - ///// Cancellation token for closing this subscription - ///// - //Task> SubscribeToUserTradeUpdatesAsync( - // Action>>> orderHandler, - // Action>>> tradeHandler, - // Action>>> positionHandler, - // CancellationToken ct = default); + /// + /// Subscribe to trading information updates + /// + /// + /// Data handler for order updates. Can be null if not interested + /// Data handler for trade execution updates. Can be null if not interested + /// Data handler for position updates. Can be null if not interested + /// Cancellation token for closing this subscription + /// + Task> SubscribeToUserTradeUpdatesAsync( + Action>>> orderHandler, + Action>>> tradeHandler, + Action>>> positionHandler, + CancellationToken ct = default); ///// ///// Subscribe to wallet information updates diff --git a/Bitfinex.Net/Objects/Internal/BitfinexChecksum.cs b/Bitfinex.Net/Objects/Internal/BitfinexChecksum.cs new file mode 100644 index 0000000..c20ef65 --- /dev/null +++ b/Bitfinex.Net/Objects/Internal/BitfinexChecksum.cs @@ -0,0 +1,19 @@ +using CryptoExchange.Net.Converters; +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Text; + +namespace Bitfinex.Net.Objects.Internal +{ + [JsonConverter(typeof(ArrayConverter))] + internal class BitfinexChecksum + { + [ArrayProperty(0)] + public int ChannelId { get; set; } + [ArrayProperty(1)] + public string Topic { get; set; } + [ArrayProperty(2)] + public int Checksum { get; set; } + } +} diff --git a/Bitfinex.Net/Objects/Models/BitfinexOrderBookEntry.cs b/Bitfinex.Net/Objects/Models/BitfinexOrderBookEntry.cs index db0352a..a4de30e 100644 --- a/Bitfinex.Net/Objects/Models/BitfinexOrderBookEntry.cs +++ b/Bitfinex.Net/Objects/Models/BitfinexOrderBookEntry.cs @@ -13,6 +13,7 @@ public class BitfinexOrderBookBase { } /// /// Order book entry /// + [JsonConverter(typeof(ArrayConverter))] public class BitfinexOrderBookEntry: BitfinexOrderBookBase, ISymbolOrderBookEntry { /// diff --git a/Bitfinex.Net/Objects/Sockets/BitfinexBookRequest.cs b/Bitfinex.Net/Objects/Sockets/BitfinexBookRequest.cs new file mode 100644 index 0000000..8c89bc1 --- /dev/null +++ b/Bitfinex.Net/Objects/Sockets/BitfinexBookRequest.cs @@ -0,0 +1,16 @@ +using Newtonsoft.Json; + +namespace Bitfinex.Net.Objects.Sockets +{ + internal class BitfinexBookRequest: BitfinexRequest + { + [JsonProperty("prec")] + public string Precision { get; set; } + [JsonProperty("freq")] + public string Frequency { get; set; } + [JsonProperty("len")] + public string Length { get; set; } + [JsonProperty("key")] + public string Key { get; set; } + } +} diff --git a/Bitfinex.Net/Objects/Sockets/BitfinexSocketConverter.cs b/Bitfinex.Net/Objects/Sockets/BitfinexSocketConverter.cs index 70164ac..535b979 100644 --- a/Bitfinex.Net/Objects/Sockets/BitfinexSocketConverter.cs +++ b/Bitfinex.Net/Objects/Sockets/BitfinexSocketConverter.cs @@ -1,34 +1,56 @@ -using CryptoExchange.Net.Converters; -using CryptoExchange.Net.Interfaces; -using CryptoExchange.Net.Objects.Sockets; - -namespace Bitfinex.Net.Objects.Sockets -{ - internal class BitfinexSocketConverter : SocketConverter - { - public override MessageInterpreterPipeline InterpreterPipeline { get; } = new MessageInterpreterPipeline - { - GetIdentity = GetIdentity - }; - - private static string GetIdentity(IMessageAccessor accessor) - { - if (!accessor.IsObject(null)) - { - var channelId = accessor.GetArrayIntValue(null, 0); - if (accessor.GetArrayStringValue(null, 1) == "hb") - return "hb"; - - return channelId.ToString(); - } - - var evnt = accessor.GetStringValue("event"); - if (evnt == "info") - return "info"; - - var channel = accessor.GetStringValue("channel"); - var symbol = accessor.GetStringValue("symbol"); - return evnt + channel + symbol; - } - } -} +//using Bitfinex.Net.Objects.Internal; +//using CryptoExchange.Net.Converters; +//using CryptoExchange.Net.Interfaces; +//using CryptoExchange.Net.Objects.Sockets; +//using CryptoExchange.Net.Sockets; + +//namespace Bitfinex.Net.Objects.Sockets +//{ +// internal class BitfinexSocketConverter : SocketConverter +// { +// public override MessageInterpreterPipeline InterpreterPipeline { get; } = new MessageInterpreterPipeline +// { +// GetStreamIdentifier = GetStreamIdentifier, +// GetTypeIdentifier = GetTypeIdentifier +// }; + +// private static string? GetStreamIdentifier(IMessageAccessor accessor) +// { +// if (!accessor.IsObject(null)) +// { +// return accessor.GetArrayIntValue(null, 0).ToString(); +// } + +// var evnt = accessor.GetStringValue("event"); +// if (evnt == "info") +// return "info"; + +// var channel = accessor.GetStringValue("channel"); +// var symbol = accessor.GetStringValue("symbol"); +// var prec = accessor.GetStringValue("prec"); +// var freq = accessor.GetStringValue("freq"); +// var len = accessor.GetStringValue("len"); +// var key = accessor.GetStringValue("key"); +// return evnt + channel + symbol + prec + freq + len + key; +// } + +// private static string? GetTypeIdentifier(IMessageAccessor accessor) +// { +// if (accessor.IsObject(null)) +// return null; + +// var topic = accessor.GetArrayStringValue(null, 1); +// if (topic == "hb") +// return "hb"; +// if (topic == "cs") +// return "cs"; + +// var dataIndex = topic == null ? 1 : 2; +// var x = "single"; +// if (accessor.IsArray(new[] { dataIndex, 0 })) +// x = "array"; + +// return x + dataIndex; +// } +// } +//} diff --git a/Bitfinex.Net/Objects/Sockets/BitfinexUpdate.cs b/Bitfinex.Net/Objects/Sockets/BitfinexUpdate.cs index 105bc63..0a9ff5e 100644 --- a/Bitfinex.Net/Objects/Sockets/BitfinexUpdate.cs +++ b/Bitfinex.Net/Objects/Sockets/BitfinexUpdate.cs @@ -1,4 +1,6 @@ -using CryptoExchange.Net.Converters; +using Bitfinex.Net.Converters; +using CryptoExchange.Net.Attributes; +using CryptoExchange.Net.Converters; using Newtonsoft.Json; namespace Bitfinex.Net.Objects.Sockets @@ -9,6 +11,19 @@ internal class BitfinexUpdate [ArrayProperty(0)] public int ChannelId { get; set; } [ArrayProperty(1)] + [JsonConversion] + public T Data { get; set; } + } + + [JsonConverter(typeof(ArrayConverter))] + internal class BitfinexUpdate3 + { + [ArrayProperty(0)] + public int ChannelId { get; set; } + [ArrayProperty(1)] + public string Topic { get; set; } + [ArrayProperty(2)] + [JsonConversion] public T Data { get; set; } } } diff --git a/Bitfinex.Net/Objects/Sockets/Queries/BitfinexAuthQuery.cs b/Bitfinex.Net/Objects/Sockets/Queries/BitfinexAuthQuery.cs new file mode 100644 index 0000000..3eb4362 --- /dev/null +++ b/Bitfinex.Net/Objects/Sockets/Queries/BitfinexAuthQuery.cs @@ -0,0 +1,16 @@ +using Bitfinex.Net.Objects.Internal; +using CryptoExchange.Net.Sockets; +using System.Collections.Generic; +using System.Globalization; + +namespace Bitfinex.Net.Objects.Sockets.Queries +{ + internal class BitfinexAuthQuery : Query + { + public override List StreamIdentifiers { get; } = new List { "auth" }; + + public BitfinexAuthQuery(BitfinexAuthentication authRequest) : base(authRequest, true) + { + } + } +} diff --git a/Bitfinex.Net/Objects/Sockets/Queries/BitfinexQuery.cs b/Bitfinex.Net/Objects/Sockets/Queries/BitfinexQuery.cs index f0e8d95..aaf7fab 100644 --- a/Bitfinex.Net/Objects/Sockets/Queries/BitfinexQuery.cs +++ b/Bitfinex.Net/Objects/Sockets/Queries/BitfinexQuery.cs @@ -1,19 +1,29 @@ using CryptoExchange.Net.Sockets; +using System; using System.Collections.Generic; +using System.Text; namespace Bitfinex.Net.Objects.Sockets.Queries { internal class BitfinexQuery : Query { - public override List Identifiers { get; } + public override List StreamIdentifiers { get; } - public BitfinexQuery(string evnt, string channel, string? symbol, bool authenticated, int weight = 1) : base(new BitfinexRequest { Channel = channel, Event = evnt, Symbol = symbol }, authenticated, weight) + public BitfinexQuery(string evnt, string channel, string symbol, string precision, string frequency, string length, string key) : base(new BitfinexBookRequest + { + Channel = channel, + Symbol = symbol, + Event = evnt, + Frequency = frequency, + Length = length, + Precision = precision, + Key = key + }, false, 1) { if (evnt == "subscribe" || evnt == "unsubscribe") evnt += "d"; - Identifiers = new List { evnt + channel + symbol }; + StreamIdentifiers = new List { evnt + channel + symbol + precision + frequency + length + key }; } - } } diff --git a/Bitfinex.Net/Objects/Sockets/Subscriptions/BitfinexHeartbeatSubscription.cs b/Bitfinex.Net/Objects/Sockets/Subscriptions/BitfinexHeartbeatSubscription.cs deleted file mode 100644 index 23cc4f0..0000000 --- a/Bitfinex.Net/Objects/Sockets/Subscriptions/BitfinexHeartbeatSubscription.cs +++ /dev/null @@ -1,23 +0,0 @@ -using CryptoExchange.Net.Objects; -using CryptoExchange.Net.Objects.Sockets; -using CryptoExchange.Net.Sockets; -using Microsoft.Extensions.Logging; -using System; -using System.Collections.Generic; -using System.Threading.Tasks; - -namespace Bitfinex.Net.Objects.Sockets.Subscriptions -{ - internal class BitfinexHeartbeatSubscription : SystemSubscription - { - public override List Identifiers { get; } = new List { "hb" }; - - public override Type ExpectedMessageType => typeof(BitfinexUpdate); - - public BitfinexHeartbeatSubscription(ILogger logger) : base(logger, false) - { - } - - public override Task DoHandleMessageAsync(SocketConnection connection, DataEvent message) => Task.FromResult(new CallResult(null)); - } -} diff --git a/Bitfinex.Net/Objects/Sockets/Subscriptions/BitfinexInfoSubscription.cs b/Bitfinex.Net/Objects/Sockets/Subscriptions/BitfinexInfoSubscription.cs index e2b4b0d..141c181 100644 --- a/Bitfinex.Net/Objects/Sockets/Subscriptions/BitfinexInfoSubscription.cs +++ b/Bitfinex.Net/Objects/Sockets/Subscriptions/BitfinexInfoSubscription.cs @@ -9,16 +9,14 @@ namespace Bitfinex.Net.Objects.Sockets.Subscriptions { - internal class BitfinexInfoSubscription : SystemSubscription + internal class BitfinexInfoSubscription : SystemSubscription { - public override List Identifiers { get; } = new List { "info" }; - - public override Type ExpectedMessageType => typeof(BitfinexSocketInfo); + public override List StreamIdentifiers { get; } = new List { "info" }; public BitfinexInfoSubscription(ILogger logger) : base(logger, false) { } - public override Task DoHandleMessageAsync(SocketConnection connection, DataEvent message) => Task.FromResult(new CallResult(null)); + public override Task HandleMessageAsync(SocketConnection connection, DataEvent> message) => Task.FromResult(new CallResult(null)); } } diff --git a/Bitfinex.Net/Objects/Sockets/Subscriptions/BitfinexSubscription.cs b/Bitfinex.Net/Objects/Sockets/Subscriptions/BitfinexSubscription.cs index 86a798e..095d35e 100644 --- a/Bitfinex.Net/Objects/Sockets/Subscriptions/BitfinexSubscription.cs +++ b/Bitfinex.Net/Objects/Sockets/Subscriptions/BitfinexSubscription.cs @@ -1,53 +1,106 @@ -using Bitfinex.Net.Objects.Sockets.Queries; +using Bitfinex.Net.Converters; +using Bitfinex.Net.Enums; +using Bitfinex.Net.Objects.Internal; +using Bitfinex.Net.Objects.Sockets.Queries; using CryptoExchange.Net.Objects; using CryptoExchange.Net.Objects.Sockets; using CryptoExchange.Net.Sockets; using Microsoft.Extensions.Logging; +using Newtonsoft.Json; using System; using System.Collections.Generic; using System.Threading.Tasks; namespace Bitfinex.Net.Objects.Sockets.Subscriptions { - internal class BitfinexSubscription : Subscription> + internal class BitfinexSubscription : Subscription { private string _channel; - private string _symbol; + private string? _symbol; + private string? _precision; + private string? _frequency; + private string? _length; + private string? _key; private int _channelId; - private Action> _handler; + private bool _firstUpdate; + private Action>> _handler; + private Action>? _checksumHandler; - private List _identifiers; + private List _streamIdentifiers; - public override List Identifiers => _identifiers; + public override Dictionary TypeMapping { get; } = new Dictionary + { + { "cs-single", typeof(BitfinexChecksum) }, + { "hb-single", typeof(BitfinexUpdate) }, + { "-single", typeof(BitfinexUpdate) }, + { "-array", typeof(BitfinexUpdate>) }, + { "te-single", typeof(BitfinexUpdate3) }, + { "te-array", typeof(BitfinexUpdate3>) }, + { "tu-single", typeof(BitfinexUpdate3) }, + { "tu-array", typeof(BitfinexUpdate3>) }, + }; + + public override List StreamIdentifiers => _streamIdentifiers; - public BitfinexSubscription(ILogger logger, string channel, string symbol, Action> handler, bool authenticated) : base(logger, authenticated) + public BitfinexSubscription(ILogger logger, + string channel, + string? symbol, + Action>> handler, + Action>? checksumHandler = null, + bool authenticated = false, + Precision? precision = null, + Frequency? frequency = null, + int? length = null, + string? key = null) + : base(logger, authenticated) { _handler = handler; - _channel = channel; + _checksumHandler = checksumHandler; _symbol = symbol; + _key = key; + _channel = channel; + _precision = precision == null ? null : JsonConvert.SerializeObject(precision, new PrecisionConverter(false)); + _frequency = frequency == null ? null: JsonConvert.SerializeObject(frequency, new FrequencyConverter(false)); + _length = length?.ToString(); } public override void HandleSubQueryResponse(ParsedMessage message) { + // TODO this doesn't update when reconnecting/subscribing _channelId = message.TypedData.ChannelId.Value; - + _firstUpdate = true; // Doesn't work, as the subscription is immediately added along with the identifiers // Would maybe be better to wait with adding the subscription untill it is confirmed? - _identifiers = new List { _channelId.ToString() }; + _streamIdentifiers = new List { _channelId.ToString() }; } public override BaseQuery? GetSubQuery(SocketConnection connection) { - return new BitfinexQuery("subscribe", _channel, _symbol, Authenticated); + return new BitfinexQuery("subscribe", _channel, _symbol, _precision, _frequency, _length, _key); } public override BaseQuery? GetUnsubQuery() { - return new BitfinexQuery("unsubscribe", _channel, _symbol, Authenticated); + return new BitfinexQuery("unsubscribe", _channel, _symbol, _precision, _frequency, _length, _key); } - public override Task HandleEventAsync(SocketConnection connection, DataEvent>> message) + public override Task DoHandleMessageAsync(SocketConnection connection, DataEvent message) { - _handler.Invoke(message.As(message.Data.TypedData.Data)); // TODo + // var typedObject = message.As(TypeMapping[message.Data.TypeIdentifier]) + + + if (message.Data.TypeIdentifier == "hb") + return Task.FromResult(new CallResult(null)); + else if (message.Data.TypeIdentifier == "cs") + _checksumHandler?.Invoke(message.As(((BitfinexChecksum)message.Data.Data).Checksum)); + else if (message.Data.TypeIdentifier == "single1") + _handler.Invoke(message.As>(new[] { ((BitfinexUpdate)message.Data.Data).Data }, _symbol, SocketUpdateType.Update)); + else if (message.Data.TypeIdentifier == "single2") + _handler.Invoke(message.As>(new[] { ((BitfinexUpdate3)message.Data.Data).Data }, _symbol, SocketUpdateType.Update)); + else if (message.Data.TypeIdentifier == "array1") + _handler.Invoke(message.As(((BitfinexUpdate>)message.Data.Data).Data, _symbol, _firstUpdate ? SocketUpdateType.Snapshot : SocketUpdateType.Update)); + else if (message.Data.TypeIdentifier == "array2") + _handler.Invoke(message.As(((BitfinexUpdate3>)message.Data.Data).Data, _symbol, _firstUpdate ? SocketUpdateType.Snapshot : SocketUpdateType.Update)); + return Task.FromResult(new CallResult(null)); } } diff --git a/Bitfinex.Net/Objects/Sockets/Subscriptions/BitfinexUserSubscription.cs b/Bitfinex.Net/Objects/Sockets/Subscriptions/BitfinexUserSubscription.cs new file mode 100644 index 0000000..76af9d7 --- /dev/null +++ b/Bitfinex.Net/Objects/Sockets/Subscriptions/BitfinexUserSubscription.cs @@ -0,0 +1,93 @@ +using Bitfinex.Net.Converters; +using Bitfinex.Net.Enums; +using Bitfinex.Net.Objects.Internal; +using Bitfinex.Net.Objects.Models; +using Bitfinex.Net.Objects.Sockets.Queries; +using CryptoExchange.Net.Objects; +using CryptoExchange.Net.Objects.Sockets; +using CryptoExchange.Net.Sockets; +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Threading.Tasks; + +namespace Bitfinex.Net.Objects.Sockets.Subscriptions +{ + internal class BitfinexUserSubscription : Subscription + { + private bool _firstUpdate; + + public override Dictionary TypeMapping { get; } = new Dictionary + { + { "hb-single", typeof(BitfinexUpdate3) }, + + { "ps-array", typeof(BitfinexUpdate3>) }, + { "pn-single", typeof(BitfinexUpdate3) }, + { "pu-single", typeof(BitfinexUpdate3) }, + { "pc-single", typeof(BitfinexUpdate3) }, + + { "bu-single", typeof(BitfinexUpdate3>) }, + + { "miu-single", typeof(BitfinexUpdate3) }, + + { "fiu-single", typeof(BitfinexUpdate3) }, + + { "ws-array", typeof(BitfinexUpdate3>) }, + { "wu-single", typeof(BitfinexUpdate3) }, + + { "os-array", typeof(BitfinexUpdate3>) }, + { "on-single", typeof(BitfinexUpdate3) }, + { "ou-single", typeof(BitfinexUpdate3) }, + { "oc-single", typeof(BitfinexUpdate3) }, + + { "te-single", typeof(BitfinexUpdate3) }, + { "tu-single", typeof(BitfinexUpdate3) }, + + { "fte-single", typeof(BitfinexUpdate3) }, + { "ftu-single", typeof(BitfinexUpdate3) }, + + { "fos-array", typeof(BitfinexUpdate3>) }, + { "fon-single", typeof(BitfinexUpdate3) }, + { "fou-single", typeof(BitfinexUpdate3) }, + { "foc-single", typeof(BitfinexUpdate3) }, + + { "fcs-array", typeof(BitfinexUpdate3>) }, + { "fcc-single", typeof(BitfinexUpdate3) }, + { "fcn-single", typeof(BitfinexUpdate3) }, + { "fcu-single", typeof(BitfinexUpdate3) }, + + { "fls-array", typeof(BitfinexUpdate3>) }, + { "flc-single", typeof(BitfinexUpdate3) }, + { "fln-single", typeof(BitfinexUpdate3) }, + { "flu-single", typeof(BitfinexUpdate3) }, + }; + + public override List StreamIdentifiers { get; } = new List() { "0" }; + + public BitfinexUserSubscription(ILogger logger) + : base(logger, true) + { + } + + public override BaseQuery? GetSubQuery(SocketConnection connection) => null; + + public override BaseQuery? GetUnsubQuery() => null; + + public override Task DoHandleMessageAsync(SocketConnection connection, DataEvent message) + { + Debug.WriteLine($"{message.Data.Data.GetType()}; {message.Data.OriginalData}"); + + if (message.Data.TypeIdentifier == "hb-single") + return Task.FromResult(new CallResult(null)); + //if (message.Data.TypeIdentifier == "bu-single") + // return Task.FromResult(new CallResult(null)); + //if (message.Data.TypeIdentifier == "ws-array") + // return Task.FromResult(new CallResult(null)); + //if (message.Data.TypeIdentifier == "wu-single") + // return Task.FromResult(new CallResult(null)); + + return Task.FromResult(new CallResult(null)); + } + } +} From d146094b1a8607a43510010d3ee9677e48b0a02e Mon Sep 17 00:00:00 2001 From: JKorf Date: Tue, 2 Jan 2024 20:58:42 +0100 Subject: [PATCH 04/25] wip --- Bitfinex.Net/Bitfinex.Net.xml | 184 +++++++-- .../SpotApi/BitfinexSocketClientSpotApi.cs | 350 ++++++++---------- Bitfinex.Net/Enums/BitfinexEventType.cs | 74 ++-- .../SpotApi/IBitfinexSocketClientSpotApi.cs | 222 ++++++----- .../Objects/Internal/BitfinexEvents.cs | 5 - .../Objects/Models/BitfinexBalance.cs | 24 ++ .../Objects/Models/BitfinexTradeSimple.cs | 6 - .../Models/Socket/BitfinexNotification.cs | 26 ++ .../Models/Socket/BitfinexSocketEvent.cs | 6 +- .../Objects/Sockets/Queries/BitfinexQuery.cs | 35 +- .../Sockets/Queries/BitfinexSubQuery.cs | 29 ++ .../Subscriptions/BitfinexSubscription.cs | 4 +- .../Subscriptions/BitfinexUserSubscription.cs | 167 +++++++-- 13 files changed, 692 insertions(+), 440 deletions(-) create mode 100644 Bitfinex.Net/Objects/Models/BitfinexBalance.cs create mode 100644 Bitfinex.Net/Objects/Models/Socket/BitfinexNotification.cs create mode 100644 Bitfinex.Net/Objects/Sockets/Queries/BitfinexSubQuery.cs diff --git a/Bitfinex.Net/Bitfinex.Net.xml b/Bitfinex.Net/Bitfinex.Net.xml index 0d8c5f1..b3adc15 100644 --- a/Bitfinex.Net/Bitfinex.Net.xml +++ b/Bitfinex.Net/Bitfinex.Net.xml @@ -527,7 +527,40 @@ - + + + + + + + + + + + + Cancel all open orders + + + + + + + + + + + + + + + + + + + + + + @@ -620,11 +653,6 @@ Multiple orders cancel request - - - Trade snapshot - - Trade executed @@ -645,11 +673,6 @@ Funding trade update - - - Historical order snapshot - - Margin info snapshot @@ -695,11 +718,6 @@ Funding offer cancel request - - - Historical funding offer snapshot - - Funding credits snapshot @@ -720,11 +738,6 @@ Funding credits closed - - - Historical funding credits snapshot - - Funding loan snapshot @@ -745,16 +758,6 @@ Funding loan closed - - - Historical funding loan snapshot - - - - - Historical funding trade snapshot - - Custom user price alert @@ -2524,7 +2527,7 @@ Cancellation token for closing this subscription - + Subscribe to trading information updates @@ -2535,6 +2538,103 @@ Cancellation token for closing this subscription + + + Places a new order + + + The order side + The type of the order + The symbol the order is for + The quantity of the order, positive for buying, negative for selling + Group id to assign to the order + Client order id to assign to the order + Price of the order + Trailing price of the order + Auxiliary limit price of the order + Oco stop price of the order + Additional flags + Leverage + Automatically cancel the order after this time + Affiliate code for the order + + + + + + Updates an order + + + The id of the order to update + The new price of the order + The new quantity of the order + The delta to change + the new aux limit price + The new trailing price + The new flags + + + + + Cancels an order + + + The id of the order to cancel + + + + + Cancels multiple orders based on their groupId + + + The group id to cancel + True if successfully committed on server + + + + Cancels multiple orders based on their groupIds + + + The group ids to cancel + True if successfully committed on server + + + + Cancels multiple orders based on their order ids + + + The order ids to cancel + True if successfully committed on server + + + + Cancels multiple orders based on their clientOrderIds + + + The client order ids to cancel, listed as (clientOrderId, Day) pair. ClientOrderIds are unique per day, so timestamp should be provided + True if successfully committed on server + + + + Submit a new funding offer + + + Offer type + Symbol + Amount (more than 0 for offer, less than 0 for bid) + Rate (or offset for FRRDELTA offers) + Time period of offer. Minimum 2 days. Maximum 120 days. + Flags + + + + + Cancel a funding offer + + + Id of the offer to cancel + + Bitfinex order book factory @@ -2686,6 +2786,21 @@ The quantity + + + Balance + + + + + Total Assets Under Management + + + + + Net Assets Under Management (total assets - total liabilities) + + Account change log @@ -4714,11 +4829,6 @@ Amount of time the funding transaction was for - - - The type of update - - Transfer info diff --git a/Bitfinex.Net/Clients/SpotApi/BitfinexSocketClientSpotApi.cs b/Bitfinex.Net/Clients/SpotApi/BitfinexSocketClientSpotApi.cs index 1c04c13..dc50cf6 100644 --- a/Bitfinex.Net/Clients/SpotApi/BitfinexSocketClientSpotApi.cs +++ b/Bitfinex.Net/Clients/SpotApi/BitfinexSocketClientSpotApi.cs @@ -174,179 +174,179 @@ public async Task> SubscribeToDerivativesUpdatesA } /// - public async Task> SubscribeToUserTradeUpdatesAsync( - Action>>> orderHandler, - Action>>> tradeHandler, - Action>>> positionHandler, + public async Task> SubscribeToUserUpdatesAsync( + Action>> orderHandler, + Action>> positionHandler, + Action>> fundingOfferHandler, + Action>> fundingCreditHandler, + Action>> fundingLoanHandler, + Action>> walletHandler, + Action> balanceHandler, + Action> tradeHandler, + Action> fundingTradeHandler, + Action> fundingInfoHandler, CancellationToken ct = default) { - var subscription = new BitfinexUserSubscription(_logger); + var subscription = new BitfinexUserSubscription(_logger, positionHandler, walletHandler, orderHandler, fundingOfferHandler, fundingCreditHandler, fundingLoanHandler, balanceHandler, tradeHandler, fundingTradeHandler, fundingInfoHandler); return await SubscribeAsync(BaseAddress.AppendPath("ws/2"), subscription, ct).ConfigureAwait(false); - - //var tokenHandler = new Action>(tokenData => - //{ - // HandleAuthUpdate(tokenData, orderHandler, "Orders"); - // HandleAuthUpdate(tokenData, tradeHandler, "Trades"); - // HandleAuthUpdate(tokenData, positionHandler, "Positions"); - //}); - - //return await SubscribeAsync(BaseAddress.AppendPath("ws/2"), null, "Orders|Trades|Positions", true, tokenHandler, ct).ConfigureAwait(false); } - // /// - // public async Task> SubscribeToBalanceUpdatesAsync(Action>>> walletHandler, CancellationToken ct = default) - // { - // var tokenHandler = new Action>(tokenData => - // { - // HandleAuthUpdate(tokenData, walletHandler, "Wallet"); - // }); - - // return await SubscribeAsync(BaseAddress.AppendPath("ws/2"), null, "Wallet", true, tokenHandler, ct).ConfigureAwait(false); - // } + /// + public async Task> PlaceOrderAsync(OrderSide side, OrderType type, string symbol, decimal quantity, long? groupId = null, long? clientOrderId = null, decimal? price = null, decimal? priceTrailing = null, decimal? priceAuxiliaryLimit = null, decimal? priceOcoStop = null, OrderFlags? flags = null, int? leverage = null, DateTime? cancelTime = null, string? affiliateCode = null) + { + symbol.ValidateBitfinexSymbol(); + _logger.Log(LogLevel.Information, "Going to place order"); + clientOrderId ??= GenerateClientOrderId(); - // /// - // public async Task> SubscribeToFundingUpdatesAsync( - // Action>>> fundingOfferHandler, - // Action>>> fundingCreditHandler, - // Action>>> fundingLoanHandler, - // CancellationToken ct = default) - // { - // var tokenHandler = new Action>(tokenData => - // { - // HandleAuthUpdate(tokenData, fundingOfferHandler, "FundingOffers"); - // HandleAuthUpdate(tokenData, fundingCreditHandler, "FundingCredits"); - // HandleAuthUpdate(tokenData, fundingLoanHandler, "FundingLoans"); - // }); + var affCode = affiliateCode ?? _affCode; + var query = new BitfinexSocketQuery(clientOrderId.ToString(), BitfinexEventType.OrderNew, new BitfinexNewOrder + { + Amount = side == OrderSide.Buy ? quantity : -quantity, + OrderType = type, + Symbol = symbol, + Price = price, + ClientOrderId = clientOrderId, + Flags = flags, + GroupId = groupId, + PriceAuxiliaryLimit = priceAuxiliaryLimit, + PriceOCOStop = priceOcoStop, + PriceTrailing = priceTrailing, + Leverage = leverage, + CancelAfter = cancelTime, + Meta = affCode == null ? null : new BitfinexMeta() { AffiliateCode = affCode } + }); + + var bitfinexQuery = new BitfinexQuery(query); + var result = await QueryAsync(BaseAddress.AppendPath("ws/2"), bitfinexQuery).ConfigureAwait(false); + return result.As(result.Data?.Data.Data); + } - // return await SubscribeAsync(BaseAddress.AppendPath("ws/2"), null, "FundingOffers|FundingCredits|FundingLoans", true, tokenHandler, ct).ConfigureAwait(false); - // } + /// + public async Task> UpdateOrderAsync(long orderId, decimal? price = null, decimal? quantity = null, decimal? delta = null, decimal? priceAuxiliaryLimit = null, decimal? priceTrailing = null, OrderFlags? flags = null) + { + _logger.Log(LogLevel.Information, "Going to update order " + orderId); + var query = new BitfinexSocketQuery(orderId.ToString(CultureInfo.InvariantCulture), BitfinexEventType.OrderUpdate, new BitfinexUpdateOrder + { + OrderId = orderId, + Amount = quantity, + Price = price, + Flags = flags, + PriceAuxiliaryLimit = priceAuxiliaryLimit?.ToString(CultureInfo.InvariantCulture), + PriceTrailing = priceTrailing?.ToString(CultureInfo.InvariantCulture) + }); + + var bitfinexQuery = new BitfinexQuery(query); + var result = await QueryAsync(BaseAddress.AppendPath("ws/2"), bitfinexQuery).ConfigureAwait(false); + return result.As(result.Data?.Data.Data); + } - // /// - // public async Task> PlaceOrderAsync(OrderSide side, OrderType type, string symbol, decimal quantity, long? groupId = null, long? clientOrderId = null, decimal? price = null, decimal? priceTrailing = null, decimal? priceAuxiliaryLimit = null, decimal? priceOcoStop = null, OrderFlags? flags = null, int? leverage = null, DateTime? cancelTime = null, string? affiliateCode = null) - // { - // symbol.ValidateBitfinexSymbol(); - // _logger.Log(LogLevel.Information, "Going to place order"); - // clientOrderId ??= GenerateClientOrderId(); + /// + /// Cancel all open orders + /// + /// + public async Task>> CancelAllOrdersAsync() + { + var query = new BitfinexSocketQuery(null, BitfinexEventType.OrderCancelMulti, new BitfinexMultiCancel { All = true }); + var bitfinexQuery = new BitfinexQuery>(query); + var result = await QueryAsync(BaseAddress.AppendPath("ws/2"), bitfinexQuery).ConfigureAwait(false); + return result.As>(result.Data?.Data.Data); + } - // var affCode = affiliateCode ?? _affCode; - // var query = new BitfinexSocketQuery(clientOrderId.ToString(), BitfinexEventType.OrderNew, new BitfinexNewOrder - // { - // Amount = side == OrderSide.Buy ? quantity : -quantity, - // OrderType = type, - // Symbol = symbol, - // Price = price, - // ClientOrderId = clientOrderId, - // Flags = flags, - // GroupId = groupId, - // PriceAuxiliaryLimit = priceAuxiliaryLimit, - // PriceOCOStop = priceOcoStop, - // PriceTrailing = priceTrailing, - // Leverage = leverage, - // CancelAfter = cancelTime, - // Meta = affCode == null ? null : new BitfinexMeta() { AffiliateCode = affCode } - // }); - - // return await QueryAsync(BaseAddress.AppendPath("ws/2"), query, true).ConfigureAwait(false); - // } + /// + public async Task> CancelOrderAsync(long orderId) + { + var query = new BitfinexSocketQuery(orderId.ToString(CultureInfo.InvariantCulture), BitfinexEventType.OrderCancel, new Dictionary { ["id"] = orderId }); + var bitfinexQuery = new BitfinexQuery(query); + var result = await QueryAsync(BaseAddress.AppendPath("ws/2"), bitfinexQuery).ConfigureAwait(false); + return result.As(result.Data?.Data.Data); + } - // /// - // public async Task> UpdateOrderAsync(long orderId, decimal? price = null, decimal? quantity = null, decimal? delta = null, decimal? priceAuxiliaryLimit = null, decimal? priceTrailing = null, OrderFlags? flags = null) - // { - // _logger.Log(LogLevel.Information, "Going to update order " + orderId); - // var query = new BitfinexSocketQuery(orderId.ToString(CultureInfo.InvariantCulture), BitfinexEventType.OrderUpdate, new BitfinexUpdateOrder - // { - // OrderId = orderId, - // Amount = quantity, - // Price = price, - // Flags = flags, - // PriceAuxiliaryLimit = priceAuxiliaryLimit?.ToString(CultureInfo.InvariantCulture), - // PriceTrailing = priceTrailing?.ToString(CultureInfo.InvariantCulture) - // }); - - // return await QueryAsync(BaseAddress.AppendPath("ws/2"), query, true).ConfigureAwait(false); - // } + /// + public async Task>> CancelOrdersByGroupIdAsync(long groupOrderId) + { + return await CancelOrdersAsync(null, null, new Dictionary { { groupOrderId, null } }).ConfigureAwait(false); + } - // ///// - // ///// Cancel all open orders - // ///// - // ///// - // //public CallResult CancelAllOrders() => CancelAllOrdersAsync().Result; - // ///// - // ///// Cancel all open orders - // ///// - // ///// - // //public async Task> CancelAllOrdersAsync() - // //{ - // // // Doesn't seem to work even though it is implemented as described at https://docs.bitfinex.com/v2/reference#ws-input-order-cancel-multi - // // _log.Write(LogLevel.Information, "Going to cancel all orders"); - // // var query = new BitfinexSocketQuery(null, BitfinexEventType.OrderCancelMulti, new BitfinexMultiCancel { All = true }); - - // // return await Query(query, true).ConfigureAwait(false); - // //} + /// + public async Task>> CancelOrdersByGroupIdsAsync(IEnumerable groupOrderIds) + { + groupOrderIds.ValidateNotNull(nameof(groupOrderIds)); + return await CancelOrdersAsync(null, null, groupOrderIds.ToDictionary(v => v, k => (long?)null)).ConfigureAwait(false); + } - // /// - // public async Task> CancelOrderAsync(long orderId) - // { - // _logger.Log(LogLevel.Information, "Going to cancel order " + orderId); - // var query = new BitfinexSocketQuery(orderId.ToString(CultureInfo.InvariantCulture), BitfinexEventType.OrderCancel, new JObject { ["id"] = orderId }); + /// + public async Task>> CancelOrdersAsync(IEnumerable orderIds) + { + orderIds.ValidateNotNull(nameof(orderIds)); + return await CancelOrdersAsync(orderIds, null).ConfigureAwait(false); + } - // return await QueryAsync(BaseAddress.AppendPath("ws/2"), query, true).ConfigureAwait(false); - // } + /// + public async Task>> CancelOrdersByClientOrderIdsAsync(Dictionary clientOrderIds) + { + return await CancelOrdersAsync(null, clientOrderIds).ConfigureAwait(false); + } - // /// - // public async Task> CancelOrdersByGroupIdAsync(long groupOrderId) - // { - // return await CancelOrdersAsync(null, null, new Dictionary { { groupOrderId, null } }).ConfigureAwait(false); - // } + private async Task>> CancelOrdersAsync(IEnumerable? orderIds = null, Dictionary? clientOrderIds = null, Dictionary? groupOrderIds = null) + { + if (orderIds == null && clientOrderIds == null && groupOrderIds == null) + throw new ArgumentException("Either orderIds, clientOrderIds or groupOrderIds should be provided"); - // /// - // public async Task> CancelOrdersByGroupIdsAsync(IEnumerable groupOrderIds) - // { - // groupOrderIds.ValidateNotNull(nameof(groupOrderIds)); - // return await CancelOrdersAsync(null, null, groupOrderIds.ToDictionary(v => v, k => (long?)null)).ConfigureAwait(false); - // } + _logger.Log(LogLevel.Information, "Going to cancel multiple orders"); + var cancelObject = new BitfinexMultiCancel { OrderIds = orderIds }; + if (clientOrderIds != null) + { + cancelObject.ClientIds = new object[clientOrderIds.Count][]; + for (var i = 0; i < cancelObject.ClientIds.Length; i++) + { + cancelObject.ClientIds[i] = new object[] + { + clientOrderIds.ElementAt(i).Key, + clientOrderIds.ElementAt(i).Value.ToString("yyyy-MM-dd") + }; + } + } + if (groupOrderIds != null) + cancelObject.GroupIds = new[] { groupOrderIds.Select(g => g.Key).ToArray() }; - // /// - // public async Task> CancelOrdersAsync(IEnumerable orderIds) - // { - // orderIds.ValidateNotNull(nameof(orderIds)); - // return await CancelOrdersAsync(orderIds, null).ConfigureAwait(false); - // } + var query = new BitfinexSocketQuery(null, BitfinexEventType.OrderCancelMulti, cancelObject); + var bitfinexQuery = new BitfinexQuery>(query); + var result = await QueryAsync(BaseAddress.AppendPath("ws/2"), bitfinexQuery).ConfigureAwait(false); + return result.As>(result.Data?.Data.Data); + } - // /// - // public async Task> CancelOrdersByClientOrderIdsAsync(Dictionary clientOrderIds) - // { - // return await CancelOrdersAsync(null, clientOrderIds).ConfigureAwait(false); - // } + /// + public async Task> SubmitFundingOfferAsync(FundingOfferType type, string symbol, decimal quantity, decimal price, int period, int? flags = null) + { + var parameters = new Dictionary + { + { "type", EnumConverter.GetString(type) }, + { "symbol", symbol }, + { "amount", quantity }, + { "rate", price }, + { "period", period }, + }; + parameters.AddOptionalParameter("flags", flags); - // /// - // public async Task> SubmitFundingOfferAsync(FundingOfferType type, string symbol, decimal quantity, decimal price, int period, int? flags = null) - // { - // var parameters = new Dictionary - // { - // { "type", EnumConverter.GetString(type) }, - // { "symbol", symbol }, - // { "amount", quantity }, - // { "rate", price }, - // { "period", period }, - // }; - // parameters.AddOptionalParameter("flags", flags); - - // var query = new BitfinexSocketQuery(ExchangeHelpers.NextId().ToString(CultureInfo.InvariantCulture), BitfinexEventType.FundingOfferNew, parameters); - // return await QueryAsync(BaseAddress.AppendPath("ws/2"), query, true).ConfigureAwait(false); - // } + var query = new BitfinexSocketQuery(ExchangeHelpers.NextId().ToString(CultureInfo.InvariantCulture), BitfinexEventType.FundingOfferNew, parameters); + var bitfinexQuery = new BitfinexQuery(query); + var result = await QueryAsync(BaseAddress.AppendPath("ws/2"), bitfinexQuery).ConfigureAwait(false); + return result.As(result.Data?.Data.Data); + } - // /// - // public async Task> CancelFundingOfferAsync(long id) - // { - // var parameters = new Dictionary - // { - // { "id", id } - // }; + /// + public async Task> CancelFundingOfferAsync(long id) + { + var parameters = new Dictionary + { + { "id", id } + }; - // var query = new BitfinexSocketQuery(id.ToString(CultureInfo.InvariantCulture), BitfinexEventType.FundingOfferCancel, parameters); - // return await QueryAsync(BaseAddress.AppendPath("ws/2"), query, true).ConfigureAwait(false); - // } + var query = new BitfinexSocketQuery(id.ToString(CultureInfo.InvariantCulture), BitfinexEventType.FundingOfferCancel, parameters); + var bitfinexQuery = new BitfinexQuery(query); + var result = await QueryAsync(BaseAddress.AppendPath("ws/2"), bitfinexQuery).ConfigureAwait(false); + return result.As(result.Data?.Data.Data); + } // #endregion private static string? GetStreamIdentifier(IMessageAccessor accessor) @@ -377,9 +377,13 @@ public async Task> SubscribeToUserTradeUpdatesAsy var topic = accessor.GetArrayStringValue(null, 1); var dataIndex = topic == null ? 1 : 2; var x = topic + "-single"; - if (accessor.IsArray(new[] { dataIndex, 0 })) + if (accessor.IsArray(new[] { dataIndex, 0 }) || accessor.IsEmptyArray(new[] { dataIndex })) x = topic + "-array"; + if (x == "os-array") + { + } + return x; } @@ -410,32 +414,6 @@ public async Task> SubscribeToUserTradeUpdatesAsy // handler(dataEvent.As(desResult.Data, symbol)); // } - // private async Task> CancelOrdersAsync(IEnumerable? orderIds = null, Dictionary? clientOrderIds = null, Dictionary? groupOrderIds = null) - // { - // if (orderIds == null && clientOrderIds == null && groupOrderIds == null) - // throw new ArgumentException("Either orderIds, clientOrderIds or groupOrderIds should be provided"); - - // _logger.Log(LogLevel.Information, "Going to cancel multiple orders"); - // var cancelObject = new BitfinexMultiCancel { OrderIds = orderIds }; - // if (clientOrderIds != null) - // { - // cancelObject.ClientIds = new object[clientOrderIds.Count][]; - // for (var i = 0; i < cancelObject.ClientIds.Length; i++) - // { - // cancelObject.ClientIds[i] = new object[] - // { - // clientOrderIds.ElementAt(i).Key, - // clientOrderIds.ElementAt(i).Value.ToString("yyyy-MM-dd") - // }; - // } - // } - // if (groupOrderIds != null) - // cancelObject.GroupIds = new[] { groupOrderIds.Select(g => g.Key).ToArray() }; - - // var query = new BitfinexSocketQuery(null, BitfinexEventType.OrderCancelMulti, cancelObject); - // return await QueryAsync(query, true).ConfigureAwait(false); - // } - // private void HandleAuthUpdate(DataEvent token, Action>>> action, string category) // { // var evntStr = token.Data[1]?.ToString(); @@ -478,12 +456,12 @@ public async Task> SubscribeToUserTradeUpdatesAsy // action(token.As(new BitfinexSocketEvent>(evntType, data))); // } - // private long GenerateClientOrderId() - // { - // var buffer = new byte[8]; - // _random.NextBytes(buffer); - // return (long)Math.Round(Math.Abs(BitConverter.ToInt32(buffer, 0)) / 1000m); - // } + private long GenerateClientOrderId() + { + var buffer = new byte[8]; + _random.NextBytes(buffer); + return (long)Math.Round(Math.Abs(BitConverter.ToInt32(buffer, 0)) / 1000m); + } // private void ConfHandler(MessageEvent messageEvent) diff --git a/Bitfinex.Net/Enums/BitfinexEventType.cs b/Bitfinex.Net/Enums/BitfinexEventType.cs index 5f0a4e3..bbcae84 100644 --- a/Bitfinex.Net/Enums/BitfinexEventType.cs +++ b/Bitfinex.Net/Enums/BitfinexEventType.cs @@ -1,4 +1,6 @@ -namespace Bitfinex.Net.Enums +using CryptoExchange.Net.Attributes; + +namespace Bitfinex.Net.Enums { /// /// Socket event types @@ -8,199 +10,209 @@ public enum BitfinexEventType /// /// Heartbeat /// + [Map("hb")] HeartBeat, /// /// Balance update /// + [Map("bu")] BalanceUpdate, /// /// Position snapshot /// + [Map("ps")] PositionSnapshot, /// /// New position /// + [Map("pn")] PositionNew, /// /// Position update /// + [Map("pu")] PositionUpdate, /// /// Position closed /// + [Map("pc")] PositionClose, /// /// Wallet snapshot /// + [Map("ws")] WalletSnapshot, /// /// Wallet update /// + [Map("wu")] WalletUpdate, /// /// Orders snapshot /// + [Map("os")] OrderSnapshot, /// /// New order /// + [Map("on")] OrderNew, /// /// New order request /// + [Map("on-req")] OrderNewRequest, /// /// Order update /// + [Map("ou")] OrderUpdate, /// /// Order update request /// + [Map("ou-req")] OrderUpdateRequest, /// /// Order canceled /// + [Map("oc")] OrderCancel, /// /// Order cancel request /// + [Map("oc-req")] OrderCancelRequest, /// /// Multiple orders canceled /// + [Map("oc_multi")] OrderCancelMulti, /// /// Multiple orders cancel request /// + [Map("oc_multi-req")] OrderCancelMultiRequest, - /// - /// Trade snapshot - /// - TradeSnapshot, /// /// Trade executed /// + [Map("te")] TradeExecuted, /// /// Trade execution update /// + [Map("tu")] TradeExecutionUpdate, /// /// Funding trade execution /// + [Map("fte")] FundingTradeExecution, /// /// Funding trade update /// + [Map("ftu")] FundingTradeUpdate, - /// - /// Historical order snapshot - /// - HistoricalOrderSnapshot, - /// /// Margin info snapshot /// + [Map("mis")] MarginInfoSnapshot, /// /// Margin info update /// + [Map("miu")] MarginInfoUpdate, /// /// Notification /// + [Map("n")] Notification, /// /// Funding offer snapshot /// + [Map("fos")] FundingOfferSnapshot, /// /// New funding offer /// + [Map("fon")] FundingOfferNew, /// /// New funding offer request /// + [Map("fon-req")] FundingOfferNewRequest, /// /// Funding offer update /// + [Map("fou")] FundingOfferUpdate, /// /// Funding offer canceled /// + [Map("foc")] FundingOfferCancel, /// /// Funding offer cancel request /// + [Map("foc-req")] FundingOfferCancelRequest, - /// - /// Historical funding offer snapshot - /// - HistoricalFundingOfferSnapshot, - /// /// Funding credits snapshot /// + [Map("fcs")] FundingCreditsSnapshot, /// /// New funding credits /// + [Map("fcn")] FundingCreditsNew, /// /// Funding credits update /// + [Map("fcu")] FundingCreditsUpdate, /// /// Funding credits closed /// + [Map("fcc")] FundingCreditsClose, - - /// - /// Historical funding credits snapshot - /// - HistoricalFundingCreditsSnapshot, - + /// /// Funding loan snapshot /// + [Map("fls")] FundingLoanSnapshot, /// /// New funding loan /// + [Map("fln")] FundingLoanNew, /// /// Funding loan update /// + [Map("flu")] FundingLoanUpdate, /// /// Funding loan closed /// + [Map("flc")] FundingLoanClose, - /// - /// Historical funding loan snapshot - /// - HistoricalFundingLoanSnapshot, - - /// - /// Historical funding trade snapshot - /// - HistoricalFundingTradeSnapshot, - /// /// Custom user price alert /// + [Map("uac")] UserCustomPriceAlert } } diff --git a/Bitfinex.Net/Interfaces/Clients/SpotApi/IBitfinexSocketClientSpotApi.cs b/Bitfinex.Net/Interfaces/Clients/SpotApi/IBitfinexSocketClientSpotApi.cs index 1463073..cf5d716 100644 --- a/Bitfinex.Net/Interfaces/Clients/SpotApi/IBitfinexSocketClientSpotApi.cs +++ b/Bitfinex.Net/Interfaces/Clients/SpotApi/IBitfinexSocketClientSpotApi.cs @@ -138,132 +138,120 @@ public interface IBitfinexSocketClientSpotApi : ISocketApiClient, IDisposable /// Data handler for position updates. Can be null if not interested /// Cancellation token for closing this subscription /// - Task> SubscribeToUserTradeUpdatesAsync( - Action>>> orderHandler, - Action>>> tradeHandler, - Action>>> positionHandler, - CancellationToken ct = default); + Task> SubscribeToUserUpdatesAsync( + Action>> orderHandler, + Action>> positionHandler, + Action>> fundingOfferHandler, + Action>> fundingCreditHandler, + Action>> fundingLoanHandler, + Action>> walletHandler, + Action> balanceHandler, + Action> tradeHandler, + Action> fundingTradeHandler, + Action> fundingInfoHandler, + CancellationToken ct = default); - ///// - ///// Subscribe to wallet information updates - ///// - ///// - ///// Data handler for wallet updates - ///// Cancellation token for closing this subscription - ///// - //Task> SubscribeToBalanceUpdatesAsync(Action>>> walletHandler, CancellationToken ct = default); - - ///// - ///// Subscribe to funding information updates - ///// - ///// - ///// - ///// - ///// Subscribe to funding offer updates. Can be null if not interested - ///// Subscribe to funding credit updates. Can be null if not interested - ///// Subscribe to funding loan updates. Can be null if not interested - ///// Cancellation token for closing this subscription - ///// - //Task> SubscribeToFundingUpdatesAsync( - // Action>>> fundingOfferHandler, - // Action>>> fundingCreditHandler, - // Action>>> fundingLoanHandler, - // CancellationToken ct = default); + /// + /// Places a new order + /// + /// + /// The order side + /// The type of the order + /// The symbol the order is for + /// The quantity of the order, positive for buying, negative for selling + /// Group id to assign to the order + /// Client order id to assign to the order + /// Price of the order + /// Trailing price of the order + /// Auxiliary limit price of the order + /// Oco stop price of the order + /// Additional flags + /// Leverage + /// Automatically cancel the order after this time + /// Affiliate code for the order + /// + Task> PlaceOrderAsync(OrderSide side, OrderType type, string symbol, decimal quantity, long? groupId = null, long? clientOrderId = null, decimal? price = null, decimal? priceTrailing = null, decimal? priceAuxiliaryLimit = null, decimal? priceOcoStop = null, OrderFlags? flags = null, int? leverage = null, DateTime? cancelTime = null, string? affiliateCode = null); - ///// - ///// Places a new order - ///// - ///// - ///// The order side - ///// The type of the order - ///// The symbol the order is for - ///// The quantity of the order, positive for buying, negative for selling - ///// Group id to assign to the order - ///// Client order id to assign to the order - ///// Price of the order - ///// Trailing price of the order - ///// Auxiliary limit price of the order - ///// Oco stop price of the order - ///// Additional flags - ///// Leverage - ///// Automatically cancel the order after this time - ///// Affiliate code for the order - ///// - //Task> PlaceOrderAsync(OrderSide side, OrderType type, string symbol, decimal quantity, long? groupId = null, long? clientOrderId = null, decimal? price = null, decimal? priceTrailing = null, decimal? priceAuxiliaryLimit = null, decimal? priceOcoStop = null, OrderFlags? flags = null, int? leverage = null, DateTime? cancelTime = null, string? affiliateCode = null); + /// + /// Cancel all orders + ///// + /// + /// + Task>> CancelAllOrdersAsync(); - ///// - ///// Updates an order - ///// - ///// - ///// The id of the order to update - ///// The new price of the order - ///// The new quantity of the order - ///// The delta to change - ///// the new aux limit price - ///// The new trailing price - ///// The new flags - ///// - //Task> UpdateOrderAsync(long orderId, decimal? price = null, decimal? quantity = null, decimal? delta = null, decimal? priceAuxiliaryLimit = null, decimal? priceTrailing = null, OrderFlags? flags = null); + /// + /// Updates an order + /// + /// + /// The id of the order to update + /// The new price of the order + /// The new quantity of the order + /// The delta to change + /// the new aux limit price + /// The new trailing price + /// The new flags + /// + Task> UpdateOrderAsync(long orderId, decimal? price = null, decimal? quantity = null, decimal? delta = null, decimal? priceAuxiliaryLimit = null, decimal? priceTrailing = null, OrderFlags? flags = null); - ///// - ///// Cancels an order - ///// - ///// - ///// The id of the order to cancel - ///// - //Task> CancelOrderAsync(long orderId); + /// + /// Cancels an order + /// + /// + /// The id of the order to cancel + /// + Task> CancelOrderAsync(long orderId); - ///// - ///// Cancels multiple orders based on their groupId - ///// - ///// - ///// The group id to cancel - ///// True if successfully committed on server - //Task> CancelOrdersByGroupIdAsync(long groupOrderId); + /// + /// Cancels multiple orders based on their groupId + /// + /// + /// The group id to cancel + /// True if successfully committed on server + Task>> CancelOrdersByGroupIdAsync(long groupOrderId); - ///// - ///// Cancels multiple orders based on their groupIds - ///// - ///// - ///// The group ids to cancel - ///// True if successfully committed on server - //Task> CancelOrdersByGroupIdsAsync(IEnumerable groupOrderIds); + /// + /// Cancels multiple orders based on their groupIds + /// + /// + /// The group ids to cancel + /// True if successfully committed on server + Task>> CancelOrdersByGroupIdsAsync(IEnumerable groupOrderIds); - ///// - ///// Cancels multiple orders based on their order ids - ///// - ///// - ///// The order ids to cancel - ///// True if successfully committed on server - //Task> CancelOrdersAsync(IEnumerable orderIds); + /// + /// Cancels multiple orders based on their order ids + /// + /// + /// The order ids to cancel + /// True if successfully committed on server + Task>> CancelOrdersAsync(IEnumerable orderIds); - ///// - ///// Cancels multiple orders based on their clientOrderIds - ///// - ///// - ///// The client order ids to cancel, listed as (clientOrderId, Day) pair. ClientOrderIds are unique per day, so timestamp should be provided - ///// True if successfully committed on server - //Task> CancelOrdersByClientOrderIdsAsync(Dictionary clientOrderIds); + /// + /// Cancels multiple orders based on their clientOrderIds + /// + /// + /// The client order ids to cancel, listed as (clientOrderId, Day) pair. ClientOrderIds are unique per day, so timestamp should be provided + /// True if successfully committed on server + Task>> CancelOrdersByClientOrderIdsAsync(Dictionary clientOrderIds); - ///// - ///// Submit a new funding offer - ///// - ///// - ///// Offer type - ///// Symbol - ///// Amount (more than 0 for offer, less than 0 for bid) - ///// Rate (or offset for FRRDELTA offers) - ///// Time period of offer. Minimum 2 days. Maximum 120 days. - ///// Flags - ///// - //Task> SubmitFundingOfferAsync(FundingOfferType type, string symbol, decimal quantity, decimal price, int period, int? flags = null); + /// + /// Submit a new funding offer + /// + /// + /// Offer type + /// Symbol + /// Amount (more than 0 for offer, less than 0 for bid) + /// Rate (or offset for FRRDELTA offers) + /// Time period of offer. Minimum 2 days. Maximum 120 days. + /// Flags + /// + Task> SubmitFundingOfferAsync(FundingOfferType type, string symbol, decimal quantity, decimal price, int period, int? flags = null); - ///// - ///// Cancel a funding offer - ///// - ///// - ///// Id of the offer to cancel - ///// - //Task> CancelFundingOfferAsync(long id); + /// + /// Cancel a funding offer + /// + /// + /// Id of the offer to cancel + /// + Task> CancelFundingOfferAsync(long id); } } \ No newline at end of file diff --git a/Bitfinex.Net/Objects/Internal/BitfinexEvents.cs b/Bitfinex.Net/Objects/Internal/BitfinexEvents.cs index c6a8caa..5b1ef61 100644 --- a/Bitfinex.Net/Objects/Internal/BitfinexEvents.cs +++ b/Bitfinex.Net/Objects/Internal/BitfinexEvents.cs @@ -89,7 +89,6 @@ public static IEnumerable GetEventsForCategory(string cat) { "tu", BitfinexEventType.TradeExecutionUpdate }, { "fte", BitfinexEventType.FundingTradeExecution }, { "ftu", BitfinexEventType.FundingTradeUpdate }, - { "hos", BitfinexEventType.HistoricalOrderSnapshot }, { "mis", BitfinexEventType.MarginInfoSnapshot }, { "miu", BitfinexEventType.MarginInfoUpdate }, { "n", BitfinexEventType.Notification }, @@ -99,18 +98,14 @@ public static IEnumerable GetEventsForCategory(string cat) { "fou", BitfinexEventType.FundingOfferUpdate }, { "foc", BitfinexEventType.FundingOfferCancel }, { "foc-req", BitfinexEventType.FundingOfferCancelRequest }, - { "hfos", BitfinexEventType.HistoricalFundingOfferSnapshot }, { "fcs", BitfinexEventType.FundingCreditsSnapshot }, { "fcn", BitfinexEventType.FundingCreditsNew }, { "fcu", BitfinexEventType.FundingCreditsUpdate }, { "fcc", BitfinexEventType.FundingCreditsClose }, - { "hfcs", BitfinexEventType.HistoricalFundingCreditsSnapshot }, { "fls", BitfinexEventType.FundingLoanSnapshot }, { "fln", BitfinexEventType.FundingLoanNew }, { "flu", BitfinexEventType.FundingLoanUpdate }, { "flc", BitfinexEventType.FundingLoanClose }, - { "hfls", BitfinexEventType.HistoricalFundingLoanSnapshot }, - { "hfts", BitfinexEventType.HistoricalFundingTradeSnapshot }, { "uac", BitfinexEventType.UserCustomPriceAlert } }; } diff --git a/Bitfinex.Net/Objects/Models/BitfinexBalance.cs b/Bitfinex.Net/Objects/Models/BitfinexBalance.cs new file mode 100644 index 0000000..0f69f1e --- /dev/null +++ b/Bitfinex.Net/Objects/Models/BitfinexBalance.cs @@ -0,0 +1,24 @@ +using CryptoExchange.Net.Converters; +using Newtonsoft.Json; + +namespace Bitfinex.Net.Objects.Models +{ + /// + /// Balance + /// + [JsonConverter(typeof(ArrayConverter))] + public class BitfinexBalance + { + /// + /// Total Assets Under Management + /// + [ArrayProperty(0)] + public decimal TotalAssets { get; set; } + + /// + /// Net Assets Under Management (total assets - total liabilities) + /// + [ArrayProperty(1)] + public decimal NetAssets { get; set; } + } +} diff --git a/Bitfinex.Net/Objects/Models/BitfinexTradeSimple.cs b/Bitfinex.Net/Objects/Models/BitfinexTradeSimple.cs index 5b2e851..ffbb9cc 100644 --- a/Bitfinex.Net/Objects/Models/BitfinexTradeSimple.cs +++ b/Bitfinex.Net/Objects/Models/BitfinexTradeSimple.cs @@ -36,11 +36,5 @@ public class BitfinexTradeSimple /// [ArrayProperty(4)] public int? Period { get; set; } - - /// - /// The type of update - /// - [JsonIgnore] - public BitfinexEventType UpdateType { get; set; } = BitfinexEventType.TradeSnapshot; } } diff --git a/Bitfinex.Net/Objects/Models/Socket/BitfinexNotification.cs b/Bitfinex.Net/Objects/Models/Socket/BitfinexNotification.cs new file mode 100644 index 0000000..d12e38e --- /dev/null +++ b/Bitfinex.Net/Objects/Models/Socket/BitfinexNotification.cs @@ -0,0 +1,26 @@ +using CryptoExchange.Net.Attributes; +using CryptoExchange.Net.Converters; +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Text; + +namespace Bitfinex.Net.Objects.Models.Socket +{ + [JsonConverter(typeof(ArrayConverter))] + public class BitfinexNotification + { + [ArrayProperty(0)] + [JsonConverter(typeof(DateTimeConverter))] + public DateTime Timestamp { get; set; } + [ArrayProperty(1)] + public string Event { get; set; } + [ArrayProperty(4)] + [JsonConversion] + public T Data { get; set; } + [ArrayProperty(6)] + public string Result { get; set; } + [ArrayProperty(7)] + public string? ErrorMessage { get; set; } + } +} diff --git a/Bitfinex.Net/Objects/Models/Socket/BitfinexSocketEvent.cs b/Bitfinex.Net/Objects/Models/Socket/BitfinexSocketEvent.cs index 8d73090..d082908 100644 --- a/Bitfinex.Net/Objects/Models/Socket/BitfinexSocketEvent.cs +++ b/Bitfinex.Net/Objects/Models/Socket/BitfinexSocketEvent.cs @@ -1,4 +1,5 @@ using Bitfinex.Net.Enums; +using CryptoExchange.Net.Attributes; using CryptoExchange.Net.Converters; using Newtonsoft.Json; @@ -19,12 +20,15 @@ public class BitfinexSocketEvent /// /// The type of the event /// + [ArrayProperty(1)] + [JsonConverter(typeof(EnumConverter))] public BitfinexEventType EventType { get; set; } /// /// The data /// - [ArrayProperty(2), JsonConverter(typeof(ArrayConverter))] + [ArrayProperty(2)] + [JsonConversion] public T Data { get; set; } = default!; /// diff --git a/Bitfinex.Net/Objects/Sockets/Queries/BitfinexQuery.cs b/Bitfinex.Net/Objects/Sockets/Queries/BitfinexQuery.cs index aaf7fab..2afc849 100644 --- a/Bitfinex.Net/Objects/Sockets/Queries/BitfinexQuery.cs +++ b/Bitfinex.Net/Objects/Sockets/Queries/BitfinexQuery.cs @@ -1,29 +1,34 @@ -using CryptoExchange.Net.Sockets; +using Bitfinex.Net.Objects.Internal; +using Bitfinex.Net.Objects.Models.Socket; +using CryptoExchange.Net.Objects; +using CryptoExchange.Net.Objects.Sockets; +using CryptoExchange.Net.Sockets; using System; using System.Collections.Generic; using System.Text; +using System.Threading.Tasks; namespace Bitfinex.Net.Objects.Sockets.Queries { - internal class BitfinexQuery : Query + internal class BitfinexQuery : Query>> { - public override List StreamIdentifiers { get; } + public override List StreamIdentifiers { get; } = new List { "0" }; - public BitfinexQuery(string evnt, string channel, string symbol, string precision, string frequency, string length, string key) : base(new BitfinexBookRequest + public BitfinexQuery(BitfinexSocketQuery request) : base(request, true, 1) { - Channel = channel, - Symbol = symbol, - Event = evnt, - Frequency = frequency, - Length = length, - Precision = precision, - Key = key - }, false, 1) + TypeMapping = new Dictionary + { + { "n-single", typeof(BitfinexSocketEvent>) }, + { "n-array", typeof(BitfinexSocketEvent>) } + }; + } + + public override Task>>> HandleMessageAsync(SocketConnection connection, DataEvent>>> message) { - if (evnt == "subscribe" || evnt == "unsubscribe") - evnt += "d"; + if (message.Data.TypedData.Data.Result != "SUCCESS") + return Task.FromResult(new CallResult>>(new ServerError(message.Data.TypedData.Data.ErrorMessage))); - StreamIdentifiers = new List { evnt + channel + symbol + precision + frequency + length + key }; + return Task.FromResult(new CallResult>>((BitfinexSocketEvent>)message.Data.Data)); } } } diff --git a/Bitfinex.Net/Objects/Sockets/Queries/BitfinexSubQuery.cs b/Bitfinex.Net/Objects/Sockets/Queries/BitfinexSubQuery.cs new file mode 100644 index 0000000..b2693aa --- /dev/null +++ b/Bitfinex.Net/Objects/Sockets/Queries/BitfinexSubQuery.cs @@ -0,0 +1,29 @@ +using CryptoExchange.Net.Sockets; +using System; +using System.Collections.Generic; +using System.Text; + +namespace Bitfinex.Net.Objects.Sockets.Queries +{ + internal class BitfinexSubQuery : Query + { + public override List StreamIdentifiers { get; } + + public BitfinexSubQuery(string evnt, string channel, string symbol, string precision, string frequency, string length, string key) : base(new BitfinexBookRequest + { + Channel = channel, + Symbol = symbol, + Event = evnt, + Frequency = frequency, + Length = length, + Precision = precision, + Key = key + }, false, 1) + { + if (evnt == "subscribe" || evnt == "unsubscribe") + evnt += "d"; + + StreamIdentifiers = new List { evnt + channel + symbol + precision + frequency + length + key }; + } + } +} diff --git a/Bitfinex.Net/Objects/Sockets/Subscriptions/BitfinexSubscription.cs b/Bitfinex.Net/Objects/Sockets/Subscriptions/BitfinexSubscription.cs index 095d35e..efbbe35 100644 --- a/Bitfinex.Net/Objects/Sockets/Subscriptions/BitfinexSubscription.cs +++ b/Bitfinex.Net/Objects/Sockets/Subscriptions/BitfinexSubscription.cs @@ -76,11 +76,11 @@ public override void HandleSubQueryResponse(ParsedMessage mess public override BaseQuery? GetSubQuery(SocketConnection connection) { - return new BitfinexQuery("subscribe", _channel, _symbol, _precision, _frequency, _length, _key); + return new BitfinexSubQuery("subscribe", _channel, _symbol, _precision, _frequency, _length, _key); } public override BaseQuery? GetUnsubQuery() { - return new BitfinexQuery("unsubscribe", _channel, _symbol, _precision, _frequency, _length, _key); + return new BitfinexSubQuery("unsubscribe", _channel, _symbol, _precision, _frequency, _length, _key); } public override Task DoHandleMessageAsync(SocketConnection connection, DataEvent message) diff --git a/Bitfinex.Net/Objects/Sockets/Subscriptions/BitfinexUserSubscription.cs b/Bitfinex.Net/Objects/Sockets/Subscriptions/BitfinexUserSubscription.cs index 76af9d7..b47194b 100644 --- a/Bitfinex.Net/Objects/Sockets/Subscriptions/BitfinexUserSubscription.cs +++ b/Bitfinex.Net/Objects/Sockets/Subscriptions/BitfinexUserSubscription.cs @@ -2,6 +2,7 @@ using Bitfinex.Net.Enums; using Bitfinex.Net.Objects.Internal; using Bitfinex.Net.Objects.Models; +using Bitfinex.Net.Objects.Models.Socket; using Bitfinex.Net.Objects.Sockets.Queries; using CryptoExchange.Net.Objects; using CryptoExchange.Net.Objects.Sockets; @@ -16,58 +17,90 @@ namespace Bitfinex.Net.Objects.Sockets.Subscriptions { internal class BitfinexUserSubscription : Subscription { - private bool _firstUpdate; + private readonly Action>> _positionHandler; + private readonly Action>> _walletHandler; + private readonly Action>> _orderHandler; + private readonly Action>> _fundingOfferHandler; + private readonly Action>> _fundingCreditHandler; + private readonly Action>> _fundingLoanHandler; + private readonly Action> _balanceHandler; + private readonly Action> _tradeHandler; + private readonly Action> _fundingTradeHandler; + private readonly Action> _marginInfoHandler; // TODO + private readonly Action> _fundingInfoHandler; public override Dictionary TypeMapping { get; } = new Dictionary { - { "hb-single", typeof(BitfinexUpdate3) }, + { "hb-single", typeof(BitfinexSocketEvent) }, - { "ps-array", typeof(BitfinexUpdate3>) }, - { "pn-single", typeof(BitfinexUpdate3) }, - { "pu-single", typeof(BitfinexUpdate3) }, - { "pc-single", typeof(BitfinexUpdate3) }, + { "ps-array", typeof(BitfinexSocketEvent>) }, + { "pn-single", typeof(BitfinexSocketEvent) }, + { "pu-single", typeof(BitfinexSocketEvent) }, + { "pc-single", typeof(BitfinexSocketEvent) }, - { "bu-single", typeof(BitfinexUpdate3>) }, + { "bu-single", typeof(BitfinexSocketEvent) }, - { "miu-single", typeof(BitfinexUpdate3) }, + { "miu-single", typeof(BitfinexSocketEvent) }, - { "fiu-single", typeof(BitfinexUpdate3) }, + { "fiu-single", typeof(BitfinexSocketEvent) }, - { "ws-array", typeof(BitfinexUpdate3>) }, - { "wu-single", typeof(BitfinexUpdate3) }, + { "ws-array", typeof(BitfinexSocketEvent>) }, + { "wu-single", typeof(BitfinexSocketEvent) }, - { "os-array", typeof(BitfinexUpdate3>) }, - { "on-single", typeof(BitfinexUpdate3) }, - { "ou-single", typeof(BitfinexUpdate3) }, - { "oc-single", typeof(BitfinexUpdate3) }, + { "os-array", typeof(BitfinexSocketEvent>) }, + { "on-single", typeof(BitfinexSocketEvent) }, + { "ou-single", typeof(BitfinexSocketEvent) }, + { "oc-single", typeof(BitfinexSocketEvent) }, - { "te-single", typeof(BitfinexUpdate3) }, - { "tu-single", typeof(BitfinexUpdate3) }, + { "te-single", typeof(BitfinexSocketEvent) }, + { "tu-single", typeof(BitfinexSocketEvent) }, - { "fte-single", typeof(BitfinexUpdate3) }, - { "ftu-single", typeof(BitfinexUpdate3) }, + { "fte-single", typeof(BitfinexSocketEvent) }, + { "ftu-single", typeof(BitfinexSocketEvent) }, - { "fos-array", typeof(BitfinexUpdate3>) }, - { "fon-single", typeof(BitfinexUpdate3) }, - { "fou-single", typeof(BitfinexUpdate3) }, - { "foc-single", typeof(BitfinexUpdate3) }, + { "fos-array", typeof(BitfinexSocketEvent>) }, + { "fon-single", typeof(BitfinexSocketEvent) }, + { "fou-single", typeof(BitfinexSocketEvent) }, + { "foc-single", typeof(BitfinexSocketEvent) }, - { "fcs-array", typeof(BitfinexUpdate3>) }, - { "fcc-single", typeof(BitfinexUpdate3) }, - { "fcn-single", typeof(BitfinexUpdate3) }, - { "fcu-single", typeof(BitfinexUpdate3) }, + { "fcs-array", typeof(BitfinexSocketEvent>) }, + { "fcc-single", typeof(BitfinexSocketEvent) }, + { "fcn-single", typeof(BitfinexSocketEvent) }, + { "fcu-single", typeof(BitfinexSocketEvent) }, - { "fls-array", typeof(BitfinexUpdate3>) }, - { "flc-single", typeof(BitfinexUpdate3) }, - { "fln-single", typeof(BitfinexUpdate3) }, - { "flu-single", typeof(BitfinexUpdate3) }, + { "fls-array", typeof(BitfinexSocketEvent>) }, + { "flc-single", typeof(BitfinexSocketEvent) }, + { "fln-single", typeof(BitfinexSocketEvent) }, + { "flu-single", typeof(BitfinexSocketEvent) }, }; public override List StreamIdentifiers { get; } = new List() { "0" }; - public BitfinexUserSubscription(ILogger logger) + public BitfinexUserSubscription(ILogger logger, + Action>> positionHandler, + Action>> walletHandler, + Action>> orderHandler, + Action>> fundingOfferHandler, + Action>> fundingCreditHandler, + Action>> fundingLoanHandler, + Action> balanceHandler, + Action> tradeHandler, + Action> fundingTradeHandler, + Action> fundingInfoHandler + //Action> marginInfoHandler + ) : base(logger, true) { + _positionHandler = positionHandler; + _walletHandler = walletHandler; + _orderHandler = orderHandler; + _fundingOfferHandler = fundingOfferHandler; + _fundingCreditHandler = fundingCreditHandler; + _fundingLoanHandler = fundingLoanHandler; + _balanceHandler = balanceHandler; + _tradeHandler = tradeHandler; + _fundingTradeHandler = fundingTradeHandler; + _fundingInfoHandler = fundingInfoHandler; } public override BaseQuery? GetSubQuery(SocketConnection connection) => null; @@ -78,15 +111,69 @@ public override Task DoHandleMessageAsync(SocketConnection connectio { Debug.WriteLine($"{message.Data.Data.GetType()}; {message.Data.OriginalData}"); - if (message.Data.TypeIdentifier == "hb-single") - return Task.FromResult(new CallResult(null)); - //if (message.Data.TypeIdentifier == "bu-single") - // return Task.FromResult(new CallResult(null)); - //if (message.Data.TypeIdentifier == "ws-array") - // return Task.FromResult(new CallResult(null)); - //if (message.Data.TypeIdentifier == "wu-single") - // return Task.FromResult(new CallResult(null)); + return message.Data.TypeIdentifier switch + { + "hb-single" => Task.FromResult(new CallResult(null)), + "ps-array" => InvokeAndReturnSnapshot(_positionHandler, message), + "pn-single" => InvokeAndReturnUpdate(_positionHandler, message), + "pu-single" => InvokeAndReturnUpdate(_positionHandler, message), + "pc-single" => InvokeAndReturnUpdate(_positionHandler, message), + + "bu-single" => InvokeAndReturnSingleUpdate(_balanceHandler, message), + + "fiu-single" => InvokeAndReturnSingleUpdate(_fundingInfoHandler, message), + + "ws-array" => InvokeAndReturnSnapshot(_walletHandler, message), + "wu-single" => InvokeAndReturnUpdate(_walletHandler, message), + + "os-array" => InvokeAndReturnSnapshot(_orderHandler, message), + "on-single" => InvokeAndReturnUpdate(_orderHandler, message), + "ou-single" => InvokeAndReturnUpdate(_orderHandler, message), + "oc-single" => InvokeAndReturnUpdate(_orderHandler, message), + + "te-single" => InvokeAndReturnSingleUpdate(_tradeHandler, message), + "tu-single" => InvokeAndReturnSingleUpdate(_tradeHandler, message), + + "fte-single" => InvokeAndReturnSingleUpdate(_fundingTradeHandler, message), + "ftu-single" => InvokeAndReturnSingleUpdate(_fundingTradeHandler, message), + + "fos-array" => InvokeAndReturnSnapshot(_fundingOfferHandler, message), + "fon-single" => InvokeAndReturnUpdate(_fundingOfferHandler, message), + "fou-single" => InvokeAndReturnUpdate(_fundingOfferHandler, message), + "foc-single" => InvokeAndReturnUpdate(_fundingOfferHandler, message), + + "fcs-array" => InvokeAndReturnSnapshot(_fundingCreditHandler, message), + "fcn-single" => InvokeAndReturnUpdate(_fundingCreditHandler, message), + "fcu-single" => InvokeAndReturnUpdate(_fundingCreditHandler, message), + "fcc-single" => InvokeAndReturnUpdate(_fundingCreditHandler, message), + + "fls-array" => InvokeAndReturnSnapshot(_fundingLoanHandler, message), + "fln-single" => InvokeAndReturnUpdate(_fundingLoanHandler, message), + "flu-single" => InvokeAndReturnUpdate(_fundingLoanHandler, message), + "flc-single" => InvokeAndReturnUpdate(_fundingLoanHandler, message), + _ => throw new NotImplementedException() + }; + } + + private Task InvokeAndReturnSnapshot(Action>> handler, DataEvent message) + { + var data = (BitfinexSocketEvent>)message.Data.Data; + handler?.Invoke(message.As>(data.Data, null, SocketUpdateType.Snapshot)); + return Task.FromResult(new CallResult(null)); + } + + private Task InvokeAndReturnUpdate(Action>> handler, DataEvent message) + { + var data = (BitfinexSocketEvent)message.Data.Data; + handler?.Invoke(message.As>(new[] { data.Data }, null, SocketUpdateType.Update)); + return Task.FromResult(new CallResult(null)); + } + + private Task InvokeAndReturnSingleUpdate(Action> handler, DataEvent message) + { + var data = (BitfinexSocketEvent)message.Data.Data; + handler?.Invoke(message.As(data.Data, null, SocketUpdateType.Update)); return Task.FromResult(new CallResult(null)); } } From 2237dd21515e20d1514d6db5a5d95d43a4a4345e Mon Sep 17 00:00:00 2001 From: JKorf Date: Tue, 2 Jan 2024 21:13:47 +0100 Subject: [PATCH 05/25] wip --- .../SpotApi/BitfinexSocketClientSpotApi.cs | 461 +----------------- .../Sockets/Queries/BitfinexUnsubQuery.cs | 18 + .../Subscriptions/BitfinexSubscription.cs | 2 +- 3 files changed, 23 insertions(+), 458 deletions(-) create mode 100644 Bitfinex.Net/Objects/Sockets/Queries/BitfinexUnsubQuery.cs diff --git a/Bitfinex.Net/Clients/SpotApi/BitfinexSocketClientSpotApi.cs b/Bitfinex.Net/Clients/SpotApi/BitfinexSocketClientSpotApi.cs index dc50cf6..e3b4055 100644 --- a/Bitfinex.Net/Clients/SpotApi/BitfinexSocketClientSpotApi.cs +++ b/Bitfinex.Net/Clients/SpotApi/BitfinexSocketClientSpotApi.cs @@ -77,14 +77,11 @@ protected override BaseQuery GetAuthenticationRequest() Nonce = n, Payload = "AUTH" + n }; - //if (filter.Any()) - // authentication.Filter = filter; + authentication.Signature = authProvider.Sign(authentication.Payload).ToLower(CultureInfo.InvariantCulture); return new BitfinexAuthQuery(authentication); } - //#region public methods - /// public async Task> SubscribeToTickerUpdatesAsync(string symbol, Action> handler, CancellationToken ct = default) { @@ -240,10 +237,7 @@ public async Task> UpdateOrderAsync(long orderId, deci return result.As(result.Data?.Data.Data); } - /// - /// Cancel all open orders - /// - /// + /// public async Task>> CancelAllOrdersAsync() { var query = new BitfinexSocketQuery(null, BitfinexEventType.OrderCancelMulti, new BitfinexMultiCancel { All = true }); @@ -347,7 +341,6 @@ public async Task> CancelFundingOfferAsync(long var result = await QueryAsync(BaseAddress.AppendPath("ws/2"), bitfinexQuery).ConfigureAwait(false); return result.As(result.Data?.Data.Data); } - // #endregion private static string? GetStreamIdentifier(IMessageAccessor accessor) { @@ -366,7 +359,8 @@ public async Task> CancelFundingOfferAsync(long var freq = accessor.GetStringValue("freq"); var len = accessor.GetStringValue("len"); var key = accessor.GetStringValue("key"); - return evnt + channel + symbol + prec + freq + len + key; + var chanId = evnt == "unsubscribed" ? accessor.GetStringValue("chanId") : ""; + return chanId + evnt + channel + symbol + prec + freq + len + key; } private static string? GetTypeIdentifier(IMessageAccessor accessor) @@ -380,82 +374,9 @@ public async Task> CancelFundingOfferAsync(long if (accessor.IsArray(new[] { dataIndex, 0 }) || accessor.IsEmptyArray(new[] { dataIndex })) x = topic + "-array"; - if (x == "os-array") - { - } - return x; } - // #region private methods - // private void HandleData(string name, JArray dataArray, string? symbol, DataEvent dataEvent, Action> handler, JsonSerializer? serializer = null) - // { - // var desResult = Deserialize(dataArray, serializer: serializer); - // if (!desResult) - // { - // _logger.Log(LogLevel.Warning, $"Failed to Deserialize {name} object: " + desResult.Error); - // return; - // } - - // handler(dataEvent.As(desResult.Data, symbol)); - // } - - // private void HandleSingleToArrayData(string name, JArray dataArray, string? symbol, DataEvent dataEvent, Action>> handler, JsonSerializer? serializer = null) - // { - // var wrapperArray = new JArray { dataArray }; - - // var desResult = Deserialize>(wrapperArray, serializer: serializer); - // if (!desResult) - // { - // _logger.Log(LogLevel.Warning, $"Failed to Deserialize {name} object: " + desResult.Error); - // return; - // } - - // handler(dataEvent.As(desResult.Data, symbol)); - // } - - // private void HandleAuthUpdate(DataEvent token, Action>>> action, string category) - // { - // var evntStr = token.Data[1]?.ToString(); - // if (evntStr == null) - // return; - - // var evntType = BitfinexEvents.EventMapping[evntStr]; - // var evnt = BitfinexEvents.Events.Single(e => e.EventType == evntType); - // if (evnt.Category != category) - // return; - - // if (action == null) - // { - // _logger.Log(LogLevel.Debug, $"Ignoring {evnt.EventType} event because not subscribed"); - // return; - // } - - // IEnumerable data; - // if (evnt.Single) - // { - // var result = Deserialize(token.Data[2]!); - // if (!result) - // { - // _logger.Log(LogLevel.Warning, "Failed to Deserialize data: " + result.Error); - // return; - // } - // data = new[] { result.Data }; - // } - // else - // { - // var result = Deserialize>(token.Data[2]!); - // if (!result) - // { - // _logger.Log(LogLevel.Warning, "Failed to Deserialize data: " + result.Error); - // return; - // } - // data = result.Data; - // } - - // action(token.As(new BitfinexSocketEvent>(evntType, data))); - // } - private long GenerateClientOrderId() { var buffer = new byte[8]; @@ -463,16 +384,6 @@ private long GenerateClientOrderId() return (long)Math.Round(Math.Abs(BitConverter.ToInt32(buffer, 0)) / 1000m); } - - // private void ConfHandler(MessageEvent messageEvent) - // { - // var confEvent = messageEvent.JsonData.Type == JTokenType.Object && messageEvent.JsonData["event"]?.ToString() == "conf"; - // if (!confEvent) - // return; - - // // Could check conf result; - // } - // private void InfoHandler(MessageEvent messageEvent) // { // var infoEvent = messageEvent.JsonData.Type == JTokenType.Object && messageEvent.JsonData["event"]?.ToString() == "info"; @@ -508,369 +419,5 @@ private long GenerateClientOrderId() // break; // } // } - - - // /// - // protected override async Task UnsubscribeAsync(SocketConnection connection, SocketSubscription subscription) - // { - // if (subscription.Request == null) - // { - // // If we don't have a request object we can't unsubscribe it. Probably is an auth subscription which gets pushed regardless - // // Just returning true here will remove the handler and close the socket if there are no other handlers left on the socket, which is the best we can do - // return true; - // } - - // var channelId = ((BitfinexSubscriptionRequest)subscription.Request!).ChannelId; - // var unsub = new BitfinexUnsubscribeRequest(channelId); - // var result = false; - // await connection.SendAndWaitAsync(unsub, ClientOptions.RequestTimeout, null, 1, data => - // { - // if (data.Type != JTokenType.Object) - // return false; - - // var evnt = data["event"]?.ToString(); - // var channel = data["chanId"]?.ToString(); - // if (evnt == null || channel == null) - // return false; - - // if (!int.TryParse(channel, out var chan)) - // return false; - - // result = evnt == "unsubscribed" && channelId == chan; - // return result; - // }).ConfigureAwait(false); - // return result; - // } - - // private static BitfinexAuthentication GetAuthObject(SocketApiClient apiClient, params string[] filter) - // { - // var authProvider = (BitfinexAuthenticationProvider)apiClient.AuthenticationProvider!; - // var n = authProvider.GetNonce().ToString(); - // var authentication = new BitfinexAuthentication - // { - // Event = "auth", - // ApiKey = authProvider.GetApiKey(), - // Nonce = n, - // Payload = "AUTH" + n - // }; - // if (filter.Any()) - // authentication.Filter = filter; - // authentication.Signature = authProvider.Sign(authentication.Payload).ToLower(CultureInfo.InvariantCulture); - // return authentication; - // } - - // #endregion - - // /// - // protected override async Task> AuthenticateSocketAsync(SocketConnection s) - // { - // if (s.ApiClient.AuthenticationProvider == null) - // return new CallResult(new NoApiCredentialsError()); - - // var authObject = GetAuthObject(s.ApiClient); - // var result = new CallResult(new ServerError("No response from server")); - // await s.SendAndWaitAsync(authObject, ClientOptions.RequestTimeout, null, 1, tokenData => - // { - // if (tokenData.Type != JTokenType.Object) - // return false; - - // if (tokenData["event"]?.ToString() != "auth") - // return false; - - // var authResponse = Deserialize(tokenData); - // if (!authResponse) - // { - // _logger.Log(LogLevel.Warning, $"Socket {s.SocketId} authentication failed: " + authResponse.Error); - // result = new CallResult(authResponse.Error!); - // return false; - // } - - // if (authResponse.Data.Status != "OK") - // { - // var error = new ServerError(authResponse.Data.ErrorCode, authResponse.Data.ErrorMessage ?? "-"); - // result = new CallResult(error); - // _logger.Log(LogLevel.Debug, $"Socket {s.SocketId} authentication failed: " + error); - // return false; - // } - - // _logger.Log(LogLevel.Debug, $"Socket {s.SocketId} authentication completed"); - // result = new CallResult(true); - // return true; - // }).ConfigureAwait(false); - - // return result; - // } - - // /// - //#pragma warning disable 8765 - // protected override bool HandleQueryResponse(SocketConnection s, object request, JToken data, out CallResult? callResult) - //#pragma warning restore 8765 - // { - // callResult = null; - // if (data.Type != JTokenType.Array) - // return false; - - // var array = (JArray)data; - // if (array.Count < 3) - // return false; - - // var bfRequest = (BitfinexSocketQuery)request; - // var evntString = data[1]!.ToString(); - // if (!BitfinexEvents.EventMapping.TryGetValue(evntString, out var eventType)) - // return false; - - // if (eventType == BitfinexEventType.Notification) - // { - // var notificationData = (JArray)data[2]!; - // var notificationType = BitfinexEvents.EventMapping[notificationData[1].ToString()]; - // if (notificationType != BitfinexEventType.OrderNewRequest - // && notificationType != BitfinexEventType.OrderCancelRequest - // && notificationType != BitfinexEventType.OrderUpdateRequest - // && notificationType != BitfinexEventType.OrderCancelMultiRequest - // && notificationType != BitfinexEventType.FundingOfferNewRequest - // && notificationType != BitfinexEventType.FundingOfferCancelRequest) - // { - // return false; - // } - - // var statusString = (notificationData[6].ToString()).ToLower(CultureInfo.InvariantCulture); - // if (statusString == "error") - // { - // if (bfRequest.QueryType == BitfinexEventType.OrderNew && notificationType == BitfinexEventType.OrderNewRequest) - // { - // var orderData = notificationData[4]; - // if (orderData[2]?.ToString() != bfRequest.Id) - // return false; - - // callResult = new CallResult(new ServerError(notificationData[7].ToString())); - // return true; - // } - - // if (bfRequest.QueryType == BitfinexEventType.OrderCancel && notificationType == BitfinexEventType.OrderCancelRequest) - // { - // var orderData = notificationData[4]; - // if (orderData[0]?.ToString() != bfRequest.Id) - // return false; - - // callResult = new CallResult(new ServerError(notificationData[7].ToString())); - // return true; - // } - - // if (bfRequest.QueryType == BitfinexEventType.OrderUpdate && notificationType == BitfinexEventType.OrderUpdateRequest) - // { - // // OrderUpdateRequest not found notification doesn't carry the order id, where as OrderCancelRequest not found notification does.. - // // Anyway, can't check for ids, so just assume its for this one - - // callResult = new CallResult(new ServerError(notificationData[7].ToString())); - // return true; - // } - - // if (bfRequest.QueryType == BitfinexEventType.OrderCancelMulti && notificationType == BitfinexEventType.OrderCancelMultiRequest) - // { - // callResult = new CallResult(new ServerError(notificationData[7].ToString())); - // return true; - // } - - // if (bfRequest.QueryType == BitfinexEventType.FundingOfferNew && notificationType == BitfinexEventType.FundingOfferNewRequest) - // { - // callResult = new CallResult(new ServerError(notificationData[7].ToString())); - // return true; - // } - - // if (bfRequest.QueryType == BitfinexEventType.FundingOfferCancel && notificationType == BitfinexEventType.FundingOfferCancelRequest) - // { - // var fundingData = notificationData[4]; - // if (fundingData[0]?.ToString() != bfRequest.Id) - // return false; - - // callResult = new CallResult(new ServerError(notificationData[7].ToString())); - // return true; - // } - // } - - // if (notificationType == BitfinexEventType.OrderNewRequest - // || notificationType == BitfinexEventType.OrderUpdateRequest - // || notificationType == BitfinexEventType.OrderCancelRequest) - // { - // if (bfRequest.QueryType == BitfinexEventType.OrderNew - // || bfRequest.QueryType == BitfinexEventType.OrderUpdate - // || bfRequest.QueryType == BitfinexEventType.OrderCancel) - // { - // var orderData = notificationData[4]; - // var dataOrderId = orderData[0]?.ToString(); - // var dataOrderClientId = orderData[2]?.ToString(); - // if (dataOrderId == bfRequest.Id || dataOrderClientId == bfRequest.Id) - // { - // var desResult = Deserialize(orderData); - // if (!desResult) - // { - // callResult = new CallResult(desResult.Error!); - // return true; - // } - - // callResult = new CallResult(desResult.Data); - // return true; - // } - // } - // } - - // if (notificationType == BitfinexEventType.OrderCancelMultiRequest) - // { - // callResult = new CallResult(Deserialize(JToken.Parse("true")).Data); - // return true; - // } - - // if (notificationType == BitfinexEventType.FundingOfferNewRequest - // || notificationType == BitfinexEventType.FundingOfferCancelRequest) - // { - // if (bfRequest.QueryType == BitfinexEventType.FundingOfferCancelRequest) - // { - // var fundingData = notificationData[4]; - // var dataOrderId = fundingData[0]?.ToString(); - // if (dataOrderId == bfRequest.Id) - // { - // var desResult = Deserialize(fundingData); - // if (!desResult) - // { - // callResult = new CallResult(desResult.Error!); - // return true; - // } - - // callResult = new CallResult(desResult.Data); - // return true; - // } - // } - // else if(bfRequest.QueryType == BitfinexEventType.FundingOfferNew) - // { - // var fundingData = notificationData[4]; - // var desResult = Deserialize(fundingData); - // if (!desResult) - // { - // callResult = new CallResult(desResult.Error!); - // return true; - // } - - // callResult = new CallResult(desResult.Data); - // return true; - // } - // } - // } - - // if (bfRequest.QueryType == BitfinexEventType.OrderCancelMulti && eventType == BitfinexEventType.OrderCancel) - // { - // callResult = new CallResult(Deserialize(JToken.Parse("true")).Data); - // return true; - // } - - // return false; - // } - - // /// - // protected override bool HandleSubscriptionResponse(SocketConnection s, SocketSubscription subscription, object request, JToken data, out CallResult? callResult) - // { - // callResult = null; - // if (data.Type != JTokenType.Object) - // return false; - - // var infoEvent = data["event"]?.ToString() == "subscribed"; - // var errorEvent = data["event"]?.ToString() == "error"; - // if (!infoEvent && !errorEvent) - // return false; - - // if (infoEvent) - // { - // var subResponse = Deserialize(data); - // if (!subResponse) - // { - // callResult = new CallResult(subResponse.Error!); - // _logger.Log(LogLevel.Warning, $"Socket {s.SocketId} subscription failed: " + subResponse.Error); - // return false; - // } - - // var bRequest = (BitfinexSubscriptionRequest)request; - // if (!bRequest.CheckResponse(data)) - // return false; - - // bRequest.ChannelId = subResponse.Data.ChannelId; - // callResult = subResponse.As(subResponse.Data); - // return true; - // } - // else - // { - // var subResponse = Deserialize(data); - // if (!subResponse) - // { - // callResult = new CallResult(subResponse.Error!); - // _logger.Log(LogLevel.Warning, $"Socket {s.SocketId} subscription failed: " + subResponse.Error); - // return false; - // } - - // var error = new ServerError(subResponse.Data.Code, subResponse.Data.Message); - // callResult = new CallResult(error); - // _logger.Log(LogLevel.Debug, $"Socket {s.SocketId} subscription failed: " + error); - // return true; - // } - // } - - // /// - // protected override bool MessageMatchesHandler(SocketConnection socketConnection, JToken message, object request) - // { - // if (message.Type != JTokenType.Array) - // return false; - - // var array = (JArray)message; - // if (array.Count < 2) - // return false; - - // if (!int.TryParse(array[0].ToString(), out var channelId)) - // return false; - - // if (channelId == 0) - // return false; - - // var subId = ((BitfinexSubscriptionRequest)request).ChannelId; - // return channelId == subId && array[1].ToString() != "hb"; - // } - - // /// - // protected override bool MessageMatchesHandler(SocketConnection socketConnection, JToken message, string identifier) - // { - // if (message.Type == JTokenType.Object) - // { - // if (identifier == "Info") - // return message["event"]?.ToString() == "info"; - // if (identifier == "Conf") - // return message["event"]?.ToString() == "conf"; - // } - - // else if (message.Type == JTokenType.Array) - // { - // var array = (JArray)message; - // if (array.Count < 2) - // return false; - - // if (identifier == "HB") - // return array[1].ToString() == "hb"; - - // if (!int.TryParse(array[0].ToString(), out var channelId)) - // return false; - - // if (channelId != 0) - // return false; - - // var split = identifier.Split(new[] { "|" }, StringSplitOptions.RemoveEmptyEntries); - // foreach (var id in split) - // { - // var events = BitfinexEvents.GetEventsForCategory(id); - // var eventTypeString = array[1].ToString(); - // var eventType = BitfinexEvents.EventMapping[eventTypeString]; - // var evnt = events.SingleOrDefault(e => e.EventType == eventType); - // if (evnt != null) - // return true; - // } - // } - - // return false; - // } } } diff --git a/Bitfinex.Net/Objects/Sockets/Queries/BitfinexUnsubQuery.cs b/Bitfinex.Net/Objects/Sockets/Queries/BitfinexUnsubQuery.cs new file mode 100644 index 0000000..e4a660d --- /dev/null +++ b/Bitfinex.Net/Objects/Sockets/Queries/BitfinexUnsubQuery.cs @@ -0,0 +1,18 @@ +using Bitfinex.Net.Objects.Internal; +using CryptoExchange.Net.Sockets; +using System; +using System.Collections.Generic; +using System.Text; + +namespace Bitfinex.Net.Objects.Sockets.Queries +{ + internal class BitfinexUnsubQuery : Query + { + public override List StreamIdentifiers { get; } + + public BitfinexUnsubQuery(int channelId) : base(new BitfinexUnsubscribeRequest(channelId), false, 1) + { + StreamIdentifiers = new List { channelId.ToString() + "unsubscribed" }; + } + } +} diff --git a/Bitfinex.Net/Objects/Sockets/Subscriptions/BitfinexSubscription.cs b/Bitfinex.Net/Objects/Sockets/Subscriptions/BitfinexSubscription.cs index efbbe35..19b4943 100644 --- a/Bitfinex.Net/Objects/Sockets/Subscriptions/BitfinexSubscription.cs +++ b/Bitfinex.Net/Objects/Sockets/Subscriptions/BitfinexSubscription.cs @@ -80,7 +80,7 @@ public override void HandleSubQueryResponse(ParsedMessage mess } public override BaseQuery? GetUnsubQuery() { - return new BitfinexSubQuery("unsubscribe", _channel, _symbol, _precision, _frequency, _length, _key); + return new BitfinexUnsubQuery(_channelId); } public override Task DoHandleMessageAsync(SocketConnection connection, DataEvent message) From 778cee34af7aae4789d805b8cba1c442ff3f7759 Mon Sep 17 00:00:00 2001 From: JKorf Date: Wed, 31 Jan 2024 21:59:20 +0100 Subject: [PATCH 06/25] wip --- Bitfinex.Net.UnitTests/BitfinexClientTests.cs | 1 + .../BitfinexSocketClientTests.cs | 441 +++++++++--------- .../TestImplementations/TestHelpers.cs | 1 + .../TestImplementations/TestSocket.cs | 32 +- Bitfinex.Net/Bitfinex.Net.xml | 39 +- .../BitfinexAuthenticationProvider.cs | 2 - Bitfinex.Net/BitfinexHelpers.cs | 2 - .../BitfinexRestClientSpotApiTrading.cs | 1 - .../SpotApi/BitfinexSocketClientSpotApi.cs | 135 ++---- .../SpotApi/IBitfinexSocketClientSpotApi.cs | 34 +- .../Objects/Internal/BitfinexChecksum.cs | 5 +- .../Objects/Internal/BitfinexResponse.cs | 24 - .../Objects/Internal/BitfinexSocketConfig.cs | 3 +- .../Models/BitfinexFundingAutoRenew.cs | 1 - .../Models/BitfinexFundingOrderBook.cs | 1 - .../Objects/Models/BitfinexKeyValue.cs | 3 - .../Objects/Models/BitfinexOrderBook.cs | 1 - .../Models/BitfinexRawFundingOrderBook.cs | 1 - .../Objects/Models/BitfinexRawOrderBook.cs | 1 - .../Objects/Models/BitfinexSummary.cs | 3 +- .../Objects/Models/BitfinexTradeSimple.cs | 1 - .../Models/Socket/BitfinexNotification.cs | 10 +- .../Models/Socket/BitfinexSocketInfo.cs | 10 +- .../Objects/Sockets/BitfinexBookRequest.cs | 8 +- .../Objects/Sockets/BitfinexConfQuery.cs | 16 + .../Objects/Sockets/BitfinexRequest.cs | 4 +- .../Objects/Sockets/BitfinexResponse.cs | 4 +- .../Sockets/BitfinexSocketConverter.cs | 56 --- .../Objects/Sockets/BitfinexUpdate.cs | 9 +- .../Sockets/Queries/BitfinexAuthQuery.cs | 3 +- .../Objects/Sockets/Queries/BitfinexQuery.cs | 27 +- .../Sockets/Queries/BitfinexSubQuery.cs | 8 +- .../Sockets/Queries/BitfinexUnsubQuery.cs | 8 +- .../Subscriptions/BitfinexInfoSubscription.cs | 36 +- .../Subscriptions/BitfinexSubscription.cs | 85 ++-- .../Subscriptions/BitfinexUserSubscription.cs | 275 +++++------ .../BitfinexSymbolOrderBook.cs | 37 +- 37 files changed, 641 insertions(+), 687 deletions(-) delete mode 100644 Bitfinex.Net/Objects/Internal/BitfinexResponse.cs create mode 100644 Bitfinex.Net/Objects/Sockets/BitfinexConfQuery.cs delete mode 100644 Bitfinex.Net/Objects/Sockets/BitfinexSocketConverter.cs diff --git a/Bitfinex.Net.UnitTests/BitfinexClientTests.cs b/Bitfinex.Net.UnitTests/BitfinexClientTests.cs index 6d8c794..691a765 100644 --- a/Bitfinex.Net.UnitTests/BitfinexClientTests.cs +++ b/Bitfinex.Net.UnitTests/BitfinexClientTests.cs @@ -13,6 +13,7 @@ using CryptoExchange.Net.Objects; using CryptoExchange.Net.Sockets; using Bitfinex.Net.Clients; +using CryptoExchange.Net.Objects.Sockets; namespace Bitfinex.Net.UnitTests { diff --git a/Bitfinex.Net.UnitTests/BitfinexSocketClientTests.cs b/Bitfinex.Net.UnitTests/BitfinexSocketClientTests.cs index f08fc4c..45c7887 100644 --- a/Bitfinex.Net.UnitTests/BitfinexSocketClientTests.cs +++ b/Bitfinex.Net.UnitTests/BitfinexSocketClientTests.cs @@ -17,6 +17,7 @@ using Bitfinex.Net.Objects.Internal; using Bitfinex.Net.Objects.Models; using Bitfinex.Net.Objects.Models.Socket; +using System.Diagnostics; namespace Bitfinex.Net.UnitTests { @@ -54,7 +55,7 @@ public async Task SubscribingToBookUpdates_Should_SubscribeSuccessfully(Precisio }; // act - socket.InvokeMessage(subResponse); + await socket.InvokeMessage(subResponse); var taskResult = await subTask.ConfigureAwait(false); @@ -70,7 +71,7 @@ public async Task SubscribingToBookUpdates_Should_SubscribeSuccessfully(Precisio [TestCase(Precision.PrecisionLevel1, Frequency.TwoSeconds)] [TestCase(Precision.PrecisionLevel2, Frequency.TwoSeconds)] [TestCase(Precision.PrecisionLevel3, Frequency.TwoSeconds)] - public void SubscribingToBookUpdates_Should_TriggerWithBookUpdate(Precision prec, Frequency freq) + public async Task SubscribingToBookUpdates_Should_TriggerWithBookUpdate(Precision prec, Frequency freq) { // arrange var socket = new TestSocket(); @@ -91,12 +92,12 @@ public void SubscribingToBookUpdates_Should_TriggerWithBookUpdate(Precision prec Precision = JsonConvert.SerializeObject(prec, new PrecisionConverter(false)), Symbol = "tBTCUSD" }; - socket.InvokeMessage(subResponse); + await socket.InvokeMessage(subResponse); subTask.Wait(5000); BitfinexOrderBookEntry[] expected = new[] { new BitfinexOrderBookEntry() }; // act - socket.InvokeMessage($"[1, {JsonConvert.SerializeObject(expected)}]"); + await socket.InvokeMessage($"[1, {JsonConvert.SerializeObject(expected)}]"); // assert Assert.IsTrue(TestHelpers.AreEqual(result[0], expected[0])); @@ -113,7 +114,7 @@ public void SubscribingToBookUpdates_Should_TriggerWithBookUpdate(Precision prec [TestCase(KlineInterval.OneDay)] [TestCase(KlineInterval.SevenDays)] [TestCase(KlineInterval.FourteenDays)] - public void SubscribingToCandleUpdates_Should_SubscribeSuccessfully(KlineInterval timeframe) + public async Task SubscribingToCandleUpdates_Should_SubscribeSuccessfully(KlineInterval timeframe) { // arrange var socket = new TestSocket(); @@ -127,12 +128,11 @@ public void SubscribingToCandleUpdates_Should_SubscribeSuccessfully(KlineInterva Channel = "candles", Event = "subscribed", ChannelId = 1, - Symbol = "BTCUSD", Key = "trade:" + JsonConvert.SerializeObject(timeframe, new KlineIntervalConverter(false)) + ":tBTCUSD" }; // act - socket.InvokeMessage(subResponse); + await socket.InvokeMessage(subResponse); subTask.Wait(5000); @@ -151,7 +151,7 @@ public void SubscribingToCandleUpdates_Should_SubscribeSuccessfully(KlineInterva [TestCase(KlineInterval.OneDay)] [TestCase(KlineInterval.SevenDays)] [TestCase(KlineInterval.FourteenDays)] - public void SubscribingToCandleUpdates_Should_TriggerWithCandleUpdate(KlineInterval timeframe) + public async Task SubscribingToCandleUpdates_Should_TriggerWithCandleUpdate(KlineInterval timeframe) { // arrange var socket = new TestSocket(); @@ -166,22 +166,21 @@ public void SubscribingToCandleUpdates_Should_TriggerWithCandleUpdate(KlineInter Channel = "candles", Event = "subscribed", ChannelId = 1, - Symbol = "BTCUSD", Key = "trade:" + JsonConvert.SerializeObject(timeframe, new KlineIntervalConverter(false)) + ":tBTCUSD" }; - socket.InvokeMessage(subResponse); + await socket.InvokeMessage(subResponse); subTask.Wait(5000); BitfinexKline[] expected = new[] { new BitfinexKline() }; // act - socket.InvokeMessage($"[1, {JsonConvert.SerializeObject(expected)}]"); + await socket.InvokeMessage($"[1, {JsonConvert.SerializeObject(expected)}]"); // assert Assert.IsTrue(TestHelpers.AreEqual(result[0], expected[0])); } [Test] - public void SubscribingToTickerUpdates_Should_SubscribeSuccessfully() + public async Task SubscribingToTickerUpdates_Should_SubscribeSuccessfully() { // arrange var socket = new TestSocket(); @@ -200,7 +199,7 @@ public void SubscribingToTickerUpdates_Should_SubscribeSuccessfully() }; // act - socket.InvokeMessage(subResponse); + await socket.InvokeMessage(subResponse); subTask.Wait(5000); @@ -209,7 +208,7 @@ public void SubscribingToTickerUpdates_Should_SubscribeSuccessfully() } [Test] - public void SubscribingToTickerUpdates_Should_TriggerWithTickerUpdate() + public async Task SubscribingToTickerUpdates_Should_TriggerWithTickerUpdate() { // arrange var socket = new TestSocket(); @@ -227,19 +226,19 @@ public void SubscribingToTickerUpdates_Should_TriggerWithTickerUpdate() Symbol = "tBTCUSD", Pair = "BTCUSD" }; - socket.InvokeMessage(subResponse); + await socket.InvokeMessage(subResponse); subTask.Wait(5000); BitfinexStreamTicker expected = new BitfinexStreamTicker(); // act - socket.InvokeMessage($"[1, {JsonConvert.SerializeObject(expected)}]"); + await socket.InvokeMessage($"[1, {JsonConvert.SerializeObject(expected)}]"); // assert Assert.IsTrue(TestHelpers.AreEqual(result, expected)); } [Test] - public void SubscribingToRawBookUpdates_Should_SubscribeSuccessfully() + public async Task SubscribingToRawBookUpdates_Should_SubscribeSuccessfully() { // arrange var socket = new TestSocket(); @@ -261,7 +260,7 @@ public void SubscribingToRawBookUpdates_Should_SubscribeSuccessfully() }; // act - socket.InvokeMessage(subResponse); + await socket.InvokeMessage(subResponse); subTask.Wait(5000); @@ -270,7 +269,7 @@ public void SubscribingToRawBookUpdates_Should_SubscribeSuccessfully() } [Test] - public void SubscribingToRawBookUpdates_Should_TriggerWithRawBookUpdate() + public async Task SubscribingToRawBookUpdates_Should_TriggerWithRawBookUpdate() { // arrange var socket = new TestSocket(); @@ -291,19 +290,19 @@ public void SubscribingToRawBookUpdates_Should_TriggerWithRawBookUpdate() Precision = "R0", Length = 10 }; - socket.InvokeMessage(subResponse); + await socket.InvokeMessage(subResponse); subTask.Wait(5000); BitfinexRawOrderBookEntry[] expected = new []{ new BitfinexRawOrderBookEntry()}; // act - socket.InvokeMessage($"[1, {JsonConvert.SerializeObject(expected)}]"); + await socket.InvokeMessage($"[1, {JsonConvert.SerializeObject(expected)}]"); // assert Assert.IsTrue(TestHelpers.AreEqual(result[0], expected[0])); } [Test] - public void SubscribingToTradeUpdates_Should_SubscribeSuccessfully() + public async Task SubscribingToTradeUpdates_Should_SubscribeSuccessfully() { // arrange var socket = new TestSocket(); @@ -322,7 +321,7 @@ public void SubscribingToTradeUpdates_Should_SubscribeSuccessfully() }; // act - socket.InvokeMessage(subResponse); + await socket.InvokeMessage(subResponse); subTask.Wait(5000); @@ -331,7 +330,7 @@ public void SubscribingToTradeUpdates_Should_SubscribeSuccessfully() } [Test] - public void SubscribingToTradeUpdates_Should_TriggerWithTradeUpdate() + public async Task SubscribingToTradeUpdates_Should_TriggerWithTradeUpdate() { // arrange var socket = new TestSocket(); @@ -349,219 +348,219 @@ public void SubscribingToTradeUpdates_Should_TriggerWithTradeUpdate() Symbol = "BTCUSD", Pair = "BTCUSD" }; - socket.InvokeMessage(subResponse); + await socket.InvokeMessage(subResponse); subTask.Wait(5000); BitfinexTradeSimple[] expected = new[] { new BitfinexTradeSimple() }; // act - socket.InvokeMessage($"[1, {JsonConvert.SerializeObject(expected)}]"); + await socket.InvokeMessage($"[1, {JsonConvert.SerializeObject(expected)}]"); // assert Assert.IsTrue(TestHelpers.AreEqual(result[0], expected[0])); } - [TestCase("ou", BitfinexEventType.OrderUpdate)] - [TestCase("on", BitfinexEventType.OrderNew)] - [TestCase("oc", BitfinexEventType.OrderCancel)] - [TestCase("os", BitfinexEventType.OrderSnapshot, false)] - public void SubscribingToOrderUpdates_Should_TriggerWithOrderUpdate(string updateType, BitfinexEventType eventType, bool single=true) - { - // arrange - var socket = new TestSocket(); - socket.CanConnect = true; - var client = TestHelpers.CreateAuthenticatedSocketClient(socket); + //[TestCase("ou", BitfinexEventType.OrderUpdate)] + //[TestCase("on", BitfinexEventType.OrderNew)] + //[TestCase("oc", BitfinexEventType.OrderCancel)] + //[TestCase("os", BitfinexEventType.OrderSnapshot, false)] + //public void SubscribingToOrderUpdates_Should_TriggerWithOrderUpdate(string updateType, BitfinexEventType eventType, bool single=true) + //{ + // // arrange + // var socket = new TestSocket(); + // socket.CanConnect = true; + // var client = TestHelpers.CreateAuthenticatedSocketClient(socket); - var rstEvent = new ManualResetEvent(false); - BitfinexSocketEvent> result = null; - var expected = new BitfinexSocketEvent(eventType, new [] { new BitfinexOrder() { StatusString = "ACTIVE" }}); - client.SpotApi.SubscribeToUserTradeUpdatesAsync(data => - { - result = data.Data; - rstEvent.Set(); - }, null, null); + // var rstEvent = new ManualResetEvent(false); + // BitfinexSocketEvent> result = null; + // var expected = new BitfinexSocketEvent(eventType, new [] { new BitfinexOrder() { StatusString = "ACTIVE" }}); + // client.SpotApi.SubscribeToUserTradeUpdatesAsync(data => + // { + // result = data.Data; + // rstEvent.Set(); + // }, null, null); - // act - socket.InvokeMessage(new BitfinexAuthenticationResponse() { Event = "auth", Status = "OK" }); - socket.InvokeMessage(single ? new object[] {0, updateType, expected.Data[0] } : new object[] {0, updateType, new[] { expected.Data[0]} }); - rstEvent.WaitOne(1000); + // // act + // socket.InvokeMessage(new BitfinexAuthenticationResponse() { Event = "auth", Status = "OK" }); + // socket.InvokeMessage(single ? new object[] {0, updateType, expected.Data[0] } : new object[] {0, updateType, new[] { expected.Data[0]} }); + // rstEvent.WaitOne(1000); - // assert - Assert.IsTrue(TestHelpers.AreEqual(result.Data.First(), expected.Data[0])); - } + // // assert + // Assert.IsTrue(TestHelpers.AreEqual(result.Data.First(), expected.Data[0])); + //} - [TestCase("te", BitfinexEventType.TradeExecuted)] - [TestCase("tu", BitfinexEventType.TradeExecutionUpdate)] - public void SubscribingToTradeUpdates_Should_TriggerWithTradeUpdate(string updateType, BitfinexEventType eventType, bool single = true) - { - // arrange - var socket = new TestSocket(); - socket.CanConnect = true; - var client = TestHelpers.CreateAuthenticatedSocketClient(socket); + //[TestCase("te", BitfinexEventType.TradeExecuted)] + //[TestCase("tu", BitfinexEventType.TradeExecutionUpdate)] + //public void SubscribingToTradeUpdates_Should_TriggerWithTradeUpdate(string updateType, BitfinexEventType eventType, bool single = true) + //{ + // // arrange + // var socket = new TestSocket(); + // socket.CanConnect = true; + // var client = TestHelpers.CreateAuthenticatedSocketClient(socket); - var rstEvent = new ManualResetEvent(false); - BitfinexSocketEvent> result = null; - var expected = new BitfinexSocketEvent(eventType, new[] { new BitfinexTradeDetails() { } }); - client.SpotApi.SubscribeToUserTradeUpdatesAsync(null, - data => - { - result = data.Data; - rstEvent.Set(); - }, null); + // var rstEvent = new ManualResetEvent(false); + // BitfinexSocketEvent> result = null; + // var expected = new BitfinexSocketEvent(eventType, new[] { new BitfinexTradeDetails() { } }); + // client.SpotApi.SubscribeToUserTradeUpdatesAsync(null, + // data => + // { + // result = data.Data; + // rstEvent.Set(); + // }, null); - // act - socket.InvokeMessage(new BitfinexAuthenticationResponse() { Event = "auth", Status = "OK" }); - socket.InvokeMessage(single ? new object[] { 0, updateType, new BitfinexTradeDetails() } : new object[] { 0, updateType, new[] { new BitfinexTradeDetails() } }); - rstEvent.WaitOne(1000); + // // act + // socket.InvokeMessage(new BitfinexAuthenticationResponse() { Event = "auth", Status = "OK" }); + // socket.InvokeMessage(single ? new object[] { 0, updateType, new BitfinexTradeDetails() } : new object[] { 0, updateType, new[] { new BitfinexTradeDetails() } }); + // rstEvent.WaitOne(1000); - // assert - Assert.IsTrue(TestHelpers.AreEqual(result.Data.First(), expected.Data[0])); - } + // // assert + // Assert.IsTrue(TestHelpers.AreEqual(result.Data.First(), expected.Data[0])); + //} - [TestCase("ws", BitfinexEventType.WalletSnapshot, false)] - [TestCase("wu", BitfinexEventType.WalletUpdate)] - public void SubscribingToWalletUpdates_Should_TriggerWithWalletUpdate(string updateType, BitfinexEventType eventType, bool single = true) - { - // arrange - var socket = new TestSocket(); - socket.CanConnect = true; - var client = TestHelpers.CreateAuthenticatedSocketClient(socket); + //[TestCase("ws", BitfinexEventType.WalletSnapshot, false)] + //[TestCase("wu", BitfinexEventType.WalletUpdate)] + //public void SubscribingToWalletUpdates_Should_TriggerWithWalletUpdate(string updateType, BitfinexEventType eventType, bool single = true) + //{ + // // arrange + // var socket = new TestSocket(); + // socket.CanConnect = true; + // var client = TestHelpers.CreateAuthenticatedSocketClient(socket); - var rstEvent = new ManualResetEvent(false); - BitfinexSocketEvent> result = null; - var expected = new BitfinexSocketEvent>(eventType, new[] { new BitfinexWallet() { } }); - client.SpotApi.SubscribeToBalanceUpdatesAsync(data => - { - result = data.Data; - rstEvent.Set(); - }); + // var rstEvent = new ManualResetEvent(false); + // BitfinexSocketEvent> result = null; + // var expected = new BitfinexSocketEvent>(eventType, new[] { new BitfinexWallet() { } }); + // client.SpotApi.SubscribeToBalanceUpdatesAsync(data => + // { + // result = data.Data; + // rstEvent.Set(); + // }); - // act - socket.InvokeMessage(new BitfinexAuthenticationResponse() { Event = "auth", Status = "OK" }); - socket.InvokeMessage(single ? new object[] { 0, updateType, new BitfinexWallet() } : new object[] { 0, updateType, new[] { new BitfinexWallet() } }); - rstEvent.WaitOne(1000); + // // act + // socket.InvokeMessage(new BitfinexAuthenticationResponse() { Event = "auth", Status = "OK" }); + // socket.InvokeMessage(single ? new object[] { 0, updateType, new BitfinexWallet() } : new object[] { 0, updateType, new[] { new BitfinexWallet() } }); + // rstEvent.WaitOne(1000); - // assert - Assert.IsTrue(TestHelpers.AreEqual(result.Data.First(), expected.Data.First())); - } + // // assert + // Assert.IsTrue(TestHelpers.AreEqual(result.Data.First(), expected.Data.First())); + //} - [TestCase("pn", BitfinexEventType.PositionNew)] - [TestCase("pc", BitfinexEventType.PositionClose)] - [TestCase("pu", BitfinexEventType.PositionUpdate)] - [TestCase("ps", BitfinexEventType.PositionSnapshot, false)] - public void SubscribingToPositionUpdates_Should_TriggerWithPositionUpdate(string updateType, BitfinexEventType eventType, bool single = true) - { - // arrange - var socket = new TestSocket(); - socket.CanConnect = true; - var client = TestHelpers.CreateAuthenticatedSocketClient(socket); + //[TestCase("pn", BitfinexEventType.PositionNew)] + //[TestCase("pc", BitfinexEventType.PositionClose)] + //[TestCase("pu", BitfinexEventType.PositionUpdate)] + //[TestCase("ps", BitfinexEventType.PositionSnapshot, false)] + //public void SubscribingToPositionUpdates_Should_TriggerWithPositionUpdate(string updateType, BitfinexEventType eventType, bool single = true) + //{ + // // arrange + // var socket = new TestSocket(); + // socket.CanConnect = true; + // var client = TestHelpers.CreateAuthenticatedSocketClient(socket); - var rstEvent = new ManualResetEvent(false); - BitfinexSocketEvent> result = null; - var expected = new BitfinexSocketEvent>(eventType, new[] { new BitfinexPosition() { } }); - client.SpotApi.SubscribeToUserTradeUpdatesAsync(null, null, data => - { - result = data.Data; - rstEvent.Set(); - }); + // var rstEvent = new ManualResetEvent(false); + // BitfinexSocketEvent> result = null; + // var expected = new BitfinexSocketEvent>(eventType, new[] { new BitfinexPosition() { } }); + // client.SpotApi.SubscribeToUserTradeUpdatesAsync(null, null, data => + // { + // result = data.Data; + // rstEvent.Set(); + // }); - // act - socket.InvokeMessage(new BitfinexAuthenticationResponse() { Event = "auth", Status = "OK" }); - socket.InvokeMessage(single ? new object[] { 0, updateType, new BitfinexPosition() } : new object[] { 0, updateType, new[] { new BitfinexPosition() } }); - rstEvent.WaitOne(1000); + // // act + // socket.InvokeMessage(new BitfinexAuthenticationResponse() { Event = "auth", Status = "OK" }); + // socket.InvokeMessage(single ? new object[] { 0, updateType, new BitfinexPosition() } : new object[] { 0, updateType, new[] { new BitfinexPosition() } }); + // rstEvent.WaitOne(1000); - // assert - Assert.IsTrue(TestHelpers.AreEqual(result.Data.First(), expected.Data.First())); - } + // // assert + // Assert.IsTrue(TestHelpers.AreEqual(result.Data.First(), expected.Data.First())); + //} - [TestCase("fcn", BitfinexEventType.FundingCreditsNew)] - [TestCase("fcu", BitfinexEventType.FundingCreditsUpdate)] - [TestCase("fcc", BitfinexEventType.FundingCreditsClose)] - [TestCase("fcs", BitfinexEventType.FundingCreditsSnapshot, false)] - public void SubscribingToFundingCreditsUpdates_Should_TriggerWithFundingCreditsUpdate(string updateType, BitfinexEventType eventType, bool single = true) - { - // arrange - var socket = new TestSocket(); - socket.CanConnect = true; - var client = TestHelpers.CreateAuthenticatedSocketClient(socket); + //[TestCase("fcn", BitfinexEventType.FundingCreditsNew)] + //[TestCase("fcu", BitfinexEventType.FundingCreditsUpdate)] + //[TestCase("fcc", BitfinexEventType.FundingCreditsClose)] + //[TestCase("fcs", BitfinexEventType.FundingCreditsSnapshot, false)] + //public void SubscribingToFundingCreditsUpdates_Should_TriggerWithFundingCreditsUpdate(string updateType, BitfinexEventType eventType, bool single = true) + //{ + // // arrange + // var socket = new TestSocket(); + // socket.CanConnect = true; + // var client = TestHelpers.CreateAuthenticatedSocketClient(socket); - var rstEvent = new ManualResetEvent(false); - BitfinexSocketEvent> result = null; - var expected = new BitfinexSocketEvent(eventType, new[] { new BitfinexFundingCredit() { StatusString="ACTIVE" } }); - client.SpotApi.SubscribeToFundingUpdatesAsync(null,data => - { - result = data.Data; - rstEvent.Set(); - }, null); + // var rstEvent = new ManualResetEvent(false); + // BitfinexSocketEvent> result = null; + // var expected = new BitfinexSocketEvent(eventType, new[] { new BitfinexFundingCredit() { StatusString="ACTIVE" } }); + // client.SpotApi.SubscribeToFundingUpdatesAsync(null,data => + // { + // result = data.Data; + // rstEvent.Set(); + // }, null); - // act - socket.InvokeMessage(new BitfinexAuthenticationResponse() { Event = "auth", Status = "OK" }); - socket.InvokeMessage(single ? new object[] { 0, updateType, new BitfinexFundingCredit() { StatusString = "ACTIVE" } } : new object[] { 0, updateType, new[] { new BitfinexFundingCredit() { StatusString = "ACTIVE" } } }); - rstEvent.WaitOne(1000); + // // act + // socket.InvokeMessage(new BitfinexAuthenticationResponse() { Event = "auth", Status = "OK" }); + // socket.InvokeMessage(single ? new object[] { 0, updateType, new BitfinexFundingCredit() { StatusString = "ACTIVE" } } : new object[] { 0, updateType, new[] { new BitfinexFundingCredit() { StatusString = "ACTIVE" } } }); + // rstEvent.WaitOne(1000); - // assert - Assert.IsTrue(TestHelpers.AreEqual(result.Data.First(), expected.Data[0])); - } + // // assert + // Assert.IsTrue(TestHelpers.AreEqual(result.Data.First(), expected.Data[0])); + //} - [TestCase("fln", BitfinexEventType.FundingLoanNew)] - [TestCase("flu", BitfinexEventType.FundingLoanUpdate)] - [TestCase("flc", BitfinexEventType.FundingLoanClose)] - [TestCase("fls", BitfinexEventType.FundingLoanSnapshot, false)] - public void SubscribingToFundingLoanUpdates_Should_TriggerWithFundingLoanUpdate(string updateType, BitfinexEventType eventType, bool single = true) - { - // arrange - var socket = new TestSocket(); - socket.CanConnect = true; - var client = TestHelpers.CreateAuthenticatedSocketClient(socket); + //[TestCase("fln", BitfinexEventType.FundingLoanNew)] + //[TestCase("flu", BitfinexEventType.FundingLoanUpdate)] + //[TestCase("flc", BitfinexEventType.FundingLoanClose)] + //[TestCase("fls", BitfinexEventType.FundingLoanSnapshot, false)] + //public void SubscribingToFundingLoanUpdates_Should_TriggerWithFundingLoanUpdate(string updateType, BitfinexEventType eventType, bool single = true) + //{ + // // arrange + // var socket = new TestSocket(); + // socket.CanConnect = true; + // var client = TestHelpers.CreateAuthenticatedSocketClient(socket); - var rstEvent = new ManualResetEvent(false); - BitfinexSocketEvent> result = null; - var expected = new BitfinexSocketEvent(eventType, new[] { new BitfinexFunding() { StatusString = "ACTIVE" } }); - client.SpotApi.SubscribeToFundingUpdatesAsync(null, null, data => - { - result = data.Data; - rstEvent.Set(); - }); + // var rstEvent = new ManualResetEvent(false); + // BitfinexSocketEvent> result = null; + // var expected = new BitfinexSocketEvent(eventType, new[] { new BitfinexFunding() { StatusString = "ACTIVE" } }); + // client.SpotApi.SubscribeToFundingUpdatesAsync(null, null, data => + // { + // result = data.Data; + // rstEvent.Set(); + // }); - // act - socket.InvokeMessage(new BitfinexAuthenticationResponse() { Event = "auth", Status = "OK" }); - socket.InvokeMessage(single ? new object[] { 0, updateType, new BitfinexFunding() { StatusString = "ACTIVE" } } : new object[] { 0, updateType, new[] { new BitfinexFunding() { StatusString = "ACTIVE" } } }); - rstEvent.WaitOne(1000); + // // act + // socket.InvokeMessage(new BitfinexAuthenticationResponse() { Event = "auth", Status = "OK" }); + // socket.InvokeMessage(single ? new object[] { 0, updateType, new BitfinexFunding() { StatusString = "ACTIVE" } } : new object[] { 0, updateType, new[] { new BitfinexFunding() { StatusString = "ACTIVE" } } }); + // rstEvent.WaitOne(1000); - // assert - Assert.IsTrue(TestHelpers.AreEqual(result.Data.First(), expected.Data[0])); - } + // // assert + // Assert.IsTrue(TestHelpers.AreEqual(result.Data.First(), expected.Data[0])); + //} - [TestCase("fon", BitfinexEventType.FundingOfferNew)] - [TestCase("fou", BitfinexEventType.FundingOfferUpdate)] - [TestCase("foc", BitfinexEventType.FundingOfferCancel)] - [TestCase("fos", BitfinexEventType.FundingOfferSnapshot, false)] - public void SubscribingToFundingOfferUpdates_Should_TriggerWithFundingOfferUpdate(string updateType, BitfinexEventType eventType, bool single = true) - { - // arrange - var socket = new TestSocket(); - socket.CanConnect = true; - var client = TestHelpers.CreateAuthenticatedSocketClient(socket); + //[TestCase("fon", BitfinexEventType.FundingOfferNew)] + //[TestCase("fou", BitfinexEventType.FundingOfferUpdate)] + //[TestCase("foc", BitfinexEventType.FundingOfferCancel)] + //[TestCase("fos", BitfinexEventType.FundingOfferSnapshot, false)] + //public void SubscribingToFundingOfferUpdates_Should_TriggerWithFundingOfferUpdate(string updateType, BitfinexEventType eventType, bool single = true) + //{ + // // arrange + // var socket = new TestSocket(); + // socket.CanConnect = true; + // var client = TestHelpers.CreateAuthenticatedSocketClient(socket); - var rstEvent = new ManualResetEvent(false); - BitfinexSocketEvent> result = null; - var expected = new BitfinexSocketEvent(eventType, new[] { new BitfinexFundingOffer() { StatusString = "ACTIVE" } }); - client.SpotApi.SubscribeToFundingUpdatesAsync(data => - { - result = data.Data; - rstEvent.Set(); - }, null, null); + // var rstEvent = new ManualResetEvent(false); + // BitfinexSocketEvent> result = null; + // var expected = new BitfinexSocketEvent(eventType, new[] { new BitfinexFundingOffer() { StatusString = "ACTIVE" } }); + // client.SpotApi.SubscribeToFundingUpdatesAsync(data => + // { + // result = data.Data; + // rstEvent.Set(); + // }, null, null); - // act - socket.InvokeMessage(new BitfinexAuthenticationResponse() { Event = "auth", Status = "OK" }); - socket.InvokeMessage(single ? new object[] { 0, updateType, new BitfinexFundingOffer() { StatusString = "ACTIVE" } } : new object[] { 0, updateType, new[] { new BitfinexFundingOffer() { StatusString = "ACTIVE" } } }); - rstEvent.WaitOne(1000); + // // act + // socket.InvokeMessage(new BitfinexAuthenticationResponse() { Event = "auth", Status = "OK" }); + // socket.InvokeMessage(single ? new object[] { 0, updateType, new BitfinexFundingOffer() { StatusString = "ACTIVE" } } : new object[] { 0, updateType, new[] { new BitfinexFundingOffer() { StatusString = "ACTIVE" } } }); + // rstEvent.WaitOne(1000); - // assert - Assert.IsTrue(TestHelpers.AreEqual(result.Data.First(), expected.Data[0])); - } + // // assert + // Assert.IsTrue(TestHelpers.AreEqual(result.Data.First(), expected.Data[0])); + //} [Test] - public void PlacingAnOrder_Should_SucceedIfSuccessResponse() + public async Task PlacingAnOrder_Should_SucceedIfSuccessResponse() { // arrange var socket = new TestSocket(); @@ -580,9 +579,9 @@ public void PlacingAnOrder_Should_SucceedIfSuccessResponse() // act var placeTask = client.SpotApi.PlaceOrderAsync(OrderSide.Buy, OrderType.ExchangeLimit, "tBTCUSD", 1, price: 1, clientOrderId: 1234); - socket.InvokeMessage(new BitfinexAuthenticationResponse() { Event = "auth", Status = "OK" }); + await socket.InvokeMessage(new BitfinexAuthenticationResponse() { Event = "auth", Status = "OK" }); Thread.Sleep(100); - socket.InvokeMessage($"[0, \"n\", [0, \"on-req\", 0, 0, {JsonConvert.SerializeObject(expected)}, 0, \"SUCCESS\", \"Submitted\"]]"); + await socket.InvokeMessage($"[0, \"n\", [0, \"on-req\", 0, 0, {JsonConvert.SerializeObject(expected)}, 0, \"SUCCESS\", \"Submitted\"]]"); var result = placeTask.Result; // assert @@ -591,7 +590,7 @@ public void PlacingAnOrder_Should_SucceedIfSuccessResponse() } [Test] - public void PlacingAnOrder_Should_FailIfErrorResponse() + public async Task PlacingAnOrder_Should_FailIfErrorResponse() { // arrange var socket = new TestSocket(); @@ -601,9 +600,9 @@ public void PlacingAnOrder_Should_FailIfErrorResponse() // act var placeTask = client.SpotApi.PlaceOrderAsync(OrderSide.Buy, OrderType.ExchangeLimit, "tBTCUSD", 1, price: 1, clientOrderId: 123); - socket.InvokeMessage(new BitfinexAuthenticationResponse() { Event = "auth", Status = "OK" }); + await socket.InvokeMessage(new BitfinexAuthenticationResponse() { Event = "auth", Status = "OK" }); Thread.Sleep(100); - socket.InvokeMessage($"[0, \"n\", [0, \"on-req\", 0, 0, {JsonConvert.SerializeObject(order)}, 0, \"error\", \"order placing failed\"]]"); + await socket.InvokeMessage($"[0, \"n\", [0, \"on-req\", 0, 0, {JsonConvert.SerializeObject(order)}, 0, \"error\", \"order placing failed\"]]"); var result = placeTask.Result; // assert @@ -612,7 +611,7 @@ public void PlacingAnOrder_Should_FailIfErrorResponse() } [Test] - public void PlacingAnOrder_Should_FailIfNoResponse() + public async Task PlacingAnOrder_Should_FailIfNoResponse() { // arrange var socket = new TestSocket(); @@ -625,7 +624,7 @@ public void PlacingAnOrder_Should_FailIfNoResponse() // act var placeTask = client.SpotApi.PlaceOrderAsync(OrderSide.Buy, OrderType.ExchangeLimit, "tBTCUSD", 1, price: 1, clientOrderId: 123); - socket.InvokeMessage(new BitfinexAuthenticationResponse() { Event = "auth", Status = "OK" }); + await socket.InvokeMessage(new BitfinexAuthenticationResponse() { Event = "auth", Status = "OK" }); var result = placeTask.Result; // assert @@ -633,7 +632,7 @@ public void PlacingAnOrder_Should_FailIfNoResponse() } [Test] - public void PlacingAnMarketOrder_Should_SucceedIfSuccessResponse() + public async Task PlacingAnMarketOrder_Should_SucceedIfSuccessResponse() { // arrange var socket = new TestSocket(); @@ -652,9 +651,9 @@ public void PlacingAnMarketOrder_Should_SucceedIfSuccessResponse() // act var placeTask = client.SpotApi.PlaceOrderAsync(OrderSide.Buy, OrderType.ExchangeMarket, "tBTCUSD", 1, price: 1, clientOrderId: 1234); - socket.InvokeMessage(new BitfinexAuthenticationResponse() { Event = "auth", Status = "OK" }); + await socket.InvokeMessage(new BitfinexAuthenticationResponse() { Event = "auth", Status = "OK" }); Thread.Sleep(100); - socket.InvokeMessage($"[0, \"n\", [0, \"on-req\", 0, 0, {JsonConvert.SerializeObject(expected)}, 0, \"SUCCESS\", \"Submitted\"]]"); + await socket.InvokeMessage($"[0, \"n\", [0, \"on-req\", 0, 0, {JsonConvert.SerializeObject(expected)}, 0, \"SUCCESS\", \"Submitted\"]]"); var result = placeTask.Result; // assert @@ -663,7 +662,7 @@ public void PlacingAnMarketOrder_Should_SucceedIfSuccessResponse() } [Test] - public void PlacingAnFOKOrder_Should_SucceedIfSuccessResponse() + public async Task PlacingAnFOKOrder_Should_SucceedIfSuccessResponse() { // arrange var socket = new TestSocket(); @@ -682,9 +681,9 @@ public void PlacingAnFOKOrder_Should_SucceedIfSuccessResponse() // act var placeTask = client.SpotApi.PlaceOrderAsync(OrderSide.Buy, OrderType.ExchangeFillOrKill, "tBTCUSD", 1, price: 1, clientOrderId: 1234); - socket.InvokeMessage(new BitfinexAuthenticationResponse() { Event = "auth", Status = "OK" }); + await socket.InvokeMessage(new BitfinexAuthenticationResponse() { Event = "auth", Status = "OK" }); Thread.Sleep(100); - socket.InvokeMessage($"[0, \"n\", [0, \"on-req\", 0, 0, {JsonConvert.SerializeObject(expected)}, 0, \"SUCCESS\", \"Submitted\"]]"); + await socket.InvokeMessage($"[0, \"n\", [0, \"on-req\", 0, 0, {JsonConvert.SerializeObject(expected)}, 0, \"SUCCESS\", \"Submitted\"]]"); var result = placeTask.Result; // assert @@ -693,7 +692,7 @@ public void PlacingAnFOKOrder_Should_SucceedIfSuccessResponse() } [Test] - public void PlacingAnFundingOffer_Should_SucceedIfSuccessResponse() + public async Task PlacingAnFundingOffer_Should_SucceedIfSuccessResponse() { // arrange var socket = new TestSocket(); @@ -712,9 +711,9 @@ public void PlacingAnFundingOffer_Should_SucceedIfSuccessResponse() // act var placeTask = client.SpotApi.SubmitFundingOfferAsync(FundingOfferType.Limit, "fUSD", 1, 1, 1); - socket.InvokeMessage(new BitfinexAuthenticationResponse() { Event = "auth", Status = "OK" }); + await socket.InvokeMessage(new BitfinexAuthenticationResponse() { Event = "auth", Status = "OK" }); Thread.Sleep(100); - socket.InvokeMessage($"[0, \"n\", [0, \"fon-req\", 0, 0, {JsonConvert.SerializeObject(expected)}, 0, \"SUCCESS\", \"Submitted\"]]"); + await socket.InvokeMessage($"[0, \"n\", [0, \"fon-req\", 0, 0, {JsonConvert.SerializeObject(expected)}, 0, \"SUCCESS\", \"Submitted\"]]"); var result = placeTask.Result; // assert @@ -724,7 +723,7 @@ public void PlacingAnFundingOffer_Should_SucceedIfSuccessResponse() [Test] - public void ReceivingAReconnectMessage_Should_ReconnectWebsocket() + public async Task ReceivingAReconnectMessage_Should_ReconnectWebsocket() { // arrange var socket = new TestSocket(); @@ -737,27 +736,25 @@ public void ReceivingAReconnectMessage_Should_ReconnectWebsocket() var rstEvent = new ManualResetEvent(false); var subTask = client.SpotApi.SubscribeToKlineUpdatesAsync("tBTCUSD", KlineInterval.FiveMinutes, data => { }); - socket.InvokeMessage(new CandleSubscriptionResponse() + await socket.InvokeMessage(new CandleSubscriptionResponse() { Channel = "candles", Event = "subscribed", ChannelId = 1, - Symbol = "tBTCUSD", Key = "trade:" + JsonConvert.SerializeObject(KlineInterval.FiveMinutes, new KlineIntervalConverter(false)) + ":tBTCUSD" }); - var subResult = subTask.Result; + var subResult = await subTask; subResult.Data.ConnectionRestored += (t) => rstEvent.Set(); // act - socket.InvokeMessage("{\"event\":\"info\", \"code\": 20051}"); + await socket.InvokeMessage("{\"event\":\"info\", \"code\": 20051}"); Thread.Sleep(100); - socket.InvokeMessage(new CandleSubscriptionResponse() + await socket.InvokeMessage(new CandleSubscriptionResponse() { Channel = "candles", Event = "subscribed", ChannelId = 1, - Symbol = "tBTCUSD", Key = "trade:" + JsonConvert.SerializeObject(KlineInterval.FiveMinutes, new KlineIntervalConverter(false)) + ":tBTCUSD" }); diff --git a/Bitfinex.Net.UnitTests/TestImplementations/TestHelpers.cs b/Bitfinex.Net.UnitTests/TestImplementations/TestHelpers.cs index cb14c8e..6772a8e 100644 --- a/Bitfinex.Net.UnitTests/TestImplementations/TestHelpers.cs +++ b/Bitfinex.Net.UnitTests/TestImplementations/TestHelpers.cs @@ -14,6 +14,7 @@ using Bitfinex.Net.Objects.Options; using CryptoExchange.Net.Authentication; using CryptoExchange.Net.Interfaces; +using CryptoExchange.Net.Objects.Sockets; using CryptoExchange.Net.Sockets; using Microsoft.Extensions.Logging; using Moq; diff --git a/Bitfinex.Net.UnitTests/TestImplementations/TestSocket.cs b/Bitfinex.Net.UnitTests/TestImplementations/TestSocket.cs index 89a8517..1f54dc4 100644 --- a/Bitfinex.Net.UnitTests/TestImplementations/TestSocket.cs +++ b/Bitfinex.Net.UnitTests/TestImplementations/TestSocket.cs @@ -1,4 +1,6 @@ using System; +using System.IO; +using System.Net.WebSockets; using System.Security.Authentication; using System.Text; using System.Threading.Tasks; @@ -10,18 +12,18 @@ namespace Binance.Net.UnitTests.TestImplementations { public class TestSocket: IWebsocket { - public bool CanConnect { get; set; } + public bool CanConnect { get; set; } = true; public bool Connected { get; set; } -#pragma warning disable 8618 - public event Action OnClose; - public event Action OnMessage; - public event Action OnError; - public event Action OnOpen; - public event Action OnReconnecting; - public event Action OnRequestSent; - public event Action OnReconnected; -#pragma warning restore 8618 + public event Func OnClose; +#pragma warning disable 0067 + public event Func OnReconnected; + public event Func OnReconnecting; +#pragma warning restore 0067 + public event Func OnRequestSent; + public event Func OnStreamMessage; + public event Func OnError; + public event Func OnOpen; public int Id { get; } public bool ShouldReconnect { get; set; } @@ -98,14 +100,16 @@ public void InvokeOpen() OnOpen?.Invoke(); } - public void InvokeMessage(string data) + public async Task InvokeMessage(string data) { - OnMessage?.Invoke(data); + var stream = new MemoryStream(Encoding.UTF8.GetBytes(data)); + await OnStreamMessage?.Invoke(WebSocketMessageType.Text, stream); } - public void InvokeMessage(T data) + public async Task InvokeMessage(T data) { - OnMessage?.Invoke(JsonConvert.SerializeObject(data)); + var stream = new MemoryStream(Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(data))); + await OnStreamMessage?.Invoke(WebSocketMessageType.Text, stream); } public void InvokeError(Exception error) diff --git a/Bitfinex.Net/Bitfinex.Net.xml b/Bitfinex.Net/Bitfinex.Net.xml index b3adc15..4eaf64e 100644 --- a/Bitfinex.Net/Bitfinex.Net.xml +++ b/Bitfinex.Net/Bitfinex.Net.xml @@ -491,10 +491,10 @@ - + - + @@ -527,7 +527,7 @@ - + @@ -537,10 +537,7 @@ - - Cancel all open orders - - + @@ -563,6 +560,9 @@ + + + Socket event types @@ -2527,7 +2527,7 @@ Cancellation token for closing this subscription - + Subscribe to trading information updates @@ -2535,6 +2535,15 @@ Data handler for order updates. Can be null if not interested Data handler for trade execution updates. Can be null if not interested Data handler for position updates. Can be null if not interested + Data handler for funding offer updates. Can be null if not interested + Data handler for funding credit updates. Can be null if not interested + Data handler for funding loan updates. Can be null if not interested + Data handler for wallet updates. Can be null if not interested + Data handler for balance updates. Can be null if not interested + Data handler for funding trade updates. Can be null if not interested + Data handler for funding info updates. Can be null if not interested + Data handler for margin base updates. Can be null if not interested + Data handler for margin symbol updates. Can be null if not interested Cancellation token for closing this subscription @@ -2559,7 +2568,13 @@ Affiliate code for the order - + + + Cancel all orders + + + + Updates an order @@ -5356,6 +5371,12 @@ Spot API options + + + + + + Bitfinex order book factory diff --git a/Bitfinex.Net/BitfinexAuthenticationProvider.cs b/Bitfinex.Net/BitfinexAuthenticationProvider.cs index 0d9cc5c..7f4a3aa 100644 --- a/Bitfinex.Net/BitfinexAuthenticationProvider.cs +++ b/Bitfinex.Net/BitfinexAuthenticationProvider.cs @@ -1,9 +1,7 @@ using System; using System.Collections.Generic; using System.Globalization; -using System.Linq; using System.Net.Http; -using System.Security.Cryptography; using System.Text; using Bitfinex.Net.Objects.Internal; using CryptoExchange.Net; diff --git a/Bitfinex.Net/BitfinexHelpers.cs b/Bitfinex.Net/BitfinexHelpers.cs index 90d4009..e0e0ab7 100644 --- a/Bitfinex.Net/BitfinexHelpers.cs +++ b/Bitfinex.Net/BitfinexHelpers.cs @@ -8,8 +8,6 @@ using System.Text.RegularExpressions; using Bitfinex.Net.Interfaces; using Bitfinex.Net.SymbolOrderBooks; -using CryptoExchange.Net.Interfaces.CommonClients; -using Bitfinex.Net.Clients.SpotApi; namespace Bitfinex.Net { diff --git a/Bitfinex.Net/Clients/SpotApi/BitfinexRestClientSpotApiTrading.cs b/Bitfinex.Net/Clients/SpotApi/BitfinexRestClientSpotApiTrading.cs index a4f81e4..d3fde2c 100644 --- a/Bitfinex.Net/Clients/SpotApi/BitfinexRestClientSpotApiTrading.cs +++ b/Bitfinex.Net/Clients/SpotApi/BitfinexRestClientSpotApiTrading.cs @@ -13,7 +13,6 @@ using System.Threading; using System.Threading.Tasks; using Bitfinex.Net.Objects.Models; -using Bitfinex.Net.Objects.Models.V1; using Bitfinex.Net.Interfaces.Clients.SpotApi; using CryptoExchange.Net.CommonObjects; diff --git a/Bitfinex.Net/Clients/SpotApi/BitfinexSocketClientSpotApi.cs b/Bitfinex.Net/Clients/SpotApi/BitfinexSocketClientSpotApi.cs index e3b4055..5cfc02b 100644 --- a/Bitfinex.Net/Clients/SpotApi/BitfinexSocketClientSpotApi.cs +++ b/Bitfinex.Net/Clients/SpotApi/BitfinexSocketClientSpotApi.cs @@ -13,22 +13,32 @@ using Bitfinex.Net.Objects.Options; using CryptoExchange.Net.Converters; using CryptoExchange.Net.Objects.Sockets; -using Bitfinex.Net.Objects.Sockets; using Bitfinex.Net.Objects.Sockets.Subscriptions; using Bitfinex.Net.Objects.Models; using Bitfinex.Net.Enums; using System.Collections.Generic; using System.Linq; -using CryptoExchange.Net.Interfaces; using CryptoExchange.Net.Sockets; using System.Globalization; using Bitfinex.Net.Objects.Sockets.Queries; +using CryptoExchange.Net.Sockets.MessageParsing; +using CryptoExchange.Net.Sockets.MessageParsing.Interfaces; namespace Bitfinex.Net.Clients.SpotApi { /// public class BitfinexSocketClientSpotApi : SocketApiClient, IBitfinexSocketClientSpotApi { + private static readonly MessagePath _0Path = MessagePath.Get().Index(0); + private static readonly MessagePath _eventPath = MessagePath.Get().Property("event"); + private static readonly MessagePath _channelPath = MessagePath.Get().Property("channel"); + private static readonly MessagePath _symbolPath = MessagePath.Get().Property("symbol"); + private static readonly MessagePath _precPath = MessagePath.Get().Property("prec"); + private static readonly MessagePath _freqPath = MessagePath.Get().Property("freq"); + private static readonly MessagePath _lenPath = MessagePath.Get().Property("len"); + private static readonly MessagePath _keyPath = MessagePath.Get().Property("key"); + private static readonly MessagePath _chanIdPath = MessagePath.Get().Property("chanId"); + #region fields private readonly JsonSerializer _bookSerializer = new JsonSerializer(); private readonly JsonSerializer _fundingBookSerializer = new JsonSerializer(); @@ -38,19 +48,12 @@ public class BitfinexSocketClientSpotApi : SocketApiClient, IBitfinexSocketClien /// public new BitfinexSocketOptions ClientOptions => (BitfinexSocketOptions)base.ClientOptions; - /// - public override MessageInterpreterPipeline Pipeline { get; } = new MessageInterpreterPipeline - { - GetStreamIdentifier = GetStreamIdentifier, - GetTypeIdentifier = GetTypeIdentifier - }; #endregion #region ctor internal BitfinexSocketClientSpotApi(ILogger logger, BitfinexSocketOptions options) : base(logger, options.Environment.SocketAddress, options, options.SpotOptions) { - ContinueOnQueryResponse = true; UnhandledMessageExpected = true; //AddGenericHandler("Conf", ConfHandler); @@ -62,11 +65,13 @@ internal BitfinexSocketClientSpotApi(ILogger logger, BitfinexSocketOptions optio _fundingBookSerializer.Converters.Add(new OrderBookFundingEntryConverter()); } #endregion + /// protected override AuthenticationProvider CreateAuthenticationProvider(ApiCredentials credentials) => new BitfinexAuthenticationProvider(credentials, ClientOptions.NonceProvider ?? new BitfinexNonceProvider()); - protected override BaseQuery GetAuthenticationRequest() + /// + protected override Query GetAuthenticationRequest() { var authProvider = (BitfinexAuthenticationProvider)AuthenticationProvider!; var n = authProvider.GetNonce().ToString(); @@ -172,19 +177,21 @@ public async Task> SubscribeToDerivativesUpdatesA /// public async Task> SubscribeToUserUpdatesAsync( - Action>> orderHandler, - Action>> positionHandler, - Action>> fundingOfferHandler, - Action>> fundingCreditHandler, - Action>> fundingLoanHandler, - Action>> walletHandler, - Action> balanceHandler, - Action> tradeHandler, - Action> fundingTradeHandler, - Action> fundingInfoHandler, + Action>>? orderHandler, + Action>>? positionHandler, + Action>>? fundingOfferHandler, + Action>>? fundingCreditHandler, + Action>>? fundingLoanHandler, + Action>>? walletHandler, + Action>? balanceHandler, + Action>? tradeHandler, + Action>? fundingTradeHandler, + Action>? fundingInfoHandler, + Action>? marginBaseHandler, + Action>? marginSymbolHandler, CancellationToken ct = default) { - var subscription = new BitfinexUserSubscription(_logger, positionHandler, walletHandler, orderHandler, fundingOfferHandler, fundingCreditHandler, fundingLoanHandler, balanceHandler, tradeHandler, fundingTradeHandler, fundingInfoHandler); + var subscription = new BitfinexUserSubscription(_logger, positionHandler, walletHandler, orderHandler, fundingOfferHandler, fundingCreditHandler, fundingLoanHandler, balanceHandler, tradeHandler, fundingTradeHandler, fundingInfoHandler, marginBaseHandler, marginSymbolHandler); return await SubscribeAsync(BaseAddress.AppendPath("ws/2"), subscription, ct).ConfigureAwait(false); } @@ -215,7 +222,7 @@ public async Task> PlaceOrderAsync(OrderSide side, Ord var bitfinexQuery = new BitfinexQuery(query); var result = await QueryAsync(BaseAddress.AppendPath("ws/2"), bitfinexQuery).ConfigureAwait(false); - return result.As(result.Data?.Data.Data); + return result.As(result.Data?.Data.Data); } /// @@ -234,7 +241,7 @@ public async Task> UpdateOrderAsync(long orderId, deci var bitfinexQuery = new BitfinexQuery(query); var result = await QueryAsync(BaseAddress.AppendPath("ws/2"), bitfinexQuery).ConfigureAwait(false); - return result.As(result.Data?.Data.Data); + return result.As(result.Data?.Data.Data); } /// @@ -252,7 +259,7 @@ public async Task> CancelOrderAsync(long orderId) var query = new BitfinexSocketQuery(orderId.ToString(CultureInfo.InvariantCulture), BitfinexEventType.OrderCancel, new Dictionary { ["id"] = orderId }); var bitfinexQuery = new BitfinexQuery(query); var result = await QueryAsync(BaseAddress.AppendPath("ws/2"), bitfinexQuery).ConfigureAwait(false); - return result.As(result.Data?.Data.Data); + return result.As(result.Data?.Data.Data); } /// @@ -325,7 +332,7 @@ public async Task> SubmitFundingOfferAsync(Fund var query = new BitfinexSocketQuery(ExchangeHelpers.NextId().ToString(CultureInfo.InvariantCulture), BitfinexEventType.FundingOfferNew, parameters); var bitfinexQuery = new BitfinexQuery(query); var result = await QueryAsync(BaseAddress.AppendPath("ws/2"), bitfinexQuery).ConfigureAwait(false); - return result.As(result.Data?.Data.Data); + return result.As(result.Data?.Data.Data); } /// @@ -339,85 +346,35 @@ public async Task> CancelFundingOfferAsync(long var query = new BitfinexSocketQuery(id.ToString(CultureInfo.InvariantCulture), BitfinexEventType.FundingOfferCancel, parameters); var bitfinexQuery = new BitfinexQuery(query); var result = await QueryAsync(BaseAddress.AppendPath("ws/2"), bitfinexQuery).ConfigureAwait(false); - return result.As(result.Data?.Data.Data); + return result.As(result.Data?.Data.Data); } - private static string? GetStreamIdentifier(IMessageAccessor accessor) + /// + public override string? GetListenerIdentifier(IMessageAccessor message) { - if (!accessor.IsObject(null)) - { - return accessor.GetArrayIntValue(null, 0).ToString(); - } + var type = message.GetNodeType(); + if (type == NodeType.Array) + return message.GetValue(_0Path).ToString(); - var evnt = accessor.GetStringValue("event"); + var evnt = message.GetValue(_eventPath); if (evnt == "info") return "info"; - var channel = accessor.GetStringValue("channel"); - var symbol = accessor.GetStringValue("symbol"); - var prec = accessor.GetStringValue("prec"); - var freq = accessor.GetStringValue("freq"); - var len = accessor.GetStringValue("len"); - var key = accessor.GetStringValue("key"); - var chanId = evnt == "unsubscribed" ? accessor.GetStringValue("chanId") : ""; + var channel = message.GetValue(_channelPath); + var symbol = message.GetValue(_symbolPath); + var prec = message.GetValue(_precPath); + var freq = message.GetValue(_freqPath); + var len = message.GetValue(_lenPath); + var key = message.GetValue(_keyPath); + var chanId = evnt == "unsubscribed" ? message.GetValue(_chanIdPath) : ""; return chanId + evnt + channel + symbol + prec + freq + len + key; } - private static string? GetTypeIdentifier(IMessageAccessor accessor) - { - if (accessor.IsObject(null)) - return null; - - var topic = accessor.GetArrayStringValue(null, 1); - var dataIndex = topic == null ? 1 : 2; - var x = topic + "-single"; - if (accessor.IsArray(new[] { dataIndex, 0 }) || accessor.IsEmptyArray(new[] { dataIndex })) - x = topic + "-array"; - - return x; - } - private long GenerateClientOrderId() { var buffer = new byte[8]; _random.NextBytes(buffer); return (long)Math.Round(Math.Abs(BitConverter.ToInt32(buffer, 0)) / 1000m); } - - // private void InfoHandler(MessageEvent messageEvent) - // { - // var infoEvent = messageEvent.JsonData.Type == JTokenType.Object && messageEvent.JsonData["event"]?.ToString() == "info"; - // if (!infoEvent) - // return; - - // _logger.Log(LogLevel.Debug, $"Socket {messageEvent.Connection.SocketId} Info event received: {messageEvent.JsonData}"); - // if (messageEvent.JsonData["code"] == null) - // { - // // welcome event, send a config message for receiving checsum updates for order book subscriptions - // messageEvent.Connection.Send(ExchangeHelpers.NextId(), new BitfinexSocketConfig { Event = "conf", Flags = 131072 }, 1); - // return; - // } - - // var code = messageEvent.JsonData["code"]?.Value(); - // switch (code) - // { - // case 20051: - // _logger.Log(LogLevel.Information, $"Socket {messageEvent.Connection.SocketId} Code {code} received, reconnecting socket"); - // messageEvent.Connection.PausedActivity = true; // Prevent new operations to be send - // _ = messageEvent.Connection.TriggerReconnectAsync(); - // break; - // case 20060: - // _logger.Log(LogLevel.Information, $"Socket {messageEvent.Connection.SocketId} Code {code} received, entering maintenance mode"); - // messageEvent.Connection.PausedActivity = true; - // break; - // case 20061: - // _logger.Log(LogLevel.Information, $"Socket {messageEvent.Connection.SocketId} Code {code} received, leaving maintenance mode. Reconnecting/Resubscribing socket."); - // _ = messageEvent.Connection.TriggerReconnectAsync(); // Closing it via socket will automatically reconnect - // break; - // default: - // _logger.Log(LogLevel.Warning, $"Socket {messageEvent.Connection.SocketId} Unknown info code received: {code}"); - // break; - // } - // } } } diff --git a/Bitfinex.Net/Interfaces/Clients/SpotApi/IBitfinexSocketClientSpotApi.cs b/Bitfinex.Net/Interfaces/Clients/SpotApi/IBitfinexSocketClientSpotApi.cs index cf5d716..636db0a 100644 --- a/Bitfinex.Net/Interfaces/Clients/SpotApi/IBitfinexSocketClientSpotApi.cs +++ b/Bitfinex.Net/Interfaces/Clients/SpotApi/IBitfinexSocketClientSpotApi.cs @@ -8,7 +8,6 @@ using CryptoExchange.Net.Interfaces; using CryptoExchange.Net.Objects; using CryptoExchange.Net.Objects.Sockets; -using CryptoExchange.Net.Sockets; namespace Bitfinex.Net.Interfaces.Clients.SpotApi { @@ -136,19 +135,30 @@ public interface IBitfinexSocketClientSpotApi : ISocketApiClient, IDisposable /// Data handler for order updates. Can be null if not interested /// Data handler for trade execution updates. Can be null if not interested /// Data handler for position updates. Can be null if not interested + /// Data handler for funding offer updates. Can be null if not interested + /// Data handler for funding credit updates. Can be null if not interested + /// Data handler for funding loan updates. Can be null if not interested + /// Data handler for wallet updates. Can be null if not interested + /// Data handler for balance updates. Can be null if not interested + /// Data handler for funding trade updates. Can be null if not interested + /// Data handler for funding info updates. Can be null if not interested + /// Data handler for margin base updates. Can be null if not interested + /// Data handler for margin symbol updates. Can be null if not interested /// Cancellation token for closing this subscription /// Task> SubscribeToUserUpdatesAsync( - Action>> orderHandler, - Action>> positionHandler, - Action>> fundingOfferHandler, - Action>> fundingCreditHandler, - Action>> fundingLoanHandler, - Action>> walletHandler, - Action> balanceHandler, - Action> tradeHandler, - Action> fundingTradeHandler, - Action> fundingInfoHandler, + Action>>? orderHandler, + Action>>? positionHandler, + Action>>? fundingOfferHandler, + Action>>? fundingCreditHandler, + Action>>? fundingLoanHandler, + Action>>? walletHandler, + Action>? balanceHandler, + Action>? tradeHandler, + Action>? fundingTradeHandler, + Action>? fundingInfoHandler, + Action>? marginBaseHandler, + Action>? marginSymbolHandler, CancellationToken ct = default); /// @@ -174,7 +184,7 @@ Task> SubscribeToUserUpdatesAsync( /// /// Cancel all orders - ///// + /// /// /// Task>> CancelAllOrdersAsync(); diff --git a/Bitfinex.Net/Objects/Internal/BitfinexChecksum.cs b/Bitfinex.Net/Objects/Internal/BitfinexChecksum.cs index c20ef65..4155812 100644 --- a/Bitfinex.Net/Objects/Internal/BitfinexChecksum.cs +++ b/Bitfinex.Net/Objects/Internal/BitfinexChecksum.cs @@ -1,8 +1,5 @@ using CryptoExchange.Net.Converters; using Newtonsoft.Json; -using System; -using System.Collections.Generic; -using System.Text; namespace Bitfinex.Net.Objects.Internal { @@ -12,7 +9,7 @@ internal class BitfinexChecksum [ArrayProperty(0)] public int ChannelId { get; set; } [ArrayProperty(1)] - public string Topic { get; set; } + public string Topic { get; set; } = string.Empty; [ArrayProperty(2)] public int Checksum { get; set; } } diff --git a/Bitfinex.Net/Objects/Internal/BitfinexResponse.cs b/Bitfinex.Net/Objects/Internal/BitfinexResponse.cs deleted file mode 100644 index 9765613..0000000 --- a/Bitfinex.Net/Objects/Internal/BitfinexResponse.cs +++ /dev/null @@ -1,24 +0,0 @@ -using Newtonsoft.Json; - -namespace Bitfinex.Net.Objects.Internal -{ - internal class BitfinexResponse - { - public string Event { get; set; } = string.Empty; - public string Channel { get; set; } = string.Empty; - } - - internal class BitfinexSubscribeResponse: BitfinexResponse - { - [JsonProperty("chanId")] - public int ChannelId { get; set; } - } - - internal class BitfinexErrorResponse: BitfinexResponse - { - [JsonProperty("msg")] - public string Message { get; set; } = string.Empty; - [JsonProperty("code")] - public int Code { get; set; } - } -} diff --git a/Bitfinex.Net/Objects/Internal/BitfinexSocketConfig.cs b/Bitfinex.Net/Objects/Internal/BitfinexSocketConfig.cs index 46c664c..fed9d84 100644 --- a/Bitfinex.Net/Objects/Internal/BitfinexSocketConfig.cs +++ b/Bitfinex.Net/Objects/Internal/BitfinexSocketConfig.cs @@ -4,7 +4,8 @@ namespace Bitfinex.Net.Objects.Internal { internal class BitfinexSocketConfig { - [JsonProperty("event")] public string Event { get; set; } = string.Empty; + [JsonProperty("event")] + public string Event { get; set; } = string.Empty; [JsonProperty("flags")] public int Flags { get; set; } diff --git a/Bitfinex.Net/Objects/Models/BitfinexFundingAutoRenew.cs b/Bitfinex.Net/Objects/Models/BitfinexFundingAutoRenew.cs index 921d83e..ddac2d6 100644 --- a/Bitfinex.Net/Objects/Models/BitfinexFundingAutoRenew.cs +++ b/Bitfinex.Net/Objects/Models/BitfinexFundingAutoRenew.cs @@ -1,6 +1,5 @@ using CryptoExchange.Net.Converters; using Newtonsoft.Json; -using System; namespace Bitfinex.Net.Objects.Models { diff --git a/Bitfinex.Net/Objects/Models/BitfinexFundingOrderBook.cs b/Bitfinex.Net/Objects/Models/BitfinexFundingOrderBook.cs index c6ac12c..4bbb921 100644 --- a/Bitfinex.Net/Objects/Models/BitfinexFundingOrderBook.cs +++ b/Bitfinex.Net/Objects/Models/BitfinexFundingOrderBook.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using CryptoExchange.Net.Interfaces; namespace Bitfinex.Net.Objects.Models { diff --git a/Bitfinex.Net/Objects/Models/BitfinexKeyValue.cs b/Bitfinex.Net/Objects/Models/BitfinexKeyValue.cs index 87468cc..1543e72 100644 --- a/Bitfinex.Net/Objects/Models/BitfinexKeyValue.cs +++ b/Bitfinex.Net/Objects/Models/BitfinexKeyValue.cs @@ -1,9 +1,6 @@ using CryptoExchange.Net.Attributes; using CryptoExchange.Net.Converters; using Newtonsoft.Json; -using System; -using System.Collections.Generic; -using System.Text; namespace Bitfinex.Net.Objects.Models { diff --git a/Bitfinex.Net/Objects/Models/BitfinexOrderBook.cs b/Bitfinex.Net/Objects/Models/BitfinexOrderBook.cs index 524241c..44faefe 100644 --- a/Bitfinex.Net/Objects/Models/BitfinexOrderBook.cs +++ b/Bitfinex.Net/Objects/Models/BitfinexOrderBook.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using CryptoExchange.Net.Interfaces; namespace Bitfinex.Net.Objects.Models { diff --git a/Bitfinex.Net/Objects/Models/BitfinexRawFundingOrderBook.cs b/Bitfinex.Net/Objects/Models/BitfinexRawFundingOrderBook.cs index 07c0bb8..a8afa30 100644 --- a/Bitfinex.Net/Objects/Models/BitfinexRawFundingOrderBook.cs +++ b/Bitfinex.Net/Objects/Models/BitfinexRawFundingOrderBook.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using CryptoExchange.Net.Interfaces; namespace Bitfinex.Net.Objects.Models { diff --git a/Bitfinex.Net/Objects/Models/BitfinexRawOrderBook.cs b/Bitfinex.Net/Objects/Models/BitfinexRawOrderBook.cs index 78774cb..b6c58ef 100644 --- a/Bitfinex.Net/Objects/Models/BitfinexRawOrderBook.cs +++ b/Bitfinex.Net/Objects/Models/BitfinexRawOrderBook.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using CryptoExchange.Net.Interfaces; namespace Bitfinex.Net.Objects.Models { diff --git a/Bitfinex.Net/Objects/Models/BitfinexSummary.cs b/Bitfinex.Net/Objects/Models/BitfinexSummary.cs index 6bf1c6b..e6e2458 100644 --- a/Bitfinex.Net/Objects/Models/BitfinexSummary.cs +++ b/Bitfinex.Net/Objects/Models/BitfinexSummary.cs @@ -1,5 +1,4 @@ -using Bitfinex.Net.Objects.Models.V1; -using CryptoExchange.Net.Attributes; +using CryptoExchange.Net.Attributes; using CryptoExchange.Net.Converters; using Newtonsoft.Json; using System.Collections.Generic; diff --git a/Bitfinex.Net/Objects/Models/BitfinexTradeSimple.cs b/Bitfinex.Net/Objects/Models/BitfinexTradeSimple.cs index ffbb9cc..2ae108e 100644 --- a/Bitfinex.Net/Objects/Models/BitfinexTradeSimple.cs +++ b/Bitfinex.Net/Objects/Models/BitfinexTradeSimple.cs @@ -1,5 +1,4 @@ using System; -using Bitfinex.Net.Enums; using CryptoExchange.Net.Converters; using Newtonsoft.Json; diff --git a/Bitfinex.Net/Objects/Models/Socket/BitfinexNotification.cs b/Bitfinex.Net/Objects/Models/Socket/BitfinexNotification.cs index d12e38e..1e7da36 100644 --- a/Bitfinex.Net/Objects/Models/Socket/BitfinexNotification.cs +++ b/Bitfinex.Net/Objects/Models/Socket/BitfinexNotification.cs @@ -2,24 +2,22 @@ using CryptoExchange.Net.Converters; using Newtonsoft.Json; using System; -using System.Collections.Generic; -using System.Text; namespace Bitfinex.Net.Objects.Models.Socket { [JsonConverter(typeof(ArrayConverter))] - public class BitfinexNotification + internal class BitfinexNotification { [ArrayProperty(0)] [JsonConverter(typeof(DateTimeConverter))] public DateTime Timestamp { get; set; } [ArrayProperty(1)] - public string Event { get; set; } + public string Event { get; set; } = string.Empty; [ArrayProperty(4)] [JsonConversion] - public T Data { get; set; } + public T Data { get; set; } = default!; [ArrayProperty(6)] - public string Result { get; set; } + public string Result { get; set; } = string.Empty; [ArrayProperty(7)] public string? ErrorMessage { get; set; } } diff --git a/Bitfinex.Net/Objects/Models/Socket/BitfinexSocketInfo.cs b/Bitfinex.Net/Objects/Models/Socket/BitfinexSocketInfo.cs index 41efff1..e193183 100644 --- a/Bitfinex.Net/Objects/Models/Socket/BitfinexSocketInfo.cs +++ b/Bitfinex.Net/Objects/Models/Socket/BitfinexSocketInfo.cs @@ -9,11 +9,15 @@ internal class BitfinexSocketInfo [JsonProperty("event")] public string Event { get; set; } = string.Empty; [JsonProperty("version")] - public int Version { get; set; } + public int? Version { get; set; } [JsonProperty("serverId")] - public string ServerId { get; set; } = string.Empty; + public string? ServerId { get; set; } = string.Empty; [JsonProperty("platform")] - public BitfinexSocketInfoDetails Platform { get; set; } = null!; + public BitfinexSocketInfoDetails? Platform { get; set; } = null!; + [JsonProperty("code")] + public int? Code { get; set; } + [JsonProperty("msg")] + public string? Message { get; set; } } internal class BitfinexSocketInfoDetails diff --git a/Bitfinex.Net/Objects/Sockets/BitfinexBookRequest.cs b/Bitfinex.Net/Objects/Sockets/BitfinexBookRequest.cs index 8c89bc1..6c8c8ee 100644 --- a/Bitfinex.Net/Objects/Sockets/BitfinexBookRequest.cs +++ b/Bitfinex.Net/Objects/Sockets/BitfinexBookRequest.cs @@ -5,12 +5,12 @@ namespace Bitfinex.Net.Objects.Sockets internal class BitfinexBookRequest: BitfinexRequest { [JsonProperty("prec")] - public string Precision { get; set; } + public string? Precision { get; set; } [JsonProperty("freq")] - public string Frequency { get; set; } + public string? Frequency { get; set; } [JsonProperty("len")] - public string Length { get; set; } + public string? Length { get; set; } [JsonProperty("key")] - public string Key { get; set; } + public string? Key { get; set; } } } diff --git a/Bitfinex.Net/Objects/Sockets/BitfinexConfQuery.cs b/Bitfinex.Net/Objects/Sockets/BitfinexConfQuery.cs new file mode 100644 index 0000000..f0ebf8b --- /dev/null +++ b/Bitfinex.Net/Objects/Sockets/BitfinexConfQuery.cs @@ -0,0 +1,16 @@ +using Bitfinex.Net.Objects.Internal; +using CryptoExchange.Net.Sockets; +using System.Collections.Generic; + +namespace Bitfinex.Net.Objects.Sockets +{ + internal class BitfinexConfQuery : Query + { + public override HashSet ListenerIdentifiers { get; set; } = new HashSet { "conf" }; + + public BitfinexConfQuery(int flags) : base(new BitfinexSocketConfig { Event = "conf", Flags = flags }, false, 1) + { + } + + } +} diff --git a/Bitfinex.Net/Objects/Sockets/BitfinexRequest.cs b/Bitfinex.Net/Objects/Sockets/BitfinexRequest.cs index ea5acc7..ab422e4 100644 --- a/Bitfinex.Net/Objects/Sockets/BitfinexRequest.cs +++ b/Bitfinex.Net/Objects/Sockets/BitfinexRequest.cs @@ -5,9 +5,9 @@ namespace Bitfinex.Net.Objects.Sockets internal class BitfinexRequest { [JsonProperty("event")] - public string Event { get; set; } + public string Event { get; set; } = string.Empty; [JsonProperty("channel")] - public string Channel { get; set; } + public string Channel { get; set; } = string.Empty; [JsonProperty("symbol")] public string? Symbol { get; set; } } diff --git a/Bitfinex.Net/Objects/Sockets/BitfinexResponse.cs b/Bitfinex.Net/Objects/Sockets/BitfinexResponse.cs index e7e9fc4..92cdb88 100644 --- a/Bitfinex.Net/Objects/Sockets/BitfinexResponse.cs +++ b/Bitfinex.Net/Objects/Sockets/BitfinexResponse.cs @@ -5,9 +5,9 @@ namespace Bitfinex.Net.Objects.Sockets internal class BitfinexResponse { [JsonProperty("event")] - public string Event { get; set; } + public string Event { get; set; } = string.Empty; [JsonProperty("channel")] - public string Channel { get; set; } + public string Channel { get; set; } = string.Empty; [JsonProperty("symbol")] public string? Symbol { get; set; } [JsonProperty("pair")] diff --git a/Bitfinex.Net/Objects/Sockets/BitfinexSocketConverter.cs b/Bitfinex.Net/Objects/Sockets/BitfinexSocketConverter.cs deleted file mode 100644 index 535b979..0000000 --- a/Bitfinex.Net/Objects/Sockets/BitfinexSocketConverter.cs +++ /dev/null @@ -1,56 +0,0 @@ -//using Bitfinex.Net.Objects.Internal; -//using CryptoExchange.Net.Converters; -//using CryptoExchange.Net.Interfaces; -//using CryptoExchange.Net.Objects.Sockets; -//using CryptoExchange.Net.Sockets; - -//namespace Bitfinex.Net.Objects.Sockets -//{ -// internal class BitfinexSocketConverter : SocketConverter -// { -// public override MessageInterpreterPipeline InterpreterPipeline { get; } = new MessageInterpreterPipeline -// { -// GetStreamIdentifier = GetStreamIdentifier, -// GetTypeIdentifier = GetTypeIdentifier -// }; - -// private static string? GetStreamIdentifier(IMessageAccessor accessor) -// { -// if (!accessor.IsObject(null)) -// { -// return accessor.GetArrayIntValue(null, 0).ToString(); -// } - -// var evnt = accessor.GetStringValue("event"); -// if (evnt == "info") -// return "info"; - -// var channel = accessor.GetStringValue("channel"); -// var symbol = accessor.GetStringValue("symbol"); -// var prec = accessor.GetStringValue("prec"); -// var freq = accessor.GetStringValue("freq"); -// var len = accessor.GetStringValue("len"); -// var key = accessor.GetStringValue("key"); -// return evnt + channel + symbol + prec + freq + len + key; -// } - -// private static string? GetTypeIdentifier(IMessageAccessor accessor) -// { -// if (accessor.IsObject(null)) -// return null; - -// var topic = accessor.GetArrayStringValue(null, 1); -// if (topic == "hb") -// return "hb"; -// if (topic == "cs") -// return "cs"; - -// var dataIndex = topic == null ? 1 : 2; -// var x = "single"; -// if (accessor.IsArray(new[] { dataIndex, 0 })) -// x = "array"; - -// return x + dataIndex; -// } -// } -//} diff --git a/Bitfinex.Net/Objects/Sockets/BitfinexUpdate.cs b/Bitfinex.Net/Objects/Sockets/BitfinexUpdate.cs index 0a9ff5e..10e58fe 100644 --- a/Bitfinex.Net/Objects/Sockets/BitfinexUpdate.cs +++ b/Bitfinex.Net/Objects/Sockets/BitfinexUpdate.cs @@ -1,5 +1,4 @@ -using Bitfinex.Net.Converters; -using CryptoExchange.Net.Attributes; +using CryptoExchange.Net.Attributes; using CryptoExchange.Net.Converters; using Newtonsoft.Json; @@ -12,7 +11,7 @@ internal class BitfinexUpdate public int ChannelId { get; set; } [ArrayProperty(1)] [JsonConversion] - public T Data { get; set; } + public T Data { get; set; } = default!; } [JsonConverter(typeof(ArrayConverter))] @@ -21,9 +20,9 @@ internal class BitfinexUpdate3 [ArrayProperty(0)] public int ChannelId { get; set; } [ArrayProperty(1)] - public string Topic { get; set; } + public string Topic { get; set; } = string.Empty; [ArrayProperty(2)] [JsonConversion] - public T Data { get; set; } + public T Data { get; set; } = default!; } } diff --git a/Bitfinex.Net/Objects/Sockets/Queries/BitfinexAuthQuery.cs b/Bitfinex.Net/Objects/Sockets/Queries/BitfinexAuthQuery.cs index 3eb4362..4cdc194 100644 --- a/Bitfinex.Net/Objects/Sockets/Queries/BitfinexAuthQuery.cs +++ b/Bitfinex.Net/Objects/Sockets/Queries/BitfinexAuthQuery.cs @@ -1,13 +1,12 @@ using Bitfinex.Net.Objects.Internal; using CryptoExchange.Net.Sockets; using System.Collections.Generic; -using System.Globalization; namespace Bitfinex.Net.Objects.Sockets.Queries { internal class BitfinexAuthQuery : Query { - public override List StreamIdentifiers { get; } = new List { "auth" }; + public override HashSet ListenerIdentifiers { get; set; } = new HashSet { "auth" }; public BitfinexAuthQuery(BitfinexAuthentication authRequest) : base(authRequest, true) { diff --git a/Bitfinex.Net/Objects/Sockets/Queries/BitfinexQuery.cs b/Bitfinex.Net/Objects/Sockets/Queries/BitfinexQuery.cs index 2afc849..4845f72 100644 --- a/Bitfinex.Net/Objects/Sockets/Queries/BitfinexQuery.cs +++ b/Bitfinex.Net/Objects/Sockets/Queries/BitfinexQuery.cs @@ -3,32 +3,37 @@ using CryptoExchange.Net.Objects; using CryptoExchange.Net.Objects.Sockets; using CryptoExchange.Net.Sockets; +using CryptoExchange.Net.Sockets.MessageParsing; +using CryptoExchange.Net.Sockets.MessageParsing.Interfaces; using System; using System.Collections.Generic; -using System.Text; using System.Threading.Tasks; namespace Bitfinex.Net.Objects.Sockets.Queries { internal class BitfinexQuery : Query>> { - public override List StreamIdentifiers { get; } = new List { "0" }; + private static readonly MessagePath _1Path = MessagePath.Get().Index(1); + public override HashSet ListenerIdentifiers { get; set; } = new HashSet { "0" }; public BitfinexQuery(BitfinexSocketQuery request) : base(request, true, 1) { - TypeMapping = new Dictionary - { - { "n-single", typeof(BitfinexSocketEvent>) }, - { "n-array", typeof(BitfinexSocketEvent>) } - }; } - public override Task>>> HandleMessageAsync(SocketConnection connection, DataEvent>>> message) + public override Type? GetMessageType(IMessageAccessor message) { - if (message.Data.TypedData.Data.Result != "SUCCESS") - return Task.FromResult(new CallResult>>(new ServerError(message.Data.TypedData.Data.ErrorMessage))); + if (message.GetValue(_1Path) != "n") + return null; - return Task.FromResult(new CallResult>>((BitfinexSocketEvent>)message.Data.Data)); + return typeof(BitfinexSocketEvent>); + } + + public override Task>>> HandleMessageAsync(SocketConnection connection, DataEvent>> message) + { + if (message.Data.Data.Result != "SUCCESS") + return Task.FromResult(new CallResult>>(new ServerError(message.Data.Data.ErrorMessage!))); + + return Task.FromResult(new CallResult>>(message.Data)); } } } diff --git a/Bitfinex.Net/Objects/Sockets/Queries/BitfinexSubQuery.cs b/Bitfinex.Net/Objects/Sockets/Queries/BitfinexSubQuery.cs index b2693aa..aafe11c 100644 --- a/Bitfinex.Net/Objects/Sockets/Queries/BitfinexSubQuery.cs +++ b/Bitfinex.Net/Objects/Sockets/Queries/BitfinexSubQuery.cs @@ -1,15 +1,13 @@ using CryptoExchange.Net.Sockets; -using System; using System.Collections.Generic; -using System.Text; namespace Bitfinex.Net.Objects.Sockets.Queries { internal class BitfinexSubQuery : Query { - public override List StreamIdentifiers { get; } + public override HashSet ListenerIdentifiers { get; set; } - public BitfinexSubQuery(string evnt, string channel, string symbol, string precision, string frequency, string length, string key) : base(new BitfinexBookRequest + public BitfinexSubQuery(string evnt, string channel, string? symbol, string? precision, string? frequency, string? length, string? key) : base(new BitfinexBookRequest { Channel = channel, Symbol = symbol, @@ -23,7 +21,7 @@ public BitfinexSubQuery(string evnt, string channel, string symbol, string preci if (evnt == "subscribe" || evnt == "unsubscribe") evnt += "d"; - StreamIdentifiers = new List { evnt + channel + symbol + precision + frequency + length + key }; + ListenerIdentifiers = new HashSet { evnt + channel + symbol + precision + frequency + length + key }; } } } diff --git a/Bitfinex.Net/Objects/Sockets/Queries/BitfinexUnsubQuery.cs b/Bitfinex.Net/Objects/Sockets/Queries/BitfinexUnsubQuery.cs index e4a660d..d38dc2e 100644 --- a/Bitfinex.Net/Objects/Sockets/Queries/BitfinexUnsubQuery.cs +++ b/Bitfinex.Net/Objects/Sockets/Queries/BitfinexUnsubQuery.cs @@ -1,18 +1,16 @@ using Bitfinex.Net.Objects.Internal; using CryptoExchange.Net.Sockets; -using System; using System.Collections.Generic; -using System.Text; namespace Bitfinex.Net.Objects.Sockets.Queries { - internal class BitfinexUnsubQuery : Query + internal class BitfinexUnsubQuery : Query { - public override List StreamIdentifiers { get; } + public override HashSet ListenerIdentifiers { get; set; } public BitfinexUnsubQuery(int channelId) : base(new BitfinexUnsubscribeRequest(channelId), false, 1) { - StreamIdentifiers = new List { channelId.ToString() + "unsubscribed" }; + ListenerIdentifiers = new HashSet { channelId.ToString() + "unsubscribed" }; } } } diff --git a/Bitfinex.Net/Objects/Sockets/Subscriptions/BitfinexInfoSubscription.cs b/Bitfinex.Net/Objects/Sockets/Subscriptions/BitfinexInfoSubscription.cs index 141c181..0047b7b 100644 --- a/Bitfinex.Net/Objects/Sockets/Subscriptions/BitfinexInfoSubscription.cs +++ b/Bitfinex.Net/Objects/Sockets/Subscriptions/BitfinexInfoSubscription.cs @@ -3,7 +3,6 @@ using CryptoExchange.Net.Objects.Sockets; using CryptoExchange.Net.Sockets; using Microsoft.Extensions.Logging; -using System; using System.Collections.Generic; using System.Threading.Tasks; @@ -11,12 +10,43 @@ namespace Bitfinex.Net.Objects.Sockets.Subscriptions { internal class BitfinexInfoSubscription : SystemSubscription { - public override List StreamIdentifiers { get; } = new List { "info" }; + public override HashSet ListenerIdentifiers { get; set; } = new HashSet { "info" }; public BitfinexInfoSubscription(ILogger logger) : base(logger, false) { } - public override Task HandleMessageAsync(SocketConnection connection, DataEvent> message) => Task.FromResult(new CallResult(null)); + public override Task HandleMessageAsync(SocketConnection connection, DataEvent message) + { + if (message.Data.Code == null) + { + // welcome event, send a config message for receiving checsum updates for order book subscriptions + _ = connection.SendAndWaitQueryAsync(new BitfinexConfQuery(131072)); + return Task.FromResult(new CallResult(null)); + } + + var code = message.Data.Code; + switch (code) + { + case 20051: + _logger.Log(LogLevel.Information, $"[Sckt {connection.SocketId}] code {code} received, reconnecting socket"); + connection.PausedActivity = true; // Prevent new operations to be send + _ = connection.TriggerReconnectAsync(); + break; + case 20060: + _logger.Log(LogLevel.Information, $"[Sckt {connection.SocketId}] code {code} received, entering maintenance mode"); + connection.PausedActivity = true; + break; + case 20061: + _logger.Log(LogLevel.Information, $"[Sckt {connection.SocketId} ] code {code} received, leaving maintenance mode. Reconnecting/Resubscribing socket."); + _ = connection.TriggerReconnectAsync(); // Closing it via socket will automatically reconnect + break; + default: + _logger.Log(LogLevel.Warning, $"[Sckt {connection.SocketId}] unknown info code received: {code}"); + break; + } + + return Task.FromResult(new CallResult(null)); + } } } diff --git a/Bitfinex.Net/Objects/Sockets/Subscriptions/BitfinexSubscription.cs b/Bitfinex.Net/Objects/Sockets/Subscriptions/BitfinexSubscription.cs index 19b4943..1d3153b 100644 --- a/Bitfinex.Net/Objects/Sockets/Subscriptions/BitfinexSubscription.cs +++ b/Bitfinex.Net/Objects/Sockets/Subscriptions/BitfinexSubscription.cs @@ -5,6 +5,8 @@ using CryptoExchange.Net.Objects; using CryptoExchange.Net.Objects.Sockets; using CryptoExchange.Net.Sockets; +using CryptoExchange.Net.Sockets.MessageParsing; +using CryptoExchange.Net.Sockets.MessageParsing.Interfaces; using Microsoft.Extensions.Logging; using Newtonsoft.Json; using System; @@ -15,6 +17,11 @@ namespace Bitfinex.Net.Objects.Sockets.Subscriptions { internal class BitfinexSubscription : Subscription { + private static readonly MessagePath _1Path = MessagePath.Get().Index(1); + private static readonly MessagePath _10Path = MessagePath.Get().Index(1).Index(0); + private static readonly MessagePath _2Path = MessagePath.Get().Index(2); + private static readonly MessagePath _20Path = MessagePath.Get().Index(2).Index(0); + private string _channel; private string? _symbol; private string? _precision; @@ -26,21 +33,7 @@ internal class BitfinexSubscription : Subscription>> _handler; private Action>? _checksumHandler; - private List _streamIdentifiers; - - public override Dictionary TypeMapping { get; } = new Dictionary - { - { "cs-single", typeof(BitfinexChecksum) }, - { "hb-single", typeof(BitfinexUpdate) }, - { "-single", typeof(BitfinexUpdate) }, - { "-array", typeof(BitfinexUpdate>) }, - { "te-single", typeof(BitfinexUpdate3) }, - { "te-array", typeof(BitfinexUpdate3>) }, - { "tu-single", typeof(BitfinexUpdate3) }, - { "tu-array", typeof(BitfinexUpdate3>) }, - }; - - public override List StreamIdentifiers => _streamIdentifiers; + public override HashSet ListenerIdentifiers { get; set; } = new HashSet(); public BitfinexSubscription(ILogger logger, string channel, @@ -64,43 +57,57 @@ public BitfinexSubscription(ILogger logger, _length = length?.ToString(); } - public override void HandleSubQueryResponse(ParsedMessage message) + /// + public override Type? GetMessageType(IMessageAccessor message) { - // TODO this doesn't update when reconnecting/subscribing - _channelId = message.TypedData.ChannelId.Value; + var identifier = message.GetValue(_1Path); + + if (identifier == "cs") + return typeof(BitfinexChecksum); + + if (identifier == "hb") + return typeof(BitfinexUpdate); + + if (identifier == null) + { + var nodeType1 = message.GetNodeType(_10Path); + return nodeType1 == NodeType.Array ? typeof(BitfinexUpdate>) : typeof(BitfinexUpdate); + } + + var nodeType = message.GetNodeType(_20Path); + return nodeType == NodeType.Array ? typeof(BitfinexUpdate3>) : typeof(BitfinexUpdate3); + } + + public override void HandleSubQueryResponse(BitfinexResponse message) + { + _channelId = message.ChannelId!.Value; _firstUpdate = true; - // Doesn't work, as the subscription is immediately added along with the identifiers - // Would maybe be better to wait with adding the subscription untill it is confirmed? - _streamIdentifiers = new List { _channelId.ToString() }; + ListenerIdentifiers = new HashSet { _channelId.ToString() }; } - public override BaseQuery? GetSubQuery(SocketConnection connection) + public override Query? GetSubQuery(SocketConnection connection) { return new BitfinexSubQuery("subscribe", _channel, _symbol, _precision, _frequency, _length, _key); } - public override BaseQuery? GetUnsubQuery() + public override Query? GetUnsubQuery() { return new BitfinexUnsubQuery(_channelId); } - public override Task DoHandleMessageAsync(SocketConnection connection, DataEvent message) + public override Task DoHandleMessageAsync(SocketConnection connection, DataEvent message) { - // var typedObject = message.As(TypeMapping[message.Data.TypeIdentifier]) - - - if (message.Data.TypeIdentifier == "hb") - return Task.FromResult(new CallResult(null)); - else if (message.Data.TypeIdentifier == "cs") - _checksumHandler?.Invoke(message.As(((BitfinexChecksum)message.Data.Data).Checksum)); - else if (message.Data.TypeIdentifier == "single1") - _handler.Invoke(message.As>(new[] { ((BitfinexUpdate)message.Data.Data).Data }, _symbol, SocketUpdateType.Update)); - else if (message.Data.TypeIdentifier == "single2") - _handler.Invoke(message.As>(new[] { ((BitfinexUpdate3)message.Data.Data).Data }, _symbol, SocketUpdateType.Update)); - else if (message.Data.TypeIdentifier == "array1") - _handler.Invoke(message.As(((BitfinexUpdate>)message.Data.Data).Data, _symbol, _firstUpdate ? SocketUpdateType.Snapshot : SocketUpdateType.Update)); - else if (message.Data.TypeIdentifier == "array2") - _handler.Invoke(message.As(((BitfinexUpdate3>)message.Data.Data).Data, _symbol, _firstUpdate ? SocketUpdateType.Snapshot : SocketUpdateType.Update)); + if (message.Data is BitfinexChecksum checksum) + _checksumHandler?.Invoke(message.As(checksum.Checksum, _symbol)); + else if (message.Data is BitfinexUpdate> arrayUpdate) + _handler?.Invoke(message.As(arrayUpdate.Data, _symbol, _firstUpdate ? SocketUpdateType.Snapshot : SocketUpdateType.Update)); + else if (message.Data is BitfinexUpdate singleUpdate) + _handler?.Invoke(message.As>(new[] { singleUpdate.Data }, _symbol, _firstUpdate ? SocketUpdateType.Snapshot : SocketUpdateType.Update)); + else if (message.Data is BitfinexUpdate3> array3Update) + _handler?.Invoke(message.As(array3Update.Data, _symbol, _firstUpdate ? SocketUpdateType.Snapshot : SocketUpdateType.Update)); + else if (message.Data is BitfinexUpdate3 single3Update) + _handler?.Invoke(message.As>(new[] { single3Update.Data }, _symbol, _firstUpdate ? SocketUpdateType.Snapshot : SocketUpdateType.Update)); + _firstUpdate = false; return Task.FromResult(new CallResult(null)); } } diff --git a/Bitfinex.Net/Objects/Sockets/Subscriptions/BitfinexUserSubscription.cs b/Bitfinex.Net/Objects/Sockets/Subscriptions/BitfinexUserSubscription.cs index b47194b..c04c30c 100644 --- a/Bitfinex.Net/Objects/Sockets/Subscriptions/BitfinexUserSubscription.cs +++ b/Bitfinex.Net/Objects/Sockets/Subscriptions/BitfinexUserSubscription.cs @@ -1,93 +1,113 @@ -using Bitfinex.Net.Converters; -using Bitfinex.Net.Enums; -using Bitfinex.Net.Objects.Internal; -using Bitfinex.Net.Objects.Models; +using Bitfinex.Net.Objects.Models; using Bitfinex.Net.Objects.Models.Socket; -using Bitfinex.Net.Objects.Sockets.Queries; using CryptoExchange.Net.Objects; using CryptoExchange.Net.Objects.Sockets; using CryptoExchange.Net.Sockets; +using CryptoExchange.Net.Sockets.MessageParsing; +using CryptoExchange.Net.Sockets.MessageParsing.Interfaces; using Microsoft.Extensions.Logging; using System; using System.Collections.Generic; -using System.Diagnostics; using System.Threading.Tasks; namespace Bitfinex.Net.Objects.Sockets.Subscriptions { internal class BitfinexUserSubscription : Subscription { - private readonly Action>> _positionHandler; - private readonly Action>> _walletHandler; - private readonly Action>> _orderHandler; - private readonly Action>> _fundingOfferHandler; - private readonly Action>> _fundingCreditHandler; - private readonly Action>> _fundingLoanHandler; - private readonly Action> _balanceHandler; - private readonly Action> _tradeHandler; - private readonly Action> _fundingTradeHandler; - private readonly Action> _marginInfoHandler; // TODO - private readonly Action> _fundingInfoHandler; - - public override Dictionary TypeMapping { get; } = new Dictionary + private static readonly MessagePath _messagePath = MessagePath.Get().Index(1); + private static readonly MessagePath _marginInfoPath = MessagePath.Get().Index(2).Index(0); + + private readonly Action>>? _positionHandler; + private readonly Action>>? _walletHandler; + private readonly Action>>? _orderHandler; + private readonly Action>>? _fundingOfferHandler; + private readonly Action>>? _fundingCreditHandler; + private readonly Action>>? _fundingLoanHandler; + private readonly Action>? _balanceHandler; + private readonly Action>? _tradeHandler; + private readonly Action>? _fundingTradeHandler; + private readonly Action>? _marginBaseHandler; + private readonly Action>? _marginSymbolHandler; + private readonly Action>? _fundingInfoHandler; + + /// + public override Type? GetMessageType(IMessageAccessor message) { - { "hb-single", typeof(BitfinexSocketEvent) }, + var identifier = message.GetValue(_messagePath); - { "ps-array", typeof(BitfinexSocketEvent>) }, - { "pn-single", typeof(BitfinexSocketEvent) }, - { "pu-single", typeof(BitfinexSocketEvent) }, - { "pc-single", typeof(BitfinexSocketEvent) }, + if (identifier == "hb") + return typeof(BitfinexSocketEvent); - { "bu-single", typeof(BitfinexSocketEvent) }, + if (identifier == "ps") + return typeof(BitfinexSocketEvent>); + if (identifier == "pn" || identifier == "pu" || identifier == "pc") + return typeof(BitfinexSocketEvent); - { "miu-single", typeof(BitfinexSocketEvent) }, + if (identifier == "bu") + return typeof(BitfinexSocketEvent); - { "fiu-single", typeof(BitfinexSocketEvent) }, - - { "ws-array", typeof(BitfinexSocketEvent>) }, - { "wu-single", typeof(BitfinexSocketEvent) }, - - { "os-array", typeof(BitfinexSocketEvent>) }, - { "on-single", typeof(BitfinexSocketEvent) }, - { "ou-single", typeof(BitfinexSocketEvent) }, - { "oc-single", typeof(BitfinexSocketEvent) }, - - { "te-single", typeof(BitfinexSocketEvent) }, - { "tu-single", typeof(BitfinexSocketEvent) }, - - { "fte-single", typeof(BitfinexSocketEvent) }, - { "ftu-single", typeof(BitfinexSocketEvent) }, - - { "fos-array", typeof(BitfinexSocketEvent>) }, - { "fon-single", typeof(BitfinexSocketEvent) }, - { "fou-single", typeof(BitfinexSocketEvent) }, - { "foc-single", typeof(BitfinexSocketEvent) }, - - { "fcs-array", typeof(BitfinexSocketEvent>) }, - { "fcc-single", typeof(BitfinexSocketEvent) }, - { "fcn-single", typeof(BitfinexSocketEvent) }, - { "fcu-single", typeof(BitfinexSocketEvent) }, - - { "fls-array", typeof(BitfinexSocketEvent>) }, - { "flc-single", typeof(BitfinexSocketEvent) }, - { "fln-single", typeof(BitfinexSocketEvent) }, - { "flu-single", typeof(BitfinexSocketEvent) }, - }; + if (identifier == "miu") + { + var marginInfoType = message.GetValue(_marginInfoPath); + return marginInfoType == "base" ? typeof(BitfinexSocketEvent) : typeof(BitfinexSocketEvent); + } + + if (identifier == "fiu") + return typeof(BitfinexSocketEvent); + + if (identifier == "ws") + return typeof(BitfinexSocketEvent>); + if (identifier == "wu") + return typeof(BitfinexSocketEvent); + + if (identifier == "os") + return typeof(BitfinexSocketEvent>); + if (identifier == "on" || identifier == "ou" || identifier == "oc") + return typeof(BitfinexSocketEvent); + + if (identifier == "te") + return typeof(BitfinexSocketEvent); + if (identifier == "tu") + return typeof(BitfinexSocketEvent); + + if (identifier == "fte") + return typeof(BitfinexSocketEvent); + if (identifier == "ftu") + return typeof(BitfinexSocketEvent); + + if (identifier == "fos") + return typeof(BitfinexSocketEvent>); + if (identifier == "fon" || identifier == "fou" || identifier == "foc") + return typeof(BitfinexSocketEvent); + + if (identifier == "fcs") + return typeof(BitfinexSocketEvent>); + if (identifier == "fcn" || identifier == "fcu" || identifier == "fcc") + return typeof(BitfinexSocketEvent); + + if (identifier == "fls") + return typeof(BitfinexSocketEvent>); + if (identifier == "fln" || identifier == "flu" || identifier == "flc") + return typeof(BitfinexSocketEvent); + + return null; + } - public override List StreamIdentifiers { get; } = new List() { "0" }; + public override HashSet ListenerIdentifiers { get; set; } = new HashSet() { "0" }; public BitfinexUserSubscription(ILogger logger, - Action>> positionHandler, - Action>> walletHandler, - Action>> orderHandler, - Action>> fundingOfferHandler, - Action>> fundingCreditHandler, - Action>> fundingLoanHandler, - Action> balanceHandler, - Action> tradeHandler, - Action> fundingTradeHandler, - Action> fundingInfoHandler - //Action> marginInfoHandler + Action>>? positionHandler, + Action>>? walletHandler, + Action>>? orderHandler, + Action>>? fundingOfferHandler, + Action>>? fundingCreditHandler, + Action>>? fundingLoanHandler, + Action>? balanceHandler, + Action>? tradeHandler, + Action>? fundingTradeHandler, + Action>? fundingInfoHandler, + Action>? marginBaseHandler, + Action>? marginSymbolHandler ) : base(logger, true) { @@ -101,79 +121,62 @@ Action> fundingInfoHandler _tradeHandler = tradeHandler; _fundingTradeHandler = fundingTradeHandler; _fundingInfoHandler = fundingInfoHandler; + _marginBaseHandler = marginBaseHandler; + _marginSymbolHandler = marginSymbolHandler; } - public override BaseQuery? GetSubQuery(SocketConnection connection) => null; + public override Query? GetSubQuery(SocketConnection connection) => null; - public override BaseQuery? GetUnsubQuery() => null; + public override Query? GetUnsubQuery() => null; - public override Task DoHandleMessageAsync(SocketConnection connection, DataEvent message) + public override Task DoHandleMessageAsync(SocketConnection connection, DataEvent message) { - Debug.WriteLine($"{message.Data.Data.GetType()}; {message.Data.OriginalData}"); - - return message.Data.TypeIdentifier switch - { - "hb-single" => Task.FromResult(new CallResult(null)), - - "ps-array" => InvokeAndReturnSnapshot(_positionHandler, message), - "pn-single" => InvokeAndReturnUpdate(_positionHandler, message), - "pu-single" => InvokeAndReturnUpdate(_positionHandler, message), - "pc-single" => InvokeAndReturnUpdate(_positionHandler, message), - - "bu-single" => InvokeAndReturnSingleUpdate(_balanceHandler, message), - - "fiu-single" => InvokeAndReturnSingleUpdate(_fundingInfoHandler, message), - - "ws-array" => InvokeAndReturnSnapshot(_walletHandler, message), - "wu-single" => InvokeAndReturnUpdate(_walletHandler, message), - - "os-array" => InvokeAndReturnSnapshot(_orderHandler, message), - "on-single" => InvokeAndReturnUpdate(_orderHandler, message), - "ou-single" => InvokeAndReturnUpdate(_orderHandler, message), - "oc-single" => InvokeAndReturnUpdate(_orderHandler, message), + if (message.Data is BitfinexSocketEvent> positionSnapshot) + _positionHandler?.Invoke(message.As>(positionSnapshot.Data)); + else if (message.Data is BitfinexSocketEvent positionUpdate) + _positionHandler?.Invoke(message.As>(new[] { positionUpdate.Data })); + + else if (message.Data is BitfinexSocketEvent> loanSnapshot) + _fundingLoanHandler?.Invoke(message.As>(loanSnapshot.Data)); + else if (message.Data is BitfinexSocketEvent loanUpdate) + _fundingLoanHandler?.Invoke(message.As>(new[] { loanUpdate.Data })); + + else if (message.Data is BitfinexSocketEvent> creditSnapshot) + _fundingCreditHandler?.Invoke(message.As>(creditSnapshot.Data)); + else if (message.Data is BitfinexSocketEvent creditUpdate) + _fundingCreditHandler?.Invoke(message.As>(new[] { creditUpdate.Data })); + + else if (message.Data is BitfinexSocketEvent> offerSnapshot) + _fundingOfferHandler?.Invoke(message.As>(offerSnapshot.Data)); + else if (message.Data is BitfinexSocketEvent offerUpdate) + _fundingOfferHandler?.Invoke(message.As>(new[] { offerUpdate.Data })); + + else if (message.Data is BitfinexSocketEvent> orderSnapshot) + _orderHandler?.Invoke(message.As>(orderSnapshot.Data)); + else if (message.Data is BitfinexSocketEvent orderUpdate) + _orderHandler?.Invoke(message.As>(new[] { orderUpdate.Data })); + + else if (message.Data is BitfinexSocketEvent fundingTrade) + _fundingTradeHandler?.Invoke(message.As(fundingTrade.Data)); + + else if (message.Data is BitfinexSocketEvent trade) + _tradeHandler?.Invoke(message.As(trade.Data)); + + else if (message.Data is BitfinexSocketEvent> walletSnapshot) + _walletHandler?.Invoke(message.As>(walletSnapshot.Data)); + else if (message.Data is BitfinexSocketEvent walletUpdate) + _walletHandler?.Invoke(message.As>(new[] { walletUpdate.Data })); + + else if (message.Data is BitfinexSocketEvent balanceUpdate) + _balanceHandler?.Invoke(message.As(balanceUpdate.Data)); + else if (message.Data is BitfinexSocketEvent fundingInfoUpdate) + _fundingInfoHandler?.Invoke(message.As(fundingInfoUpdate.Data)); + + else if (message.Data is BitfinexSocketEvent marginBaseUpdate) + _marginBaseHandler?.Invoke(message.As(marginBaseUpdate.Data)); + else if (message.Data is BitfinexSocketEvent marginSymbolUpdate) + _marginSymbolHandler?.Invoke(message.As(marginSymbolUpdate.Data)); - "te-single" => InvokeAndReturnSingleUpdate(_tradeHandler, message), - "tu-single" => InvokeAndReturnSingleUpdate(_tradeHandler, message), - - "fte-single" => InvokeAndReturnSingleUpdate(_fundingTradeHandler, message), - "ftu-single" => InvokeAndReturnSingleUpdate(_fundingTradeHandler, message), - - "fos-array" => InvokeAndReturnSnapshot(_fundingOfferHandler, message), - "fon-single" => InvokeAndReturnUpdate(_fundingOfferHandler, message), - "fou-single" => InvokeAndReturnUpdate(_fundingOfferHandler, message), - "foc-single" => InvokeAndReturnUpdate(_fundingOfferHandler, message), - - "fcs-array" => InvokeAndReturnSnapshot(_fundingCreditHandler, message), - "fcn-single" => InvokeAndReturnUpdate(_fundingCreditHandler, message), - "fcu-single" => InvokeAndReturnUpdate(_fundingCreditHandler, message), - "fcc-single" => InvokeAndReturnUpdate(_fundingCreditHandler, message), - - "fls-array" => InvokeAndReturnSnapshot(_fundingLoanHandler, message), - "fln-single" => InvokeAndReturnUpdate(_fundingLoanHandler, message), - "flu-single" => InvokeAndReturnUpdate(_fundingLoanHandler, message), - "flc-single" => InvokeAndReturnUpdate(_fundingLoanHandler, message), - _ => throw new NotImplementedException() - }; - } - - private Task InvokeAndReturnSnapshot(Action>> handler, DataEvent message) - { - var data = (BitfinexSocketEvent>)message.Data.Data; - handler?.Invoke(message.As>(data.Data, null, SocketUpdateType.Snapshot)); - return Task.FromResult(new CallResult(null)); - } - - private Task InvokeAndReturnUpdate(Action>> handler, DataEvent message) - { - var data = (BitfinexSocketEvent)message.Data.Data; - handler?.Invoke(message.As>(new[] { data.Data }, null, SocketUpdateType.Update)); - return Task.FromResult(new CallResult(null)); - } - - private Task InvokeAndReturnSingleUpdate(Action> handler, DataEvent message) - { - var data = (BitfinexSocketEvent)message.Data.Data; - handler?.Invoke(message.As(data.Data, null, SocketUpdateType.Update)); return Task.FromResult(new CallResult(null)); } } diff --git a/Bitfinex.Net/SymbolOrderBooks/BitfinexSymbolOrderBook.cs b/Bitfinex.Net/SymbolOrderBooks/BitfinexSymbolOrderBook.cs index 0c95cf2..7f4917f 100644 --- a/Bitfinex.Net/SymbolOrderBooks/BitfinexSymbolOrderBook.cs +++ b/Bitfinex.Net/SymbolOrderBooks/BitfinexSymbolOrderBook.cs @@ -7,14 +7,12 @@ using Bitfinex.Net.Clients; using Bitfinex.Net.Enums; using Bitfinex.Net.Interfaces.Clients; -using Bitfinex.Net.Objects; using Bitfinex.Net.Objects.Models; using Bitfinex.Net.Objects.Options; using CryptoExchange.Net.Interfaces; using CryptoExchange.Net.Objects; using CryptoExchange.Net.Objects.Sockets; using CryptoExchange.Net.OrderBook; -using CryptoExchange.Net.Sockets; using Force.Crc32; using Microsoft.Extensions.Logging; @@ -72,24 +70,23 @@ public BitfinexSymbolOrderBook(string symbol, /// protected override async Task> DoStartAsync(CancellationToken ct) { - return new CallResult(new ServerError(null)); - //if(_precision == Precision.R0) - // throw new ArgumentException("Invalid precision: R0"); + if (_precision == Precision.R0) + throw new ArgumentException("Invalid precision: R0"); - //var result = await _socketClient.SpotApi.SubscribeToOrderBookUpdatesAsync(Symbol, _precision, Frequency.Realtime, Levels!.Value, ProcessUpdate, ProcessChecksum).ConfigureAwait(false); - //if (!result) - // return result; + var result = await _socketClient.SpotApi.SubscribeToOrderBookUpdatesAsync(Symbol, _precision, Frequency.Realtime, Levels!.Value, ProcessUpdate, ProcessChecksum).ConfigureAwait(false); + if (!result) + return result; - //if (ct.IsCancellationRequested) - //{ - // await result.Data.CloseAsync().ConfigureAwait(false); - // return result.AsError(new CancellationRequestedError()); - //} + if (ct.IsCancellationRequested) + { + await result.Data.CloseAsync().ConfigureAwait(false); + return result.AsError(new CancellationRequestedError()); + } - //Status = OrderBookStatus.Syncing; - - //var setResult = await WaitForSetOrderBookAsync(_initialDataTimeout, ct).ConfigureAwait(false); - //return setResult ? result : new CallResult(setResult.Error!); + Status = OrderBookStatus.Syncing; + + var setResult = await WaitForSetOrderBookAsync(_initialDataTimeout, ct).ConfigureAwait(false); + return setResult ? result : new CallResult(setResult.Error!); } /// @@ -138,7 +135,9 @@ private void ProcessUpdate(DataEvent> data) askEntries.Add(entry); } else + { bidEntries.Add(entry); + } } } @@ -175,7 +174,9 @@ protected override bool DoChecksum(int checksum) checksumValues.Add(bid.RawQuantity); } else + { _logger.Log(LogLevel.Trace, $"Skipping checksum bid level {i}, no data"); + } if (_asks.Count > i) { @@ -184,7 +185,9 @@ protected override bool DoChecksum(int checksum) checksumValues.Add(ask.RawQuantity); } else + { _logger.Log(LogLevel.Trace, $"Skipping checksum ask level {i}, no data"); + } } var checksumString = string.Join(":", checksumValues); var ourChecksumUtf = (int)Crc32Algorithm.Compute(Encoding.UTF8.GetBytes(checksumString)); From 8c9812cb02821ff9c30822acc5d6d3a2351a0467 Mon Sep 17 00:00:00 2001 From: JKorf Date: Fri, 2 Feb 2024 16:54:59 +0100 Subject: [PATCH 07/25] wip --- Bitfinex.Net/Bitfinex.Net.xml | 5 ----- Bitfinex.Net/BitfinexHelpers.cs | 3 ++- Bitfinex.Net/Clients/BitfinexRestClient.cs | 10 ++-------- Bitfinex.Net/CryptoExchangeClientExtensions.cs | 14 ++++++++++++++ 4 files changed, 18 insertions(+), 14 deletions(-) create mode 100644 Bitfinex.Net/CryptoExchangeClientExtensions.cs diff --git a/Bitfinex.Net/Bitfinex.Net.xml b/Bitfinex.Net/Bitfinex.Net.xml index 4eaf64e..393ad3c 100644 --- a/Bitfinex.Net/Bitfinex.Net.xml +++ b/Bitfinex.Net/Bitfinex.Net.xml @@ -81,11 +81,6 @@ Option configuration delegate - - - Create a new instance of the BitfinexRestClient using provided options - - Create a new instance of the BitfinexRestClient using provided options diff --git a/Bitfinex.Net/BitfinexHelpers.cs b/Bitfinex.Net/BitfinexHelpers.cs index e0e0ab7..c3b92f2 100644 --- a/Bitfinex.Net/BitfinexHelpers.cs +++ b/Bitfinex.Net/BitfinexHelpers.cs @@ -8,6 +8,7 @@ using System.Text.RegularExpressions; using Bitfinex.Net.Interfaces; using Bitfinex.Net.SymbolOrderBooks; +using CryptoExchange.Net.Clients; namespace Bitfinex.Net { @@ -57,8 +58,8 @@ public static IServiceCollection AddBitfinex( return handler; }); + services.AddTransient(); services.AddSingleton(); - services.AddTransient(); services.AddTransient(x => x.GetRequiredService().SpotApi.CommonSpotClient); if (socketClientLifeTime == null) services.AddSingleton(); diff --git a/Bitfinex.Net/Clients/BitfinexRestClient.cs b/Bitfinex.Net/Clients/BitfinexRestClient.cs index 8fdce4d..b551086 100644 --- a/Bitfinex.Net/Clients/BitfinexRestClient.cs +++ b/Bitfinex.Net/Clients/BitfinexRestClient.cs @@ -9,6 +9,7 @@ using Microsoft.Extensions.Logging; using System.Net.Http; using Bitfinex.Net.Objects.Options; +using Microsoft.Extensions.DependencyInjection; namespace Bitfinex.Net.Clients { @@ -29,14 +30,7 @@ public class BitfinexRestClient : BaseRestClient, IBitfinexRestClient /// Create a new instance of the BitfinexRestClient using provided options /// /// Option configuration delegate - public BitfinexRestClient(Action optionsDelegate) : this(null, null, optionsDelegate) - { - } - - /// - /// Create a new instance of the BitfinexRestClient using provided options - /// - public BitfinexRestClient(ILoggerFactory? loggerFactory = null, HttpClient? httpClient = null) : this(httpClient, loggerFactory, null) + public BitfinexRestClient(Action? optionsDelegate = null) : this(null, null, optionsDelegate) { } diff --git a/Bitfinex.Net/CryptoExchangeClientExtensions.cs b/Bitfinex.Net/CryptoExchangeClientExtensions.cs new file mode 100644 index 0000000..1900df1 --- /dev/null +++ b/Bitfinex.Net/CryptoExchangeClientExtensions.cs @@ -0,0 +1,14 @@ +using Bitfinex.Net.Clients; +using Bitfinex.Net.Interfaces.Clients; +using CryptoExchange.Net.Interfaces.CommonClients; +using System; +using System.Collections.Generic; +using System.Text; + +namespace CryptoExchange.Net.Clients +{ + public static class CryptoExchangeClientExtensions + { + public static IBitfinexRestClient Bitfinex(this ICryptoExchangeClient baseClient) => baseClient.TryGet() ?? new BitfinexRestClient(); + } +} From ac300d7e0d5752684ef5a5e6e13749587df28d47 Mon Sep 17 00:00:00 2001 From: JKorf Date: Sat, 3 Feb 2024 16:23:14 +0100 Subject: [PATCH 08/25] wip --- .../BitfinexSocketClientTests.cs | 15 ++++--- Bitfinex.Net/Bitfinex.Net.xml | 19 ++++++++ Bitfinex.Net/BitfinexHelpers.cs | 2 +- .../SpotApi/BitfinexSocketClientSpotApi.cs | 26 ++++++----- .../Converters/OrderBookEntryConverter.cs | 7 ++- Bitfinex.Net/CryptoClientExtensions.cs | 29 +++++++++++++ .../CryptoExchangeClientExtensions.cs | 14 ------ .../SpotApi/IBitfinexSocketClientSpotApi.cs | 24 +++++------ .../BitfinexAuthenticationResponse.cs | 43 ------------------- .../Objects/Models/BitfinexOrderBookEntry.cs | 5 ++- .../Objects/Sockets/BitfinexResponse.cs | 4 ++ .../Objects/Sockets/BitfinexUpdate.cs | 2 +- .../Sockets/Queries/BitfinexAuthQuery.cs | 11 +++++ .../Sockets/Queries/BitfinexSubQuery.cs | 19 +++++++- .../Sockets/Queries/BitfinexUnsubQuery.cs | 5 ++- .../Subscriptions/BitfinexSubscription.cs | 7 ++- 16 files changed, 130 insertions(+), 102 deletions(-) create mode 100644 Bitfinex.Net/CryptoClientExtensions.cs delete mode 100644 Bitfinex.Net/CryptoExchangeClientExtensions.cs delete mode 100644 Bitfinex.Net/Objects/Internal/BitfinexAuthenticationResponse.cs diff --git a/Bitfinex.Net.UnitTests/BitfinexSocketClientTests.cs b/Bitfinex.Net.UnitTests/BitfinexSocketClientTests.cs index 45c7887..d17e81c 100644 --- a/Bitfinex.Net.UnitTests/BitfinexSocketClientTests.cs +++ b/Bitfinex.Net.UnitTests/BitfinexSocketClientTests.cs @@ -18,6 +18,7 @@ using Bitfinex.Net.Objects.Models; using Bitfinex.Net.Objects.Models.Socket; using System.Diagnostics; +using Bitfinex.Net.Objects.Sockets; namespace Bitfinex.Net.UnitTests { @@ -94,7 +95,7 @@ public async Task SubscribingToBookUpdates_Should_TriggerWithBookUpdate(Precisio }; await socket.InvokeMessage(subResponse); subTask.Wait(5000); - BitfinexOrderBookEntry[] expected = new[] { new BitfinexOrderBookEntry() }; + BitfinexOrderBookEntry[] expected = new[] { new BitfinexOrderBookEntry() { RawPrice = "1", RawQuantity = "2", Count = 3, Price = 1, Quantity = 2 } }; // act await socket.InvokeMessage($"[1, {JsonConvert.SerializeObject(expected)}]"); @@ -579,7 +580,7 @@ public async Task PlacingAnOrder_Should_SucceedIfSuccessResponse() // act var placeTask = client.SpotApi.PlaceOrderAsync(OrderSide.Buy, OrderType.ExchangeLimit, "tBTCUSD", 1, price: 1, clientOrderId: 1234); - await socket.InvokeMessage(new BitfinexAuthenticationResponse() { Event = "auth", Status = "OK" }); + await socket.InvokeMessage(new BitfinexResponse() { Event = "auth", Message = "OK" }); Thread.Sleep(100); await socket.InvokeMessage($"[0, \"n\", [0, \"on-req\", 0, 0, {JsonConvert.SerializeObject(expected)}, 0, \"SUCCESS\", \"Submitted\"]]"); var result = placeTask.Result; @@ -600,7 +601,7 @@ public async Task PlacingAnOrder_Should_FailIfErrorResponse() // act var placeTask = client.SpotApi.PlaceOrderAsync(OrderSide.Buy, OrderType.ExchangeLimit, "tBTCUSD", 1, price: 1, clientOrderId: 123); - await socket.InvokeMessage(new BitfinexAuthenticationResponse() { Event = "auth", Status = "OK" }); + await socket.InvokeMessage(new BitfinexResponse() { Event = "auth", Message = "OK" }); Thread.Sleep(100); await socket.InvokeMessage($"[0, \"n\", [0, \"on-req\", 0, 0, {JsonConvert.SerializeObject(order)}, 0, \"error\", \"order placing failed\"]]"); var result = placeTask.Result; @@ -624,7 +625,7 @@ public async Task PlacingAnOrder_Should_FailIfNoResponse() // act var placeTask = client.SpotApi.PlaceOrderAsync(OrderSide.Buy, OrderType.ExchangeLimit, "tBTCUSD", 1, price: 1, clientOrderId: 123); - await socket.InvokeMessage(new BitfinexAuthenticationResponse() { Event = "auth", Status = "OK" }); + await socket.InvokeMessage(new BitfinexResponse() { Event = "auth", Message = "OK" }); var result = placeTask.Result; // assert @@ -651,7 +652,7 @@ public async Task PlacingAnMarketOrder_Should_SucceedIfSuccessResponse() // act var placeTask = client.SpotApi.PlaceOrderAsync(OrderSide.Buy, OrderType.ExchangeMarket, "tBTCUSD", 1, price: 1, clientOrderId: 1234); - await socket.InvokeMessage(new BitfinexAuthenticationResponse() { Event = "auth", Status = "OK" }); + await socket.InvokeMessage(new BitfinexResponse() { Event = "auth", Message = "OK" }); Thread.Sleep(100); await socket.InvokeMessage($"[0, \"n\", [0, \"on-req\", 0, 0, {JsonConvert.SerializeObject(expected)}, 0, \"SUCCESS\", \"Submitted\"]]"); var result = placeTask.Result; @@ -681,7 +682,7 @@ public async Task PlacingAnFOKOrder_Should_SucceedIfSuccessResponse() // act var placeTask = client.SpotApi.PlaceOrderAsync(OrderSide.Buy, OrderType.ExchangeFillOrKill, "tBTCUSD", 1, price: 1, clientOrderId: 1234); - await socket.InvokeMessage(new BitfinexAuthenticationResponse() { Event = "auth", Status = "OK" }); + await socket.InvokeMessage(new BitfinexResponse() { Event = "auth", Message = "OK" }); Thread.Sleep(100); await socket.InvokeMessage($"[0, \"n\", [0, \"on-req\", 0, 0, {JsonConvert.SerializeObject(expected)}, 0, \"SUCCESS\", \"Submitted\"]]"); var result = placeTask.Result; @@ -711,7 +712,7 @@ public async Task PlacingAnFundingOffer_Should_SucceedIfSuccessResponse() // act var placeTask = client.SpotApi.SubmitFundingOfferAsync(FundingOfferType.Limit, "fUSD", 1, 1, 1); - await socket.InvokeMessage(new BitfinexAuthenticationResponse() { Event = "auth", Status = "OK" }); + await socket.InvokeMessage(new BitfinexResponse() { Event = "auth", Message = "OK" }); Thread.Sleep(100); await socket.InvokeMessage($"[0, \"n\", [0, \"fon-req\", 0, 0, {JsonConvert.SerializeObject(expected)}, 0, \"SUCCESS\", \"Submitted\"]]"); var result = placeTask.Result; diff --git a/Bitfinex.Net/Bitfinex.Net.xml b/Bitfinex.Net/Bitfinex.Net.xml index 393ad3c..e06f80e 100644 --- a/Bitfinex.Net/Bitfinex.Net.xml +++ b/Bitfinex.Net/Bitfinex.Net.xml @@ -5434,5 +5434,24 @@ Dispose + + + Extensions for the ICryptoRestClient and ICryptoSocketClient interfaces + + + + + Get the Bitfinex REST Api client + + + + + + + Get the Bitfinex Websocket Api client + + + + diff --git a/Bitfinex.Net/BitfinexHelpers.cs b/Bitfinex.Net/BitfinexHelpers.cs index c3b92f2..bd58736 100644 --- a/Bitfinex.Net/BitfinexHelpers.cs +++ b/Bitfinex.Net/BitfinexHelpers.cs @@ -58,7 +58,7 @@ public static IServiceCollection AddBitfinex( return handler; }); - services.AddTransient(); + services.AddTransient(); services.AddSingleton(); services.AddTransient(x => x.GetRequiredService().SpotApi.CommonSpotClient); if (socketClientLifeTime == null) diff --git a/Bitfinex.Net/Clients/SpotApi/BitfinexSocketClientSpotApi.cs b/Bitfinex.Net/Clients/SpotApi/BitfinexSocketClientSpotApi.cs index 5cfc02b..c89a355 100644 --- a/Bitfinex.Net/Clients/SpotApi/BitfinexSocketClientSpotApi.cs +++ b/Bitfinex.Net/Clients/SpotApi/BitfinexSocketClientSpotApi.cs @@ -56,8 +56,6 @@ internal BitfinexSocketClientSpotApi(ILogger logger, BitfinexSocketOptions optio { UnhandledMessageExpected = true; - //AddGenericHandler("Conf", ConfHandler); - AddSystemSubscription(new BitfinexInfoSubscription(_logger)); _affCode = options.AffiliateCode; @@ -177,18 +175,18 @@ public async Task> SubscribeToDerivativesUpdatesA /// public async Task> SubscribeToUserUpdatesAsync( - Action>>? orderHandler, - Action>>? positionHandler, - Action>>? fundingOfferHandler, - Action>>? fundingCreditHandler, - Action>>? fundingLoanHandler, - Action>>? walletHandler, - Action>? balanceHandler, - Action>? tradeHandler, - Action>? fundingTradeHandler, - Action>? fundingInfoHandler, - Action>? marginBaseHandler, - Action>? marginSymbolHandler, + Action>>? orderHandler = null, + Action>>? positionHandler = null, + Action>>? fundingOfferHandler = null, + Action>>? fundingCreditHandler = null, + Action>>? fundingLoanHandler = null, + Action>>? walletHandler = null, + Action>? balanceHandler = null, + Action>? tradeHandler = null, + Action>? fundingTradeHandler = null, + Action>? fundingInfoHandler = null, + Action>? marginBaseHandler = null, + Action>? marginSymbolHandler = null, CancellationToken ct = default) { var subscription = new BitfinexUserSubscription(_logger, positionHandler, walletHandler, orderHandler, fundingOfferHandler, fundingCreditHandler, fundingLoanHandler, balanceHandler, tradeHandler, fundingTradeHandler, fundingInfoHandler, marginBaseHandler, marginSymbolHandler); diff --git a/Bitfinex.Net/Converters/OrderBookEntryConverter.cs b/Bitfinex.Net/Converters/OrderBookEntryConverter.cs index 73bb7df..8ebb6e6 100644 --- a/Bitfinex.Net/Converters/OrderBookEntryConverter.cs +++ b/Bitfinex.Net/Converters/OrderBookEntryConverter.cs @@ -72,7 +72,12 @@ private static BitfinexOrderBookEntry ParseEntry(JArray data) public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer) { - throw new NotImplementedException(); + var obj = (BitfinexOrderBookEntry)value!; + writer.WriteStartArray(); + writer.WriteValue(obj.RawPrice); + writer.WriteValue(obj.Count); + writer.WriteValue(obj.RawQuantity); + writer.WriteEndArray(); } } } diff --git a/Bitfinex.Net/CryptoClientExtensions.cs b/Bitfinex.Net/CryptoClientExtensions.cs new file mode 100644 index 0000000..0e1aeee --- /dev/null +++ b/Bitfinex.Net/CryptoClientExtensions.cs @@ -0,0 +1,29 @@ +using Bitfinex.Net.Clients; +using Bitfinex.Net.Interfaces.Clients; +using CryptoExchange.Net.Interfaces.CommonClients; +using System; +using System.Collections.Generic; +using System.Text; + +namespace CryptoExchange.Net.Clients +{ + /// + /// Extensions for the ICryptoRestClient and ICryptoSocketClient interfaces + /// + public static class CryptoClientExtensions + { + /// + /// Get the Bitfinex REST Api client + /// + /// + /// + public static IBitfinexRestClient Bitfinex(this ICryptoRestClient baseClient) => baseClient.TryGet(() => new BitfinexRestClient()); + + /// + /// Get the Bitfinex Websocket Api client + /// + /// + /// + public static IBitfinexSocketClient Bitfinex(this ICryptoSocketClient baseClient) => baseClient.TryGet(() => new BitfinexSocketClient()); + } +} diff --git a/Bitfinex.Net/CryptoExchangeClientExtensions.cs b/Bitfinex.Net/CryptoExchangeClientExtensions.cs deleted file mode 100644 index 1900df1..0000000 --- a/Bitfinex.Net/CryptoExchangeClientExtensions.cs +++ /dev/null @@ -1,14 +0,0 @@ -using Bitfinex.Net.Clients; -using Bitfinex.Net.Interfaces.Clients; -using CryptoExchange.Net.Interfaces.CommonClients; -using System; -using System.Collections.Generic; -using System.Text; - -namespace CryptoExchange.Net.Clients -{ - public static class CryptoExchangeClientExtensions - { - public static IBitfinexRestClient Bitfinex(this ICryptoExchangeClient baseClient) => baseClient.TryGet() ?? new BitfinexRestClient(); - } -} diff --git a/Bitfinex.Net/Interfaces/Clients/SpotApi/IBitfinexSocketClientSpotApi.cs b/Bitfinex.Net/Interfaces/Clients/SpotApi/IBitfinexSocketClientSpotApi.cs index 636db0a..44a3434 100644 --- a/Bitfinex.Net/Interfaces/Clients/SpotApi/IBitfinexSocketClientSpotApi.cs +++ b/Bitfinex.Net/Interfaces/Clients/SpotApi/IBitfinexSocketClientSpotApi.cs @@ -147,18 +147,18 @@ public interface IBitfinexSocketClientSpotApi : ISocketApiClient, IDisposable /// Cancellation token for closing this subscription /// Task> SubscribeToUserUpdatesAsync( - Action>>? orderHandler, - Action>>? positionHandler, - Action>>? fundingOfferHandler, - Action>>? fundingCreditHandler, - Action>>? fundingLoanHandler, - Action>>? walletHandler, - Action>? balanceHandler, - Action>? tradeHandler, - Action>? fundingTradeHandler, - Action>? fundingInfoHandler, - Action>? marginBaseHandler, - Action>? marginSymbolHandler, + Action>>? orderHandler = null, + Action>>? positionHandler = null, + Action>>? fundingOfferHandler = null, + Action>>? fundingCreditHandler = null, + Action>>? fundingLoanHandler = null, + Action>>? walletHandler = null, + Action>? balanceHandler = null, + Action>? tradeHandler = null, + Action>? fundingTradeHandler = null, + Action>? fundingInfoHandler = null, + Action>? marginBaseHandler = null, + Action>? marginSymbolHandler = null, CancellationToken ct = default); /// diff --git a/Bitfinex.Net/Objects/Internal/BitfinexAuthenticationResponse.cs b/Bitfinex.Net/Objects/Internal/BitfinexAuthenticationResponse.cs deleted file mode 100644 index 11dcef5..0000000 --- a/Bitfinex.Net/Objects/Internal/BitfinexAuthenticationResponse.cs +++ /dev/null @@ -1,43 +0,0 @@ -using Newtonsoft.Json; - -namespace Bitfinex.Net.Objects.Internal -{ - internal class BitfinexAuthenticationResponse - { - [JsonProperty("event")] - public string Event { get; set; } = string.Empty; - [JsonProperty("status")] - public string Status { get; set; } = string.Empty; - [JsonProperty("userId")] - public long UserId { get; set; } - [JsonProperty("chanId")] - public long ChannelId { get; set; } - [JsonProperty("auth_id")] - public string AuthenticationId { get; set; } = string.Empty; - [JsonProperty("caps")] - public BitfinexApiKeyPermissions Permissions { get; set; } = default!; - - // In case of error - [JsonProperty("code")] - public int ErrorCode { get; set; } - [JsonProperty("msg")] - public string? ErrorMessage { get; set; } - } - - internal class BitfinexApiKeyPermissions - { - public BitfinexReadWritePermission Account { get; set; } = default!; - public BitfinexReadWritePermission History { get; set; } = default!; - public BitfinexReadWritePermission Orders { get; set; } = default!; - public BitfinexReadWritePermission Positions { get; set; } = default!; - public BitfinexReadWritePermission Funding { get; set; } = default!; - public BitfinexReadWritePermission Wallets { get; set; } = default!; - public BitfinexReadWritePermission Withdraw { get; set; } = default!; - } - - internal class BitfinexReadWritePermission - { - public bool Read { get; set; } - public bool Write { get; set; } - } -} diff --git a/Bitfinex.Net/Objects/Models/BitfinexOrderBookEntry.cs b/Bitfinex.Net/Objects/Models/BitfinexOrderBookEntry.cs index a4de30e..e3ef055 100644 --- a/Bitfinex.Net/Objects/Models/BitfinexOrderBookEntry.cs +++ b/Bitfinex.Net/Objects/Models/BitfinexOrderBookEntry.cs @@ -1,4 +1,5 @@ -using CryptoExchange.Net.Converters; +using Bitfinex.Net.Converters; +using CryptoExchange.Net.Converters; using CryptoExchange.Net.Interfaces; using Newtonsoft.Json; @@ -13,7 +14,7 @@ public class BitfinexOrderBookBase { } /// /// Order book entry /// - [JsonConverter(typeof(ArrayConverter))] + [JsonConverter(typeof(OrderBookEntryConverter))] public class BitfinexOrderBookEntry: BitfinexOrderBookBase, ISymbolOrderBookEntry { /// diff --git a/Bitfinex.Net/Objects/Sockets/BitfinexResponse.cs b/Bitfinex.Net/Objects/Sockets/BitfinexResponse.cs index 92cdb88..5beaac9 100644 --- a/Bitfinex.Net/Objects/Sockets/BitfinexResponse.cs +++ b/Bitfinex.Net/Objects/Sockets/BitfinexResponse.cs @@ -4,8 +4,12 @@ namespace Bitfinex.Net.Objects.Sockets { internal class BitfinexResponse { + [JsonProperty("code")] + public int? Code { get; set; } [JsonProperty("event")] public string Event { get; set; } = string.Empty; + [JsonProperty("msg")] + public string Message { get; set; } = string.Empty; [JsonProperty("channel")] public string Channel { get; set; } = string.Empty; [JsonProperty("symbol")] diff --git a/Bitfinex.Net/Objects/Sockets/BitfinexUpdate.cs b/Bitfinex.Net/Objects/Sockets/BitfinexUpdate.cs index 10e58fe..4dac9c7 100644 --- a/Bitfinex.Net/Objects/Sockets/BitfinexUpdate.cs +++ b/Bitfinex.Net/Objects/Sockets/BitfinexUpdate.cs @@ -15,7 +15,7 @@ internal class BitfinexUpdate } [JsonConverter(typeof(ArrayConverter))] - internal class BitfinexUpdate3 + internal class BitfinexTopicUpdate { [ArrayProperty(0)] public int ChannelId { get; set; } diff --git a/Bitfinex.Net/Objects/Sockets/Queries/BitfinexAuthQuery.cs b/Bitfinex.Net/Objects/Sockets/Queries/BitfinexAuthQuery.cs index 4cdc194..5a9f706 100644 --- a/Bitfinex.Net/Objects/Sockets/Queries/BitfinexAuthQuery.cs +++ b/Bitfinex.Net/Objects/Sockets/Queries/BitfinexAuthQuery.cs @@ -1,6 +1,9 @@ using Bitfinex.Net.Objects.Internal; +using CryptoExchange.Net.Objects; +using CryptoExchange.Net.Objects.Sockets; using CryptoExchange.Net.Sockets; using System.Collections.Generic; +using System.Threading.Tasks; namespace Bitfinex.Net.Objects.Sockets.Queries { @@ -11,5 +14,13 @@ internal class BitfinexAuthQuery : Query public BitfinexAuthQuery(BitfinexAuthentication authRequest) : base(authRequest, true) { } + + public override Task> HandleMessageAsync(SocketConnection connection, DataEvent message) + { + if (message.Data.Message != "OK") + return Task.FromResult(new CallResult(new ServerError(message.Data.Code!.Value, message.Data.Message!))); + + return Task.FromResult(new CallResult(message.Data, message.OriginalData, null)); + } } } diff --git a/Bitfinex.Net/Objects/Sockets/Queries/BitfinexSubQuery.cs b/Bitfinex.Net/Objects/Sockets/Queries/BitfinexSubQuery.cs index aafe11c..4e27a17 100644 --- a/Bitfinex.Net/Objects/Sockets/Queries/BitfinexSubQuery.cs +++ b/Bitfinex.Net/Objects/Sockets/Queries/BitfinexSubQuery.cs @@ -1,5 +1,8 @@ -using CryptoExchange.Net.Sockets; +using CryptoExchange.Net.Objects; +using CryptoExchange.Net.Objects.Sockets; +using CryptoExchange.Net.Sockets; using System.Collections.Generic; +using System.Threading.Tasks; namespace Bitfinex.Net.Objects.Sockets.Queries { @@ -21,7 +24,19 @@ public BitfinexSubQuery(string evnt, string channel, string? symbol, string? pre if (evnt == "subscribe" || evnt == "unsubscribe") evnt += "d"; - ListenerIdentifiers = new HashSet { evnt + channel + symbol + precision + frequency + length + key }; + ListenerIdentifiers = new HashSet + { + evnt + channel + symbol + precision + frequency + length + key, + "error" + channel + symbol + precision + frequency + length + key + }; + } + + public override Task> HandleMessageAsync(SocketConnection connection, DataEvent message) + { + if (message.Data.Event == "error") + return Task.FromResult(new CallResult(new ServerError(message.Data.Message!))); + + return Task.FromResult(new CallResult(message.Data)); } } } diff --git a/Bitfinex.Net/Objects/Sockets/Queries/BitfinexUnsubQuery.cs b/Bitfinex.Net/Objects/Sockets/Queries/BitfinexUnsubQuery.cs index d38dc2e..9ef651e 100644 --- a/Bitfinex.Net/Objects/Sockets/Queries/BitfinexUnsubQuery.cs +++ b/Bitfinex.Net/Objects/Sockets/Queries/BitfinexUnsubQuery.cs @@ -10,7 +10,10 @@ internal class BitfinexUnsubQuery : Query public BitfinexUnsubQuery(int channelId) : base(new BitfinexUnsubscribeRequest(channelId), false, 1) { - ListenerIdentifiers = new HashSet { channelId.ToString() + "unsubscribed" }; + ListenerIdentifiers = new HashSet + { + channelId.ToString() + "unsubscribed" + }; } } } diff --git a/Bitfinex.Net/Objects/Sockets/Subscriptions/BitfinexSubscription.cs b/Bitfinex.Net/Objects/Sockets/Subscriptions/BitfinexSubscription.cs index 1d3153b..6573e96 100644 --- a/Bitfinex.Net/Objects/Sockets/Subscriptions/BitfinexSubscription.cs +++ b/Bitfinex.Net/Objects/Sockets/Subscriptions/BitfinexSubscription.cs @@ -19,7 +19,6 @@ internal class BitfinexSubscription : Subscription>) : typeof(BitfinexUpdate3); + return nodeType == NodeType.Array ? typeof(BitfinexTopicUpdate>) : typeof(BitfinexTopicUpdate); } public override void HandleSubQueryResponse(BitfinexResponse message) @@ -102,9 +101,9 @@ public override Task DoHandleMessageAsync(SocketConnection connectio _handler?.Invoke(message.As(arrayUpdate.Data, _symbol, _firstUpdate ? SocketUpdateType.Snapshot : SocketUpdateType.Update)); else if (message.Data is BitfinexUpdate singleUpdate) _handler?.Invoke(message.As>(new[] { singleUpdate.Data }, _symbol, _firstUpdate ? SocketUpdateType.Snapshot : SocketUpdateType.Update)); - else if (message.Data is BitfinexUpdate3> array3Update) + else if (message.Data is BitfinexTopicUpdate> array3Update) _handler?.Invoke(message.As(array3Update.Data, _symbol, _firstUpdate ? SocketUpdateType.Snapshot : SocketUpdateType.Update)); - else if (message.Data is BitfinexUpdate3 single3Update) + else if (message.Data is BitfinexTopicUpdate single3Update) _handler?.Invoke(message.As>(new[] { single3Update.Data }, _symbol, _firstUpdate ? SocketUpdateType.Snapshot : SocketUpdateType.Update)); _firstUpdate = false; From acfdf465ebbe2f419171107cd42b4518f8e2071f Mon Sep 17 00:00:00 2001 From: JKorf Date: Sun, 4 Feb 2024 13:47:30 +0100 Subject: [PATCH 09/25] Warning fixes --- Bitfinex.Net.UnitTests/TestImplementations/TestSocket.cs | 4 ++-- Bitfinex.Net/Bitfinex.Net.csproj | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Bitfinex.Net.UnitTests/TestImplementations/TestSocket.cs b/Bitfinex.Net.UnitTests/TestImplementations/TestSocket.cs index 1f54dc4..4495d57 100644 --- a/Bitfinex.Net.UnitTests/TestImplementations/TestSocket.cs +++ b/Bitfinex.Net.UnitTests/TestImplementations/TestSocket.cs @@ -125,9 +125,9 @@ public async Task ProcessAsync() public async Task ReconnectAsync() { - OnReconnecting(); + await OnReconnecting(); await Task.Delay(10); - OnReconnected(); + await OnReconnected(); } } } diff --git a/Bitfinex.Net/Bitfinex.Net.csproj b/Bitfinex.Net/Bitfinex.Net.csproj index fba7ee1..f021fe4 100644 --- a/Bitfinex.Net/Bitfinex.Net.csproj +++ b/Bitfinex.Net/Bitfinex.Net.csproj @@ -51,7 +51,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive From 3f96671e5eba6ad1ea62d289e465881e8efd5623 Mon Sep 17 00:00:00 2001 From: JKorf Date: Sun, 4 Feb 2024 21:27:13 +0100 Subject: [PATCH 10/25] Added missing service registration --- Bitfinex.Net/BitfinexHelpers.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Bitfinex.Net/BitfinexHelpers.cs b/Bitfinex.Net/BitfinexHelpers.cs index bd58736..6ce2137 100644 --- a/Bitfinex.Net/BitfinexHelpers.cs +++ b/Bitfinex.Net/BitfinexHelpers.cs @@ -59,6 +59,7 @@ public static IServiceCollection AddBitfinex( }); services.AddTransient(); + services.AddTransient(); services.AddSingleton(); services.AddTransient(x => x.GetRequiredService().SpotApi.CommonSpotClient); if (socketClientLifeTime == null) From bafb25995527ffce6e8c73e8efd1a7bc7ffadc09 Mon Sep 17 00:00:00 2001 From: JKorf Date: Tue, 6 Feb 2024 16:24:40 +0100 Subject: [PATCH 11/25] Updated extension methods namespaces --- Bitfinex.Net.UnitTests/BitfinexClientTests.cs | 1 + Bitfinex.Net/Bitfinex.Net.xml | 75 ++++++++++--------- .../BitfinexRestClientGeneralApiFunding.cs | 1 + .../BitfinexRestClientSpotApiAccount.cs | 1 + .../BitfinexRestClientSpotApiExchangeData.cs | 1 + .../BitfinexRestClientSpotApiTrading.cs | 1 + .../SpotApi/BitfinexSocketClientSpotApi.cs | 1 + .../BitfinexExtensionMethods.cs | 51 +++++++++++++ .../CryptoClientExtensions.cs | 6 +- .../ServiceCollectionExtensions.cs} | 58 ++------------ .../BitfinexSymbolOrderBook.cs | 1 + 11 files changed, 106 insertions(+), 91 deletions(-) create mode 100644 Bitfinex.Net/ExtensionMethods/BitfinexExtensionMethods.cs rename Bitfinex.Net/{ => ExtensionMethods}/CryptoClientExtensions.cs (85%) rename Bitfinex.Net/{BitfinexHelpers.cs => ExtensionMethods/ServiceCollectionExtensions.cs} (55%) diff --git a/Bitfinex.Net.UnitTests/BitfinexClientTests.cs b/Bitfinex.Net.UnitTests/BitfinexClientTests.cs index 691a765..2e52db5 100644 --- a/Bitfinex.Net.UnitTests/BitfinexClientTests.cs +++ b/Bitfinex.Net.UnitTests/BitfinexClientTests.cs @@ -13,6 +13,7 @@ using CryptoExchange.Net.Objects; using CryptoExchange.Net.Sockets; using Bitfinex.Net.Clients; +using Bitfinex.Net.ExtensionMethods; using CryptoExchange.Net.Objects.Sockets; namespace Bitfinex.Net.UnitTests diff --git a/Bitfinex.Net/Bitfinex.Net.xml b/Bitfinex.Net/Bitfinex.Net.xml index e06f80e..be0b915 100644 --- a/Bitfinex.Net/Bitfinex.Net.xml +++ b/Bitfinex.Net/Bitfinex.Net.xml @@ -33,39 +33,6 @@ - - - Helper functions - - - - - Add the IBitfinexClient and IBitfinexSocketClient to the sevice collection so they can be injected - - The service collection - Set default options for the rest client - Set default options for the socket client - The lifetime of the IBitfinexSocketClient for the service collection. Defaults to Singleton. - - - - - Validate the string is a valid Bitfinex symbol. - - string to validate - - - - Validate the string is a valid Bitfinex symbol. - - string to validate - - - - Validate the string is a valid Bitfinex symbol. - - string to validate - @@ -1373,6 +1340,29 @@ Deposit + + + Extension methods specific to using the Bitfinex API + + + + + Validate the string is a valid Bitfinex symbol. + + string to validate + + + + Validate the string is a valid Bitfinex symbol. + + string to validate + + + + Validate the string is a valid Bitfinex symbol. + + string to validate + General API endpoints @@ -5439,19 +5429,34 @@ Extensions for the ICryptoRestClient and ICryptoSocketClient interfaces - + Get the Bitfinex REST Api client - + Get the Bitfinex Websocket Api client + + + Extensions for DI + + + + + Add the IBitfinexClient and IBitfinexSocketClient to the sevice collection so they can be injected + + The service collection + Set default options for the rest client + Set default options for the socket client + The lifetime of the IBitfinexSocketClient for the service collection. Defaults to Singleton. + + diff --git a/Bitfinex.Net/Clients/GeneralApi/BitfinexRestClientGeneralApiFunding.cs b/Bitfinex.Net/Clients/GeneralApi/BitfinexRestClientGeneralApiFunding.cs index 5aa41ed..8e24fc7 100644 --- a/Bitfinex.Net/Clients/GeneralApi/BitfinexRestClientGeneralApiFunding.cs +++ b/Bitfinex.Net/Clients/GeneralApi/BitfinexRestClientGeneralApiFunding.cs @@ -12,6 +12,7 @@ using System.Threading.Tasks; using Bitfinex.Net.Objects.Models; using Bitfinex.Net.Interfaces.Clients.GeneralApi; +using Bitfinex.Net.ExtensionMethods; namespace Bitfinex.Net.Clients.GeneralApi { diff --git a/Bitfinex.Net/Clients/SpotApi/BitfinexRestClientSpotApiAccount.cs b/Bitfinex.Net/Clients/SpotApi/BitfinexRestClientSpotApiAccount.cs index 1f2e9af..578d548 100644 --- a/Bitfinex.Net/Clients/SpotApi/BitfinexRestClientSpotApiAccount.cs +++ b/Bitfinex.Net/Clients/SpotApi/BitfinexRestClientSpotApiAccount.cs @@ -14,6 +14,7 @@ using Bitfinex.Net.Objects.Models; using Bitfinex.Net.Objects.Models.V1; using Bitfinex.Net.Interfaces.Clients.SpotApi; +using Bitfinex.Net.ExtensionMethods; namespace Bitfinex.Net.Clients.SpotApi { diff --git a/Bitfinex.Net/Clients/SpotApi/BitfinexRestClientSpotApiExchangeData.cs b/Bitfinex.Net/Clients/SpotApi/BitfinexRestClientSpotApiExchangeData.cs index 02bd41c..88fb6c2 100644 --- a/Bitfinex.Net/Clients/SpotApi/BitfinexRestClientSpotApiExchangeData.cs +++ b/Bitfinex.Net/Clients/SpotApi/BitfinexRestClientSpotApiExchangeData.cs @@ -13,6 +13,7 @@ using System.Threading.Tasks; using Bitfinex.Net.Objects.Models; using Bitfinex.Net.Interfaces.Clients.SpotApi; +using Bitfinex.Net.ExtensionMethods; namespace Bitfinex.Net.Clients.SpotApi { diff --git a/Bitfinex.Net/Clients/SpotApi/BitfinexRestClientSpotApiTrading.cs b/Bitfinex.Net/Clients/SpotApi/BitfinexRestClientSpotApiTrading.cs index d3fde2c..33c0b36 100644 --- a/Bitfinex.Net/Clients/SpotApi/BitfinexRestClientSpotApiTrading.cs +++ b/Bitfinex.Net/Clients/SpotApi/BitfinexRestClientSpotApiTrading.cs @@ -15,6 +15,7 @@ using Bitfinex.Net.Objects.Models; using Bitfinex.Net.Interfaces.Clients.SpotApi; using CryptoExchange.Net.CommonObjects; +using Bitfinex.Net.ExtensionMethods; namespace Bitfinex.Net.Clients.SpotApi { diff --git a/Bitfinex.Net/Clients/SpotApi/BitfinexSocketClientSpotApi.cs b/Bitfinex.Net/Clients/SpotApi/BitfinexSocketClientSpotApi.cs index c89a355..32961ac 100644 --- a/Bitfinex.Net/Clients/SpotApi/BitfinexSocketClientSpotApi.cs +++ b/Bitfinex.Net/Clients/SpotApi/BitfinexSocketClientSpotApi.cs @@ -23,6 +23,7 @@ using Bitfinex.Net.Objects.Sockets.Queries; using CryptoExchange.Net.Sockets.MessageParsing; using CryptoExchange.Net.Sockets.MessageParsing.Interfaces; +using Bitfinex.Net.ExtensionMethods; namespace Bitfinex.Net.Clients.SpotApi { diff --git a/Bitfinex.Net/ExtensionMethods/BitfinexExtensionMethods.cs b/Bitfinex.Net/ExtensionMethods/BitfinexExtensionMethods.cs new file mode 100644 index 0000000..ade1f7e --- /dev/null +++ b/Bitfinex.Net/ExtensionMethods/BitfinexExtensionMethods.cs @@ -0,0 +1,51 @@ +using System; +using System.Text.RegularExpressions; + +namespace Bitfinex.Net.ExtensionMethods +{ + /// + /// Extension methods specific to using the Bitfinex API + /// + public static class BitfinexExtensionMethods + { + /// + /// Validate the string is a valid Bitfinex symbol. + /// + /// string to validate + public static void ValidateBitfinexSymbol(this string symbolString) + { + if (string.IsNullOrEmpty(symbolString)) + throw new ArgumentException("Symbol is not provided"); + + if (!Regex.IsMatch(symbolString, "^([t]([A-Z0-9|:]{6,}))$") && !Regex.IsMatch(symbolString, "^([f]([A-Z0-9]{3,}))$")) + throw new ArgumentException($"{symbolString} is not a valid Bitfinex symbol. Should be [t][QuoteAsset][BaseAsset] for trading pairs " + + "or [f][Asset] for margin symbols, e.g. tBTCUSD or fUSD"); + } + + /// + /// Validate the string is a valid Bitfinex symbol. + /// + /// string to validate + public static void ValidateBitfinexFundingSymbol(this string symbolString) + { + if (string.IsNullOrEmpty(symbolString)) + throw new ArgumentException("Symbol is not provided"); + + if (!Regex.IsMatch(symbolString, "^([f]([A-Z0-9]{3,}))$")) + throw new ArgumentException($"{symbolString} is not a valid Bitfinex funding symbol. Should be [f][Asset] for funding symbols, e.g. fUSD"); + } + + /// + /// Validate the string is a valid Bitfinex symbol. + /// + /// string to validate + public static void ValidateBitfinexTradingSymbol(this string symbolString) + { + if (string.IsNullOrEmpty(symbolString)) + throw new ArgumentException("Symbol is not provided"); + + if (!Regex.IsMatch(symbolString, "^([t]([A-Z0-9|:]{6,}))$")) + throw new ArgumentException($"{symbolString} is not a valid Bitfinex symbol. Should be [t][QuoteAsset][BaseAsset] for trading pairs, e.g. tBTCUSD"); + } + } +} diff --git a/Bitfinex.Net/CryptoClientExtensions.cs b/Bitfinex.Net/ExtensionMethods/CryptoClientExtensions.cs similarity index 85% rename from Bitfinex.Net/CryptoClientExtensions.cs rename to Bitfinex.Net/ExtensionMethods/CryptoClientExtensions.cs index 0e1aeee..00c671c 100644 --- a/Bitfinex.Net/CryptoClientExtensions.cs +++ b/Bitfinex.Net/ExtensionMethods/CryptoClientExtensions.cs @@ -1,11 +1,7 @@ using Bitfinex.Net.Clients; using Bitfinex.Net.Interfaces.Clients; -using CryptoExchange.Net.Interfaces.CommonClients; -using System; -using System.Collections.Generic; -using System.Text; -namespace CryptoExchange.Net.Clients +namespace CryptoExchange.Net.Interfaces { /// /// Extensions for the ICryptoRestClient and ICryptoSocketClient interfaces diff --git a/Bitfinex.Net/BitfinexHelpers.cs b/Bitfinex.Net/ExtensionMethods/ServiceCollectionExtensions.cs similarity index 55% rename from Bitfinex.Net/BitfinexHelpers.cs rename to Bitfinex.Net/ExtensionMethods/ServiceCollectionExtensions.cs index 6ce2137..cb6ab09 100644 --- a/Bitfinex.Net/BitfinexHelpers.cs +++ b/Bitfinex.Net/ExtensionMethods/ServiceCollectionExtensions.cs @@ -1,21 +1,18 @@ using Bitfinex.Net.Clients; +using Bitfinex.Net.Interfaces; using Bitfinex.Net.Interfaces.Clients; using Bitfinex.Net.Objects.Options; -using Microsoft.Extensions.DependencyInjection; +using Bitfinex.Net.SymbolOrderBooks; using System; -using System.Net.Http; using System.Net; -using System.Text.RegularExpressions; -using Bitfinex.Net.Interfaces; -using Bitfinex.Net.SymbolOrderBooks; -using CryptoExchange.Net.Clients; +using System.Net.Http; -namespace Bitfinex.Net +namespace Microsoft.Extensions.DependencyInjection { /// - /// Helper functions + /// Extensions for DI /// - public static class BitfinexHelpers + public static class ServiceCollectionExtensions { /// /// Add the IBitfinexClient and IBitfinexSocketClient to the sevice collection so they can be injected @@ -58,9 +55,8 @@ public static IServiceCollection AddBitfinex( return handler; }); - services.AddTransient(); - services.AddTransient(); services.AddSingleton(); + services.AddTransient(); services.AddTransient(x => x.GetRequiredService().SpotApi.CommonSpotClient); if (socketClientLifeTime == null) services.AddSingleton(); @@ -68,45 +64,5 @@ public static IServiceCollection AddBitfinex( services.Add(new ServiceDescriptor(typeof(IBitfinexSocketClient), typeof(BitfinexSocketClient), socketClientLifeTime.Value)); return services; } - - /// - /// Validate the string is a valid Bitfinex symbol. - /// - /// string to validate - public static void ValidateBitfinexSymbol(this string symbolString) - { - if (string.IsNullOrEmpty(symbolString)) - throw new ArgumentException("Symbol is not provided"); - - if (!Regex.IsMatch(symbolString, "^([t]([A-Z0-9|:]{6,}))$") && !Regex.IsMatch(symbolString, "^([f]([A-Z0-9]{3,}))$")) - throw new ArgumentException($"{symbolString} is not a valid Bitfinex symbol. Should be [t][QuoteAsset][BaseAsset] for trading pairs " + - "or [f][Asset] for margin symbols, e.g. tBTCUSD or fUSD"); - } - - /// - /// Validate the string is a valid Bitfinex symbol. - /// - /// string to validate - public static void ValidateBitfinexFundingSymbol(this string symbolString) - { - if (string.IsNullOrEmpty(symbolString)) - throw new ArgumentException("Symbol is not provided"); - - if (!Regex.IsMatch(symbolString, "^([f]([A-Z0-9]{3,}))$")) - throw new ArgumentException($"{symbolString} is not a valid Bitfinex funding symbol. Should be [f][Asset] for funding symbols, e.g. fUSD"); - } - - /// - /// Validate the string is a valid Bitfinex symbol. - /// - /// string to validate - public static void ValidateBitfinexTradingSymbol(this string symbolString) - { - if (string.IsNullOrEmpty(symbolString)) - throw new ArgumentException("Symbol is not provided"); - - if (!Regex.IsMatch(symbolString, "^([t]([A-Z0-9|:]{6,}))$")) - throw new ArgumentException($"{symbolString} is not a valid Bitfinex symbol. Should be [t][QuoteAsset][BaseAsset] for trading pairs, e.g. tBTCUSD"); - } } } diff --git a/Bitfinex.Net/SymbolOrderBooks/BitfinexSymbolOrderBook.cs b/Bitfinex.Net/SymbolOrderBooks/BitfinexSymbolOrderBook.cs index 7f4917f..8fa1537 100644 --- a/Bitfinex.Net/SymbolOrderBooks/BitfinexSymbolOrderBook.cs +++ b/Bitfinex.Net/SymbolOrderBooks/BitfinexSymbolOrderBook.cs @@ -6,6 +6,7 @@ using System.Threading.Tasks; using Bitfinex.Net.Clients; using Bitfinex.Net.Enums; +using Bitfinex.Net.ExtensionMethods; using Bitfinex.Net.Interfaces.Clients; using Bitfinex.Net.Objects.Models; using Bitfinex.Net.Objects.Options; From 295c85b103c1af03aebcd4dbd7f111f72ef62090 Mon Sep 17 00:00:00 2001 From: JKorf Date: Tue, 6 Feb 2024 20:24:17 +0100 Subject: [PATCH 12/25] Updated CryptoExchange.Net reference --- Bitfinex.Net/Bitfinex.Net.csproj | 4 +--- Bitfinex.Net/Bitfinex.Net.xml | 6 +++--- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/Bitfinex.Net/Bitfinex.Net.csproj b/Bitfinex.Net/Bitfinex.Net.csproj index f021fe4..e159032 100644 --- a/Bitfinex.Net/Bitfinex.Net.csproj +++ b/Bitfinex.Net/Bitfinex.Net.csproj @@ -51,12 +51,10 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive + all runtime; build; native; contentfiles; analyzers; buildtransitive - - - \ No newline at end of file diff --git a/Bitfinex.Net/Bitfinex.Net.xml b/Bitfinex.Net/Bitfinex.Net.xml index be0b915..d19a2c9 100644 --- a/Bitfinex.Net/Bitfinex.Net.xml +++ b/Bitfinex.Net/Bitfinex.Net.xml @@ -5424,19 +5424,19 @@ Dispose - + Extensions for the ICryptoRestClient and ICryptoSocketClient interfaces - + Get the Bitfinex REST Api client - + Get the Bitfinex Websocket Api client From d3e87f0d601aff3e7c892161b28e3f0157dd9572 Mon Sep 17 00:00:00 2001 From: JKorf Date: Tue, 6 Feb 2024 20:25:51 +0100 Subject: [PATCH 13/25] Updated version --- Bitfinex.Net/Bitfinex.Net.csproj | 8 ++++---- README.md | 7 +++++++ 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/Bitfinex.Net/Bitfinex.Net.csproj b/Bitfinex.Net/Bitfinex.Net.csproj index e159032..6bbf0dc 100644 --- a/Bitfinex.Net/Bitfinex.Net.csproj +++ b/Bitfinex.Net/Bitfinex.Net.csproj @@ -7,9 +7,9 @@ Bitfinex.Net JKorf - 7.0.5 - 7.0.5 - 7.0.5 + 7.1.0-beta1 + 7.1.0-beta1 + 7.1.0-beta1 Bitfinex.Net is a .Net wrapper for the Bitfinex API. It includes all features the API provides, REST API and Websocket, using clear and readable objects including but not limited to Reading market info, Placing and managing orders and Reading balances and funds false Bitfinex Bitfinex.Net C# .Net CryptoCurrency Exchange API wrapper @@ -21,7 +21,7 @@ README.md en true - 7.0.5 - Updated CryptoExchange.Net + 7.1.0-beta1 - Updated CryptoExchange.Net and implemented reworked websocket message handling. For release notes for the CryptoExchange.Net base library see: https://github.com/JKorf/CryptoExchange.Net/tree/beta?tab=readme-ov-file#release-notes, Combined multiple private websocket subscriptions into single subscription, Fixed issue in DI registration causing http client to not be correctly injected, Removed excessive constructor overload for BitfinexRestClient, Removed UpdateType from BitfinexTradeSimple model in favor of the UpdateType in the DataEvent wrapper Bitfinex.Net.xml diff --git a/README.md b/README.md index 8b11502..b8d7bd7 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,13 @@ Alternatively, sponsor me on Github using [Github Sponsors](https://github.com/s A Discord server is available [here](https://discord.gg/MSpeEtSY8t). Feel free to join for discussion and/or questions around the CryptoExchange.Net and implementation libraries. ## Release notes +* Version 7.1.0-beta1 - 06 Feb 2024 + * Updated CryptoExchange.Net and implemented reworked websocket message handling. For release notes for the CryptoExchange.Net base library see: https://github.com/JKorf/CryptoExchange.Net/tree/beta?tab=readme-ov-file#release-notes + * Combined multiple private websocket subscriptions into single subscription + * Fixed issue in DI registration causing http client to not be correctly injected + * Removed excessive constructor overload for BitfinexRestClient + * Removed UpdateType from BitfinexTradeSimple model in favor of the UpdateType in the DataEvent wrapper + * Version 7.0.5 - 03 Dec 2023 * Updated CryptoExchange.Net From 03e41bb8ef1d42f84fd168a9ce7a71ff96907fa2 Mon Sep 17 00:00:00 2001 From: JKorf Date: Tue, 6 Feb 2024 20:27:22 +0100 Subject: [PATCH 14/25] Updated version error --- Bitfinex.Net/Bitfinex.Net.csproj | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Bitfinex.Net/Bitfinex.Net.csproj b/Bitfinex.Net/Bitfinex.Net.csproj index 6bbf0dc..0ffa76d 100644 --- a/Bitfinex.Net/Bitfinex.Net.csproj +++ b/Bitfinex.Net/Bitfinex.Net.csproj @@ -1,4 +1,4 @@ - + netstandard2.0;netstandard2.1 enable @@ -8,8 +8,8 @@ Bitfinex.Net JKorf 7.1.0-beta1 - 7.1.0-beta1 - 7.1.0-beta1 + 7.1.0 + 7.1.0 Bitfinex.Net is a .Net wrapper for the Bitfinex API. It includes all features the API provides, REST API and Websocket, using clear and readable objects including but not limited to Reading market info, Placing and managing orders and Reading balances and funds false Bitfinex Bitfinex.Net C# .Net CryptoCurrency Exchange API wrapper From c4236927a7cb6fbe1a878d806db87716062b8ecf Mon Sep 17 00:00:00 2001 From: JKorf Date: Sun, 11 Feb 2024 20:27:56 +0100 Subject: [PATCH 15/25] Update README.md --- README.md | 73 +++++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 60 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index b8d7bd7..1bed161 100644 --- a/README.md +++ b/README.md @@ -1,22 +1,72 @@ -# Bitfinex.Net -[![.NET](https://github.com/JKorf/Bitfinex.Net/actions/workflows/dotnet.yml/badge.svg)](https://github.com/JKorf/Bitfinex.Net/actions/workflows/dotnet.yml) [![Nuget version](https://img.shields.io/nuget/v/bitfinex.net.svg)](https://www.nuget.org/packages/Bitfinex.Net) [![Nuget downloads](https://img.shields.io/nuget/dt/Bitfinex.Net.svg)](https://www.nuget.org/packages/Bitfinex.Net) +# ![.Bitfinex.Net](https://github.com/JKorf/Bitfinex.Net/blob/beta/Bitfinex.Net/Icon/icon.png?raw=true) Bitfinex.Net -Bitfinex.Net is a wrapper around the Bitfinex API as described on [Bitfinex](https://docs.bitfinex.com/docs), including all features the API provides using clear and readable objects, both for the REST as the websocket API's. +[![.NET](https://github.com/JKorf/Bitfinex.Net/actions/workflows/dotnet.yml/badge.svg)](https://github.com/JKorf/Bitfinex.Net/actions/workflows/dotnet.yml) -**If you think something is broken, something is missing or have any questions, please open an [Issue](https://github.com/JKorf/Bitfinex.Net/issues)** +Bitfinex.Net is a wrapper around the Bitfinex API as described on [Bitfinex](https://docs.bitfinex.com/docs), including all features the API provides using clear and readable objects, both for the REST as the websocket API's. -[Documentation](https://jkorf.github.io/Bitfinex.Net/) +## Get the library +Available on Nuget + [![Nuget version](https://img.shields.io/nuget/v/bitfinex.net.svg)](https://www.nuget.org/packages/Bitfinex.Net) [![Nuget downloads](https://img.shields.io/nuget/dt/Bitfinex.Net.svg)](https://www.nuget.org/packages/Bitfinex.Net) -## Installation `dotnet add package Bitfinex.Net` +## How to use +Simplest usage +```csharp +// Get the ETH/USDT ticker via rest request +var restClient = new BitfinexRestClient(); +var tickerResult = await restClient.SpotApi.ExchangeData.GetTickerAsync("tETHUST"); +var lastPrice = tickerResult.Data.LastPrice; +``` + +```csharp +// Subscribe to ETH/USDT ticker updates via the websocket API +var socketClient = new BitfinexSocketClient(); +var tickerSubscriptionResult = socketClient.SpotApi.SubscribeToTickerUpdatesAsync("tETHUST", (update) => +{ + var lastPrice = update.Data.LastPrice; +}); +``` + +For information on the clients, dependency injection, response processing and more see the [documentation](https://jkorf.github.io/CryptoExchange.Net), or have a look at the examples [here](https://github.com/JKorf/CryptoExchange.Net/tree/master/Examples). + +## CryptoExchange.Net +Binance.Net is based on the [CryptoExchange.Net](https://github.com/JKorf/CryptoExchange.Net) base library. Other exchange API implementations based on the CryptoExchange.Net base library are available and follow the same logic. + +CryptoExchange.Net also allows for [easy access to different exchange API's](https://jkorf.github.io/CryptoExchange.Net#idocs_common). + +|Exchange|Repository|Nuget| +|--|--|--| +|Binance|[JKorf/Binance.Net](https://github.com/JKorf/Binance.Net)|[![Nuget version](https://img.shields.io/nuget/v/Binance.net.svg)](https://www.nuget.org/packages/Binance.Net)| +|Bitget|[JKorf/Bitget.Net](https://github.com/JKorf/Bitget.Net)|[![Nuget version](https://img.shields.io/nuget/v/Bitget.net.svg)](https://www.nuget.org/packages/Bitget.Net)| +|Bybit|[JKorf/Bybit.Net](https://github.com/JKorf/Bybit.Net)|[![Nuget version](https://img.shields.io/nuget/v/Bybit.net.svg)](https://www.nuget.org/packages/Bybit.Net)| +|CoinEx|[JKorf/CoinEx.Net](https://github.com/JKorf/CoinEx.Net)|[![Nuget version](https://img.shields.io/nuget/v/CoinEx.net.svg)](https://www.nuget.org/packages/CoinEx.Net)| +|CoinGecko|[JKorf/CoinGecko.Net](https://github.com/JKorf/CoinGecko.Net)|[![Nuget version](https://img.shields.io/nuget/v/CoinGecko.net.svg)](https://www.nuget.org/packages/CoinGecko.Net)| +|Huobi/HTX|[JKorf/Huobi.Net](https://github.com/JKorf/Huobi.Net)|[![Nuget version](https://img.shields.io/nuget/v/Huobi.net.svg)](https://www.nuget.org/packages/Huobi.Net)| +|Kraken|[JKorf/Kraken.Net](https://github.com/JKorf/Kraken.Net)|[![Nuget version](https://img.shields.io/nuget/v/KrakenExchange.net.svg)](https://www.nuget.org/packages/KrakenExchange.Net)| +|Kucoin|[JKorf/Kucoin.Net](https://github.com/JKorf/Kucoin.Net)|[![Nuget version](https://img.shields.io/nuget/v/Kucoin.net.svg)](https://www.nuget.org/packages/Kucoin.Net)| +|Mexc|[JKorf/Mexc.Net](https://github.com/JKorf/Mexc.Net)|[![Nuget version](https://img.shields.io/nuget/v/JK.Mexc.net.svg)](https://www.nuget.org/packages/JK.Mexc.Net)| +|OKX|[JKorf/OKX.Net](https://github.com/JKorf/OKX.Net)|[![Nuget version](https://img.shields.io/nuget/v/JK.OKX.net.svg)](https://www.nuget.org/packages/JK.OKX.Net)| + +## Discord +A Discord server is available [here](https://discord.gg/MSpeEtSY8t). Feel free to join for discussion and/or questions around the CryptoExchange.Net and implementation libraries. + +## Supported functionality +|API|Supported|Location| +|--|--:|--| +|Rest Public Endpoints|✓|`restClient.SpotApi.ExchangeData`| +|Rest Public Pulse Endpoints|X|| +|Calculation Endpoints|✓|`restClient.SpotApi.ExchangeData`| +|Rest Authenticated Endpoints|✓|`restClient.SpotApi.Account` / `restClient.SpotApi.Trading`| +|Rest Authenticated Pulse Endpoints|X|| +|Rest Authenticated Merchant Endpoints|X|| +|Websocket Public Channels|✓|`socketClient.SpotApi`| +|Websocket Authenticated Channels|✓|`socketClient.SpotApi`| +|Websocket Authenticated Inputs|✓|`socketClient.SpotApi`| + ## Support the project I develop and maintain this package on my own for free in my spare time, any support is greatly appreciated. -### Referral link -Sign up using the following referral link to pay a small percentage of the trading fees you pay to support the project instead of paying them straight to Bitfinex. This doesn't cost you a thing! -[Link](https://www.bitfinex.com/sign-up?refcode=kCCe-CNBO) - ### Donate Make a one time donation in a crypto currency of your choice. If you prefer to donate a currency not listed here please contact me. @@ -26,9 +76,6 @@ Make a one time donation in a crypto currency of your choice. If you prefer to d ### Sponsor Alternatively, sponsor me on Github using [Github Sponsors](https://github.com/sponsors/JKorf). -## Discord -A Discord server is available [here](https://discord.gg/MSpeEtSY8t). Feel free to join for discussion and/or questions around the CryptoExchange.Net and implementation libraries. - ## Release notes * Version 7.1.0-beta1 - 06 Feb 2024 * Updated CryptoExchange.Net and implemented reworked websocket message handling. For release notes for the CryptoExchange.Net base library see: https://github.com/JKorf/CryptoExchange.Net/tree/beta?tab=readme-ov-file#release-notes From 342a56f5ac538ddf51c71ac387bdf95a37d127ea Mon Sep 17 00:00:00 2001 From: JKorf Date: Mon, 12 Feb 2024 11:29:27 +0100 Subject: [PATCH 16/25] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1bed161..3f97a94 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ var tickerSubscriptionResult = socketClient.SpotApi.SubscribeToTickerUpdatesAsyn For information on the clients, dependency injection, response processing and more see the [documentation](https://jkorf.github.io/CryptoExchange.Net), or have a look at the examples [here](https://github.com/JKorf/CryptoExchange.Net/tree/master/Examples). ## CryptoExchange.Net -Binance.Net is based on the [CryptoExchange.Net](https://github.com/JKorf/CryptoExchange.Net) base library. Other exchange API implementations based on the CryptoExchange.Net base library are available and follow the same logic. +Bitfinex.Net is based on the [CryptoExchange.Net](https://github.com/JKorf/CryptoExchange.Net) base library. Other exchange API implementations based on the CryptoExchange.Net base library are available and follow the same logic. CryptoExchange.Net also allows for [easy access to different exchange API's](https://jkorf.github.io/CryptoExchange.Net#idocs_common). From 0c73ef96727262880f2c02d90c5d4df7166d6d5a Mon Sep 17 00:00:00 2001 From: JKorf Date: Mon, 12 Feb 2024 17:12:04 +0100 Subject: [PATCH 17/25] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3f97a94..d84b552 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# ![.Bitfinex.Net](https://github.com/JKorf/Bitfinex.Net/blob/beta/Bitfinex.Net/Icon/icon.png?raw=true) Bitfinex.Net +# ![.Bitfinex.Net](https://github.com/JKorf/Bitfinex.Net/blob/master/Bitfinex.Net/Icon/icon.png?raw=true) Bitfinex.Net [![.NET](https://github.com/JKorf/Bitfinex.Net/actions/workflows/dotnet.yml/badge.svg)](https://github.com/JKorf/Bitfinex.Net/actions/workflows/dotnet.yml) From 12aa151ba213871495c462d55aa1b350981afa4d Mon Sep 17 00:00:00 2001 From: JKorf Date: Tue, 13 Feb 2024 20:04:31 +0100 Subject: [PATCH 18/25] Update README.md --- README.md | 53 +++++++++++++++++++++++++++++++++-------------------- 1 file changed, 33 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index d84b552..2b5d9fd 100644 --- a/README.md +++ b/README.md @@ -1,32 +1,44 @@ # ![.Bitfinex.Net](https://github.com/JKorf/Bitfinex.Net/blob/master/Bitfinex.Net/Icon/icon.png?raw=true) Bitfinex.Net -[![.NET](https://github.com/JKorf/Bitfinex.Net/actions/workflows/dotnet.yml/badge.svg)](https://github.com/JKorf/Bitfinex.Net/actions/workflows/dotnet.yml) +[![.NET](https://github.com/JKorf/Bitfinex.Net/actions/workflows/dotnet.yml/badge.svg?style=for-the-badge)](https://github.com/JKorf/Bitfinex.Net/actions/workflows/dotnet.yml) ![License](https://img.shields.io/github/license/JKorf/Bitfinex.Net?style=for-the-badge) Bitfinex.Net is a wrapper around the Bitfinex API as described on [Bitfinex](https://docs.bitfinex.com/docs), including all features the API provides using clear and readable objects, both for the REST as the websocket API's. +## Supported Frameworks +The library is targeting both `.NET Standard 2.0` and `.NET Standard 2.1` for optimal compatibility + +|.NET implementation|Version Support| +|--|--| +|.NET Core|`2.0` and higher| +|.NET Framework|`4.6.1` and higher| +|Mono|`5.4` and higher| +|Xamarin.iOS|`10.14` and higher| +|Xamarin.Android|`8.0` and higher| +|UWP|`10.0.16299` and higher| +|Unity|`2018.1` and higher| + ## Get the library -Available on Nuget - [![Nuget version](https://img.shields.io/nuget/v/bitfinex.net.svg)](https://www.nuget.org/packages/Bitfinex.Net) [![Nuget downloads](https://img.shields.io/nuget/dt/Bitfinex.Net.svg)](https://www.nuget.org/packages/Bitfinex.Net) + [![Nuget version](https://img.shields.io/nuget/v/bitfinex.net.svg?style=for-the-badge)](https://www.nuget.org/packages/Bitfinex.Net) [![Nuget downloads](https://img.shields.io/nuget/dt/Bitfinex.Net.svg?style=for-the-badge)](https://www.nuget.org/packages/Bitfinex.Net) -`dotnet add package Bitfinex.Net` + `dotnet add package Bitfinex.Net` ## How to use -Simplest usage -```csharp -// Get the ETH/USDT ticker via rest request -var restClient = new BitfinexRestClient(); -var tickerResult = await restClient.SpotApi.ExchangeData.GetTickerAsync("tETHUST"); -var lastPrice = tickerResult.Data.LastPrice; -``` - -```csharp -// Subscribe to ETH/USDT ticker updates via the websocket API -var socketClient = new BitfinexSocketClient(); -var tickerSubscriptionResult = socketClient.SpotApi.SubscribeToTickerUpdatesAsync("tETHUST", (update) => -{ - var lastPrice = update.Data.LastPrice; -}); -``` +* REST Endpoints + ```csharp + // Get the ETH/USDT ticker via rest request + var restClient = new BitfinexRestClient(); + var tickerResult = await restClient.SpotApi.ExchangeData.GetTickerAsync("tETHUST"); + var lastPrice = tickerResult.Data.LastPrice; + ``` +* Websocket streams + ```csharp + // Subscribe to ETH/USDT ticker updates via the websocket API + var socketClient = new BitfinexSocketClient(); + var tickerSubscriptionResult = socketClient.SpotApi.SubscribeToTickerUpdatesAsync("tETHUST", (update) => + { + var lastPrice = update.Data.LastPrice; + }); + ``` For information on the clients, dependency injection, response processing and more see the [documentation](https://jkorf.github.io/CryptoExchange.Net), or have a look at the examples [here](https://github.com/JKorf/CryptoExchange.Net/tree/master/Examples). @@ -49,6 +61,7 @@ CryptoExchange.Net also allows for [easy access to different exchange API's](htt |OKX|[JKorf/OKX.Net](https://github.com/JKorf/OKX.Net)|[![Nuget version](https://img.shields.io/nuget/v/JK.OKX.net.svg)](https://www.nuget.org/packages/JK.OKX.Net)| ## Discord +[![Nuget version](https://img.shields.io/discord/847020490588422145?style=for-the-badge)](https://discord.gg/MSpeEtSY8t) A Discord server is available [here](https://discord.gg/MSpeEtSY8t). Feel free to join for discussion and/or questions around the CryptoExchange.Net and implementation libraries. ## Supported functionality From 36d0bbaa087e3b4beaf3fc225aa50d85a55dbace Mon Sep 17 00:00:00 2001 From: JKorf Date: Tue, 13 Feb 2024 20:06:45 +0100 Subject: [PATCH 19/25] Update README.md --- README.md | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 2b5d9fd..b78401d 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # ![.Bitfinex.Net](https://github.com/JKorf/Bitfinex.Net/blob/master/Bitfinex.Net/Icon/icon.png?raw=true) Bitfinex.Net -[![.NET](https://github.com/JKorf/Bitfinex.Net/actions/workflows/dotnet.yml/badge.svg?style=for-the-badge)](https://github.com/JKorf/Bitfinex.Net/actions/workflows/dotnet.yml) ![License](https://img.shields.io/github/license/JKorf/Bitfinex.Net?style=for-the-badge) +[![.NET](https://img.shields.io/github/actions/workflow/status/JKorf/Bitfinex.Net/dotnet.yml?style=for-the-badge)](https://github.com/JKorf/Bitfinex.Net/actions/workflows/dotnet.yml) ![License](https://img.shields.io/github/license/JKorf/Bitfinex.Net?style=for-the-badge) Bitfinex.Net is a wrapper around the Bitfinex API as described on [Bitfinex](https://docs.bitfinex.com/docs), including all features the API provides using clear and readable objects, both for the REST as the websocket API's. @@ -20,7 +20,7 @@ The library is targeting both `.NET Standard 2.0` and `.NET Standard 2.1` for op ## Get the library [![Nuget version](https://img.shields.io/nuget/v/bitfinex.net.svg?style=for-the-badge)](https://www.nuget.org/packages/Bitfinex.Net) [![Nuget downloads](https://img.shields.io/nuget/dt/Bitfinex.Net.svg?style=for-the-badge)](https://www.nuget.org/packages/Bitfinex.Net) - `dotnet add package Bitfinex.Net` + dotnet add package Bitfinex.Net ## How to use * REST Endpoints @@ -49,16 +49,16 @@ CryptoExchange.Net also allows for [easy access to different exchange API's](htt |Exchange|Repository|Nuget| |--|--|--| -|Binance|[JKorf/Binance.Net](https://github.com/JKorf/Binance.Net)|[![Nuget version](https://img.shields.io/nuget/v/Binance.net.svg)](https://www.nuget.org/packages/Binance.Net)| -|Bitget|[JKorf/Bitget.Net](https://github.com/JKorf/Bitget.Net)|[![Nuget version](https://img.shields.io/nuget/v/Bitget.net.svg)](https://www.nuget.org/packages/Bitget.Net)| -|Bybit|[JKorf/Bybit.Net](https://github.com/JKorf/Bybit.Net)|[![Nuget version](https://img.shields.io/nuget/v/Bybit.net.svg)](https://www.nuget.org/packages/Bybit.Net)| -|CoinEx|[JKorf/CoinEx.Net](https://github.com/JKorf/CoinEx.Net)|[![Nuget version](https://img.shields.io/nuget/v/CoinEx.net.svg)](https://www.nuget.org/packages/CoinEx.Net)| -|CoinGecko|[JKorf/CoinGecko.Net](https://github.com/JKorf/CoinGecko.Net)|[![Nuget version](https://img.shields.io/nuget/v/CoinGecko.net.svg)](https://www.nuget.org/packages/CoinGecko.Net)| -|Huobi/HTX|[JKorf/Huobi.Net](https://github.com/JKorf/Huobi.Net)|[![Nuget version](https://img.shields.io/nuget/v/Huobi.net.svg)](https://www.nuget.org/packages/Huobi.Net)| -|Kraken|[JKorf/Kraken.Net](https://github.com/JKorf/Kraken.Net)|[![Nuget version](https://img.shields.io/nuget/v/KrakenExchange.net.svg)](https://www.nuget.org/packages/KrakenExchange.Net)| -|Kucoin|[JKorf/Kucoin.Net](https://github.com/JKorf/Kucoin.Net)|[![Nuget version](https://img.shields.io/nuget/v/Kucoin.net.svg)](https://www.nuget.org/packages/Kucoin.Net)| -|Mexc|[JKorf/Mexc.Net](https://github.com/JKorf/Mexc.Net)|[![Nuget version](https://img.shields.io/nuget/v/JK.Mexc.net.svg)](https://www.nuget.org/packages/JK.Mexc.Net)| -|OKX|[JKorf/OKX.Net](https://github.com/JKorf/OKX.Net)|[![Nuget version](https://img.shields.io/nuget/v/JK.OKX.net.svg)](https://www.nuget.org/packages/JK.OKX.Net)| +|Binance|[JKorf/Binance.Net](https://github.com/JKorf/Binance.Net)|[![Nuget version](https://img.shields.io/nuget/v/Binance.net.svg?style=for-the-badge)](https://www.nuget.org/packages/Binance.Net)| +|Bitget|[JKorf/Bitget.Net](https://github.com/JKorf/Bitget.Net)|[![Nuget version](https://img.shields.io/nuget/v/Bitget.net.svg?style=for-the-badge)](https://www.nuget.org/packages/Bitget.Net)| +|Bybit|[JKorf/Bybit.Net](https://github.com/JKorf/Bybit.Net)|[![Nuget version](https://img.shields.io/nuget/v/Bybit.net.svg?style=for-the-badge)](https://www.nuget.org/packages/Bybit.Net)| +|CoinEx|[JKorf/CoinEx.Net](https://github.com/JKorf/CoinEx.Net)|[![Nuget version](https://img.shields.io/nuget/v/CoinEx.net.svg?style=for-the-badge)](https://www.nuget.org/packages/CoinEx.Net)| +|CoinGecko|[JKorf/CoinGecko.Net](https://github.com/JKorf/CoinGecko.Net)|[![Nuget version](https://img.shields.io/nuget/v/CoinGecko.net.svg?style=for-the-badge)](https://www.nuget.org/packages/CoinGecko.Net)| +|Huobi/HTX|[JKorf/Huobi.Net](https://github.com/JKorf/Huobi.Net)|[![Nuget version](https://img.shields.io/nuget/v/Huobi.net.svg?style=for-the-badge)](https://www.nuget.org/packages/Huobi.Net)| +|Kraken|[JKorf/Kraken.Net](https://github.com/JKorf/Kraken.Net)|[![Nuget version](https://img.shields.io/nuget/v/KrakenExchange.net.svg?style=for-the-badge)](https://www.nuget.org/packages/KrakenExchange.Net)| +|Kucoin|[JKorf/Kucoin.Net](https://github.com/JKorf/Kucoin.Net)|[![Nuget version](https://img.shields.io/nuget/v/Kucoin.net.svg?style=for-the-badge)](https://www.nuget.org/packages/Kucoin.Net)| +|Mexc|[JKorf/Mexc.Net](https://github.com/JKorf/Mexc.Net)|[![Nuget version](https://img.shields.io/nuget/v/JK.Mexc.net.svg?style=for-the-badge)](https://www.nuget.org/packages/JK.Mexc.Net)| +|OKX|[JKorf/OKX.Net](https://github.com/JKorf/OKX.Net)|[![Nuget version](https://img.shields.io/nuget/v/JK.OKX.net.svg?style=for-the-badge)](https://www.nuget.org/packages/JK.OKX.Net)| ## Discord [![Nuget version](https://img.shields.io/discord/847020490588422145?style=for-the-badge)](https://discord.gg/MSpeEtSY8t) From d00b570f87df56134f3e2835b01b65dd2c025c2a Mon Sep 17 00:00:00 2001 From: JKorf Date: Tue, 13 Feb 2024 20:12:54 +0100 Subject: [PATCH 20/25] Update README.md --- README.md | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index b78401d..bebad6f 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ The library is targeting both `.NET Standard 2.0` and `.NET Standard 2.1` for op |Unity|`2018.1` and higher| ## Get the library - [![Nuget version](https://img.shields.io/nuget/v/bitfinex.net.svg?style=for-the-badge)](https://www.nuget.org/packages/Bitfinex.Net) [![Nuget downloads](https://img.shields.io/nuget/dt/Bitfinex.Net.svg?style=for-the-badge)](https://www.nuget.org/packages/Bitfinex.Net) +[![Nuget version](https://img.shields.io/nuget/v/bitfinex.net.svg?style=for-the-badge)](https://www.nuget.org/packages/Bitfinex.Net) [![Nuget downloads](https://img.shields.io/nuget/dt/Bitfinex.Net.svg?style=for-the-badge)](https://www.nuget.org/packages/Bitfinex.Net) dotnet add package Bitfinex.Net @@ -49,16 +49,16 @@ CryptoExchange.Net also allows for [easy access to different exchange API's](htt |Exchange|Repository|Nuget| |--|--|--| -|Binance|[JKorf/Binance.Net](https://github.com/JKorf/Binance.Net)|[![Nuget version](https://img.shields.io/nuget/v/Binance.net.svg?style=for-the-badge)](https://www.nuget.org/packages/Binance.Net)| -|Bitget|[JKorf/Bitget.Net](https://github.com/JKorf/Bitget.Net)|[![Nuget version](https://img.shields.io/nuget/v/Bitget.net.svg?style=for-the-badge)](https://www.nuget.org/packages/Bitget.Net)| -|Bybit|[JKorf/Bybit.Net](https://github.com/JKorf/Bybit.Net)|[![Nuget version](https://img.shields.io/nuget/v/Bybit.net.svg?style=for-the-badge)](https://www.nuget.org/packages/Bybit.Net)| -|CoinEx|[JKorf/CoinEx.Net](https://github.com/JKorf/CoinEx.Net)|[![Nuget version](https://img.shields.io/nuget/v/CoinEx.net.svg?style=for-the-badge)](https://www.nuget.org/packages/CoinEx.Net)| -|CoinGecko|[JKorf/CoinGecko.Net](https://github.com/JKorf/CoinGecko.Net)|[![Nuget version](https://img.shields.io/nuget/v/CoinGecko.net.svg?style=for-the-badge)](https://www.nuget.org/packages/CoinGecko.Net)| -|Huobi/HTX|[JKorf/Huobi.Net](https://github.com/JKorf/Huobi.Net)|[![Nuget version](https://img.shields.io/nuget/v/Huobi.net.svg?style=for-the-badge)](https://www.nuget.org/packages/Huobi.Net)| -|Kraken|[JKorf/Kraken.Net](https://github.com/JKorf/Kraken.Net)|[![Nuget version](https://img.shields.io/nuget/v/KrakenExchange.net.svg?style=for-the-badge)](https://www.nuget.org/packages/KrakenExchange.Net)| -|Kucoin|[JKorf/Kucoin.Net](https://github.com/JKorf/Kucoin.Net)|[![Nuget version](https://img.shields.io/nuget/v/Kucoin.net.svg?style=for-the-badge)](https://www.nuget.org/packages/Kucoin.Net)| -|Mexc|[JKorf/Mexc.Net](https://github.com/JKorf/Mexc.Net)|[![Nuget version](https://img.shields.io/nuget/v/JK.Mexc.net.svg?style=for-the-badge)](https://www.nuget.org/packages/JK.Mexc.Net)| -|OKX|[JKorf/OKX.Net](https://github.com/JKorf/OKX.Net)|[![Nuget version](https://img.shields.io/nuget/v/JK.OKX.net.svg?style=for-the-badge)](https://www.nuget.org/packages/JK.OKX.Net)| +|Binance|[JKorf/Binance.Net](https://github.com/JKorf/Binance.Net)|[![Nuget version](https://img.shields.io/nuget/v/Binance.net.svg?style=flat-square)](https://www.nuget.org/packages/Binance.Net)| +|Bitget|[JKorf/Bitget.Net](https://github.com/JKorf/Bitget.Net)|[![Nuget version](https://img.shields.io/nuget/v/Bitget.net.svg?style=flat-square)](https://www.nuget.org/packages/Bitget.Net)| +|Bybit|[JKorf/Bybit.Net](https://github.com/JKorf/Bybit.Net)|[![Nuget version](https://img.shields.io/nuget/v/Bybit.net.svg?style=flat-square)](https://www.nuget.org/packages/Bybit.Net)| +|CoinEx|[JKorf/CoinEx.Net](https://github.com/JKorf/CoinEx.Net)|[![Nuget version](https://img.shields.io/nuget/v/CoinEx.net.svg?style=flat-square)](https://www.nuget.org/packages/CoinEx.Net)| +|CoinGecko|[JKorf/CoinGecko.Net](https://github.com/JKorf/CoinGecko.Net)|[![Nuget version](https://img.shields.io/nuget/v/CoinGecko.net.svg?style=flat-square)](https://www.nuget.org/packages/CoinGecko.Net)| +|Huobi/HTX|[JKorf/Huobi.Net](https://github.com/JKorf/Huobi.Net)|[![Nuget version](https://img.shields.io/nuget/v/Huobi.net.svg?style=flat-square)](https://www.nuget.org/packages/Huobi.Net)| +|Kraken|[JKorf/Kraken.Net](https://github.com/JKorf/Kraken.Net)|[![Nuget version](https://img.shields.io/nuget/v/KrakenExchange.net.svg?style=flat-square)](https://www.nuget.org/packages/KrakenExchange.Net)| +|Kucoin|[JKorf/Kucoin.Net](https://github.com/JKorf/Kucoin.Net)|[![Nuget version](https://img.shields.io/nuget/v/Kucoin.net.svg?style=flat-square)](https://www.nuget.org/packages/Kucoin.Net)| +|Mexc|[JKorf/Mexc.Net](https://github.com/JKorf/Mexc.Net)|[![Nuget version](https://img.shields.io/nuget/v/JK.Mexc.net.svg?style=flat-square)](https://www.nuget.org/packages/JK.Mexc.Net)| +|OKX|[JKorf/OKX.Net](https://github.com/JKorf/OKX.Net)|[![Nuget version](https://img.shields.io/nuget/v/JK.OKX.net.svg?style=flat-square)](https://www.nuget.org/packages/JK.OKX.Net)| ## Discord [![Nuget version](https://img.shields.io/discord/847020490588422145?style=for-the-badge)](https://discord.gg/MSpeEtSY8t) From 03a07ed2b7f460921d50af67f88aa7bcaff7b772 Mon Sep 17 00:00:00 2001 From: JKorf Date: Sat, 17 Feb 2024 13:47:26 +0100 Subject: [PATCH 21/25] Fixed socket auth --- Bitfinex.Net/Objects/Sockets/BitfinexResponse.cs | 2 ++ Bitfinex.Net/Objects/Sockets/Queries/BitfinexAuthQuery.cs | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/Bitfinex.Net/Objects/Sockets/BitfinexResponse.cs b/Bitfinex.Net/Objects/Sockets/BitfinexResponse.cs index 5beaac9..c5fc2db 100644 --- a/Bitfinex.Net/Objects/Sockets/BitfinexResponse.cs +++ b/Bitfinex.Net/Objects/Sockets/BitfinexResponse.cs @@ -10,6 +10,8 @@ internal class BitfinexResponse public string Event { get; set; } = string.Empty; [JsonProperty("msg")] public string Message { get; set; } = string.Empty; + [JsonProperty("status")] + public string Status { get; set; } = string.Empty; [JsonProperty("channel")] public string Channel { get; set; } = string.Empty; [JsonProperty("symbol")] diff --git a/Bitfinex.Net/Objects/Sockets/Queries/BitfinexAuthQuery.cs b/Bitfinex.Net/Objects/Sockets/Queries/BitfinexAuthQuery.cs index 5a9f706..65722c3 100644 --- a/Bitfinex.Net/Objects/Sockets/Queries/BitfinexAuthQuery.cs +++ b/Bitfinex.Net/Objects/Sockets/Queries/BitfinexAuthQuery.cs @@ -17,7 +17,7 @@ public BitfinexAuthQuery(BitfinexAuthentication authRequest) : base(authRequest, public override Task> HandleMessageAsync(SocketConnection connection, DataEvent message) { - if (message.Data.Message != "OK") + if (message.Data.Status != "OK") return Task.FromResult(new CallResult(new ServerError(message.Data.Code!.Value, message.Data.Message!))); return Task.FromResult(new CallResult(message.Data, message.OriginalData, null)); From dae62da46255054bc30869a7729d2d127fdb2f98 Mon Sep 17 00:00:00 2001 From: JKorf Date: Sat, 17 Feb 2024 13:52:06 +0100 Subject: [PATCH 22/25] Fixed tests --- Bitfinex.Net.UnitTests/BitfinexSocketClientTests.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Bitfinex.Net.UnitTests/BitfinexSocketClientTests.cs b/Bitfinex.Net.UnitTests/BitfinexSocketClientTests.cs index d17e81c..8eca51f 100644 --- a/Bitfinex.Net.UnitTests/BitfinexSocketClientTests.cs +++ b/Bitfinex.Net.UnitTests/BitfinexSocketClientTests.cs @@ -580,7 +580,7 @@ public async Task PlacingAnOrder_Should_SucceedIfSuccessResponse() // act var placeTask = client.SpotApi.PlaceOrderAsync(OrderSide.Buy, OrderType.ExchangeLimit, "tBTCUSD", 1, price: 1, clientOrderId: 1234); - await socket.InvokeMessage(new BitfinexResponse() { Event = "auth", Message = "OK" }); + await socket.InvokeMessage(new BitfinexResponse() { Event = "auth", Status = "OK" }); Thread.Sleep(100); await socket.InvokeMessage($"[0, \"n\", [0, \"on-req\", 0, 0, {JsonConvert.SerializeObject(expected)}, 0, \"SUCCESS\", \"Submitted\"]]"); var result = placeTask.Result; @@ -601,7 +601,7 @@ public async Task PlacingAnOrder_Should_FailIfErrorResponse() // act var placeTask = client.SpotApi.PlaceOrderAsync(OrderSide.Buy, OrderType.ExchangeLimit, "tBTCUSD", 1, price: 1, clientOrderId: 123); - await socket.InvokeMessage(new BitfinexResponse() { Event = "auth", Message = "OK" }); + await socket.InvokeMessage(new BitfinexResponse() { Event = "auth", Status = "OK" }); Thread.Sleep(100); await socket.InvokeMessage($"[0, \"n\", [0, \"on-req\", 0, 0, {JsonConvert.SerializeObject(order)}, 0, \"error\", \"order placing failed\"]]"); var result = placeTask.Result; @@ -652,7 +652,7 @@ public async Task PlacingAnMarketOrder_Should_SucceedIfSuccessResponse() // act var placeTask = client.SpotApi.PlaceOrderAsync(OrderSide.Buy, OrderType.ExchangeMarket, "tBTCUSD", 1, price: 1, clientOrderId: 1234); - await socket.InvokeMessage(new BitfinexResponse() { Event = "auth", Message = "OK" }); + await socket.InvokeMessage(new BitfinexResponse() { Event = "auth", Status = "OK" }); Thread.Sleep(100); await socket.InvokeMessage($"[0, \"n\", [0, \"on-req\", 0, 0, {JsonConvert.SerializeObject(expected)}, 0, \"SUCCESS\", \"Submitted\"]]"); var result = placeTask.Result; @@ -682,7 +682,7 @@ public async Task PlacingAnFOKOrder_Should_SucceedIfSuccessResponse() // act var placeTask = client.SpotApi.PlaceOrderAsync(OrderSide.Buy, OrderType.ExchangeFillOrKill, "tBTCUSD", 1, price: 1, clientOrderId: 1234); - await socket.InvokeMessage(new BitfinexResponse() { Event = "auth", Message = "OK" }); + await socket.InvokeMessage(new BitfinexResponse() { Event = "auth", Status = "OK" }); Thread.Sleep(100); await socket.InvokeMessage($"[0, \"n\", [0, \"on-req\", 0, 0, {JsonConvert.SerializeObject(expected)}, 0, \"SUCCESS\", \"Submitted\"]]"); var result = placeTask.Result; @@ -712,7 +712,7 @@ public async Task PlacingAnFundingOffer_Should_SucceedIfSuccessResponse() // act var placeTask = client.SpotApi.SubmitFundingOfferAsync(FundingOfferType.Limit, "fUSD", 1, 1, 1); - await socket.InvokeMessage(new BitfinexResponse() { Event = "auth", Message = "OK" }); + await socket.InvokeMessage(new BitfinexResponse() { Event = "auth", Status = "OK" }); Thread.Sleep(100); await socket.InvokeMessage($"[0, \"n\", [0, \"fon-req\", 0, 0, {JsonConvert.SerializeObject(expected)}, 0, \"SUCCESS\", \"Submitted\"]]"); var result = placeTask.Result; From 26240353374dc1045f3591ab06d9ee2263b48b92 Mon Sep 17 00:00:00 2001 From: JKorf Date: Sat, 17 Feb 2024 13:52:24 +0100 Subject: [PATCH 23/25] Updated version --- Bitfinex.Net/Bitfinex.Net.csproj | 10 +++++----- README.md | 3 +++ 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/Bitfinex.Net/Bitfinex.Net.csproj b/Bitfinex.Net/Bitfinex.Net.csproj index 0ffa76d..a69efbb 100644 --- a/Bitfinex.Net/Bitfinex.Net.csproj +++ b/Bitfinex.Net/Bitfinex.Net.csproj @@ -1,4 +1,4 @@ - + netstandard2.0;netstandard2.1 enable @@ -7,9 +7,9 @@ Bitfinex.Net JKorf - 7.1.0-beta1 - 7.1.0 - 7.1.0 + 7.1.0-beta2 + 7.1.0-beta2 + 7.1.0-beta2 Bitfinex.Net is a .Net wrapper for the Bitfinex API. It includes all features the API provides, REST API and Websocket, using clear and readable objects including but not limited to Reading market info, Placing and managing orders and Reading balances and funds false Bitfinex Bitfinex.Net C# .Net CryptoCurrency Exchange API wrapper @@ -21,7 +21,7 @@ README.md en true - 7.1.0-beta1 - Updated CryptoExchange.Net and implemented reworked websocket message handling. For release notes for the CryptoExchange.Net base library see: https://github.com/JKorf/CryptoExchange.Net/tree/beta?tab=readme-ov-file#release-notes, Combined multiple private websocket subscriptions into single subscription, Fixed issue in DI registration causing http client to not be correctly injected, Removed excessive constructor overload for BitfinexRestClient, Removed UpdateType from BitfinexTradeSimple model in favor of the UpdateType in the DataEvent wrapper + 7.1.0-beta2 - Fixed socket authentication Bitfinex.Net.xml diff --git a/README.md b/README.md index bebad6f..cb69679 100644 --- a/README.md +++ b/README.md @@ -90,6 +90,9 @@ Make a one time donation in a crypto currency of your choice. If you prefer to d Alternatively, sponsor me on Github using [Github Sponsors](https://github.com/sponsors/JKorf). ## Release notes +* Version 7.1.0-beta2 - 17 Feb 2024 + * Fixed socket authentication + * Version 7.1.0-beta1 - 06 Feb 2024 * Updated CryptoExchange.Net and implemented reworked websocket message handling. For release notes for the CryptoExchange.Net base library see: https://github.com/JKorf/CryptoExchange.Net/tree/beta?tab=readme-ov-file#release-notes * Combined multiple private websocket subscriptions into single subscription From 985ad9d8b64d65fb568044ca521eca51b2e8fdea Mon Sep 17 00:00:00 2001 From: JKorf Date: Sat, 17 Feb 2024 14:15:02 +0100 Subject: [PATCH 24/25] Fixed version --- Bitfinex.Net/Bitfinex.Net.csproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Bitfinex.Net/Bitfinex.Net.csproj b/Bitfinex.Net/Bitfinex.Net.csproj index a69efbb..4dd2b8c 100644 --- a/Bitfinex.Net/Bitfinex.Net.csproj +++ b/Bitfinex.Net/Bitfinex.Net.csproj @@ -8,8 +8,8 @@ Bitfinex.Net JKorf 7.1.0-beta2 - 7.1.0-beta2 - 7.1.0-beta2 + 7.1.0 + 7.1.0 Bitfinex.Net is a .Net wrapper for the Bitfinex API. It includes all features the API provides, REST API and Websocket, using clear and readable objects including but not limited to Reading market info, Placing and managing orders and Reading balances and funds false Bitfinex Bitfinex.Net C# .Net CryptoCurrency Exchange API wrapper From e33003d43c631550b6931a5a38ffd52a45e1190b Mon Sep 17 00:00:00 2001 From: JKorf Date: Sat, 24 Feb 2024 21:58:02 +0100 Subject: [PATCH 25/25] Updated CryptoExchange.Net version --- Bitfinex.Net/Bitfinex.Net.csproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Bitfinex.Net/Bitfinex.Net.csproj b/Bitfinex.Net/Bitfinex.Net.csproj index 4dd2b8c..cda1e13 100644 --- a/Bitfinex.Net/Bitfinex.Net.csproj +++ b/Bitfinex.Net/Bitfinex.Net.csproj @@ -1,4 +1,4 @@ - + netstandard2.0;netstandard2.1 enable @@ -51,7 +51,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive