From 72e35dec584c9d8b4203099b76404bee036cb838 Mon Sep 17 00:00:00 2001 From: shanj <18996038927@163.com> Date: Wed, 11 May 2022 06:26:40 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A4=9A=E4=BA=A4=E5=8F=89=E4=BF=A1=E5=8F=B7?= =?UTF-8?q?=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .editorconfig | 3 + .../Binance.TradeRobot.Business.xml | 29 +++++ .../Business/DingBusiness.cs | 32 +++++ .../Business/SpotPolicyBusiness.cs | 3 - .../TradeBusiness/D21TradeBusiness.cs | 114 +++++++++++++++--- .../Business/TradeBusiness/ITradeBusiness.cs | 27 ++++- .../Extensions/KIDExtension.cs | 74 ++++++++++++ Binance.TradeRobot.Business/GlobalContext.cs | 12 ++ .../RuningInfo/D21RuningInfo.cs | 17 ++- 9 files changed, 288 insertions(+), 23 deletions(-) create mode 100644 Binance.TradeRobot.Business/Business/DingBusiness.cs create mode 100644 Binance.TradeRobot.Business/Extensions/KIDExtension.cs diff --git a/.editorconfig b/.editorconfig index cd7ccf1..6332a8c 100644 --- a/.editorconfig +++ b/.editorconfig @@ -8,3 +8,6 @@ dotnet_diagnostic.CS1591.severity = none # CS8625: 无法将 null 字面量转换为非 null 的引用类型。 dotnet_diagnostic.CS8625.severity = none + +# CS8602: 解引用可能出现空引用。 +dotnet_diagnostic.CS8602.severity = none diff --git a/Binance.TradeRobot.Business/Binance.TradeRobot.Business.xml b/Binance.TradeRobot.Business/Binance.TradeRobot.Business.xml index 2daae90..dcda4d5 100644 --- a/Binance.TradeRobot.Business/Binance.TradeRobot.Business.xml +++ b/Binance.TradeRobot.Business/Binance.TradeRobot.Business.xml @@ -66,6 +66,35 @@ 现货策略 + + + 趋势变化 + + + + + + + + + 多交叉 + + + + + + 是否为补救信号 + + + + 空交叉 + + + + + + 是否为补救信号 + 单向资产变更 diff --git a/Binance.TradeRobot.Business/Business/DingBusiness.cs b/Binance.TradeRobot.Business/Business/DingBusiness.cs new file mode 100644 index 0000000..782f319 --- /dev/null +++ b/Binance.TradeRobot.Business/Business/DingBusiness.cs @@ -0,0 +1,32 @@ +using Binance.TradeRobot.Common.DI; +using Binance.TradeRobot.Common.Http; +using Microsoft.Extensions.DependencyInjection; + +namespace Binance.TradeRobot.Business +{ + [BatchRegistration(ServiceLifetime.Singleton, RegistrationType.Self)] + public class DingBusiness + { + private RestApiService restApiService; + public DingBusiness(RestApiService restApiService) + { + this.restApiService = restApiService; + } + + public void Send(string content) + { + try + { + restApiService.SendRequest("https://oapi.dingtalk.com/robot/send", "?access_token=be2f574475269e83584fa3ef1a93ef68e28c15402afa1d193aa82a1a51164850", new + { + msgtype = "text", + text = new + { + content + } + }, null, System.Net.Http.HttpMethod.Post); + } + catch { } + } + } +} diff --git a/Binance.TradeRobot.Business/Business/SpotPolicyBusiness.cs b/Binance.TradeRobot.Business/Business/SpotPolicyBusiness.cs index 0892cce..e726c8c 100644 --- a/Binance.TradeRobot.Business/Business/SpotPolicyBusiness.cs +++ b/Binance.TradeRobot.Business/Business/SpotPolicyBusiness.cs @@ -5,9 +5,6 @@ using Binance.TradeRobot.Model.Db; using Binance.TradeRobot.Model.Dto; using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.DependencyInjection; -using System; -using System.Collections.Generic; -using System.Text; using Yitter.IdGenerator; namespace Binance.TradeRobot.Business diff --git a/Binance.TradeRobot.Business/Business/TradeBusiness/D21TradeBusiness.cs b/Binance.TradeRobot.Business/Business/TradeBusiness/D21TradeBusiness.cs index 734c5bb..70266fd 100644 --- a/Binance.TradeRobot.Business/Business/TradeBusiness/D21TradeBusiness.cs +++ b/Binance.TradeRobot.Business/Business/TradeBusiness/D21TradeBusiness.cs @@ -1,4 +1,5 @@ -using Binance.TradeRobot.Common.DI; +using Binance.TradeRobot.Business.Extensions; +using Binance.TradeRobot.Common.DI; using Binance.TradeRobot.Model.Base; using Binance.TradeRobot.Model.Db; using Binance.TradeRobot.Model.Dto; @@ -7,7 +8,9 @@ using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.DependencyInjection; using Newtonsoft.Json; using System; +using System.Collections.Generic; using System.Text; +using System.Threading; using Yitter.IdGenerator; namespace Binance.TradeRobot.Business @@ -15,13 +18,22 @@ namespace Binance.TradeRobot.Business [BatchRegistration(ServiceLifetime.Singleton, RegistrationType.Interface)] public class D21TradeBusiness : BaseBusiness, ITradeBusiness { - public D21TradeBusiness(IFreeSql fsql, NLogManager logManager, IIdGenerator idGenerator, IMemoryCache memoryCache) : base(fsql, logManager, idGenerator, memoryCache) - { - - } + private DingBusiness dingBusiness; + private GlobalContext globalContext; 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) + { + this.dingBusiness = dingBusiness; + this.globalContext = globalContext; + } + public void TrendChanged(T singalRequest, T1 robot) where T : BaseSingalRequest where T1 : RobotResponse { try @@ -32,12 +44,10 @@ namespace Binance.TradeRobot.Business RobotId = robot.Id, CreateTime = DateTime.Now, SourceSingal = singalRequest.SingalType, - Content = $"收到趋势信号【{singalRequest.SingalType}】" + Content = $"收到信号{singalRequest.SingalType}" }; fsql.Insert(executionLog).ExecuteAffrows(); - var d21RuningInfo = RedisHelper.Get(robot.Id.ToString()); - if (d21RuningInfo == null) - d21RuningInfo = new D21RuningInfo() { RobotId = robot.Id }; + var d21RuningInfo = RedisHelper.Get(robot.Id.ToString()) ?? new D21RuningInfo() { RobotId = robot.Id }; d21RuningInfo.RecentSmallTrendSingal = singalRequest.SingalType; RedisHelper.Set(robot.Id.ToString(), d21RuningInfo); } @@ -50,18 +60,88 @@ namespace Binance.TradeRobot.Business } } - public void LongCross(T singalRequest, T1 robot, decimal newestPrice, bool isRemedy) - where T : BaseSingalRequest - where T1 : RobotResponse + public void LongCross(T singalRequest, T1 robot, bool isRemedy) where T : BaseSingalRequest where T1 : RobotResponse { - throw new NotImplementedException(); + string step = string.Empty; + var logList = new List(); + logList.Add(new ExecutionLog() + { + Id = idGenerator.NewLong(), + SourceSingal = Enums.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 限制追高 + var d21Robot = robot as D21PolicyRobotResponse; + var newestPrice = globalContext.GetSpotNewestPrice(robot.KLineKey) ?? singalRequest.ClosePrice; + + step = "限制追高"; + if (d21RuningInfo.RecentShortCrossSignalTradePrice != null && newestPrice > d21RuningInfo.RecentShortCrossSignalTradePrice) + { + var diffRatio = Math.Round((newestPrice / d21RuningInfo.RecentShortCrossSignalTradePrice.Value - 1) * 100, 2); + if (diffRatio > d21Robot.D21Policy.MaxFollowPurchaseRatio) + { + throw new BusinessException($"触发限制追高,最近空交叉成交价{d21RuningInfo.RecentShortCrossSignalTradePrice},当前价格{newestPrice},追高比例{d21Robot.D21Policy.MaxFollowPurchaseRatio}%,当前比例{diffRatio}%,终止多交叉信号执行"); + } + } + #endregion + } + 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}"); + } } - public void ShortCross(T singalRequest, T1 robot, decimal newestPrice, bool isRemedy) - where T : BaseSingalRequest - where T1 : RobotResponse + public void ShortCross(T singalRequest, T1 robot, bool isRemedy) where T : BaseSingalRequest where T1 : RobotResponse { - throw new NotImplementedException(); + } } } diff --git a/Binance.TradeRobot.Business/Business/TradeBusiness/ITradeBusiness.cs b/Binance.TradeRobot.Business/Business/TradeBusiness/ITradeBusiness.cs index f253f75..1cf147a 100644 --- a/Binance.TradeRobot.Business/Business/TradeBusiness/ITradeBusiness.cs +++ b/Binance.TradeRobot.Business/Business/TradeBusiness/ITradeBusiness.cs @@ -7,10 +7,33 @@ namespace Binance.TradeRobot.Business { Enums.TradePolicy TradePolicy { get; } + /// + /// 趋势变化 + /// + /// + /// + /// + /// void TrendChanged(T singalRequest, T1 robot) where T : BaseSingalRequest where T1 : RobotResponse; - void LongCross(T singalRequest, T1 robot, decimal newestPrice,bool isRemedy) where T : BaseSingalRequest where T1 : RobotResponse; + /// + /// 多交叉 + /// + /// + /// + /// + /// + /// 是否为补救信号 + void LongCross(T singalRequest, T1 robot, bool isRemedy) where T : BaseSingalRequest where T1 : RobotResponse; - void ShortCross(T singalRequest, T1 robot, decimal newestPrice, bool isRemedy) where T : BaseSingalRequest where T1 : RobotResponse; + /// + /// 空交叉 + /// + /// + /// + /// + /// + /// 是否为补救信号 + void ShortCross(T singalRequest, T1 robot, bool isRemedy) where T : BaseSingalRequest where T1 : RobotResponse; } } diff --git a/Binance.TradeRobot.Business/Extensions/KIDExtension.cs b/Binance.TradeRobot.Business/Extensions/KIDExtension.cs new file mode 100644 index 0000000..81e6b71 --- /dev/null +++ b/Binance.TradeRobot.Business/Extensions/KIDExtension.cs @@ -0,0 +1,74 @@ +using Binance.TradeRobot.Common.Extensions; +using Binance.TradeRobot.Model.Base; +using System; + +namespace Binance.TradeRobot.Business.Extensions +{ + public static class KIDExtension + { + public static long GetKID(this DateTime datetime, Enums.SignalPeriod kLinePeriodic, bool isPrevious) + { + DateTime dt = datetime.AddSeconds(datetime.Second * -1).AddMilliseconds(datetime.Millisecond * -1); + if (kLinePeriodic == Enums.SignalPeriod._1m) + { + if (isPrevious) + dt = dt.AddMinutes(-1); + } + else if (kLinePeriodic == Enums.SignalPeriod._5m) + { + var fiveMinCount = dt.Minute / 5; //5分钟的次数 + dt = dt.AddMinutes(dt.Minute * -1).AddMinutes(fiveMinCount * 5); + if (isPrevious) + dt = dt.AddMinutes(-5); + } + else if (kLinePeriodic == Enums.SignalPeriod._15m) + { + var fifteenMinCount = dt.Minute / 15; //15分钟次数 + dt = dt.AddMinutes(dt.Minute * -1).AddMinutes(fifteenMinCount * 15); + if (isPrevious) + dt = dt.AddMinutes(-15); + } + else if (kLinePeriodic == Enums.SignalPeriod._30m) + { + var fifteenMinCount = dt.Minute / 30; //30分钟次数 + dt = dt.AddMinutes(dt.Minute * -1).AddMinutes(fifteenMinCount * 30); + if (isPrevious) + dt = dt.AddMinutes(-30); + } + else if (kLinePeriodic == Enums.SignalPeriod._1h) + { + dt = dt.AddMinutes(dt.Minute * -1); + if (isPrevious) + dt = dt.AddHours(-1); + } + else if (kLinePeriodic == Enums.SignalPeriod._4h) + { + var hCount = dt.Hour / 4; //4小时的次数 + dt = dt.AddMinutes(dt.Minute * -1).AddHours(dt.Hour * -1).AddHours(hCount * 4); + if (isPrevious) + dt = dt.AddHours(-4); + } + else if (kLinePeriodic == Enums.SignalPeriod._1d) + { + dt = dt.AddMinutes(dt.Minute * -1).AddHours(dt.Hour * -1); + if (isPrevious) + dt = dt.AddDays(-1); + } + else if (kLinePeriodic == Enums.SignalPeriod._1w) + { + var week = dt.DayOfWeek; + dt = dt.AddMinutes(dt.Minute * -1).AddHours(dt.Hour * -1).AddDays((int)week * -1); + if (isPrevious) + dt = dt.AddDays(-7); + } + else if (kLinePeriodic == Enums.SignalPeriod._1M) + { + dt = dt.AddMinutes(dt.Minute * -1).AddHours(dt.Hour * -1).AddDays((dt.Day - 1) * -1); + if (isPrevious) + dt = dt.AddMonths(-1); + } + Console.WriteLine(dt); + return dt.DateTimeToStamp(len13: false); + } + } +} diff --git a/Binance.TradeRobot.Business/GlobalContext.cs b/Binance.TradeRobot.Business/GlobalContext.cs index fbcec25..4bddd55 100644 --- a/Binance.TradeRobot.Business/GlobalContext.cs +++ b/Binance.TradeRobot.Business/GlobalContext.cs @@ -62,5 +62,17 @@ namespace Binance.TradeRobot.Business { } + + /// + /// 获取指定交易对现货最新成交价 + /// + /// + /// + public decimal? GetSpotNewestPrice(string key) + { + if (spotMarketWebSocketClientDictionary.TryGetValue(key, out SpotMarketWebSocketClient spotMarketWebSocketClient)) + return spotMarketWebSocketClient.NewestPrice; + return null; + } } } diff --git a/Binance.TradeRobot.Model/RuningInfo/D21RuningInfo.cs b/Binance.TradeRobot.Model/RuningInfo/D21RuningInfo.cs index d0a7cb6..09a1225 100644 --- a/Binance.TradeRobot.Model/RuningInfo/D21RuningInfo.cs +++ b/Binance.TradeRobot.Model/RuningInfo/D21RuningInfo.cs @@ -9,6 +9,21 @@ namespace Binance.TradeRobot.Model.RuningInfo /// /// 最近一次小趋势信号 /// - public Enums.SingalType RecentSmallTrendSingal { get; set; } + public Enums.SingalType? RecentSmallTrendSingal { get; set; } + + /// + /// 错误的交叉信号 + /// + public Enums.SingalType? ErrorCrossSingal { get; set; } + + /// + /// 错误交叉信号的K线时间戳 + /// + public long ErrorCrossSingalTime { get; set; } + + /// + /// 最近一次空交叉时的成交价 + /// + public decimal? RecentShortCrossSignalTradePrice { get; set; } } }