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.
374 lines
18 KiB
374 lines
18 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.Linq;
|
|
using System.Threading;
|
|
using Yitter.IdGenerator;
|
|
|
|
namespace Binance.TradeRobot.Business
|
|
{
|
|
[BatchRegistration(ServiceLifetime.Singleton, RegistrationType.Interface)]
|
|
public class D21TradeBusiness : BaseTradeBusiness, ITradeBusiness
|
|
{
|
|
|
|
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, dingBusiness, globalContext)
|
|
{
|
|
}
|
|
|
|
public void TrendChanged<T, T1>(T singalRequest, T1 robot, SymbolInfoResponse symbolInfo) where T : BaseSingalRequest where T1 : RobotResponse
|
|
{
|
|
var logList = new List<ExecutionLog>();
|
|
logList.Add(new ExecutionLog()
|
|
{
|
|
Id = idGenerator.NewLong(),
|
|
RobotId = robot.Id,
|
|
CreateTime = DateTime.Now,
|
|
SourceSingal = singalRequest.SingalType,
|
|
Content = $"收到信号{singalRequest.SingalType}"
|
|
});
|
|
try
|
|
{
|
|
fsql.Insert(logList).ExecuteAffrows();
|
|
var d21RuningInfo = RedisHelper.Get<D21RuningInfo>(robot.Id.ToString()) ?? new D21RuningInfo() { RobotId = robot.Id };
|
|
d21RuningInfo.RecentSmallTrendSingal = singalRequest.SingalType;
|
|
RedisHelper.Set(robot.Id.ToString(), d21RuningInfo);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
HandleError(ex, singalRequest.SingalType, logList, robot, string.Empty);
|
|
}
|
|
}
|
|
|
|
public void LongCross<T, T1>(T singalRequest, T1 robot, bool isRemedy, SymbolInfoResponse symbolInfo) where T : BaseSingalRequest where T1 : RobotResponse
|
|
{
|
|
string step = string.Empty;
|
|
var logList = new List<ExecutionLog>();
|
|
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<D21RuningInfo>(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<D21RuningInfo>(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
|
|
|
|
#region 获取账户余额
|
|
step = "获取账户余额";
|
|
var apiClient = GetBaseAPIClient(robot.ExchangeId, robot.ExchangeAPIKey.AccountId, robot.ExchangeAPIKey.APIKey, robot.ExchangeAPIKey.SecretKey);
|
|
//逐仓杠杆账户余额
|
|
var balance = apiClient.GetIsolatedMarginAccountAssets().FirstOrDefault(m => m.Symbol == robot.Symbol)?.QuoteFree ?? 0M;
|
|
if (balance == 0M)
|
|
throw new BusinessException("可用资产为0");
|
|
#endregion
|
|
|
|
#region 计算下单数量
|
|
step = "计算下单数量";
|
|
var diffAmount = 0M; //下单缺口金额
|
|
var accountLoanAmount = robot.RobotAccount.LoanAmount; //账户借币金额
|
|
var previewTradeAmount = d21Robot.D21Policy.Position; //预估交易额
|
|
|
|
if (balance < previewTradeAmount)
|
|
{
|
|
#region 验证借币
|
|
step = "验证借币";
|
|
diffAmount = previewTradeAmount - balance;
|
|
var diffRatio = Math.Round(diffAmount / balance * 100, 2); //借币比例
|
|
|
|
#region 验证策略中的最大借币比例
|
|
if (diffRatio > d21Robot.D21Policy.MaxExchangeLoanRatio)
|
|
{
|
|
logList.Add(new ExecutionLog()
|
|
{
|
|
Id = idGenerator.NewLong(),
|
|
SourceSingal = Enums.SingalType.多交叉,
|
|
RobotId = robot.Id,
|
|
CreateTime = DateTime.Now,
|
|
Content = $"触发策略中交易所最大借币比例限制,交易所最大借币比例{d21Robot.D21Policy.MaxExchangeLoanRatio}%,当前借币比例{diffRatio}%,下单仓位:{previewTradeAmount},账户余额:{balance},按交易所最大借币比例借币"
|
|
});
|
|
diffAmount = previewTradeAmount * (d21Robot.D21Policy.MaxExchangeLoanRatio / 100M);
|
|
//previewTradeAmount = balance + diffAmount; //在策略允许的借币比例范围内的最大下单金额
|
|
}
|
|
#endregion
|
|
|
|
#region 验证交易所的最大可借额度
|
|
try
|
|
{
|
|
step = "验证交易所的最大可借额度";
|
|
var exchangeMaxLoanAmount = apiClient.QueryMaxLoanAmount(robot.Symbol);
|
|
if (exchangeMaxLoanAmount < diffAmount)
|
|
{
|
|
if (exchangeMaxLoanAmount == 0M)
|
|
throw new Exception("可借额度为0");
|
|
diffAmount = exchangeMaxLoanAmount;
|
|
}
|
|
previewTradeAmount = balance + diffAmount;
|
|
}
|
|
catch (Exception borrowex)
|
|
{
|
|
logList.Add(new ExecutionLog()
|
|
{
|
|
Id = idGenerator.NewLong(),
|
|
SourceSingal = Enums.SingalType.多交叉,
|
|
RobotId = robot.Id,
|
|
CreateTime = DateTime.Now,
|
|
Content = $"验证交易所的最大可借额度失败 {borrowex.Message}"
|
|
});
|
|
previewTradeAmount = balance; //无法借币,使用余额下单
|
|
diffAmount = 0M;
|
|
}
|
|
#endregion
|
|
|
|
#endregion
|
|
}
|
|
#endregion
|
|
|
|
#region 借币
|
|
if (diffAmount > 0M)
|
|
{
|
|
step = "借币";
|
|
try
|
|
{
|
|
var loanResponse = apiClient.IsolatedMarginLoan(robot.Symbol, diffAmount);
|
|
diffAmount = loanResponse.CurrentLoanAmount;
|
|
accountLoanAmount = loanResponse.AccountLoanAmount;
|
|
}
|
|
catch (Exception borrowex)
|
|
{
|
|
logList.Add(new ExecutionLog()
|
|
{
|
|
Id = idGenerator.NewLong(),
|
|
SourceSingal = Enums.SingalType.多交叉,
|
|
RobotId = robot.Id,
|
|
CreateTime = DateTime.Now,
|
|
Content = $"借币失败 {borrowex.Message}"
|
|
});
|
|
previewTradeAmount = balance; //无法借币,使用余额下单
|
|
diffAmount = 0M;
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
#region 下单
|
|
step = "下单";
|
|
var clientOrderId = CreateClientOrderId(robot.Id, robot.TradePolicy);
|
|
var orderId = apiClient.IsolatedMarginPlaceOrder(robot.Symbol,
|
|
Enums.TradeDirection.Buy,
|
|
Enums.OrderType.MARKET,
|
|
quoteAmount: previewTradeAmount,
|
|
newClientOrderId: clientOrderId);
|
|
var buyOrder = new SpotOrder()
|
|
{
|
|
Id = orderId,
|
|
ClientOrderId = clientOrderId,
|
|
CreateTime = DateTime.Now,
|
|
ExchangeId = robot.ExchangeId,
|
|
LoanAmount = diffAmount,
|
|
OrderType = Enums.OrderType.MARKET,
|
|
PolicyType = Enums.TradePolicy.D21,
|
|
RobotId = robot.Id,
|
|
State = Enums.SpotOrderState.Created,
|
|
Symbol = robot.Symbol,
|
|
TradeDirection = Enums.TradeDirection.Buy
|
|
};
|
|
|
|
logList.Add(new ExecutionLog()
|
|
{
|
|
Id = idGenerator.NewLong(),
|
|
SourceSingal = singalRequest.SingalType,
|
|
RobotId = robot.Id,
|
|
OrderId = orderId,
|
|
CreateTime = DateTime.Now,
|
|
Content = $"市价买单挂单成功,订单号:{orderId},挂单金额:{previewTradeAmount},借币金额:{diffAmount}"
|
|
});
|
|
fsql.Transaction(() =>
|
|
{
|
|
fsql.Insert(logList).ExecuteAffrows();
|
|
fsql.Insert(buyOrder).ExecuteAffrows();
|
|
if (diffAmount > 0M)
|
|
fsql.Update<RobotAccount>(robot.RobotAccount.Id).Set(ra => ra.LoanAmount, accountLoanAmount).ExecuteAffrows();
|
|
if (previewTradeAmount != d21Robot.D21Policy.Position) //借币失败 仓位减少
|
|
fsql.Update<D21Policy>(d21Robot.D21Policy.Id).Set(d => d.Position, previewTradeAmount).ExecuteAffrows();
|
|
});
|
|
#endregion
|
|
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
HandleError(ex, singalRequest.SingalType, logList, robot, step);
|
|
}
|
|
}
|
|
|
|
public void ShortCross<T, T1>(T singalRequest, T1 robot, bool isRemedy, SymbolInfoResponse symbolInfo) where T : BaseSingalRequest where T1 : RobotResponse
|
|
{
|
|
string step = string.Empty;
|
|
var logList = new List<ExecutionLog>();
|
|
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<D21RuningInfo>(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<D21RuningInfo>(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 = "取消止损限价单";
|
|
CancelStopLossOrder(robot);
|
|
#endregion
|
|
|
|
#region 验证卖币数量
|
|
step = "验证卖币数量";
|
|
Thread.Sleep(1000);
|
|
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, robot.TradePolicy);
|
|
var orderId = apiClient.IsolatedMarginPlaceOrder(robot.Symbol,
|
|
Enums.TradeDirection.Sell,
|
|
Enums.OrderType.MARKET,
|
|
quantity: saleQuantity,
|
|
newClientOrderId: clientOrderId);
|
|
|
|
var sellOrder = new SpotOrder()
|
|
{
|
|
Id = orderId,
|
|
ClientOrderId = clientOrderId,
|
|
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,
|
|
OrderId = orderId,
|
|
CreateTime = DateTime.Now,
|
|
Content = $"市价卖单挂单成功,订单号:{orderId},挂单数量:{saleQuantity}"
|
|
});
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|