This commit is contained in:
2025-02-11 14:49:16 +08:00
parent 1c5df7ad90
commit 18cbf359b7
10 changed files with 1319 additions and 612 deletions

View File

@ -10,6 +10,7 @@ import (
"go-admin/common/const/rediskey"
"go-admin/common/global"
"go-admin/common/helper"
models2 "go-admin/models"
"go-admin/pkg/utility"
"go-admin/pkg/utility/snowflakehelper"
"strconv"
@ -157,6 +158,7 @@ func handleMainReduceFilled(db *gorm.DB, preOrder *DbModels.LinePreOrder) {
}
price := utility.StrToDecimal(preOrder.Price)
parentOrder, _ := GetOrderById(db, preOrder.Pid)
orders := make([]models.LinePreOrder, 0)
rate := utility.StringAsFloat(preOrder.Rate)
ext := models.LinePreOrderExt{}
@ -165,34 +167,11 @@ func handleMainReduceFilled(db *gorm.DB, preOrder *DbModels.LinePreOrder) {
// 不是100%减仓 就需要挂止盈止损
if rate < 100 {
client := GetClient(&apiUserInfo)
resp, _, err := client.SendSpotAuth("/api/v3/account", "GET", map[string]interface{}{
"omitZeroBalances": true,
})
num, err := getSpotPositionNum(apiUserInfo, preOrder, tradeSet)
if err != nil {
logger.Errorf("api_id:%d 交易对:%s 查询用户信息失败:%v", apiUserInfo.Id, preOrder.Symbol, err)
return
}
var balanceInfo SpotAccountInfo
sonic.Unmarshal(resp, &balanceInfo)
suffix := utility.ReplaceSuffix(preOrder.Symbol, preOrder.QuoteSymbol, "")
var num decimal.Decimal
for _, item := range balanceInfo.Balances {
if item.Asset == suffix {
if utility.StrToDecimal(item.Free).Cmp(decimal.Zero) <= 0 {
logger.Error("handleMainReduceFilled 账户余额不足,交易对:%s 订单号:%s", preOrder.Symbol, preOrder.OrderSn)
return
}
num = utility.StrToDecimal(item.Free).Mul(decimal.NewFromFloat(0.998)).Truncate(int32(tradeSet.AmountDigit))
break
}
}
takeProfitOrder := models.LinePreOrder{}
copier.Copy(&takeProfitOrder, &preOrder)
takeProfitOrder.Id = 0
@ -253,6 +232,17 @@ func handleMainReduceFilled(db *gorm.DB, preOrder *DbModels.LinePreOrder) {
}
}
//计算实际亏损
if parentOrder.Price != "" {
parentPrice := utility.StrToDecimal(parentOrder.Price)
reduceNum := utility.StrToDecimal(preOrder.Num)
lossAmountU := price.Sub(parentPrice).Abs().Mul(reduceNum).Truncate(2)
if err := db.Model(&parentOrder).Update("loss_amount", lossAmountU).Error; err != nil {
logger.Errorf("修改主单实际亏损失败 订单号:%s err:%v", parentOrder.OrderSn, err)
}
}
//加仓待触发
addPositionOrder := DbModels.LinePreOrder{}
@ -285,6 +275,38 @@ func handleMainReduceFilled(db *gorm.DB, preOrder *DbModels.LinePreOrder) {
}
}
// 获取现货持仓数量
func getSpotPositionNum(apiUserInfo DbModels.LineApiUser, preOrder *DbModels.LinePreOrder, tradeSet models2.TradeSet) (decimal.Decimal, error) {
client := GetClient(&apiUserInfo)
resp, _, err := client.SendSpotAuth("/api/v3/account", "GET", map[string]interface{}{
"omitZeroBalances": true,
})
if err != nil {
logger.Errorf("api_id:%d 交易对:%s 查询用户信息失败:%v", apiUserInfo.Id, preOrder.Symbol, err)
return decimal.Decimal{}, err
}
var balanceInfo SpotAccountInfo
sonic.Unmarshal(resp, &balanceInfo)
suffix := utility.ReplaceSuffix(preOrder.Symbol, preOrder.QuoteSymbol, "")
var num decimal.Decimal
for _, item := range balanceInfo.Balances {
if item.Asset == suffix {
if utility.StrToDecimal(item.Free).Cmp(decimal.Zero) <= 0 {
logger.Error("handleMainReduceFilled 账户余额不足,交易对:%s 订单号:%s", preOrder.Symbol, preOrder.OrderSn)
return decimal.Decimal{}, errors.New("账户余额不足")
}
num = utility.StrToDecimal(item.Free).Mul(decimal.NewFromFloat(0.998)).Truncate(int32(tradeSet.AmountDigit))
break
}
}
return num, nil
}
// 主单取消
func handleMainOrderCancel(preOrder *DbModels.LinePreOrder) {
preOrderKey := fmt.Sprintf(rediskey.PreSpotOrderList, global.EXCHANGE_BINANCE)
@ -359,9 +381,13 @@ func removeSpotLossAndAddPosition(preOrder *DbModels.LinePreOrder) {
stoplossVal, _ := helper.DefaultRedis.GetAllList(stoplossKey)
addPositionKey := fmt.Sprintf(rediskey.SpotAddPositionList, global.EXCHANGE_BINANCE)
addPositionVal, _ := helper.DefaultRedis.GetAllList(addPositionKey)
reduceKey := fmt.Sprintf(rediskey.SpotReduceList, global.EXCHANGE_BINANCE)
reduceVal, _ := helper.DefaultRedis.GetAllList(reduceKey)
stoploss := dto.StopLossRedisList{}
addPosition := AddPositionList{}
reduce := ReduceListItem{}
//止损缓存
for _, v := range stoplossVal {
sonic.Unmarshal([]byte(v), &stoploss)
if stoploss.MainId == preOrder.MainId {
@ -373,6 +399,7 @@ func removeSpotLossAndAddPosition(preOrder *DbModels.LinePreOrder) {
}
}
//加仓缓存
for _, v := range addPositionVal {
sonic.Unmarshal([]byte(v), &addPosition)
if addPosition.MainId == preOrder.MainId {
@ -383,6 +410,18 @@ func removeSpotLossAndAddPosition(preOrder *DbModels.LinePreOrder) {
}
}
}
//减仓缓存
for _, v := range reduceVal {
sonic.Unmarshal([]byte(v), &reduce)
if reduce.MainId == preOrder.MainId {
_, err := helper.DefaultRedis.LRem(reduceKey, v)
if err != nil {
logger.Errorf("订单回调失败, 回调订单号:%s 删除减仓缓存失败:%v", preOrder.OrderSn, err)
}
}
}
}
// 主单成交
@ -517,14 +556,28 @@ func updateOrderStatus(db *gorm.DB, preOrder *models.LinePreOrder, status int, r
// preOrder 主单
func processTakeProfitAndStopLossOrders(db *gorm.DB, preOrder *models.LinePreOrder) {
orders := []models.LinePreOrder{}
if err := db.Model(&DbModels.LinePreOrder{}).Where("pid = ? AND order_category = 1 AND order_type > 0 AND status = '0' ", preOrder.Id).Find(&orders).Error; err != nil {
tradeSet, _ := GetTradeSet(preOrder.Symbol, 0)
if tradeSet.Coin == "" {
logger.Error("获取交易对失败")
return
}
if err := db.Model(&DbModels.LinePreOrder{}).
Where("pid = ? AND order_category = 1 AND order_type > 0 AND status = '0' ", preOrder.Id).
Find(&orders).Error; err != nil && errors.Is(err, gorm.ErrRecordNotFound) {
logger.Error("订单回调查询止盈止损单失败:", err)
return
} else if len(orders) == 0 && preOrder.OrderCategory == 3 {
orders, err = makeSpotTakeAndReduce(preOrder, db, tradeSet, orders)
if err != nil {
return
}
}
spotApi := SpotRestApi{}
num, _ := decimal.NewFromString(preOrder.Num)
// num = num.Mul(decimal.NewFromFloat(0.998))
for i, order := range orders {
if i >= 2 { // 最多处理 2 个订单
@ -540,10 +593,123 @@ func processTakeProfitAndStopLossOrders(db *gorm.DB, preOrder *models.LinePreOrd
processTakeProfitOrder(db, spotApi, order, num)
case 2: // 止损
processStopLossOrder(order)
case 4: //减仓
processSpotReduceOrder(order, num)
}
}
}
// 构建现货止盈、减仓单
func makeSpotTakeAndReduce(preOrder *DbModels.LinePreOrder, db *gorm.DB, tradeSet models2.TradeSet, orders []DbModels.LinePreOrder) ([]DbModels.LinePreOrder, error) {
ext := models.LinePreOrderExt{}
apiInfo, err := GetApiInfo(preOrder.ApiId)
price := utility.StrToDecimal(preOrder.Price)
if apiInfo.Id == 0 {
logger.Error("订单回调查询apiuserinfo失败 err:", err)
return nil, errors.New("订单回调查询apiuserinfo失败")
}
if err := db.Model(&ext).Where("order_id = ?", preOrder.Id).First(&ext).Error; err != nil {
logger.Error("订单回调查询止盈止损单扩展表失败:", err)
return nil, errors.New("订单回调查询止盈止损单扩展表失败")
}
totalLossAmountU, _ := GetTotalLossAmount(db, preOrder.MainId)
num, err := getSpotPositionNum(apiInfo, preOrder, tradeSet)
if err != nil {
logger.Error("订单回调查询持仓数量失败:", err)
return nil, errors.New("订单回调查询持仓数量失败")
}
//止盈单
if ext.TakeProfitRatio.Cmp(decimal.Zero) > 0 {
profitOrder := models.LinePreOrder{}
copier.Copy(&profitOrder, preOrder)
profitOrder.OrderSn = strconv.FormatInt(snowflakehelper.GetOrderId(), 10)
profitOrder.Pid = preOrder.Id
profitOrder.OrderType = 1
profitOrder.Status = 0
profitOrder.Rate = ext.TakeProfitRatio.String()
profitOrder.MainId = ext.MainOrderId
profitOrder.Num = num.String()
//止盈需要累加之前的亏损
if totalLossAmountU.Cmp(decimal.Zero) > 0 {
profitOrder.Rate = totalLossAmountU.Div(num).Div(price).Sub(decimal.NewFromInt(1)).Abs().Add(ext.TakeProfitRatio).Truncate(2).String()
} else {
profitOrder.Rate = ext.TakeProfitRatio.String()
}
orders = append(orders, profitOrder)
}
//减仓单
if ext.ReducePriceRatio.Cmp(decimal.Zero) > 0 {
stopOrder := models.LinePreOrder{}
copier.Copy(&stopOrder, preOrder)
stopOrder.OrderSn = strconv.FormatInt(snowflakehelper.GetOrderId(), 10)
stopOrder.Pid = preOrder.Id
stopOrder.MainId = ext.MainOrderId
stopOrder.OrderType = 4
stopOrder.Status = 0
stopOrder.Rate = ext.ReducePriceRatio.String()
stopOrder.Num = num.String()
orders = append(orders, stopOrder)
}
for index := range orders {
if strings.ToUpper(preOrder.Site) == "BUY" {
orders[index].Site = "SELL"
orders[index].Price = utility.StrToDecimal(preOrder.Price).Mul(decimal.NewFromInt(1).Sub(ext.ReducePriceRatio.Div(decimal.NewFromInt(100)))).Truncate(int32(tradeSet.PriceDigit)).String()
} else {
orders[index].Site = "BUY"
orders[index].Price = utility.StrToDecimal(preOrder.Price).Mul(decimal.NewFromInt(1).Add(ext.ReducePriceRatio.Div(decimal.NewFromInt(100)))).Truncate(int32(tradeSet.PriceDigit)).String()
}
}
if len(orders) > 0 {
if err := db.Create(&orders).Error; err != nil {
logger.Error("主单回调,创建止盈、减仓单失败")
return orders, errors.New("主单回调,创建止盈、减仓单失败")
}
}
return orders, nil
}
// 现货减仓
func processSpotReduceOrder(preOrder DbModels.LinePreOrder, num decimal.Decimal) {
key := fmt.Sprintf(rediskey.SpotReduceList, global.EXCHANGE_BINANCE)
item := ReduceListItem{
Id: preOrder.Id,
ApiId: preOrder.ApiId,
Pid: preOrder.Pid,
MainId: preOrder.MainId,
Price: utility.StrToDecimal(preOrder.Price),
Num: num,
Side: preOrder.Site,
Symbol: preOrder.Symbol,
OrderSn: preOrder.OrderSn,
}
itemVal, err := sonic.MarshalString(&item)
if err != nil {
logger.Errorf("序列化失败 err:%v", err)
return
}
if err := helper.DefaultRedis.RPushList(key, itemVal); err != nil {
logger.Errorf("减仓单写入缓存失败 err:%v", err)
return
}
}
// 处理止盈订单
func processTakeProfitOrder(db *gorm.DB, spotApi SpotRestApi, order models.LinePreOrder, num decimal.Decimal) {
tradeSet, _ := GetTradeSet(order.Symbol, 0)