using Binance.Net.Clients; using Binance.Net.Objects; using Binance.TradeRobot.Model.Base; using CryptoExchange.Net.Authentication; using Newtonsoft.Json; using SDKAdapter.Model; using System; using System.Collections.Generic; using System.Text; using System.Text.RegularExpressions; using System.Threading; namespace SDKAdapter.WebSockets.Order.Spot { public class BinanceSpotOrderWebSocketClient : SpotOrderWebSocketClient { private BinanceSocketClient binanceSocketClient; private BinanceClient binanceClient; private CancellationTokenSource cancellationTokenSource; private string listenKey; private IList ignoreOrderStateList; public BinanceSpotOrderWebSocketClient(Enums.BusinessType businessType, long accountId, string apiKey, string secret, NLog.ILogger logger, Action onOrderUpdated) : base(businessType, accountId, apiKey, secret, logger, onOrderUpdated) { var spotClientOption = new BinanceApiClientOptions() { BaseAddress = "https://api.binance.com", ApiCredentials = new ApiCredentials(apiKey, secret) }; //var usdFuturesClientOption = new BinanceApiClientOptions() //{ // BaseAddress = "https://fapi.binance.com", // ApiCredentials = new ApiCredentials(apiKey, secret) //}; binanceClient = new BinanceClient(new BinanceClientOptions() { //UsdFuturesApiOptions = usdFuturesClientOption, SpotApiOptions = spotClientOption }); binanceSocketClient = new BinanceSocketClient(); listenKey = string.Empty; ignoreOrderStateList = new List() { Binance.Net.Enums.OrderStatus.New, Binance.Net.Enums.OrderStatus.PendingCancel, //Binance.Net.Enums.OrderStatus.PartiallyFilled, //由于无法确定手续费时候存在局部,暂时忽略局部成交 Binance.Net.Enums.OrderStatus.Insurance, Binance.Net.Enums.OrderStatus.Adl }; } public override void Start(string symbol = "") { if (IsConnected) return; IsConnected = true; cancellationTokenSource = new CancellationTokenSource(); var getListenKeyResponse = binanceClient.SpotApi.Account.StartIsolatedMarginUserStreamAsync(symbol).Result; if (!getListenKeyResponse.Success) throw new Exception(getListenKeyResponse.Error?.Message ?? ""); listenKey = getListenKeyResponse.Data; _ = binanceSocketClient.SpotStreams.SubscribeToUserDataUpdatesAsync(listenKey, (e) => { try { var originData = new StringBuilder(); var isStop = false; if (ignoreOrderStateList.Contains(e.Data.Status)) { originData.Append($"不支持的订单状态{e.Data.Status}"); isStop = true; } else if (string.IsNullOrEmpty(e.Data.ClientOrderId)) { originData.Append($"缺少ClientOrderId"); isStop = true; } long? robotId = null; Enums.TradePolicy? tradePolicy = null; if (!isStop) { var match = Regex.Match(e.Data.ClientOrderId, @"^([a-z0-9]{5})_(\d{15,})_(\d{1,2})$"); if (!match.Success) { originData.Append($"非机器人交易的ClientOrderId"); isStop = true; } else { robotId = long.Parse(match.Groups[2].Value); tradePolicy = (Enums.TradePolicy)int.Parse(match.Groups[3].Value); } } originData.Append($" {JsonConvert.SerializeObject(e.Data)}"); logger.Info(originData); if (isStop) return; var orderState = Enums.SpotOrderState.Unknow; switch (e.Data.Status) { case Binance.Net.Enums.OrderStatus.PartiallyFilled: case Binance.Net.Enums.OrderStatus.Filled: case Binance.Net.Enums.OrderStatus.Canceled: orderState = (Enums.SpotOrderState)(int)e.Data.Status; break; case Binance.Net.Enums.OrderStatus.Rejected: orderState = Enums.SpotOrderState.Rejected; break; case Binance.Net.Enums.OrderStatus.Expired: orderState = Enums.SpotOrderState.Expired; break; } OnOrderUpdated?.Invoke(new SpotOrderPublishInfo() { OrderId = e.Data.Id, RobotId = robotId.Value, TradePolicy = tradePolicy.Value, Symbol = e.Data.Symbol, AccountId = this.AccountId, OrderType = (Enums.OrderType)(int)e.Data.Type, SpotOrderState = orderState, TradeDirection = (Enums.TradeDirection)(int)e.Data.Side, ClientOrderId = e.Data.ClientOrderId, CummulativeTradeAmount = e.Data.QuoteQuantityFilled, CummulativeTradeQuantity = e.Data.QuantityFilled, Exchange = Enums.Exchange.Binance, Fee = e.Data.Fee, FeeUnit = e.Data.FeeAsset, LastTradeAmount = e.Data.LastQuoteQuantity, LastTradePrice = e.Data.LastPriceFilled, LastTradeQuantity = e.Data.LastQuantityFilled, LastTradeTime = e.Data.UpdateTime, CreateTime = e.Data.CreateTime, LoggerName = logger.Name, RejectedReason = e.Data.RejectReason.ToString() }); } catch (Exception ex) { logger.Error(ex); } }, (e) => { }, (e) => { }, (e) => { }, cancellationTokenSource.Token); } public override void Stop(string symbol = "") { if (!IsConnected) return; IsConnected = false; cancellationTokenSource.Cancel(); binanceSocketClient.SpotStreams.Dispose(); cancellationTokenSource = null; _ = binanceClient.SpotApi.Account.CloseIsolatedMarginUserStreamAsync(symbol, listenKey).Result; listenKey = string.Empty; } } }