From e02a00137f28214896dbc4d641170a4bc17f1799 Mon Sep 17 00:00:00 2001 From: shanj <18996038927@163.com> Date: Mon, 16 May 2022 06:40:17 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AF=B9=E6=8E=A5=E8=AE=A2=E5=8D=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Business/BaseBusiness.cs | 6 +- .../Business/ExchangeBusiness.cs | 2 +- .../Business/RobotBusiness.cs | 2 + .../TradeBusiness/BaseTradeBusiness.cs | 44 +++++++ .../TradeBusiness/D21TradeBusiness.cs | 71 +++++------ .../Extensions/RobotExtension.cs | 4 +- Binance.TradeRobot.Business/GlobalContext.cs | 46 ++++--- Binance.TradeRobot.Model/Base/Enums.cs | 12 +- .../Binance.TradeRobot.Model.xml | 9 +- .../Dto/Response/Robot/RobotResponse.cs | 35 +++++- SDKAdapter/APIClient/BaseAPIClient.cs | 8 +- SDKAdapter/APIClient/BinanceAPIClient.cs | 4 +- SDKAdapter/Model/SpotOrderTradePublishInfo.cs | 44 +++++++ SDKAdapter/SDKAdapter.csproj | 2 +- .../BinanceSpotMarketWebSocketClient.cs | 17 +-- .../Market/SpotMarketWebSocketClient.cs | 16 +-- .../BinanceSpotOrderWebSocketClient.cs | 112 ++++++++++++++++++ .../SpotOrder/SpotOrderWebSocketClient.cs | 45 +++++++ SDKTestConsole/NLog.config | 14 +++ SDKTestConsole/Program.cs | 88 +++++++++----- SDKTestConsole/SDKTestConsole.csproj | 14 +++ 21 files changed, 470 insertions(+), 125 deletions(-) create mode 100644 Binance.TradeRobot.Business/Business/TradeBusiness/BaseTradeBusiness.cs create mode 100644 SDKAdapter/Model/SpotOrderTradePublishInfo.cs create mode 100644 SDKAdapter/WebSockets/Order/SpotOrder/BinanceSpotOrderWebSocketClient.cs create mode 100644 SDKAdapter/WebSockets/Order/SpotOrder/SpotOrderWebSocketClient.cs create mode 100644 SDKTestConsole/NLog.config diff --git a/Binance.TradeRobot.Business/Business/BaseBusiness.cs b/Binance.TradeRobot.Business/Business/BaseBusiness.cs index ff2d937..aa1f892 100644 --- a/Binance.TradeRobot.Business/Business/BaseBusiness.cs +++ b/Binance.TradeRobot.Business/Business/BaseBusiness.cs @@ -27,12 +27,12 @@ namespace Binance.TradeRobot.Business expirationTimeSpan = TimeSpan.FromDays(1); } - protected BaseAPIClient GetBaseAPIClient(Enums.Exchange exchange, long uid, string apiKey, string secret) + protected BaseAPIClient GetBaseAPIClient(Enums.Exchange exchange, long accountId, string apiKey, string secret) { - var cacheKey = exchange == Enums.Exchange.Binance ? uid.ToString() : apiKey; + var cacheKey = exchange == Enums.Exchange.Binance ? accountId.ToString() : apiKey; if (!memoryCache.TryGetValue(cacheKey, out BaseAPIClient baseAPIClient)) { - baseAPIClient = BaseAPIClient.Create(exchange, uid, apiKey, secret); + baseAPIClient = BaseAPIClient.Create(exchange, accountId, apiKey, secret); memoryCache.Set(cacheKey, baseAPIClient, expirationTimeSpan); } return baseAPIClient; diff --git a/Binance.TradeRobot.Business/Business/ExchangeBusiness.cs b/Binance.TradeRobot.Business/Business/ExchangeBusiness.cs index 1d91ee3..4b487ff 100644 --- a/Binance.TradeRobot.Business/Business/ExchangeBusiness.cs +++ b/Binance.TradeRobot.Business/Business/ExchangeBusiness.cs @@ -176,7 +176,7 @@ namespace Binance.TradeRobot.Business { } - else if (exchangeAccount.BusinessType == Enums.BusinessType.Spot_Margin) + else if (exchangeAccount.BusinessType == Enums.BusinessType.IsolateMargin) { var isolatedMarginAccountAssetList = apiClient.GetIsolatedMarginAccountAssets(); foreach (var apiKey in exchangeAccount.ExchangeAPIKeyList) diff --git a/Binance.TradeRobot.Business/Business/RobotBusiness.cs b/Binance.TradeRobot.Business/Business/RobotBusiness.cs index d76282c..92fc6f3 100644 --- a/Binance.TradeRobot.Business/Business/RobotBusiness.cs +++ b/Binance.TradeRobot.Business/Business/RobotBusiness.cs @@ -56,6 +56,7 @@ namespace Binance.TradeRobot.Business //监听K线和订单 globalContext.SubscribeKLine(robot); + globalContext.SubscribeOrderPublish(robot); } public void StopRobot(long robotId) @@ -68,6 +69,7 @@ namespace Binance.TradeRobot.Business { //取消监听K线和订单 globalContext.UnSubscribeKLine(robot); + globalContext.UnSubscribeOrderPublish(robot); } } diff --git a/Binance.TradeRobot.Business/Business/TradeBusiness/BaseTradeBusiness.cs b/Binance.TradeRobot.Business/Business/TradeBusiness/BaseTradeBusiness.cs new file mode 100644 index 0000000..0d0e18f --- /dev/null +++ b/Binance.TradeRobot.Business/Business/TradeBusiness/BaseTradeBusiness.cs @@ -0,0 +1,44 @@ +using Binance.TradeRobot.Model.Base; +using Binance.TradeRobot.Model.Db; +using Binance.TradeRobot.Model.Dto; +using Microsoft.Extensions.Caching.Memory; +using System; +using System.Collections.Generic; +using System.Text; +using Yitter.IdGenerator; + +namespace Binance.TradeRobot.Business +{ + public class BaseTradeBusiness : BaseBusiness + { + + protected DingBusiness dingBusiness { get; private set; } + protected GlobalContext globalContext { get; private set; } + + public BaseTradeBusiness(IFreeSql fsql, NLogManager logManager, IIdGenerator idGenerator, IMemoryCache memoryCache, DingBusiness dingBusiness, GlobalContext globalContext) : base(fsql, logManager, idGenerator, memoryCache) + { + this.dingBusiness = dingBusiness; + this.globalContext = globalContext; + } + + public void HandleError(Exception ex, + Enums.SingalType singalType, + List logList, + RobotResponse robot, + string step) + { + logList.Add(new ExecutionLog() + { + Id = idGenerator.NewLong(), + SourceSingal = singalType, + RobotId = robot.Id, + CreateTime = DateTime.Now, + Content = ex.Message + }); + try { fsql.Insert(logList).ExecuteAffrows(); } catch { } + var errorMsg = $"交易警报,{singalType},{robot.ExecuteKey},{robot.Id},{step}"; + logManager.GetLogger(robot.ExecuteKey).Error(ex, errorMsg); + dingBusiness.Send($"{errorMsg} {ex.Message}"); + } + } +} diff --git a/Binance.TradeRobot.Business/Business/TradeBusiness/D21TradeBusiness.cs b/Binance.TradeRobot.Business/Business/TradeBusiness/D21TradeBusiness.cs index 9d04074..712df47 100644 --- a/Binance.TradeRobot.Business/Business/TradeBusiness/D21TradeBusiness.cs +++ b/Binance.TradeRobot.Business/Business/TradeBusiness/D21TradeBusiness.cs @@ -17,53 +17,36 @@ using Yitter.IdGenerator; namespace Binance.TradeRobot.Business { [BatchRegistration(ServiceLifetime.Singleton, RegistrationType.Interface)] - public class D21TradeBusiness : BaseBusiness, ITradeBusiness + public class D21TradeBusiness : BaseTradeBusiness, ITradeBusiness { - private DingBusiness dingBusiness; - private GlobalContext globalContext; - private IList validStateList; + public Enums.TradePolicy TradePolicy => Enums.TradePolicy.D21; - public D21TradeBusiness(IFreeSql fsql, - NLogManager logManager, - IIdGenerator idGenerator, - IMemoryCache memoryCache, - DingBusiness dingBusiness, - GlobalContext globalContext) : base(fsql, logManager, idGenerator, memoryCache) + public D21TradeBusiness(IFreeSql fsql, NLogManager logManager, IIdGenerator idGenerator, IMemoryCache memoryCache, DingBusiness dingBusiness, GlobalContext globalContext) : base(fsql, logManager, idGenerator, memoryCache, dingBusiness, globalContext) { - this.dingBusiness = dingBusiness; - this.globalContext = globalContext; - validStateList = new List() - { - Enums.SpotOrderState.Filled, - Enums.SpotOrderState.PartiallyFilled, - Enums.SpotOrderState.Expired - }; } public void TrendChanged(T singalRequest, T1 robot, SymbolInfoResponse symbolInfo) where T : BaseSingalRequest where T1 : RobotResponse { + var logList = new List(); + logList.Add(new ExecutionLog() + { + Id = idGenerator.NewLong(), + RobotId = robot.Id, + CreateTime = DateTime.Now, + SourceSingal = singalRequest.SingalType, + Content = $"收到信号{singalRequest.SingalType}" + }); try { - var executionLog = new ExecutionLog() - { - Id = idGenerator.NewLong(), - RobotId = robot.Id, - CreateTime = DateTime.Now, - SourceSingal = singalRequest.SingalType, - Content = $"收到信号{singalRequest.SingalType}" - }; - fsql.Insert(executionLog).ExecuteAffrows(); + fsql.Insert(logList).ExecuteAffrows(); var d21RuningInfo = RedisHelper.Get(robot.Id.ToString()) ?? new D21RuningInfo() { RobotId = robot.Id }; d21RuningInfo.RecentSmallTrendSingal = singalRequest.SingalType; RedisHelper.Set(robot.Id.ToString(), d21RuningInfo); } catch (Exception ex) { - var stringBuilder = new StringBuilder("TrendChanged"); - stringBuilder.AppendLine($"SingalRequest {JsonConvert.SerializeObject(singalRequest)}"); - stringBuilder.AppendLine($"Robot {JsonConvert.SerializeObject(robot)}"); - logManager.GetLogger(robot.ExecuteLogKey).Error(ex, stringBuilder.ToString()); + HandleError(ex, singalRequest.SingalType, logList, robot, string.Empty); } } @@ -247,6 +230,7 @@ namespace Binance.TradeRobot.Business fsql.Transaction(() => { + fsql.Insert(logList).ExecuteAffrows(); fsql.Insert(buyOrder).ExecuteAffrows(); if (previewTradeAmount != d21Robot.D21Policy.Position) //借币失败 仓位减少 fsql.Update(d21Robot.D21Policy.Id).Set(d => d.Position, previewTradeAmount).ExecuteAffrows(); @@ -256,18 +240,19 @@ namespace Binance.TradeRobot.Business } catch (Exception ex) { - logList.Add(new ExecutionLog() - { - Id = idGenerator.NewLong(), - SourceSingal = Enums.SingalType.多交叉, - RobotId = robot.Id, - CreateTime = DateTime.Now, - Content = ex.Message - }); - try { fsql.Insert(logList).ExecuteAffrows(); } catch { } - var errorMsg = $"交易警报 {robot.ExecuteLogKey} {robot.Id} {step}"; - logManager.GetLogger(robot.ExecuteLogKey).Error(ex, errorMsg); - dingBusiness.Send($"{errorMsg} {ex.Message}"); + HandleError(ex, singalRequest.SingalType, logList, robot, step); + //logList.Add(new ExecutionLog() + //{ + // Id = idGenerator.NewLong(), + // SourceSingal = Enums.SingalType.多交叉, + // RobotId = robot.Id, + // CreateTime = DateTime.Now, + // Content = ex.Message + //}); + //try { fsql.Insert(logList).ExecuteAffrows(); } catch { } + //var errorMsg = $"交易警报,{singalRequest.SingalType},{robot.ExecuteLogKey},{robot.Id},{step}"; + //logManager.GetLogger(robot.ExecuteLogKey).Error(ex, errorMsg); + //dingBusiness.Send($"{errorMsg} {ex.Message}"); } } diff --git a/Binance.TradeRobot.Business/Extensions/RobotExtension.cs b/Binance.TradeRobot.Business/Extensions/RobotExtension.cs index bf82484..13f73a0 100644 --- a/Binance.TradeRobot.Business/Extensions/RobotExtension.cs +++ b/Binance.TradeRobot.Business/Extensions/RobotExtension.cs @@ -8,8 +8,8 @@ namespace Binance.TradeRobot.Business.Extensions private static IDictionary BusinessTypeDic = new Dictionary() { {Enums.TradePolicy.Pyramid, Enums.BusinessType.UPrep }, - { Enums.TradePolicy.D2, Enums.BusinessType.Spot_Margin}, - { Enums.TradePolicy.D21, Enums.BusinessType.Spot_Margin} + { Enums.TradePolicy.D2, Enums.BusinessType.IsolateMargin}, + { Enums.TradePolicy.D21, Enums.BusinessType.IsolateMargin} }; /// diff --git a/Binance.TradeRobot.Business/GlobalContext.cs b/Binance.TradeRobot.Business/GlobalContext.cs index 4bddd55..712611a 100644 --- a/Binance.TradeRobot.Business/GlobalContext.cs +++ b/Binance.TradeRobot.Business/GlobalContext.cs @@ -2,6 +2,7 @@ using Binance.TradeRobot.Model.Dto; using Microsoft.Extensions.DependencyInjection; using SDKAdapter.WebSockets.Market; +using SDKAdapter.WebSockets.Order.SpotOrder; using System.Collections.Generic; namespace Binance.TradeRobot.Business @@ -11,11 +12,13 @@ namespace Binance.TradeRobot.Business { private NLogManager logManager; private IDictionary spotMarketWebSocketClientDictionary; + private IDictionary spotOrderWebSocketClientDictionary; public GlobalContext(NLogManager logManager) { this.logManager = logManager; spotMarketWebSocketClientDictionary = new Dictionary(); + spotOrderWebSocketClientDictionary = new Dictionary(); } /// @@ -26,51 +29,62 @@ namespace Binance.TradeRobot.Business { if (!spotMarketWebSocketClientDictionary.TryGetValue(robot.KLineKey, out SpotMarketWebSocketClient spotMarketWebSocketClient)) { - var loggerName = $"SpotKLine-{robot.ExchangeId}-{robot.Symbol}"; - spotMarketWebSocketClient = SpotMarketWebSocketClient.Create(robot.ExchangeId, robot.Symbol, logManager.GetLogger(loggerName)); + spotMarketWebSocketClient = SpotMarketWebSocketClient.Create(robot.ExchangeId, robot.Symbol, logManager.GetLogger(robot.KLineKey)); spotMarketWebSocketClientDictionary.TryAdd(robot.KLineKey, spotMarketWebSocketClient); } - if (!spotMarketWebSocketClient.IsConnected) - spotMarketWebSocketClient.Start(); + spotMarketWebSocketClient.Start(); } /// - /// 订阅订单推送 + /// 取消订阅K线 /// /// - public void SubscribeOrderPublish(RobotResponse robot) + public void UnSubscribeKLine(RobotResponse robot) { - + //停止订阅k线 + if (spotMarketWebSocketClientDictionary.TryGetValue(robot.KLineKey, out SpotMarketWebSocketClient spotMarketWebSocketClient)) + spotMarketWebSocketClient.Stop(); } /// - /// 取消订阅K线 + /// 订阅订单推送 /// /// - public void UnSubscribeKLine(RobotResponse robot) + public void SubscribeOrderPublish(RobotResponse robot) { - //停止订阅k线 - if (spotMarketWebSocketClientDictionary.TryGetValue(robot.KLineKey, out SpotMarketWebSocketClient spotMarketWebSocketClient)) - spotMarketWebSocketClient.Stop(); + if (!spotOrderWebSocketClientDictionary.TryGetValue(robot.OrderPublishListenKey, out SpotOrderWebSocketClient spotOrderWebSocketClient)) + { + spotOrderWebSocketClient = SpotOrderWebSocketClient.Create(robot.BusinessType, + robot.ExchangeId, + robot.ExchangeAPIKey.AccountId, + robot.ExchangeAPIKey.APIKey, + robot.ExchangeAPIKey.SecretKey, + logManager.GetLogger(robot.OrderPublishListenKey), + (e) => { }); + } + spotOrderWebSocketClient.Start(); } + + /// /// 取消订阅订单推送 /// /// public void UnSubscribeOrderPublish(RobotResponse robot) { - + if (spotOrderWebSocketClientDictionary.TryGetValue(robot.OrderPublishListenKey, out SpotOrderWebSocketClient spotOrderWebSocketClient)) + spotOrderWebSocketClient.Stop(); } /// /// 获取指定交易对现货最新成交价 /// - /// + /// /// - public decimal? GetSpotNewestPrice(string key) + public decimal? GetSpotNewestPrice(string kLineKey) { - if (spotMarketWebSocketClientDictionary.TryGetValue(key, out SpotMarketWebSocketClient spotMarketWebSocketClient)) + if (spotMarketWebSocketClientDictionary.TryGetValue(kLineKey, out SpotMarketWebSocketClient spotMarketWebSocketClient)) return spotMarketWebSocketClient.NewestPrice; return null; } diff --git a/Binance.TradeRobot.Model/Base/Enums.cs b/Binance.TradeRobot.Model/Base/Enums.cs index d62ec66..6456698 100644 --- a/Binance.TradeRobot.Model/Base/Enums.cs +++ b/Binance.TradeRobot.Model/Base/Enums.cs @@ -42,13 +42,13 @@ namespace Binance.TradeRobot.Model.Base public enum BusinessType { /// - /// 币币 + /// 币币(现货) /// Spot = 0, /// /// 逐仓杠杆 /// - Spot_Margin = 1, + IsolateMargin = 1, /// /// U本位合约 /// @@ -135,9 +135,13 @@ namespace Binance.TradeRobot.Model.Base /// Rejected, /// - /// 交易引擎取消 没有完全成交 + /// 订单过期 /// - Expired + Expired, + /// + /// 未知状态 + /// + Unknow } /// diff --git a/Binance.TradeRobot.Model/Binance.TradeRobot.Model.xml b/Binance.TradeRobot.Model/Binance.TradeRobot.Model.xml index 4af71c6..dfa9409 100644 --- a/Binance.TradeRobot.Model/Binance.TradeRobot.Model.xml +++ b/Binance.TradeRobot.Model/Binance.TradeRobot.Model.xml @@ -49,7 +49,7 @@ 币币 - + 逐仓杠杆 @@ -126,7 +126,12 @@ - 交易引擎取消 没有完全成交 + 订单过期 + + + + + 未知状态 diff --git a/Binance.TradeRobot.Model/Dto/Response/Robot/RobotResponse.cs b/Binance.TradeRobot.Model/Dto/Response/Robot/RobotResponse.cs index 8611609..bda1ada 100644 --- a/Binance.TradeRobot.Model/Dto/Response/Robot/RobotResponse.cs +++ b/Binance.TradeRobot.Model/Dto/Response/Robot/RobotResponse.cs @@ -22,9 +22,40 @@ namespace Binance.TradeRobot.Model.Dto public Enums.Exchange ExchangeId { get; set; } - public virtual string ExecuteLogKey { get { return $"Execute-{ExchangeId}-{TradePolicy}-{Symbol}"; } } + public virtual string ExecuteKey { get { return $"Execute-{ExchangeId}-{TradePolicy}-{Symbol}"; } } - public virtual string KLineKey { get { return $"KLine-{ExchangeId}-{Symbol}"; } } + public virtual string KLineKey { get { return $"KLine-{ExchangeId}-{BusinessType}-{Symbol}"; } } + + /// + /// 订单推送监听实例Key + /// + public virtual string OrderPublishListenKey + { + get + { + string key = string.Empty; + if (ExchangeId == Enums.Exchange.Binance) + { + if (BusinessType == Enums.BusinessType.IsolateMargin) + key = $"{BusinessType}-{ExchangeAPIKey.AccountId}-{Symbol}"; //币安逐仓杠杆,同一个账户内的每个交易对需要区分websocket实例 + else + key = $"{BusinessType}-{ExchangeAPIKey.AccountId}"; //币安现货,币安合约,同一个账户内不区分websocket实例 + } + + return $"OrderPublish(Origin)-{ExchangeId}-{key}"; + } + } + + /// + /// 订单推送日志Key + /// + public virtual string OrderPublishLogKey + { + get + { + return $"OrderPublish-{ExchangeId}-{BusinessType}-{Symbol}"; + } + } public SymbolInfoResponse SymbolInfo { get; set; } diff --git a/SDKAdapter/APIClient/BaseAPIClient.cs b/SDKAdapter/APIClient/BaseAPIClient.cs index 00a142a..c9715bb 100644 --- a/SDKAdapter/APIClient/BaseAPIClient.cs +++ b/SDKAdapter/APIClient/BaseAPIClient.cs @@ -7,10 +7,10 @@ namespace SDKAdapter.APIClient { public class BaseAPIClient { - public static BaseAPIClient Create(Enums.Exchange exchange, long uid, string apiKey, string secret) + public static BaseAPIClient Create(Enums.Exchange exchange, long accountId, string apiKey, string secret) { if (exchange == Enums.Exchange.Binance) - return new BinanceAPIClient(uid, apiKey, secret); + return new BinanceAPIClient(accountId, apiKey, secret); return null; } @@ -18,9 +18,9 @@ namespace SDKAdapter.APIClient protected string ApiKey { get; private set; } protected string Secret { get; private set; } - public BaseAPIClient(long uid, string apiKey, string secret) + public BaseAPIClient(long accountId, string apiKey, string secret) { - this.AccountId = uid; + this.AccountId = accountId; this.ApiKey = apiKey; this.Secret = secret; } diff --git a/SDKAdapter/APIClient/BinanceAPIClient.cs b/SDKAdapter/APIClient/BinanceAPIClient.cs index a16a1a2..9d1da6c 100644 --- a/SDKAdapter/APIClient/BinanceAPIClient.cs +++ b/SDKAdapter/APIClient/BinanceAPIClient.cs @@ -13,7 +13,7 @@ namespace SDKAdapter.APIClient { private BinanceClient binanceClient; - public BinanceAPIClient(long uid, string apiKey, string secret) : base(uid, apiKey, secret) + public BinanceAPIClient(long accountId, string apiKey, string secret) : base(accountId, apiKey, secret) { var spotClientOption = new BinanceApiClientOptions() { @@ -116,7 +116,7 @@ namespace SDKAdapter.APIClient var binanceOrderSite = (Binance.Net.Enums.OrderSide)(int)tradeDirection; var binanceOrderType = (Binance.Net.Enums.SpotOrderType)(int)orderType; Binance.Net.Enums.TimeInForce? timeInForce = null; - if (orderType == Enums.OrderType.STOP_LOSS_LIMIT) + if (orderType == Enums.OrderType.LIMIT || orderType == Enums.OrderType.STOP_LOSS_LIMIT) timeInForce = Binance.Net.Enums.TimeInForce.GoodTillCanceled; var r = binanceClient.SpotApi.Trading.PlaceMarginOrderAsync(symbol, diff --git a/SDKAdapter/Model/SpotOrderTradePublishInfo.cs b/SDKAdapter/Model/SpotOrderTradePublishInfo.cs new file mode 100644 index 0000000..5eced01 --- /dev/null +++ b/SDKAdapter/Model/SpotOrderTradePublishInfo.cs @@ -0,0 +1,44 @@ +using Binance.TradeRobot.Model.Base; +using System; +using System.Collections.Generic; +using System.Text; + +namespace SDKAdapter.Model +{ + public class SpotOrderTradePublishInfo + { + public Enums.Exchange Exchange { get; set; } + + public long AccountId { get; set; } + + public long OrderId { get; set; } + + public string ClientOrderId { get; set; } + + public string Symbol { get; set; } + + public Enums.TradeDirection TradeDirection { get; set; } + + public Enums.OrderType OrderType { get; set; } + + public Enums.SpotOrderState SpotOrderState { get; set; } + + public decimal LastTradePrice { get; set; } + + public decimal LastTradeAmount { get; set; } + + public decimal LastTradeQuantity { get; set; } + + public decimal Fee { get; set; } + + public string FeeUnit { get; set; } + + public decimal CummulativeTradeAmount { get; set; } + + public decimal CummulativeTradeQuantity { get; set; } + + public DateTime CreateTime { get; set; } + + public DateTime LastTradeTime { get; set; } + } +} diff --git a/SDKAdapter/SDKAdapter.csproj b/SDKAdapter/SDKAdapter.csproj index 8f07c50..80ec790 100644 --- a/SDKAdapter/SDKAdapter.csproj +++ b/SDKAdapter/SDKAdapter.csproj @@ -6,7 +6,7 @@ - + diff --git a/SDKAdapter/WebSockets/Market/BinanceSpotMarketWebSocketClient.cs b/SDKAdapter/WebSockets/Market/BinanceSpotMarketWebSocketClient.cs index 687c518..4a9d1a9 100644 --- a/SDKAdapter/WebSockets/Market/BinanceSpotMarketWebSocketClient.cs +++ b/SDKAdapter/WebSockets/Market/BinanceSpotMarketWebSocketClient.cs @@ -6,30 +6,33 @@ namespace SDKAdapter.WebSockets.Market { public class BinanceSpotMarketWebSocketClient : SpotMarketWebSocketClient { - private BinanceSocketClient client; + private BinanceSocketClient binanceSocketClient; private CancellationTokenSource cancellationTokenSource; public BinanceSpotMarketWebSocketClient(string symbol, NLog.ILogger logger) : base(symbol, logger) { - client = new BinanceSocketClient(); - + binanceSocketClient = new BinanceSocketClient(); } public override void Start() { + if (IsConnected) + return; + IsConnected = true; cancellationTokenSource = new CancellationTokenSource(); - client.SpotStreams.SubscribeToKlineUpdatesAsync(Symbol, KlineInterval.OneMinute, (e) => + binanceSocketClient.SpotStreams.SubscribeToKlineUpdatesAsync(Symbol, KlineInterval.OneMinute, (e) => { base.OnReceived(e.Data.Data.ClosePrice); }, cancellationTokenSource.Token); - base.Start(); } public override void Stop() { + if (!IsConnected) + return; + IsConnected = false; cancellationTokenSource.Cancel(); - client.SpotStreams.Dispose(); - base.Stop(); + binanceSocketClient.SpotStreams.Dispose(); cancellationTokenSource = null; } } diff --git a/SDKAdapter/WebSockets/Market/SpotMarketWebSocketClient.cs b/SDKAdapter/WebSockets/Market/SpotMarketWebSocketClient.cs index 9ed4e7a..ffab12e 100644 --- a/SDKAdapter/WebSockets/Market/SpotMarketWebSocketClient.cs +++ b/SDKAdapter/WebSockets/Market/SpotMarketWebSocketClient.cs @@ -5,13 +5,6 @@ namespace SDKAdapter.WebSockets.Market { public class SpotMarketWebSocketClient { - public static SpotMarketWebSocketClient Create(Enums.Exchange exchange, string symbol, NLog.ILogger logger) - { - if (exchange == Enums.Exchange.Binance) - return new BinanceSpotMarketWebSocketClient(symbol, logger); - return null; - } - /// /// 更新间隔(ms) /// @@ -34,7 +27,14 @@ namespace SDKAdapter.WebSockets.Market public NLog.ILogger logger { get; private set; } - public bool IsConnected { get; protected set; } + protected bool IsConnected { get; set; } + + public static SpotMarketWebSocketClient Create(Enums.Exchange exchange, string symbol, NLog.ILogger logger) + { + if (exchange == Enums.Exchange.Binance) + return new BinanceSpotMarketWebSocketClient(symbol, logger); + return null; + } public SpotMarketWebSocketClient(string symbol, NLog.ILogger logger) { diff --git a/SDKAdapter/WebSockets/Order/SpotOrder/BinanceSpotOrderWebSocketClient.cs b/SDKAdapter/WebSockets/Order/SpotOrder/BinanceSpotOrderWebSocketClient.cs new file mode 100644 index 0000000..a11f6ad --- /dev/null +++ b/SDKAdapter/WebSockets/Order/SpotOrder/BinanceSpotOrderWebSocketClient.cs @@ -0,0 +1,112 @@ +using Binance.Net.Clients; +using Binance.Net.Objects; +using Binance.TradeRobot.Model.Base; +using CryptoExchange.Net.Authentication; +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Threading; + +namespace SDKAdapter.WebSockets.Order.SpotOrder +{ + public class BinanceSpotOrderWebSocketClient : SpotOrderWebSocketClient + { + private BinanceSocketClient binanceSocketClient; + private BinanceClient binanceClient; + private CancellationTokenSource cancellationTokenSource; + private string listenKey; + private IList unSupportStateList; + + public BinanceSpotOrderWebSocketClient(Enums.BusinessType businessType, long accountId, string apiKey, string secret, NLog.ILogger logger) + : base(businessType, accountId, apiKey, secret, logger) + { + 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; + unSupportStateList = new List() + { + Binance.Net.Enums.OrderStatus.PendingCancel, + 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) => + { + logger.Info(JsonConvert.SerializeObject(e.Data)); + if (unSupportStateList.Contains(e.Data.Status)) + return; + OnOrderUpdated?.Invoke(new Model.SpotOrderTradePublishInfo() + { + OrderId = e.Data.Id, + Symbol = e.Data.Symbol, + AccountId = this.AccountId, + OrderType = (Enums.OrderType)(int)e.Data.Type, + SpotOrderState = (Enums.SpotOrderState)(int)e.Data.Status, + 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 + }); + }, + (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; + } + } +} diff --git a/SDKAdapter/WebSockets/Order/SpotOrder/SpotOrderWebSocketClient.cs b/SDKAdapter/WebSockets/Order/SpotOrder/SpotOrderWebSocketClient.cs new file mode 100644 index 0000000..bc918c7 --- /dev/null +++ b/SDKAdapter/WebSockets/Order/SpotOrder/SpotOrderWebSocketClient.cs @@ -0,0 +1,45 @@ +using Binance.TradeRobot.Model.Base; +using SDKAdapter.Model; +using System; + +namespace SDKAdapter.WebSockets.Order.SpotOrder +{ + public class SpotOrderWebSocketClient + { + protected long AccountId { get; private set; } + protected string ApiKey { get; private set; } + protected string Secret { get; private set; } + protected NLog.ILogger logger { get; private set; } + protected bool IsConnected { get; set; } + protected Enums.BusinessType BusinessType { get; private set; } + + public Action OnOrderUpdated { get; private set; } + + + public static SpotOrderWebSocketClient Create(Enums.BusinessType businessType, Enums.Exchange exchange, long accountId, string apiKey, string secret, NLog.ILogger logger, Action onOrderUpdated) + { + if (exchange == Enums.Exchange.Binance) + return new BinanceSpotOrderWebSocketClient(businessType, accountId, apiKey, secret, logger); + return null; + } + + public SpotOrderWebSocketClient(Enums.BusinessType businessType, long accountId, string apiKey, string secret, NLog.ILogger logger) + { + this.BusinessType = businessType; + this.AccountId = accountId; + this.ApiKey = apiKey; + this.Secret = secret; + this.logger = logger; + } + + public virtual void Start(string symbol = "") + { + + } + + public virtual void Stop(string symbol = "") + { + + } + } +} diff --git a/SDKTestConsole/NLog.config b/SDKTestConsole/NLog.config new file mode 100644 index 0000000..1a5b523 --- /dev/null +++ b/SDKTestConsole/NLog.config @@ -0,0 +1,14 @@ + + + + + + + + + + \ No newline at end of file diff --git a/SDKTestConsole/Program.cs b/SDKTestConsole/Program.cs index 35a7178..d855305 100644 --- a/SDKTestConsole/Program.cs +++ b/SDKTestConsole/Program.cs @@ -5,6 +5,9 @@ using CryptoExchange.Net.Authentication; using Newtonsoft.Json; using SDKAdapter.APIClient; using System; +using Binance.TradeRobot.Common.Extensions; +using SDKAdapter.WebSockets.Order.SpotOrder; +using NLog; namespace SDKTestConsole { @@ -50,8 +53,14 @@ namespace SDKTestConsole SpotApiOptions = spotClientOption }); + var binanceSocketClient = new BinanceSocketClient(); //市价买币 + //var r = client.IsolatedMarginPlaceOrder("ETHUSDT", + // Enums.TradeDirection.Buy, + // Enums.OrderType.MARKET, + // quoteAmount: 20M); + //var r = binanceClient.SpotApi.Trading.PlaceMarginOrderAsync("ETHUSDT", // Binance.Net.Enums.OrderSide.Buy, // Binance.Net.Enums.SpotOrderType.Market, @@ -61,9 +70,15 @@ namespace SDKTestConsole // //stopPrice: 1899M, // isIsolated: true, // orderResponseType: Binance.Net.Enums.OrderResponseType.Full).Result; - //var s = JsonConvert.SerializeObject(r); + // //市价卖币 + //var qty = 0.00985570M.CutDecimal(4); + //var r = client.IsolatedMarginPlaceOrder("ETHUSDT", + // Enums.TradeDirection.Sell, + // Enums.OrderType.MARKET, + // quantity: qty); + //var r = binanceClient.SpotApi.Trading.PlaceMarginOrderAsync("ETHUSDT", // Binance.Net.Enums.OrderSide.Sell, // Binance.Net.Enums.SpotOrderType.Market, @@ -74,38 +89,51 @@ namespace SDKTestConsole // //stopPrice: 1899M, // isIsolated: true, // orderResponseType: Binance.Net.Enums.OrderResponseType.Full).Result; - //var s = JsonConvert.SerializeObject(r); + //止损卖币 - var r = binanceClient.SpotApi.Trading.PlaceMarginOrderAsync("ETHUSDT", - Binance.Net.Enums.OrderSide.Sell, - Binance.Net.Enums.SpotOrderType.StopLossLimit, - quantity: 0.0149M, - price: 1699M, - //quoteQuantity: 20M, //报价币金额 - //quantity: 100M, - timeInForce: Binance.Net.Enums.TimeInForce.GoodTillCanceled, - stopPrice: 1699M, - isIsolated: true, - orderResponseType: Binance.Net.Enums.OrderResponseType.Full).Result; - - var s = JsonConvert.SerializeObject(r); - Console.WriteLine(s); - - var r1 = binanceClient.SpotApi.Trading.PlaceMarginOrderAsync("ETHUSDT", - Binance.Net.Enums.OrderSide.Sell, - Binance.Net.Enums.SpotOrderType.StopLossLimit, - quantity: 0.0149M, - price: 1699M, - //quoteQuantity: 20M, //报价币金额 - //quantity: 100M, - timeInForce: Binance.Net.Enums.TimeInForce.GoodTillCanceled, - stopPrice: 1699M, - isIsolated: true, - orderResponseType: Binance.Net.Enums.OrderResponseType.Full).Result; - var s1 = JsonConvert.SerializeObject(r1); - Console.WriteLine(s1); + //var qty = 0.01224350M.CutDecimal(4); + //var r = client.IsolatedMarginPlaceOrder("ETHUSDT", + // Enums.TradeDirection.Sell, + // Enums.OrderType.STOP_LOSS_LIMIT, + // quantity: qty, + // price: 2033M, + // stopPrice: 2035M); + + + + //var r = binanceClient.SpotApi.Trading.PlaceMarginOrderAsync("ETHUSDT", + // Binance.Net.Enums.OrderSide.Sell, + // Binance.Net.Enums.SpotOrderType.StopLossLimit, + // quantity: 0.0149M, + // price: 1699M, + // //quoteQuantity: 20M, //报价币金额 + // //quantity: 100M, + // timeInForce: Binance.Net.Enums.TimeInForce.GoodTillCanceled, + // stopPrice: 1699M, + // isIsolated: true, + // orderResponseType: Binance.Net.Enums.OrderResponseType.Full).Result; + // + //var s = JsonConvert.SerializeObject(r); + //Console.WriteLine(s); + // + //var r1 = binanceClient.SpotApi.Trading.PlaceMarginOrderAsync("ETHUSDT", + // Binance.Net.Enums.OrderSide.Sell, + // Binance.Net.Enums.SpotOrderType.StopLossLimit, + // quantity: 0.0149M, + // price: 1699M, + // //quoteQuantity: 20M, //报价币金额 + // //quantity: 100M, + // timeInForce: Binance.Net.Enums.TimeInForce.GoodTillCanceled, + // stopPrice: 1699M, + // isIsolated: true, + // orderResponseType: Binance.Net.Enums.OrderResponseType.Full).Result; + //var s1 = JsonConvert.SerializeObject(r1); + //var s = JsonConvert.SerializeObject(r); + //Console.WriteLine(s); + //var orderClient = new BinanceSpotOrderWebSocketClient(Enums.BusinessType.IsolateMargin, 0, apiKey, secret, LogManager.GetCurrentClassLogger()); + //orderClient.Start("ETHUSDT"); Console.ReadKey(); } } diff --git a/SDKTestConsole/SDKTestConsole.csproj b/SDKTestConsole/SDKTestConsole.csproj index 40d2d0d..67cba98 100644 --- a/SDKTestConsole/SDKTestConsole.csproj +++ b/SDKTestConsole/SDKTestConsole.csproj @@ -5,11 +5,25 @@ netcoreapp3.1 + + + + + + + PreserveNewest + true + PreserveNewest + + + + +