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 singalRequest, T1 robot, SymbolInfoResponse symbolInfo) where T : BaseSingalRequest where T1 : RobotResponse { var logList = new List(); 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(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 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 限制追高 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, 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(robot.RobotAccount.Id).Set(ra => ra.LoanAmount, accountLoanAmount).ExecuteAffrows(); if (previewTradeAmount != d21Robot.D21Policy.Position) //借币失败 仓位减少 fsql.Update(d21Robot.D21Policy.Id).Set(d => d.Position, previewTradeAmount).ExecuteAffrows(); }); #endregion } catch (Exception ex) { HandleError(ex, singalRequest.SingalType, logList, robot, step); } } 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, robot.TradePolicy); 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, 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); } } } }