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; }
}
}