You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
402 lines
19 KiB
402 lines
19 KiB
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 System;
|
|
using System.Collections.Generic;
|
|
using System.Data.Common;
|
|
using System.Linq;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using Yitter.IdGenerator;
|
|
|
|
namespace Binance.TradeRobot.Business
|
|
{
|
|
[BatchRegistration(ServiceLifetime.Singleton, RegistrationType.Self)]
|
|
public class RobotBusiness : BaseBusiness
|
|
{
|
|
private Lazy<GlobalContext> globalContextLazy;
|
|
private GlobalContext globalContext => globalContextLazy.Value;
|
|
|
|
private IEnumerable<ITradeBusiness> tradeBusinessList;
|
|
private TaskSchedulerManager taskSchedulerManager;
|
|
private ExchangeBusiness exchangeBusiness;
|
|
|
|
public RobotBusiness(IFreeSql fsql,
|
|
NLogManager logManager,
|
|
IIdGenerator idGenerator,
|
|
IMemoryCache memoryCache,
|
|
IEnumerable<ITradeBusiness> tradeBusinessList,
|
|
TaskSchedulerManager taskSchedulerManager,
|
|
ExchangeBusiness exchangeBusiness,
|
|
IServiceProvider serviceProvider) : base(fsql, logManager, idGenerator, memoryCache)
|
|
{
|
|
this.globalContextLazy = new Lazy<GlobalContext>(() => serviceProvider.GetService<GlobalContext>());
|
|
this.tradeBusinessList = tradeBusinessList;
|
|
this.taskSchedulerManager = taskSchedulerManager;
|
|
this.exchangeBusiness = exchangeBusiness;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 检查机器人注册条件
|
|
/// </summary>
|
|
/// <param name="addRobotRequest"></param>
|
|
/// <param name="exchangeAPIKey"></param>
|
|
/// <exception cref="BusinessException"></exception>
|
|
private void CheckRobotRegister(AddRobotRequest addRobotRequest, out ExchangeAPIKey exchangeAPIKey)
|
|
{
|
|
exchangeAPIKey = null;
|
|
if (string.IsNullOrEmpty(addRobotRequest.Symbol) || addRobotRequest.ExchangeAPIKeyId == 0)
|
|
throw new BusinessException("参数不全");
|
|
exchangeAPIKey = fsql.Select<ExchangeAPIKey>(addRobotRequest.ExchangeAPIKeyId).ToOne();
|
|
if (exchangeAPIKey == null)
|
|
throw new BusinessException("选择的APIKey不存在");
|
|
if (exchangeAPIKey.RobotId != null)
|
|
throw new BusinessException("APIKey已被其他机器人使用");
|
|
|
|
var accountId = exchangeAPIKey.AccountId;
|
|
if (fsql.Select<ExchangeAccount, ExchangeAPIKey, Robot>().InnerJoin((ea, ek, r) => ea.Id == ek.AccountId)
|
|
.LeftJoin((ea, ek, r) => ek.RobotId == r.Id)
|
|
.Where((ea, ek, r) => ea.Id == accountId &&
|
|
r.Symbol == addRobotRequest.Symbol).Any())
|
|
throw new BusinessException("同一个交易所账号下只允许存在一个交易对");
|
|
}
|
|
|
|
public void StartRobot(long robotId)
|
|
{
|
|
fsql.Update<Robot>(robotId).Set(r => r.State, Enums.RobotState.Runing).ExecuteAffrows();
|
|
var robot = GetRobotList(robotId).FirstOrDefault();
|
|
|
|
//监听K线和订单
|
|
globalContext.SubscribeKLine(robot);
|
|
globalContext.SubscribeOrderPublish(robot);
|
|
}
|
|
|
|
public void StopRobot(long robotId)
|
|
{
|
|
var robot = GetRobotList(robotId).FirstOrDefault();
|
|
fsql.Update<Robot>(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)
|
|
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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 刷新机器人运行时长
|
|
/// </summary>
|
|
public void RefreshRobotRuningTime()
|
|
{
|
|
fsql.Update<Robot>().Set(r => r.RunningTime + 60).Where(r => r.State == Enums.RobotState.Runing).ExecuteAffrows();
|
|
}
|
|
|
|
/// <summary>
|
|
/// 添加机器人和账户
|
|
/// </summary>
|
|
/// <param name="addRobotRequest"></param>
|
|
/// <param name="tran"></param>
|
|
/// <returns>机器人Id</returns>
|
|
private long AddRobotWithTran(AddRobotRequest addRobotRequest, DbTransaction tran)
|
|
{
|
|
var robotId = idGenerator.NewLong();
|
|
fsql.Insert(new Robot()
|
|
{
|
|
Id = robotId,
|
|
Symbol = addRobotRequest.Symbol.ToUpper(),
|
|
TradePolicy = addRobotRequest.TradePolicy,
|
|
BusinessType = addRobotRequest.TradePolicy.GetBusinessType(),
|
|
ExchangeId = addRobotRequest.ExchangeId,
|
|
CreateTime = DateTime.Now,
|
|
RunningTime = 0,
|
|
State = Enums.RobotState.Stop
|
|
}).WithTransaction(tran).ExecuteAffrows();
|
|
|
|
fsql.Insert(new RobotAccount()
|
|
{
|
|
Id = idGenerator.NewLong(),
|
|
RobotId = robotId
|
|
}).WithTransaction(tran).ExecuteAffrows();
|
|
|
|
fsql.Update<ExchangeAPIKey>(addRobotRequest.ExchangeAPIKeyId).WithTransaction(tran).Set(ek => ek.RobotId, robotId).ExecuteAffrows();
|
|
|
|
return robotId;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 添加金字塔策略机器人
|
|
/// </summary>
|
|
/// <param name="addRobotRequest"></param>
|
|
public void AddPyramidPolicyRobot(AddRobotRequest addRobotRequest)
|
|
{
|
|
CheckRobotRegister(addRobotRequest, out ExchangeAPIKey exchangeAPIKey);
|
|
var pyramidPolicy = new PyramidPolicy();
|
|
pyramidPolicy.Id = idGenerator.NewLong();
|
|
fsql.Transaction(() =>
|
|
{
|
|
var tran = fsql.Ado.TransactionCurrentThread;
|
|
pyramidPolicy.RobotId = AddRobotWithTran(addRobotRequest, tran);
|
|
fsql.Insert(pyramidPolicy).ExecuteAffrows();
|
|
});
|
|
|
|
//调整仓位杠杆倍数
|
|
}
|
|
|
|
/// <summary>
|
|
/// 添加动2.1策略机器人
|
|
/// </summary>
|
|
/// <param name="addRobotRequest"></param>
|
|
public void AddD21PolicyRobot(AddRobotRequest addRobotRequest)
|
|
{
|
|
addRobotRequest.TradePolicy = Enums.TradePolicy.D21;
|
|
CheckRobotRegister(addRobotRequest, out _);
|
|
var d21Policy = new D21Policy()
|
|
{
|
|
Id = idGenerator.NewLong(),
|
|
CreateTime = DateTime.Now,
|
|
ExecutionMode = Enums.ExecutionMode.Both,
|
|
IsEnabledIncreasePurchase = true,
|
|
IsEnableRemedyForErrorCrossSignal = true
|
|
};
|
|
fsql.Transaction(() =>
|
|
{
|
|
var tran = fsql.Ado.TransactionCurrentThread;
|
|
d21Policy.RobotId = AddRobotWithTran(addRobotRequest, tran);
|
|
fsql.Insert(d21Policy).ExecuteAffrows();
|
|
});
|
|
}
|
|
|
|
/// <summary>
|
|
/// 查询机器人基本信息
|
|
/// </summary>
|
|
/// <param name="robotId">如果robotId有值,将忽略其他条件</param>
|
|
/// <param name="symbol"></param>
|
|
/// <param name="robotState"></param>
|
|
/// <param name="exchange"></param>
|
|
/// <param name="accountId">交易所账号Id</param>
|
|
/// <returns></returns>
|
|
public IList<RobotResponse> GetRobotList(long? robotId = null,
|
|
string symbol = "",
|
|
Enums.RobotState? robotState = null,
|
|
Enums.Exchange? exchange = null,
|
|
long? accountId = null)
|
|
{
|
|
var select = fsql.Select<Robot, ExchangeAPIKey>().InnerJoin((r, e) => r.Id == e.RobotId);
|
|
if (robotId != null)
|
|
select = select.Where((r, e) => r.Id == robotId);
|
|
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(accountId != null, (r, e) => e.AccountId == accountId);
|
|
|
|
return select.ToList((r, e) => new Robot()
|
|
{
|
|
Id = r.Id,
|
|
BusinessType = r.BusinessType,
|
|
ExchangeId = r.ExchangeId,
|
|
Symbol = r.Symbol,
|
|
State = r.State,
|
|
RunningTime = r.RunningTime,
|
|
CreateTime = r.CreateTime,
|
|
TradePolicy = r.TradePolicy,
|
|
|
|
ExchangeAccountId = e.AccountId,
|
|
ExchangeAPIKey = e.APIKey,
|
|
ExchangeSecretKey = e.SecretKey
|
|
}).Map<IList<RobotResponse>>();
|
|
}
|
|
|
|
private void GetSpotRobotRecentProfit<T>(IList<T> robotList) where T : RobotResponse
|
|
{
|
|
var robotIds = robotList.Select(r => r.Id).ToList();
|
|
var recentProftList = fsql.Select<Robot>().Where(r => robotIds.Contains(r.Id)).ToList(r => new
|
|
{
|
|
RobotId = r.Id,
|
|
FiveTimesProfit = fsql.Select<SpotOrder>().Where(o => o.RobotId == r.Id).OrderByDescending(o => o.CreateTime).Take(5).Sum(o => o.Profit),
|
|
TenTimesProfit = fsql.Select<SpotOrder>().Where(o => o.RobotId == r.Id).OrderByDescending(o => o.CreateTime).Take(10).Sum(o => o.Profit)
|
|
});
|
|
foreach (var recentProft in recentProftList)
|
|
{
|
|
var robot = robotList.FirstOrDefault(r => r.Id == recentProft.RobotId);
|
|
if (robot != null)
|
|
{
|
|
robot.RobotAccount.FiveTimesProfit = recentProft.FiveTimesProfit;
|
|
robot.RobotAccount.TenTimesProfit = recentProft.TenTimesProfit;
|
|
}
|
|
}
|
|
}
|
|
|
|
private void GetSpotRobotFloatingProfitAndLoss<T>(IList<T> robotList) where T : RobotResponse
|
|
{
|
|
foreach (var robot in robotList)
|
|
{
|
|
var newestPrice = globalContext.GetSpotNewestPrice(robot.KLineKey) ?? 0;
|
|
if (newestPrice == 0)
|
|
continue;
|
|
robot.RobotAccount.FloatingProfitAndLoss = (newestPrice - robot.RobotAccount.SpotCurrencyAvgPrice) * robot.RobotAccount.SpotCurrencyQuantity;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 获取动2.1策略机器人列表
|
|
/// </summary>
|
|
/// <param name="robotId"></param>
|
|
/// <param name="robotState">机器人状态</param>
|
|
/// <param name="signalPeriod">信号周期</param>
|
|
/// <param name="symbol">交易对</param>
|
|
/// <param name="isLoadRecentTradeProfit">是否加载近期交易利润,默认true</param>
|
|
/// <param name="isLoadAPIKey">是否加载机器人绑定的APIKey,默认false</param>
|
|
/// <returns></returns>
|
|
public IList<D21PolicyRobotResponse> GetD21PolicyRobotList(long? robotId = null,
|
|
Enums.RobotState? robotState = null,
|
|
Enums.SignalPeriod? signalPeriod = null,
|
|
string symbol = "",
|
|
bool isLoadRecentTradeProfit = true,
|
|
bool isLoadAPIKey = false)
|
|
{
|
|
var select = fsql.Select<Robot, RobotAccount, D21Policy, ExchangeAPIKey>().InnerJoin((r, ra, d, e) => r.Id == ra.RobotId)
|
|
.InnerJoin((r, ra, d, e) => r.Id == d.RobotId)
|
|
.InnerJoin((r, ra, d, e) => r.Id == e.RobotId);
|
|
if (robotId != null)
|
|
{
|
|
select = select.Where((r, ra, d, e) => r.Id == robotId);
|
|
}
|
|
else
|
|
{
|
|
select = select.WhereIf(robotState != null, (r, ra, d, e) => r.State == robotState)
|
|
.WhereIf(signalPeriod != null, (r, ra, d, e) => d.PeriodicSignal == signalPeriod)
|
|
.WhereIf(!string.IsNullOrEmpty(symbol), (r, ra, d, e) => r.Symbol == symbol)
|
|
.Where((r, ra, d, e) => r.TradePolicy == Enums.TradePolicy.D21);
|
|
}
|
|
|
|
var robotList = select.ToList((r, ra, d, e) => new Robot
|
|
{
|
|
Id = r.Id,
|
|
BusinessType = r.BusinessType,
|
|
ExchangeId = r.ExchangeId,
|
|
Symbol = r.Symbol,
|
|
State = r.State,
|
|
RunningTime = r.RunningTime,
|
|
CreateTime = r.CreateTime,
|
|
TradePolicy = r.TradePolicy,
|
|
|
|
//SymbolId = s.Id,
|
|
//SymbolSaleQuantityAccuracy = s.SaleQuantityAccuracy,
|
|
|
|
RobotAccountId = ra.Id,
|
|
ClosePositionCount = ra.ClosePositionCount,
|
|
WinCount = ra.WinCount,
|
|
LoanAmount = ra.LoanAmount,
|
|
SpotCurrencyAmount = ra.SpotCurrencyAmount,
|
|
SpotCurrencyQuantity = ra.SpotCurrencyQuantity,
|
|
TotalProfit = ra.TotalProfit,
|
|
|
|
ExchangeAccountId = e.AccountId,
|
|
ExchangeAPIKey = e.APIKey,
|
|
ExchangeSecretKey = e.SecretKey,
|
|
|
|
D21ExecutionMode = d.ExecutionMode,
|
|
D21IsEnabledIncreasePurchase = d.IsEnabledIncreasePurchase,
|
|
D21IsEnableRemedyForErrorCrossSignal = d.IsEnableRemedyForErrorCrossSignal,
|
|
D21MaxFollowPurchaseRatio = d.MaxFollowPurchaseRatio,
|
|
D21PeriodicSignal = d.PeriodicSignal,
|
|
D21PolicyId = d.Id,
|
|
D21Position = d.Position,
|
|
D21Assets = d.Assets,
|
|
D21Level1PositionStopLossRatio = d.Level1PositionStopLossRatio,
|
|
D21Level1PriceStopLossRatio = d.Level1PriceStopLossRatio,
|
|
D21Level2PositionStopLossRatio = d.Level2PositionStopLossRatio,
|
|
D21Level2PriceStopLossRatio = d.Level2PriceStopLossRatio,
|
|
D21MaxExchangeLoanRatio = d.MaxExchangeLoanRatio,
|
|
D21MaxSystemLoanRatio = d.MaxSystemLoanRatio,
|
|
D21CreateTime = d.CreateTime
|
|
}).Map<IList<D21PolicyRobotResponse>>();
|
|
|
|
|
|
if (isLoadRecentTradeProfit)
|
|
{
|
|
//统计近期订单利润
|
|
GetSpotRobotRecentProfit(robotList);
|
|
GetSpotRobotFloatingProfitAndLoss(robotList);
|
|
}
|
|
|
|
if (!isLoadAPIKey)
|
|
{
|
|
foreach (var r in robotList)
|
|
r.ExchangeAPIKey = null;
|
|
}
|
|
return robotList;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 补救检查
|
|
/// </summary>
|
|
/// <exception cref="BusinessException"></exception>
|
|
public void D21Remedy()
|
|
{
|
|
var d21RobotList = GetD21PolicyRobotList(robotState: Enums.RobotState.Runing, isLoadRecentTradeProfit: false, isLoadAPIKey: true);
|
|
if (d21RobotList == null || d21RobotList.Count() == 0)
|
|
throw new BusinessException("未找到符合条件的机器人");
|
|
|
|
foreach (var d21Robot in d21RobotList)
|
|
{
|
|
var runingInfo = RedisHelper.Get<D21RuningInfo>(d21Robot.Id.ToString()) ?? new D21RuningInfo() { RobotId = d21Robot.Id };
|
|
if (runingInfo.ErrorCrossSingal == null || runingInfo.ErrorCrossSingalTime == 0)
|
|
continue;
|
|
|
|
var previouKID = DateTime.Now.GetKID(d21Robot.D21Policy.PeriodicSignal, true);
|
|
var currentKID = DateTime.Now.GetKID(d21Robot.D21Policy.PeriodicSignal, false);
|
|
var cycle = currentKID - previouKID; //单位周期的时间戳差距
|
|
|
|
if ((previouKID - runingInfo.ErrorCrossSingalTime) / cycle > 3)
|
|
{
|
|
var errorCrossSingal = runingInfo.ErrorCrossSingal.Value;
|
|
runingInfo.ErrorCrossSingal = null;
|
|
runingInfo.ErrorCrossSingalTime = 0;
|
|
RedisHelper.Set(d21Robot.Id.ToString(), runingInfo);
|
|
|
|
var symbolInfo = exchangeBusiness.GetSymbol(d21Robot.ExchangeId, d21Robot.Symbol);
|
|
var d21TradeBusiness = tradeBusinessList.FirstOrDefault(t => t.TradePolicy == Enums.TradePolicy.D21);
|
|
|
|
if (errorCrossSingal == Enums.SingalType.多交叉)
|
|
{
|
|
Task.Factory.StartNew(() => d21TradeBusiness.LongCross(new D21SingalRequest()
|
|
{
|
|
RobotId = d21Robot.Id,
|
|
KLinePeriodic = d21Robot.D21Policy.PeriodicSignal,
|
|
SingalType = errorCrossSingal,
|
|
Symbol = d21Robot.Symbol
|
|
}, d21Robot, true, symbolInfo), CancellationToken.None, TaskCreationOptions.LongRunning, taskSchedulerManager.SingalTaskScheduler);
|
|
}
|
|
else if (errorCrossSingal == Enums.SingalType.空交叉)
|
|
{
|
|
Task.Factory.StartNew(() => d21TradeBusiness.ShortCross(new D21SingalRequest()
|
|
{
|
|
RobotId = d21Robot.Id,
|
|
KLinePeriodic = d21Robot.D21Policy.PeriodicSignal,
|
|
SingalType = errorCrossSingal,
|
|
Symbol = d21Robot.Symbol
|
|
}, d21Robot, true, symbolInfo), CancellationToken.None, TaskCreationOptions.LongRunning, taskSchedulerManager.SingalTaskScheduler);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|