|
|
|
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 FreeSql;
|
|
|
|
using Microsoft.Extensions.Caching.Memory;
|
|
|
|
using Microsoft.Extensions.DependencyInjection;
|
|
|
|
using SDKAdapter.APIClient;
|
|
|
|
using SDKAdapter.Model;
|
|
|
|
using System;
|
|
|
|
using System.Collections.Concurrent;
|
|
|
|
using System.Collections.Generic;
|
|
|
|
using System.Linq;
|
|
|
|
using Yitter.IdGenerator;
|
|
|
|
|
|
|
|
namespace Binance.TradeRobot.Business
|
|
|
|
{
|
|
|
|
[BatchRegistration(ServiceLifetime.Singleton, RegistrationType.Interface)]
|
|
|
|
internal class D21OrderPublishBusiness : BaseSpotOrderPublishBusiness, ISpotOrderPublishBusiness
|
|
|
|
{
|
|
|
|
private ConcurrentDictionary<long, decimal> spotFeeDictionary;
|
|
|
|
|
|
|
|
public Enums.TradePolicy TradePolicy => Enums.TradePolicy.D21;
|
|
|
|
|
|
|
|
public D21OrderPublishBusiness(IFreeSql fsql, NLogManager logManager, IIdGenerator idGenerator, IMemoryCache memoryCache, DingBusiness dingBusiness, RobotBusiness robotBusiness, UserBusiness userBusiness, ExchangeBusiness exchangeBusiness) : base(fsql, logManager, idGenerator, memoryCache, dingBusiness, robotBusiness, userBusiness, exchangeBusiness)
|
|
|
|
{
|
|
|
|
spotFeeDictionary = new ConcurrentDictionary<long, decimal>();
|
|
|
|
}
|
|
|
|
|
|
|
|
private decimal AddSpotFee(long orderId, decimal currentTradeFee, bool deleteKey)
|
|
|
|
{
|
|
|
|
if (!spotFeeDictionary.TryGetValue(orderId, out decimal fee))
|
|
|
|
{
|
|
|
|
fee = 0M;
|
|
|
|
spotFeeDictionary.TryAdd(orderId, fee);
|
|
|
|
}
|
|
|
|
fee += currentTradeFee;
|
|
|
|
spotFeeDictionary[orderId] = fee;
|
|
|
|
if (deleteKey)
|
|
|
|
spotFeeDictionary.TryRemove(orderId, out _);
|
|
|
|
return fee;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public void OnSpotOrderPublish(SpotOrderPublishInfo spotOrderPublishInfo)
|
|
|
|
{
|
|
|
|
var step = string.Empty;
|
|
|
|
var logList = new List<ExecutionLog>();
|
|
|
|
try
|
|
|
|
{
|
|
|
|
//step = "检查订单是否入库";
|
|
|
|
CheckOrderExists(spotOrderPublishInfo.OrderId);
|
|
|
|
|
|
|
|
//step = "查询订单所属机器人";
|
|
|
|
var robot = robotBusiness.GetD21PolicyRobotList(spotOrderPublishInfo.RobotId, isLoadRecentTradeProfit: false, isLoadAPIKey: true).FirstOrDefault();
|
|
|
|
if (robot == null)
|
|
|
|
throw new BusinessException($"未找到机器人");
|
|
|
|
|
|
|
|
var symbolInfo = exchangeBusiness.GetSymbol(spotOrderPublishInfo.Exchange, spotOrderPublishInfo.Symbol);
|
|
|
|
|
|
|
|
var apiClient = GetBaseAPIClient(robot.ExchangeId, robot.ExchangeAPIKey.AccountId, robot.ExchangeAPIKey.APIKey, robot.ExchangeAPIKey.SecretKey);
|
|
|
|
|
|
|
|
IUpdate<SpotOrder> updateSpotOrder = fsql.Update<SpotOrder>(spotOrderPublishInfo.OrderId).Set(o => o.State, spotOrderPublishInfo.SpotOrderState);
|
|
|
|
IUpdate<RobotAccount> updateRobotAccount = null;
|
|
|
|
IList<IUpdate<User>> updateUserList = null;
|
|
|
|
List<UserAccountProfitLossRecord> insertUserAccountProfitLossRecordList = null;
|
|
|
|
IUpdate<D21Policy> updateD21Policy = null;
|
|
|
|
List<SpotOrder> insertStopLossOrderList = null;
|
|
|
|
|
|
|
|
|
|
|
|
if (spotOrderPublishInfo.SpotOrderState == Enums.SpotOrderState.Rejected ||
|
|
|
|
spotOrderPublishInfo.SpotOrderState == Enums.SpotOrderState.Expired ||
|
|
|
|
spotOrderPublishInfo.SpotOrderState == Enums.SpotOrderState.Canceled)
|
|
|
|
{
|
|
|
|
logList.Add(new ExecutionLog()
|
|
|
|
{
|
|
|
|
Id = idGenerator.NewLong(),
|
|
|
|
CreateTime = DateTime.Now,
|
|
|
|
OrderId = spotOrderPublishInfo.OrderId,
|
|
|
|
RobotId = spotOrderPublishInfo.RobotId,
|
|
|
|
SourceSingal = Enums.SingalType.订单推送,
|
|
|
|
Content = $"收到订单推送,订单号:{spotOrderPublishInfo.OrderId},订单方向:{spotOrderPublishInfo.TradeDirection},订单类型:{spotOrderPublishInfo.OrderType},订单状态:{spotOrderPublishInfo.SpotOrderState}{(spotOrderPublishInfo.SpotOrderState == Enums.SpotOrderState.Rejected ? $",拒绝原因:{spotOrderPublishInfo.RejectedReason}" : string.Empty)}"
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
if (spotOrderPublishInfo.SpotOrderState == Enums.SpotOrderState.PartiallyFilled)
|
|
|
|
{
|
|
|
|
logList.Add(new ExecutionLog()
|
|
|
|
{
|
|
|
|
Id = idGenerator.NewLong(),
|
|
|
|
CreateTime = DateTime.Now,
|
|
|
|
OrderId = spotOrderPublishInfo.OrderId,
|
|
|
|
RobotId = spotOrderPublishInfo.RobotId,
|
|
|
|
SourceSingal = Enums.SingalType.订单推送,
|
|
|
|
Content = $"收到订单推送,订单号:{spotOrderPublishInfo.OrderId},订单方向:{spotOrderPublishInfo.TradeDirection},订单类型:{spotOrderPublishInfo.OrderType},订单状态:{spotOrderPublishInfo.SpotOrderState},成交额:{spotOrderPublishInfo.LastTradeAmount},成交量:{spotOrderPublishInfo.LastTradeQuantity},成交价:{spotOrderPublishInfo.LastTradePrice},手续费({spotOrderPublishInfo.FeeUnit}):{spotOrderPublishInfo.Fee}"
|
|
|
|
});
|
|
|
|
updateSpotOrder = null;
|
|
|
|
|
|
|
|
_ = AddSpotFee(spotOrderPublishInfo.OrderId, spotOrderPublishInfo.Fee, false);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (spotOrderPublishInfo.SpotOrderState == Enums.SpotOrderState.Filled)
|
|
|
|
{
|
|
|
|
var fee = AddSpotFee(spotOrderPublishInfo.OrderId, spotOrderPublishInfo.Fee, true);
|
|
|
|
|
|
|
|
var avgTradePrice = spotOrderPublishInfo.CummulativeTradeAmount / spotOrderPublishInfo.CummulativeTradeQuantity; //计算成交均价
|
|
|
|
logList.Add(new ExecutionLog()
|
|
|
|
{
|
|
|
|
Id = idGenerator.NewLong(),
|
|
|
|
CreateTime = DateTime.Now,
|
|
|
|
OrderId = spotOrderPublishInfo.OrderId,
|
|
|
|
RobotId = spotOrderPublishInfo.RobotId,
|
|
|
|
SourceSingal = Enums.SingalType.订单推送,
|
|
|
|
Content = $"收到订单推送,订单号:{spotOrderPublishInfo.OrderId},订单方向:{spotOrderPublishInfo.TradeDirection},订单类型:{spotOrderPublishInfo.OrderType},订单状态:{spotOrderPublishInfo.SpotOrderState},成交额:{spotOrderPublishInfo.LastTradeAmount},成交量:{spotOrderPublishInfo.LastTradeQuantity},成交价:{spotOrderPublishInfo.LastTradePrice},手续费({spotOrderPublishInfo.FeeUnit}):{spotOrderPublishInfo.Fee},总手续费:{fee}"
|
|
|
|
});
|
|
|
|
//更新交易信息
|
|
|
|
updateSpotOrder = updateSpotOrder.Set(o => o.TradeAmount, spotOrderPublishInfo.CummulativeTradeAmount)
|
|
|
|
.Set(o => o.TradeQuantity, spotOrderPublishInfo.CummulativeTradeQuantity)
|
|
|
|
.Set(o => o.TradePrice, avgTradePrice)
|
|
|
|
.Set(o => o.TradeFee, fee)
|
|
|
|
.Set(o => o.TradeFeeUnit, spotOrderPublishInfo.FeeUnit)
|
|
|
|
.Set(o => o.LastTradeTime, spotOrderPublishInfo.LastTradeTime);
|
|
|
|
|
|
|
|
//查询交易所真实资产
|
|
|
|
var currentAsset = apiClient.GetIsolatedMarginAccountAssets().FirstOrDefault(x => x.Symbol == spotOrderPublishInfo.Symbol);
|
|
|
|
|
|
|
|
if (spotOrderPublishInfo.TradeDirection == Enums.TradeDirection.Buy)
|
|
|
|
{
|
|
|
|
var quantity = spotOrderPublishInfo.CummulativeTradeQuantity - fee; //扣除基础币手续费,得到真实购买数量
|
|
|
|
//基础币可用资产,交易所账户持币数量
|
|
|
|
|
|
|
|
//更新机器人账户
|
|
|
|
updateRobotAccount = fsql.Update<RobotAccount>(robot.RobotAccount.Id).Set(ra => ra.SpotCurrencyQuantity, currentAsset.BaseFree)
|
|
|
|
.Set(ra => ra.SpotCurrencyAmount + spotOrderPublishInfo.CummulativeTradeAmount);
|
|
|
|
|
|
|
|
//交易所账户真实持币数量
|
|
|
|
|
|
|
|
updateSpotOrder = updateSpotOrder.Set(o => o.RobotAccountSpotCurrencyQuantity, currentAsset.BaseFree);
|
|
|
|
|
|
|
|
if (spotOrderPublishInfo.OrderType == Enums.OrderType.MARKET)
|
|
|
|
{
|
|
|
|
#region 市价买单挂止损
|
|
|
|
insertStopLossOrderList = new List<SpotOrder>();
|
|
|
|
try
|
|
|
|
{
|
|
|
|
if (robot.D21Policy.Level1PositionStopLossRatio > 0 && robot.D21Policy.Level1PriceStopLossRatio > 0)
|
|
|
|
StopLossOrderPlace(robot, symbolInfo, avgTradePrice, quantity, insertStopLossOrderList, logList, apiClient, true);
|
|
|
|
|
|
|
|
if (robot.D21Policy.Level2PositionStopLossRatio > 0 &&
|
|
|
|
robot.D21Policy.Level2PriceStopLossRatio > 0 &&
|
|
|
|
robot.D21Policy.Level2PositionStopLossRatio + robot.D21Policy.Level1PositionStopLossRatio == 100)
|
|
|
|
StopLossOrderPlace(robot, symbolInfo, avgTradePrice, quantity, insertStopLossOrderList, logList, apiClient, false);
|
|
|
|
}
|
|
|
|
catch (Exception ex)
|
|
|
|
{
|
|
|
|
logList.Add(new ExecutionLog()
|
|
|
|
{
|
|
|
|
Id = idGenerator.NewLong(),
|
|
|
|
SourceSingal = Enums.SingalType.订单推送,
|
|
|
|
RobotId = robot.Id,
|
|
|
|
CreateTime = DateTime.Now,
|
|
|
|
Content = $"止损单挂单失败,{ex.Message}"
|
|
|
|
});
|
|
|
|
}
|
|
|
|
#endregion
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (spotOrderPublishInfo.TradeDirection == Enums.TradeDirection.Sell)
|
|
|
|
{
|
|
|
|
updateUserList = new List<IUpdate<User>>();
|
|
|
|
insertUserAccountProfitLossRecordList = new List<UserAccountProfitLossRecord>();
|
|
|
|
|
|
|
|
|
|
|
|
var interest = 0M; //借币利息
|
|
|
|
var loanAmount = currentAsset.QuoteBorrowed; //借币金额
|
|
|
|
if (loanAmount > 0M)
|
|
|
|
{
|
|
|
|
//还币
|
|
|
|
interest = apiClient.IsolatedMarginRepay(robot.Symbol, loanAmount);
|
|
|
|
logList[0].Content = $"{logList[0].Content},借币金额:{loanAmount},还币利息:{interest}";
|
|
|
|
}
|
|
|
|
|
|
|
|
var buyAmount = spotOrderPublishInfo.CummulativeTradeQuantity * robot.RobotAccount.SpotCurrencyAvgPrice; //本次卖出对应的持仓金额
|
|
|
|
var profit = spotOrderPublishInfo.CummulativeTradeQuantity * (avgTradePrice - robot.RobotAccount.SpotCurrencyAvgPrice) - fee - interest; //计算利润
|
|
|
|
|
|
|
|
|
|
|
|
updateRobotAccount = fsql.Update<RobotAccount>(robot.RobotAccount.Id).Set(ra => ra.SpotCurrencyQuantity, currentAsset.BaseFree)
|
|
|
|
.Set(ra => ra.SpotCurrencyAmount - buyAmount)
|
|
|
|
.Set(ra => ra.TotalProfit + profit)
|
|
|
|
.Set(ra => ra.ClosePositionCount + 1)
|
|
|
|
.SetIf(profit > 0M, ra => ra.WinCount + 1)
|
|
|
|
.SetIf(interest > 0M, ra => ra.LoanAmount, 0M);
|
|
|
|
|
|
|
|
|
|
|
|
updateSpotOrder = updateSpotOrder.SetIf(interest > 0M, o => o.LoanInterest, interest)
|
|
|
|
.Set(o => o.Profit, profit)
|
|
|
|
.Set(o => o.HistoryTotalProfit, robot.RobotAccount.TotalProfit + profit)
|
|
|
|
.Set(o => o.RobotAccountSpotCurrencyQuantity, currentAsset.BaseFree);
|
|
|
|
|
|
|
|
if (profit > 0) //盈利复投
|
|
|
|
updateD21Policy = fsql.Update<D21Policy>(robot.D21Policy.Id).Set(p => p.Position + profit);
|
|
|
|
|
|
|
|
|
|
|
|
var capitalChangeType = profit > 0M ? Enums.CapitalChangeType.Add : Enums.CapitalChangeType.Reduce;
|
|
|
|
var userList = userBusiness.GetUserList(multiplyBy100: false);
|
|
|
|
foreach (var user in userList)
|
|
|
|
{
|
|
|
|
var changeAmount = profit * user.DividendRatio; //根据用户分红比例计算本次分红或亏损
|
|
|
|
|
|
|
|
user.ChangeAmount(capitalChangeType, Math.Abs(changeAmount), false);
|
|
|
|
|
|
|
|
var updateUser = fsql.Update<User>(user.Id).Set(u => u.TotalAssets, user.TotalAssets);
|
|
|
|
|
|
|
|
updateUserList.Add(updateUser);
|
|
|
|
insertUserAccountProfitLossRecordList.Add(new UserAccountProfitLossRecord()
|
|
|
|
{
|
|
|
|
Id = idGenerator.NewLong(),
|
|
|
|
BusinessType = robot.BusinessType,
|
|
|
|
ChangeAmount = changeAmount,
|
|
|
|
CreateTime = DateTime.Now,
|
|
|
|
ExchangeId = robot.ExchangeId,
|
|
|
|
OrderId = spotOrderPublishInfo.OrderId,
|
|
|
|
OrderProfit = profit,
|
|
|
|
UserId = user.Id,
|
|
|
|
RobotId = robot.Id,
|
|
|
|
DividendRatio = user.DividendRatio,
|
|
|
|
CumulativeProfitAndLoss = user.CumulativeProfitAndLoss
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fsql.Transaction(() =>
|
|
|
|
{
|
|
|
|
fsql.Insert(logList).ExecuteAffrows();
|
|
|
|
updateSpotOrder?.ExecuteAffrows();
|
|
|
|
updateRobotAccount?.ExecuteAffrows();
|
|
|
|
updateD21Policy?.ExecuteAffrows();
|
|
|
|
if (insertUserAccountProfitLossRecordList != null && insertUserAccountProfitLossRecordList.Count() > 0)
|
|
|
|
fsql.Insert(insertUserAccountProfitLossRecordList).ExecuteAffrows();
|
|
|
|
if (updateUserList != null && updateUserList.Count() > 0)
|
|
|
|
foreach (var u in updateUserList)
|
|
|
|
u.ExecuteAffrows();
|
|
|
|
if (insertStopLossOrderList != null && insertStopLossOrderList.Count() > 0)
|
|
|
|
fsql.Insert(insertStopLossOrderList).ExecuteAffrows();
|
|
|
|
});
|
|
|
|
|
|
|
|
}
|
|
|
|
catch (Exception ex)
|
|
|
|
{
|
|
|
|
HandleError(ex, logList, spotOrderPublishInfo.LoggerName, spotOrderPublishInfo.RobotId, spotOrderPublishInfo.OrderId, step);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// 挂止损单
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="d21Robot"></param>
|
|
|
|
/// <param name="symbolInfo"></param>
|
|
|
|
/// <param name="avgTradePrice"></param>
|
|
|
|
/// <param name="buyQuantity"></param>
|
|
|
|
/// <param name="insertStopLossOrderList"></param>
|
|
|
|
/// <param name="logList"></param>
|
|
|
|
/// <param name="baseAPIClient"></param>
|
|
|
|
/// <param name="isFirstStopLoss"></param>
|
|
|
|
private void StopLossOrderPlace(D21PolicyRobotResponse d21Robot,
|
|
|
|
SymbolInfo symbolInfo,
|
|
|
|
decimal avgTradePrice,
|
|
|
|
decimal buyQuantity,
|
|
|
|
IList<SpotOrder> insertStopLossOrderList,
|
|
|
|
IList<ExecutionLog> logList,
|
|
|
|
BaseAPIClient baseAPIClient,
|
|
|
|
bool isFirstStopLoss)
|
|
|
|
{
|
|
|
|
var positionStopLossRatio = (isFirstStopLoss ? d21Robot.D21Policy.Level1PositionStopLossRatio : d21Robot.D21Policy.Level2PositionStopLossRatio) / 100;
|
|
|
|
var priceStopLossRatio = (isFirstStopLoss ? d21Robot.D21Policy.Level1PriceStopLossRatio : d21Robot.D21Policy.Level2PriceStopLossRatio) / 100;
|
|
|
|
|
|
|
|
var stopPrice = (avgTradePrice - avgTradePrice * priceStopLossRatio).CutDecimal(symbolInfo.PriceAccuracy);
|
|
|
|
var stopQuantity = (buyQuantity * positionStopLossRatio).CutDecimal(symbolInfo.SaleQuantityAccuracy);
|
|
|
|
var stopLossClientOrderId = CreateClientOrderId(d21Robot.Id, d21Robot.TradePolicy);
|
|
|
|
var stopOrderId = baseAPIClient.IsolatedMarginPlaceOrder(d21Robot.Symbol,
|
|
|
|
Enums.TradeDirection.Sell,
|
|
|
|
Enums.OrderType.STOP_LOSS_LIMIT,
|
|
|
|
stopPrice: stopPrice,
|
|
|
|
price: stopPrice,
|
|
|
|
quantity: stopQuantity,
|
|
|
|
newClientOrderId: stopLossClientOrderId);
|
|
|
|
var stopLossOrder = new SpotOrder()
|
|
|
|
{
|
|
|
|
Id = stopOrderId,
|
|
|
|
ClientOrderId = stopLossClientOrderId,
|
|
|
|
CreateTime = DateTime.Now,
|
|
|
|
ExchangeId = d21Robot.ExchangeId,
|
|
|
|
LoanAmount = 0M,
|
|
|
|
OrderType = Enums.OrderType.STOP_LOSS_LIMIT,
|
|
|
|
PolicyType = Enums.TradePolicy.D21,
|
|
|
|
RobotId = d21Robot.Id,
|
|
|
|
State = Enums.SpotOrderState.Created,
|
|
|
|
Symbol = d21Robot.Symbol,
|
|
|
|
TradeDirection = Enums.TradeDirection.Sell
|
|
|
|
};
|
|
|
|
insertStopLossOrderList.Add(stopLossOrder);
|
|
|
|
|
|
|
|
logList.Add(new ExecutionLog()
|
|
|
|
{
|
|
|
|
Id = idGenerator.NewLong(),
|
|
|
|
SourceSingal = Enums.SingalType.订单推送,
|
|
|
|
RobotId = d21Robot.Id,
|
|
|
|
OrderId = stopOrderId,
|
|
|
|
CreateTime = DateTime.Now,
|
|
|
|
Content = $"{(isFirstStopLoss ? 1 : 2)}级止损挂单成功,订单号:{stopOrderId},挂单数量:{stopQuantity}"
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|