From 3def6fc74781c175a90f095efdf3c94016998a7d Mon Sep 17 00:00:00 2001 From: shanji <18996038927@163.com> Date: Mon, 16 May 2022 18:20:54 +0800 Subject: [PATCH] =?UTF-8?q?=E5=93=8D=E5=BA=94=E5=A4=9A=E4=BA=A4=E5=8F=89?= =?UTF-8?q?=E4=BF=A1=E5=8F=B7=EF=BC=8C=E7=A9=BA=E4=BA=A4=E5=8F=89=E4=BF=A1?= =?UTF-8?q?=E5=8F=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Binance.TradeRobot.API/Startup.cs | 1 + .../Business/RobotBusiness.cs | 22 ++- .../Business/SingalBusiness.cs | 30 +++- .../TradeBusiness/BaseTradeBusiness.cs | 7 + .../TradeBusiness/D21TradeBusiness.cs | 130 +++++++++++++++--- .../TaskSchedulerManager.cs | 17 +++ .../Dto/Response/Robot/RobotResponse.cs | 13 +- 7 files changed, 183 insertions(+), 37 deletions(-) create mode 100644 Binance.TradeRobot.Business/TaskSchedulerManager.cs diff --git a/Binance.TradeRobot.API/Startup.cs b/Binance.TradeRobot.API/Startup.cs index 1238fa8..7f2c013 100644 --- a/Binance.TradeRobot.API/Startup.cs +++ b/Binance.TradeRobot.API/Startup.cs @@ -33,6 +33,7 @@ namespace Binance.TradeRobot.API services.AddMemoryCache(); services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); var fsql = new FreeSql.FreeSqlBuilder().UseConnectionString(FreeSql.DataType.MySql, Configuration.GetConnectionString("DB")).Build(); services.AddSingleton(typeof(IFreeSql), fsql); diff --git a/Binance.TradeRobot.Business/Business/RobotBusiness.cs b/Binance.TradeRobot.Business/Business/RobotBusiness.cs index 5e94b85..85dc49d 100644 --- a/Binance.TradeRobot.Business/Business/RobotBusiness.cs +++ b/Binance.TradeRobot.Business/Business/RobotBusiness.cs @@ -64,14 +64,21 @@ namespace Binance.TradeRobot.Business var robot = GetRobotList(robotId).FirstOrDefault(); fsql.Update(robotId).Set(r => r.State, Enums.RobotState.Stop).ExecuteAffrows(); + //取消监听K线和订单 var sameRunningCount = GetRobotList(symbol: robot.Symbol, robotState: Enums.RobotState.Runing, exchange: robot.ExchangeId).Count(); if (sameRunningCount == 0) - { - //取消监听K线和订单 globalContext.UnSubscribeKLine(robot); - } - + //取消订单监听 + var symbolParam = string.Empty; + if (robot.ExchangeId == Enums.Exchange.Binance && robot.BusinessType == Enums.BusinessType.IsolateMargin) + symbolParam = robot.Symbol; + var sameRunningCount2 = GetRobotList(symbol: symbolParam, + robotState: Enums.RobotState.Runing, + exchange: robot.ExchangeId, + accountId: robot.ExchangeAPIKey.AccountId).Count(); + if (sameRunningCount2 == 0) + globalContext.UnSubscribeOrderPublish(robot); } /// @@ -156,11 +163,13 @@ namespace Binance.TradeRobot.Business /// /// /// + /// 交易所账号Id /// public IList GetRobotList(long? robotId = null, string symbol = "", Enums.RobotState? robotState = null, - Enums.Exchange? exchange = null) + Enums.Exchange? exchange = null, + long? accountId = null) { var select = fsql.Select().InnerJoin((r, e) => r.Id == e.RobotId); if (robotId != null) @@ -168,7 +177,8 @@ namespace Binance.TradeRobot.Business else select = select.WhereIf(!string.IsNullOrEmpty(symbol), (r, e) => r.Symbol == symbol) .WhereIf(robotState != null, (r, e) => r.State == robotState) - .WhereIf(exchange != null, (r, e) => r.ExchangeId == exchange); + .WhereIf(exchange != null, (r, e) => r.ExchangeId == exchange) + .WhereIf(accountId != null, (r, e) => e.AccountId == accountId); return select.ToList((r, e) => new Robot() { diff --git a/Binance.TradeRobot.Business/Business/SingalBusiness.cs b/Binance.TradeRobot.Business/Business/SingalBusiness.cs index 6b8bc5a..4b4edfc 100644 --- a/Binance.TradeRobot.Business/Business/SingalBusiness.cs +++ b/Binance.TradeRobot.Business/Business/SingalBusiness.cs @@ -6,6 +6,7 @@ using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.DependencyInjection; using System.Collections.Generic; using System.Linq; +using System.Threading; using System.Threading.Tasks; using Yitter.IdGenerator; @@ -17,6 +18,7 @@ namespace Binance.TradeRobot.Business private RobotBusiness robotBusiness; private ExchangeBusiness exchangeBusiness; private IEnumerable tradeBusinessList; + private TaskSchedulerManager taskSchedulerManager; public SingalBusiness(IFreeSql fsql, NLogManager logManager, @@ -24,21 +26,28 @@ namespace Binance.TradeRobot.Business IMemoryCache memoryCache, RobotBusiness robotBusiness, ExchangeBusiness exchangeBusiness, - IEnumerable tradeBusinessList) : base(fsql, logManager, idGenerator, memoryCache) + IEnumerable tradeBusinessList, + TaskSchedulerManager taskSchedulerManager) : base(fsql, logManager, idGenerator, memoryCache) { this.robotBusiness = robotBusiness; this.exchangeBusiness = exchangeBusiness; this.tradeBusinessList = tradeBusinessList; + this.taskSchedulerManager = taskSchedulerManager; } public void D21Singal(D21SingalRequest d21SingalRequest) { //logManager.GetLogger("D21").Info(JsonConvert.SerializeObject(d21SingalRequest)); - var robotList = robotBusiness.GetD21PolicyRobotList(Enums.RobotState.Runing, d21SingalRequest.KLinePeriodic, d21SingalRequest.Symbol, false, true); + var robotList = robotBusiness.GetD21PolicyRobotList(Enums.RobotState.Runing, + d21SingalRequest.KLinePeriodic, + d21SingalRequest.Symbol, + false, + true); if (robotList == null || robotList.Count() == 0) throw new BusinessException("未找到符合条件的机器人"); - var symbolInfo = exchangeBusiness.GetSymbol(robotList[0].ExchangeId, robotList[0].Symbol); + var symbolInfo = exchangeBusiness.GetSymbol(robotList[0].ExchangeId, + robotList[0].Symbol); if (symbolInfo == null) throw new BusinessException($"未找到交易对{robotList[0].Symbol}({robotList[0].ExchangeId})"); var d21TradeBusiness = tradeBusinessList.FirstOrDefault(t => t.TradePolicy == Enums.TradePolicy.D21); @@ -49,13 +58,22 @@ namespace Binance.TradeRobot.Business { case Enums.SingalType.小趋势看空: case Enums.SingalType.小趋势看多: - Task.Factory.StartNew(() => d21TradeBusiness.TrendChanged(d21SingalRequest, robot, symbolInfo)); + Task.Factory.StartNew(() => d21TradeBusiness.TrendChanged(d21SingalRequest, robot, symbolInfo), + CancellationToken.None, + TaskCreationOptions.LongRunning, + taskSchedulerManager.SingalTaskScheduler); break; case Enums.SingalType.多交叉: - + Task.Factory.StartNew(() => d21TradeBusiness.LongCross(d21SingalRequest, robot, false, symbolInfo), + CancellationToken.None, + TaskCreationOptions.LongRunning, + taskSchedulerManager.SingalTaskScheduler); break; case Enums.SingalType.空交叉: - + Task.Factory.StartNew(() => d21TradeBusiness.ShortCross(d21SingalRequest, robot, false, symbolInfo), + CancellationToken.None, + TaskCreationOptions.LongRunning, + taskSchedulerManager.SingalTaskScheduler); break; } } diff --git a/Binance.TradeRobot.Business/Business/TradeBusiness/BaseTradeBusiness.cs b/Binance.TradeRobot.Business/Business/TradeBusiness/BaseTradeBusiness.cs index 0d0e18f..a3a5a4a 100644 --- a/Binance.TradeRobot.Business/Business/TradeBusiness/BaseTradeBusiness.cs +++ b/Binance.TradeRobot.Business/Business/TradeBusiness/BaseTradeBusiness.cs @@ -40,5 +40,12 @@ namespace Binance.TradeRobot.Business logManager.GetLogger(robot.ExecuteKey).Error(ex, errorMsg); dingBusiness.Send($"{errorMsg} {ex.Message}"); } + + protected string CreateClientOrderId(long robotId) + { + var guid = Guid.NewGuid(); + var random = new Random(guid.GetHashCode()); + return $"{Convert.ToChar(random.Next(97, 123))}{guid.ToString().Substring(0, 4)}_{robotId}"; + } } } diff --git a/Binance.TradeRobot.Business/Business/TradeBusiness/D21TradeBusiness.cs b/Binance.TradeRobot.Business/Business/TradeBusiness/D21TradeBusiness.cs index 712df47..4fc8feb 100644 --- a/Binance.TradeRobot.Business/Business/TradeBusiness/D21TradeBusiness.cs +++ b/Binance.TradeRobot.Business/Business/TradeBusiness/D21TradeBusiness.cs @@ -1,16 +1,15 @@ using Binance.TradeRobot.Business.Extensions; using Binance.TradeRobot.Common.DI; +using Binance.TradeRobot.Common.Extensions; using Binance.TradeRobot.Model.Base; using Binance.TradeRobot.Model.Db; using Binance.TradeRobot.Model.Dto; using Binance.TradeRobot.Model.RuningInfo; using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.DependencyInjection; -using Newtonsoft.Json; using System; using System.Collections.Generic; using System.Linq; -using System.Text; using System.Threading; using Yitter.IdGenerator; @@ -57,7 +56,7 @@ namespace Binance.TradeRobot.Business logList.Add(new ExecutionLog() { Id = idGenerator.NewLong(), - SourceSingal = Enums.SingalType.多交叉, + SourceSingal = singalRequest.SingalType, RobotId = robot.Id, CreateTime = DateTime.Now, Content = $"收到信号{singalRequest.SingalType}{(isRemedy ? "(补救)" : string.Empty)}" @@ -210,10 +209,12 @@ namespace Binance.TradeRobot.Business #region 下单 step = "下单"; + var clientOrderId = CreateClientOrderId(robot.Id); var orderId = apiClient.IsolatedMarginPlaceOrder(robot.Symbol, Enums.TradeDirection.Buy, Enums.OrderType.MARKET, - quoteAmount: previewTradeAmount); + quoteAmount: previewTradeAmount, + newClientOrderId: clientOrderId); var buyOrder = new SpotOrder() { Id = orderId, @@ -228,6 +229,14 @@ namespace Binance.TradeRobot.Business TradeDirection = Enums.TradeDirection.Buy }; + logList.Add(new ExecutionLog() + { + Id = idGenerator.NewLong(), + SourceSingal = singalRequest.SingalType, + RobotId = robot.Id, + CreateTime = DateTime.Now, + Content = $"市价买单挂单成功,orderId:{orderId}" + }); fsql.Transaction(() => { fsql.Insert(logList).ExecuteAffrows(); @@ -241,24 +250,113 @@ namespace Binance.TradeRobot.Business catch (Exception ex) { 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}"); } } public void ShortCross(T singalRequest, T1 robot, bool isRemedy, SymbolInfoResponse symbolInfo) where T : BaseSingalRequest where T1 : RobotResponse { + string step = string.Empty; + var logList = new List(); + logList.Add(new ExecutionLog() + { + Id = idGenerator.NewLong(), + SourceSingal = singalRequest.SingalType, + RobotId = robot.Id, + CreateTime = DateTime.Now, + Content = $"收到信号{singalRequest.SingalType}{(isRemedy ? "(补救)" : string.Empty)}" + }); + + try + { + var d21RuningInfo = RedisHelper.Get(robot.Id.ToString()) ?? new D21RuningInfo() { RobotId = robot.Id }; + + #region 验证信号 + step = "验证信号"; + if (!isRemedy) + { + if (d21RuningInfo.ErrorCrossSingal != null) + { + d21RuningInfo.ErrorCrossSingal = null; + d21RuningInfo.ErrorCrossSingalTime = 0; + RedisHelper.Set(robot.Id.ToString(), d21RuningInfo); + throw new BusinessException("前一个信号错误,终止空交叉信号执行"); + } + + if (d21RuningInfo.RecentSmallTrendSingal == null) + throw new BusinessException("缺少小趋势,终止空交叉信号执行"); + + if (d21RuningInfo.RecentSmallTrendSingal == Enums.SingalType.小趋势看多) + { + var errorTimeStamp = DateTime.Now.GetKID(singalRequest.KLinePeriodic, false); + Thread.Sleep(5000); //防止空交叉和小趋势看空同时接收造成误判 + d21RuningInfo = RedisHelper.Get(robot.Id.ToString()) ?? new D21RuningInfo() { RobotId = robot.Id }; + if (d21RuningInfo.RecentSmallTrendSingal == Enums.SingalType.小趋势看多) + { + d21RuningInfo.ErrorCrossSingal = singalRequest.SingalType; + d21RuningInfo.ErrorCrossSingalTime = errorTimeStamp; + RedisHelper.Set(robot.Id.ToString(), d21RuningInfo); + throw new BusinessException("小趋势看多,终止空交叉信号执行"); + } + } + } + #endregion + #region 验证卖币数量 + step = "验证卖币数量"; + var saleQuantity = robot.RobotAccount.SpotCurrencyQuantity.CutDecimal(symbolInfo.SaleQuantityAccuracy); + if (saleQuantity == 0M) + throw new BusinessException("没有足够的卖币数量"); + #endregion + + #region 下单 + step = "下单"; + var newestPrice = globalContext.GetSpotNewestPrice(robot.KLineKey) ?? singalRequest.ClosePrice; + var apiClient = GetBaseAPIClient(robot.ExchangeId, robot.ExchangeAPIKey.AccountId, robot.ExchangeAPIKey.APIKey, robot.ExchangeAPIKey.SecretKey); + + var clientOrderId = CreateClientOrderId(robot.Id); + var orderId = apiClient.IsolatedMarginPlaceOrder(robot.Symbol, + Enums.TradeDirection.Sell, + Enums.OrderType.MARKET, + quantity: saleQuantity, + newClientOrderId: clientOrderId); + + var sellOrder = new SpotOrder() + { + Id = orderId, + CreateTime = DateTime.Now, + ExchangeId = robot.ExchangeId, + OrderType = Enums.OrderType.MARKET, + PolicyType = Enums.TradePolicy.D21, + RobotId = robot.Id, + State = Enums.SpotOrderState.Created, + Symbol = robot.Symbol, + TradeDirection = Enums.TradeDirection.Sell + }; + + logList.Add(new ExecutionLog() + { + Id = idGenerator.NewLong(), + SourceSingal = singalRequest.SingalType, + RobotId = robot.Id, + CreateTime = DateTime.Now, + Content = $"市价卖单挂单成功,orderId:{orderId}" + }); + fsql.Transaction(() => + { + fsql.Insert(logList).ExecuteAffrows(); + fsql.Insert(sellOrder).ExecuteAffrows(); + }); + #endregion + + #region 更新空交叉卖出成功时的成交价 + d21RuningInfo.RecentShortCrossSignalTradePrice = newestPrice; + RedisHelper.Set(robot.Id.ToString(), d21RuningInfo); + #endregion + } + catch (Exception ex) + { + HandleError(ex, singalRequest.SingalType, logList, robot, step); + } } } } diff --git a/Binance.TradeRobot.Business/TaskSchedulerManager.cs b/Binance.TradeRobot.Business/TaskSchedulerManager.cs new file mode 100644 index 0000000..bb2f20d --- /dev/null +++ b/Binance.TradeRobot.Business/TaskSchedulerManager.cs @@ -0,0 +1,17 @@ +using Binance.TradeRobot.Common.Tasks; + +namespace Binance.TradeRobot.Business +{ + public class TaskSchedulerManager + { + public LimitedConcurrencyLevelTaskScheduler SingalTaskScheduler { get; private set; } + + public LimitedConcurrencyLevelTaskScheduler OrderPublishTaskScheduler { get; private set; } + + public TaskSchedulerManager() + { + SingalTaskScheduler = new LimitedConcurrencyLevelTaskScheduler(10); + OrderPublishTaskScheduler = new LimitedConcurrencyLevelTaskScheduler(10); + } + } +} diff --git a/Binance.TradeRobot.Model/Dto/Response/Robot/RobotResponse.cs b/Binance.TradeRobot.Model/Dto/Response/Robot/RobotResponse.cs index bda1ada..43d4b63 100644 --- a/Binance.TradeRobot.Model/Dto/Response/Robot/RobotResponse.cs +++ b/Binance.TradeRobot.Model/Dto/Response/Robot/RobotResponse.cs @@ -28,20 +28,15 @@ namespace Binance.TradeRobot.Model.Dto /// /// 订单推送监听实例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实例 - } - + string key = $"{BusinessType}-{ExchangeAPIKey.AccountId}"; //币安现货,币安合约,同一个账户内不区分websocket实例 + if (ExchangeId == Enums.Exchange.Binance && BusinessType == Enums.BusinessType.IsolateMargin) + key = $"{BusinessType}-{ExchangeAPIKey.AccountId}-{Symbol}"; //币安逐仓杠杆,同一个账户内的每个交易对需要区分websocket实例 return $"OrderPublish(Origin)-{ExchangeId}-{key}"; } }