From 7da62d8f7e576623a82422c1e8dca21f9c84c41d Mon Sep 17 00:00:00 2001 From: hucan <951870319@qq.com> Date: Mon, 10 Feb 2025 18:21:44 +0800 Subject: [PATCH] 1 --- app/admin/models/line_pre_order.go | 3 +- app/admin/models/line_pre_order_ext.go | 2 + app/admin/service/dto/line_pre_order.go | 3 +- app/admin/service/line_pre_order.go | 9 +- common/const/rediskey/redis_key.go | 6 +- services/binanceservice/commonservice.go | 20 +- services/binanceservice/futuresrest.go | 191 +++++++++++++- services/binanceservice/models.go | 17 +- services/binanceservice/orderservice.go | 11 + services/binanceservice/spotreset.go | 307 ++++++++++++++++++++--- 10 files changed, 511 insertions(+), 58 deletions(-) diff --git a/app/admin/models/line_pre_order.go b/app/admin/models/line_pre_order.go index 27bbcce..313ec9d 100644 --- a/app/admin/models/line_pre_order.go +++ b/app/admin/models/line_pre_order.go @@ -24,7 +24,7 @@ type LinePreOrder struct { Num string `json:"num" gorm:"type:decimal(18,8);omitempty;comment:购买数量"` BuyPrice string `json:"buyPrice" gorm:"type:decimal(18,8);omitempty;comment:购买金额"` SymbolType int `json:"symbolType" gorm:"type:int;comment:交易对类型:1=现货;2=合约"` - OrderCategory int `json:"orderCategory" gorm:"type:int;comment:订单类型: 1=主单 2=对冲单"` + OrderCategory int `json:"orderCategory" gorm:"type:int;comment:订单类型: 1=主单 2=对冲单 3-加仓单"` Site string `json:"site" gorm:"type:enum('BUY','SELL');omitempty;comment:购买方向:BUY=买;SELL=卖"` OrderSn string `json:"orderSn" gorm:"type:varchar(255);omitempty;comment:订单号"` OrderType int `json:"orderType" gorm:"type:int;omitempty;comment:订单类型:0=主单 1=止盈 2=止损 3=平仓 4=减仓"` @@ -33,6 +33,7 @@ type LinePreOrder struct { CoverType int `json:"coverType" gorm:"type:int unsigned;omitempty;comment:对冲类型 1= 现货对合约 2=合约对合约 3 合约对现货"` ExpireTime time.Time `json:"expireTime" gorm:"comment:过期时间"` MainOrderType string `json:"mainOrderType" gorm:"type:enum;comment:第一笔(主单类型) 限价(LIMIT)市价(MARKET)"` + LossAmount decimal.Decimal `json:"lossAmount" gorm:"type:decimal(15,2);comment:亏损金额(U)"` Child []LinePreOrder `json:"child" gorm:"-"` ApiName string `json:"api_name" gorm:"->"` ChildNum int64 `json:"child_num" gorm:"->"` diff --git a/app/admin/models/line_pre_order_ext.go b/app/admin/models/line_pre_order_ext.go index e1dd8f8..9872e1e 100644 --- a/app/admin/models/line_pre_order_ext.go +++ b/app/admin/models/line_pre_order_ext.go @@ -12,10 +12,12 @@ type LinePreOrderExt struct { MainOrderId int `json:"mainOrderId" gorm:"type:bigint;comment:主单id"` OrderId int `json:"orderId" gorm:"type:bigint;comment:订单id"` TakeProfitRatio decimal.Decimal `json:"takeProfitRatio" gorm:"type:decimal(10,2);comment:止盈百分比"` + ReduceOrderType string `json:"reduceOrderType" gorm:"type:varchar(20);comment:减仓类型 LIMIT-限价 MARKET-市价"` ReducePriceRatio decimal.Decimal `json:"reducePriceRatio" gorm:"type:decimal(10,2);comment:减仓价格百分比"` ReduceNumRatio decimal.Decimal `json:"reduceNumRatio" gorm:"type:decimal(10,2);comment:减仓数量百分比"` ReduceTakeProfitRatio decimal.Decimal `json:"reduceTakeProfitRatio" gorm:"type:decimal(10,2);comment:减仓后止盈百分比"` ReduceStopLossRatio decimal.Decimal `json:"reduceStopLossRatio" gorm:"type:decimal(10,2);comment:减仓后止损百分比"` + AddPositionOrderType string `json:"addPositionOrderType" gorm:"type:varchar(20);comment:加仓类型 LIMIT-限价 MARKET-市价"` AddPositionPriceRatio decimal.Decimal `json:"addPositionPriceRatio" gorm:"type:decimal(10,2);comment:加仓价格百分比"` AddPositionType int `json:"addPositionType" gorm:"type:int;comment:加仓类型 1-百分比 2-实际金额"` AddPositionVal decimal.Decimal `json:"addPositionVal" gorm:"type:decimal(10,2);comment:加仓值"` diff --git a/app/admin/service/dto/line_pre_order.go b/app/admin/service/dto/line_pre_order.go index 4574778..f4acc99 100644 --- a/app/admin/service/dto/line_pre_order.go +++ b/app/admin/service/dto/line_pre_order.go @@ -361,7 +361,8 @@ type PreOrderRedisList struct { type StopLossRedisList struct { Id int `json:"id"` - PId int `json:"pid"` + PId int `json:"pid"` //父级id + MainId int `json:"mainId"` //主单id OrderTye int `json:"orderType"` SymbolType int `json:"symbolType"` OrderCategory int `json:"orderCategory"` diff --git a/app/admin/service/line_pre_order.go b/app/admin/service/line_pre_order.go index 7bece41..6fa8576 100644 --- a/app/admin/service/line_pre_order.go +++ b/app/admin/service/line_pre_order.go @@ -831,14 +831,16 @@ func (e *LinePreOrder) CancelOpenOrder(req *dto.CancelOpenOrderReq, errs *[]erro // ClearAll 一键清除数据 func (e *LinePreOrder) ClearAll() error { - _, err := helper.DefaultRedis.BatchDeleteKeys([]string{rediskey.PreSpotOrderList, rediskey.PreFutOrderList, - rediskey.SpotStopLossList, rediskey.FuturesStopLossList, rediskey.SpotAddPositionList, rediskey.FuturesAddPositionList, - }) + _, err := helper.DefaultRedis.BatchDeleteKeys([]string{rediskey.PreSpotOrderList, rediskey.PreFutOrderList}) if err != nil { e.Log.Errorf("Service RemoveLinePreOrder error:%s \r\n", err) return err } prefixs := []string{ + "spot_stoploss_list", + "futures_stoploss_list", + "spot_add_position_list", + "futures_add_position_list", "api_user_hold", "spot_trigger_lock", "fut_trigger_lock", @@ -861,6 +863,7 @@ func (e *LinePreOrder) ClearAll() error { e.Log.Errorf("Service RemoveLinePreOrder error:%s \r\n", err) return err } + e.Orm.Model(&models.LinePreOrder{}).Exec("TRUNCATE TABLE line_pre_order") //订单表 e.Orm.Model(&models.LinePreOrder{}).Exec("TRUNCATE TABLE line_pre_order_status") //订单拓展状态 e.Orm.Model(&models.LinePreOrder{}).Exec("TRUNCATE TABLE line_pre_order_ext") //订单拓展配置 diff --git a/common/const/rediskey/redis_key.go b/common/const/rediskey/redis_key.go index 6bfad09..c6d8c41 100644 --- a/common/const/rediskey/redis_key.go +++ b/common/const/rediskey/redis_key.go @@ -42,10 +42,12 @@ const ( //需要清理键值---------BEGIN--------------- SpotStopLossList = "spot_stoploss_list:%s" //现货止损待触发列表 {交易所类型code} + SpotReduceList = "spot_reduce_list:%s" //现货减仓待触发 {交易所类型code} FuturesStopLossList = "futures_stoploss_list:%s" //合约止损待触发列表 {交易所类型code} + FuturesReduceList = "futures_reduce_list:%s" //合约减仓待触发 {交易所类型code} - SpotAddPositionList = "spot_add_position_list" //现货加仓待触发 - FuturesAddPositionList = "futures_add_position_list" //合约加仓待触发 + SpotAddPositionList = "spot_add_position_list:%s" //现货加仓待触发 {交易所code} + FuturesAddPositionList = "futures_add_position_list:%s" //合约加仓待触发 {交易所code} //需要清理键值---------END----------------- diff --git a/services/binanceservice/commonservice.go b/services/binanceservice/commonservice.go index 73f591a..fea6845 100644 --- a/services/binanceservice/commonservice.go +++ b/services/binanceservice/commonservice.go @@ -278,8 +278,10 @@ func (e *AddPosition) CalculateAmount(req dto.ManuallyCover, totalNum, lastPrice // coverType 1现货->合约 2->合约->合约 3合约->现货 func MainClosePositionClearCache(mainOrderId int, coverType int) { if coverType == 1 { - spotStopArray, _ := helper.DefaultRedis.GetAllList(rediskey.SpotStopLossList) - spotAddpositionArray, _ := helper.DefaultRedis.GetAllList(rediskey.SpotAddPositionList) + keySpotStop := fmt.Sprintf(rediskey.SpotStopLossList, global.EXCHANGE_BINANCE) + keySpotAddposition := fmt.Sprintf(rediskey.SpotAddPositionList, global.EXCHANGE_BINANCE) + spotStopArray, _ := helper.DefaultRedis.GetAllList(keySpotStop) + spotAddpositionArray, _ := helper.DefaultRedis.GetAllList(keySpotAddposition) var position AddPositionList var stop dto.StopLossRedisList @@ -289,7 +291,7 @@ func MainClosePositionClearCache(mainOrderId int, coverType int) { } if position.Pid == mainOrderId { - helper.DefaultRedis.LRem(rediskey.SpotAddPositionList, item) + helper.DefaultRedis.LRem(keySpotAddposition, item) } } @@ -299,13 +301,15 @@ func MainClosePositionClearCache(mainOrderId int, coverType int) { } if stop.PId == mainOrderId { - helper.DefaultRedis.LRem(rediskey.SpotStopLossList, item) + helper.DefaultRedis.LRem(keySpotStop, item) } } } else { - futAddpositionArray, _ := helper.DefaultRedis.GetAllList(rediskey.FuturesAddPositionList) - futStopArray, _ := helper.DefaultRedis.GetAllList(rediskey.FuturesStopLossList) + keyFutStop := fmt.Sprintf(rediskey.FuturesAddPositionList, global.EXCHANGE_BINANCE) + keyFutAddposition := fmt.Sprintf(rediskey.FuturesStopLossList, global.EXCHANGE_BINANCE) + futAddpositionArray, _ := helper.DefaultRedis.GetAllList(keyFutStop) + futStopArray, _ := helper.DefaultRedis.GetAllList(keyFutAddposition) var position AddPositionList var stop dto.StopLossRedisList @@ -315,7 +319,7 @@ func MainClosePositionClearCache(mainOrderId int, coverType int) { } if position.Pid == mainOrderId { - helper.DefaultRedis.LRem(rediskey.FuturesAddPositionList, item) + helper.DefaultRedis.LRem(keyFutAddposition, item) } } @@ -325,7 +329,7 @@ func MainClosePositionClearCache(mainOrderId int, coverType int) { } if stop.PId == mainOrderId { - helper.DefaultRedis.LRem(rediskey.FuturesStopLossList, item) + helper.DefaultRedis.LRem(keyFutStop, item) } } } diff --git a/services/binanceservice/futuresrest.go b/services/binanceservice/futuresrest.go index f0f0d2a..b1768a2 100644 --- a/services/binanceservice/futuresrest.go +++ b/services/binanceservice/futuresrest.go @@ -16,6 +16,7 @@ import ( "github.com/bytedance/sonic" "github.com/go-admin-team/go-admin-core/logger" + "github.com/jinzhu/copier" "github.com/shopspring/decimal" "gorm.io/gorm" ) @@ -96,6 +97,10 @@ func handleFutOrderByType(db *gorm.DB, preOrder *DbModels.LinePreOrder, orderSta //止盈成交 case preOrder.OrderType == 1 && orderStatus == 6: handleTakeProfit(db, preOrder) + //减仓回调 + case preOrder.OrderType == 4 && orderStatus == 6: + handleReduceFilled(db, preOrder) + //止损成交 case preOrder.OrderType == 2 && orderStatus == 6: handleStopLoss(db, preOrder) @@ -105,6 +110,179 @@ func handleFutOrderByType(db *gorm.DB, preOrder *DbModels.LinePreOrder, orderSta } } +// 减仓回调 +func handleReduceFilled(db *gorm.DB, preOrder *DbModels.LinePreOrder) { + apiUserInfo, _ := GetApiInfo(preOrder.ApiId) + + if apiUserInfo.Id == 0 { + logger.Errorf("handleMainReduceFilled 获取api信息失败,订单号:%s", preOrder.OrderSn) + return + } + + tradeSet, err := GetTradeSet(preOrder.Symbol, 1) + + if err != nil { + logger.Errorf("handleMainReduceFilled 获取交易对设置失败,订单号:%s", preOrder.OrderSn) + return + } + + price := utility.StrToDecimal(preOrder.Price) + parentOrder, err := GetOrderById(db, preOrder.Pid) + + if err != nil { + logger.Errorf("handleMainReduceFilled 获取主单失败,订单号:%s", preOrder.OrderSn) + return + } + parentPrice := utility.StrToDecimal(parentOrder.Price) + num := utility.StrToDecimal(preOrder.Num) + lossAmount := price.Sub(parentPrice).Abs().Mul(num) + + if !strings.HasSuffix(preOrder.Symbol, "USDT") { + tradeSetU, err := GetTradeSet(utility.ReplaceSuffix(preOrder.Symbol, preOrder.QuoteSymbol, ""), 1) + + if err != nil { + logger.Errorf("handleMainReduceFilled 获取币本位对应U交易对设置失败,订单号:%s", preOrder.OrderSn) + return + } + + lossAmount = lossAmount.Mul(utility.StrToDecimal(tradeSetU.LastPrice)).Truncate(2) + } + + if err := db.Model(&parentOrder).Where("loss_amount=0", preOrder.Pid).Update("loss_amount", lossAmount).Error; err != nil { + logger.Errorf("handleMainReduceFilled 更新亏损金额失败,订单号:%s", preOrder.OrderSn) + return + } + + orders := make([]models.LinePreOrder, 0) + rate := utility.StringAsFloat(preOrder.Rate) + ext := models.LinePreOrderExt{} + //获取订单配置 + db.Model(&ext).Where("order_id =?", preOrder.Pid).First(&ext) + + // 不是100%减仓 就需要挂止盈止损 + if rate < 100 { + futApi := FutRestApi{} + positions, err := futApi.GetPositionV3(&apiUserInfo, preOrder.Symbol) + var num decimal.Decimal + + if err != nil { + logger.Errorf("handleMainReduceFilled 获取持仓信息失败,交易对:%s ,订单号:%s", preOrder.Symbol, preOrder.OrderSn) + return + } + + for _, item := range positions { + if item.Symbol == preOrder.Symbol { + positionAmt := utility.StrToDecimal(item.PositionAmt) + + //多 + if positionAmt.Cmp(decimal.Zero) > 0 && preOrder.Site == "SELL" { + num = positionAmt.Abs().Truncate(int32(tradeSet.AmountDigit)) + break + } else if positionAmt.Cmp(decimal.Zero) < 0 && preOrder.Site == "BUY" { + //空 + num = positionAmt.Abs().Truncate(int32(tradeSet.AmountDigit)) + break + } + } + } + + if num.Cmp(decimal.Zero) <= 0 { + logger.Errorf("handleMainReduceFilled 获取持仓数量为0,交易对:%s ,订单号:%s", preOrder.Symbol, preOrder.OrderSn) + return + } + + takeProfitOrder := models.LinePreOrder{} + copier.Copy(&takeProfitOrder, &preOrder) + takeProfitOrder.Id = 0 + takeProfitOrder.OrderSn = utility.Int64ToString(snowflakehelper.GetOrderId()) + takeProfitOrder.Status = 0 + takeProfitOrder.Price = price.Mul(decimal.NewFromInt(1).Add(ext.TakeProfitRatio)).Truncate(int32(tradeSet.PriceDigit)).String() + takeProfitOrder.OrderType = 1 + takeProfitOrder.Rate = "100" + takeProfitOrder.SignPrice = preOrder.Price + takeProfitOrder.CreatedAt = time.Now() + takeProfitOrder.BuyPrice = "0" + takeProfitOrder.MainOrderType = "LIMIT" + takeProfitOrder.Num = num.String() + orders = append(orders, takeProfitOrder) + + //有止损单 + if ext.ReduceStopLossRatio.Cmp(decimal.Zero) > 0 { + var stoploss models.LinePreOrder + + copier.Copy(&stoploss, &preOrder) + stoploss.Id = 0 + stoploss.OrderSn = utility.Int64ToString(snowflakehelper.GetOrderId()) + stoploss.Status = 0 + stoploss.CreatedAt = time.Now() + stoploss.OrderType = 2 + stoploss.SignPrice = preOrder.Price + stoploss.BuyPrice = "0" + stoploss.Rate = "100" + stoploss.MainOrderType = "LIMIT" + stoploss.Num = num.String() + + orders = append(orders, stoploss) + } + + if err := db.Create(&orders).Error; err != nil { + logger.Errorf("handleMainReduceFilled 创建止盈止损单失败:%v", err) + return + } + + spotApi := SpotRestApi{} + paramsMap := OrderPlacementService{ + ApiId: takeProfitOrder.ApiId, + Symbol: takeProfitOrder.Symbol, + Side: takeProfitOrder.Site, + Type: "LIMIT", + TimeInForce: "GTC", + Price: utility.StrToDecimal(takeProfitOrder.Price), + Quantity: num, + NewClientOrderId: takeProfitOrder.OrderSn, + StopPrice: utility.StrToDecimal(takeProfitOrder.Price), + } + if err := spotApi.OrderPlace(db, paramsMap); err != nil { + logger.Errorf("减仓后重下止盈失败 减仓order_sn:%s err:%v", preOrder.OrderSn, err) + + if err2 := db.Model(&takeProfitOrder).Updates(map[string]interface{}{"status": 2, "": err.Error()}).Error; err2 != nil { + logger.Errorf("handleMainReduceFilled 更新止盈单失败:%v", err2) + } + } + } + + //加仓待触发 + addPositionOrder := DbModels.LinePreOrder{} + + if err := db.Model(&addPositionOrder).Where("main_id =? AND order_category=3 AND status=0", preOrder.MainId).First(addPositionOrder).Error; err != nil { + logger.Errorf("handleMainReduceFilled 获取加仓单失败,订单号:%s err:%v", preOrder.OrderSn, err) + return + } + + keySpotAddPosition := fmt.Sprintf(rediskey.SpotAddPositionList, global.EXCHANGE_BINANCE) + + addPositionData := AddPositionList{ + MainId: addPositionOrder.MainId, + Pid: addPositionOrder.Pid, + Price: utility.StrToDecimal(addPositionOrder.Price), + ApiId: addPositionOrder.ApiId, + Symbol: addPositionOrder.Symbol, + Side: addPositionOrder.Site, + SymbolType: addPositionOrder.SymbolType, + } + + addVal, err := sonic.MarshalString(addPositionData) + + if err != nil { + logger.Errorf("handleMainReduceFilled 序列化加仓单失败,订单号:%s err:%v", preOrder.OrderSn, err) + return + } + + if err := helper.DefaultRedis.RPushList(keySpotAddPosition, addVal); err != nil { + logger.Errorf("handleMainReduceFilled 添加加仓单失败,订单号:%s err:%v", preOrder.OrderSn, err) + } +} + // 平仓单成交 func handleClosePosition(db *gorm.DB, preOrder *DbModels.LinePreOrder) { panic("unimplemented") @@ -301,7 +479,7 @@ func handleTakeProfit(db *gorm.DB, preOrder *DbModels.LinePreOrder) { // preOrder 主单 func handleFutMainOrderFilled(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 { + if err := db.Model(&DbModels.LinePreOrder{}).Where("pid = ? AND order_type > 0 AND status = '0' ", preOrder.Id).Find(&orders).Error; err != nil { logger.Error("订单回调查询止盈止损单失败:", err) return } @@ -332,10 +510,17 @@ func handleFutMainOrderFilled(db *gorm.DB, preOrder *models.LinePreOrder) { processFutTakeProfitOrder(db, futApi, order, num) case 2: // 止损 processFutStopLossOrder(db, order, price, num) + case 4: //减仓 + processFutReduceOrder(db, order, price, num) } } } +// 减仓单 +func processFutReduceOrder(db *gorm.DB, order DbModels.LinePreOrder, price, num decimal.Decimal) { + +} + // 处理止盈订单 func processFutTakeProfitOrder(db *gorm.DB, futApi FutRestApi, order models.LinePreOrder, num decimal.Decimal) { price, _ := decimal.NewFromString(order.Price) @@ -346,7 +531,7 @@ func processFutTakeProfitOrder(db *gorm.DB, futApi FutRestApi, order models.Line Side: order.Site, Price: price, Quantity: num, - OrderType: "TAKE_PROFIT_LIMIT", + OrderType: "TAKE_PROFIT", StopPrice: price, NewClientOrderId: order.OrderSn, } @@ -392,7 +577,7 @@ func processFutStopLossOrder(db *gorm.DB, order models.LinePreOrder, price, num Side: order.Site, Price: price, Quantity: num, - OrderType: "STOP_MARKET", + OrderType: "STOP", StopPrice: price, NewClientOrderId: order.OrderSn, } diff --git a/services/binanceservice/models.go b/services/binanceservice/models.go index 1e483f4..3f25b0d 100644 --- a/services/binanceservice/models.go +++ b/services/binanceservice/models.go @@ -70,7 +70,7 @@ type FutOrderPlace struct { OpenOrder int `json:"open_order"` //是否开启限价单止盈止损 Profit decimal.Decimal `json:"profit"` //止盈价格 StopPrice decimal.Decimal `json:"stopprice"` //止损价格 - OrderType string `json:"order_type"` //订单类型,市价或限价MARKET(市价单) TAKE_PROFIT_MARKET(止盈) STOP_MARKET(止损) + OrderType string `json:"order_type"` //订单类型,市价或限价MARKET(市价单) TAKE_PROFIT_MARKET(市价止盈) TAKE_PROFIT(限价止盈) STOP (限价止损) STOP_MARKET(市价止损) NewClientOrderId string `json:"newClientOrderId"` } @@ -188,14 +188,13 @@ type OpenOrders struct { // 待触发加仓单 type AddPositionList struct { - Pid int `json:"pid"` //主单id - ApiId int `json:"apiId"` //触发账户id - Symbol string `json:"symbol"` //交易对 - Price decimal.Decimal `json:"price"` //触发价 - Side string `json:"side"` //买卖方向 - AddPositionMainType string `json:"addPositionType"` //A账号加仓类型 - AddPositionHedgeType string `json:"addPositionHedgeType"` //B账号加仓类型 - SymbolType int `json:"type" comment:"交易对类别 1-现货 2-合约"` + Pid int `json:"pid"` //父级id + MainId int `json:"mainId"` //主单Id + ApiId int `json:"apiId"` //触发账户id + Symbol string `json:"symbol"` //交易对 + Price decimal.Decimal `json:"price"` //触发价 + Side string `json:"side"` //买卖方向 + SymbolType int `json:"type" comment:"交易对类别 1-现货 2-合约"` } // SpotAccountInfo 现货账户信息 diff --git a/services/binanceservice/orderservice.go b/services/binanceservice/orderservice.go index ea18798..a52283e 100644 --- a/services/binanceservice/orderservice.go +++ b/services/binanceservice/orderservice.go @@ -101,3 +101,14 @@ func GetLastStop(db *gorm.DB, pid int) (DbModels.LinePreOrder, error) { return result, nil } + +// 获取主单配置 +// mainId 主单Id +func GetOrderExts(db *gorm.DB, mainId int) ([]models.LinePreOrderExt, error) { + result := make([]models.LinePreOrderExt, 0) + if err := db.Model(&result).Where("main_id =?", mainId).Find(&result).Error; err != nil { + return result, err + } + + return result, nil +} diff --git a/services/binanceservice/spotreset.go b/services/binanceservice/spotreset.go index b81da03..c30eeab 100644 --- a/services/binanceservice/spotreset.go +++ b/services/binanceservice/spotreset.go @@ -11,12 +11,14 @@ import ( "go-admin/common/global" "go-admin/common/helper" "go-admin/pkg/utility" + "go-admin/pkg/utility/snowflakehelper" "strconv" "strings" "time" "github.com/bytedance/sonic" "github.com/go-admin-team/go-admin-core/logger" + "github.com/jinzhu/copier" "github.com/shopspring/decimal" "gorm.io/gorm" ) @@ -111,32 +113,175 @@ func handleOrderByType(db *gorm.DB, preOrder *DbModels.LinePreOrder, orderStatus case preOrder.OrderType == 0 && orderStatus == 6: handleMainOrderFilled(db, preOrder) + //主单减仓完毕 + case preOrder.OrderCategory == 1 && preOrder.OrderType == 4 && orderStatus == 6: + handleMainReduceFilled(db, preOrder) //主单取消 case preOrder.OrderType == 0 && preOrder.Pid == 0 && orderStatus == 4: handleMainOrderCancel(preOrder) - // 止盈成交 case preOrder.OrderType == 1 && orderStatus == 6: handleSpotTakeProfitFilled(db, preOrder) - //平仓单 case preOrder.OrderType == 3 && orderStatus == 6: handleMainOrderClosePosition(db, preOrder) //主单止损回调 - case preOrder.OrderType == 2 && preOrder.OrderCategory == 1 && orderStatus == 6: - if preOrder.CoverType == 0 { - if err := db.Model(&DbModels.LinePreOrder{}).Where("id =?", preOrder.Pid).Update("status", 9).Error; err != nil { - logger.Errorf("主单止损回调 订单号:%s 修改主单状态失败:%v", preOrder.OrderSn, err) - } - } else { - order, err := GetOrderById(db, preOrder.Pid) - if err != nil { - logger.Errorf("主单止损回调 获取主单失败 订单号:%s err:%v", preOrder.OrderSn, err) - return - } - triggerHedgeOrder(order, db, preOrder) + case preOrder.OrderType == 2 && orderStatus == 6: + removeSpotLossAndAddPosition(preOrder) + + if err := db.Model(&DbModels.LinePreOrder{}).Where("id =?", preOrder.MainId).Update("status", 9).Error; err != nil { + logger.Errorf("主单止损回调 订单号:%s 修改主单状态失败:%v", preOrder.OrderSn, err) } + + if err := db.Model(&DbModels.LinePreOrder{}).Where("main_id =? AND status =0", preOrder.MainId).Update("status", 4).Error; err != nil { + logger.Errorf("主单止损回调 订单号:%s 修改主单状态失败:%v", preOrder.OrderSn, err) + } + } +} + +// 主单减仓完毕 +func handleMainReduceFilled(db *gorm.DB, preOrder *DbModels.LinePreOrder) { + apiUserInfo, _ := GetApiInfo(preOrder.ApiId) + + if apiUserInfo.Id == 0 { + logger.Errorf("handleMainReduceFilled 获取api信息失败,订单号:%s", preOrder.OrderSn) + return + } + + tradeSet, err := GetTradeSet(preOrder.Symbol, 0) + + if err != nil { + logger.Errorf("handleMainReduceFilled 获取交易对设置失败,订单号:%s", preOrder.OrderSn) + return + } + + price := utility.StrToDecimal(preOrder.Price) + orders := make([]models.LinePreOrder, 0) + rate := utility.StringAsFloat(preOrder.Rate) + ext := models.LinePreOrderExt{} + //获取订单配置 + db.Model(&ext).Where("order_id =?", preOrder.Pid).First(&ext) + + // 不是100%减仓 就需要挂止盈止损 + if rate < 100 { + 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 + } + + 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 + takeProfitOrder.OrderSn = utility.Int64ToString(snowflakehelper.GetOrderId()) + takeProfitOrder.Status = 0 + takeProfitOrder.Price = price.Mul(decimal.NewFromInt(1).Add(ext.TakeProfitRatio)).Truncate(int32(tradeSet.PriceDigit)).String() + takeProfitOrder.OrderType = 1 + takeProfitOrder.Rate = "100" + takeProfitOrder.SignPrice = preOrder.Price + takeProfitOrder.CreatedAt = time.Now() + takeProfitOrder.BuyPrice = "0" + takeProfitOrder.MainOrderType = "LIMIT" + takeProfitOrder.Num = num.String() + orders = append(orders, takeProfitOrder) + + //有止损单 + if ext.ReduceStopLossRatio.Cmp(decimal.Zero) > 0 { + var stoploss models.LinePreOrder + + copier.Copy(&stoploss, &preOrder) + stoploss.Id = 0 + stoploss.OrderSn = utility.Int64ToString(snowflakehelper.GetOrderId()) + stoploss.Status = 0 + stoploss.CreatedAt = time.Now() + stoploss.OrderType = 2 + stoploss.SignPrice = preOrder.Price + stoploss.BuyPrice = "0" + stoploss.Rate = "100" + stoploss.MainOrderType = "LIMIT" + stoploss.Num = num.String() + + orders = append(orders, stoploss) + } + + if err := db.Create(&orders).Error; err != nil { + logger.Errorf("handleMainReduceFilled 创建止盈止损单失败:%v", err) + return + } + + spotApi := SpotRestApi{} + paramsMap := OrderPlacementService{ + ApiId: takeProfitOrder.ApiId, + Symbol: takeProfitOrder.Symbol, + Side: takeProfitOrder.Site, + Type: "LIMIT", + TimeInForce: "GTC", + Price: utility.StrToDecimal(takeProfitOrder.Price), + Quantity: num, + NewClientOrderId: takeProfitOrder.OrderSn, + StopPrice: utility.StrToDecimal(takeProfitOrder.Price), + } + if err := spotApi.OrderPlace(db, paramsMap); err != nil { + logger.Errorf("减仓后重下止盈失败 减仓order_sn:%s err:%v", preOrder.OrderSn, err) + + if err2 := db.Model(&takeProfitOrder).Updates(map[string]interface{}{"status": 2, "": err.Error()}).Error; err2 != nil { + logger.Errorf("handleMainReduceFilled 更新止盈单失败:%v", err2) + } + } + } + + //加仓待触发 + addPositionOrder := DbModels.LinePreOrder{} + + if err := db.Model(&addPositionOrder).Where("main_id =? AND order_category=3 AND status=0", preOrder.MainId).First(addPositionOrder).Error; err != nil { + logger.Errorf("handleMainReduceFilled 获取加仓单失败,订单号:%s err:%v", preOrder.OrderSn, err) + return + } + + keySpotAddPosition := fmt.Sprintf(rediskey.SpotAddPositionList, global.EXCHANGE_BINANCE) + + addPositionData := AddPositionList{ + MainId: addPositionOrder.MainId, + Pid: addPositionOrder.Pid, + Price: utility.StrToDecimal(addPositionOrder.Price), + ApiId: addPositionOrder.ApiId, + Symbol: addPositionOrder.Symbol, + Side: addPositionOrder.Site, + SymbolType: addPositionOrder.SymbolType, + } + + addVal, err := sonic.MarshalString(addPositionData) + + if err != nil { + logger.Errorf("handleMainReduceFilled 序列化加仓单失败,订单号:%s err:%v", preOrder.OrderSn, err) + return + } + + if err := helper.DefaultRedis.RPushList(keySpotAddPosition, addVal); err != nil { + logger.Errorf("handleMainReduceFilled 添加加仓单失败,订单号:%s err:%v", preOrder.OrderSn, err) } } @@ -166,36 +311,75 @@ func handleMainOrderCancel(preOrder *DbModels.LinePreOrder) { func handleMainOrderClosePosition(db *gorm.DB, preOrder *DbModels.LinePreOrder) { //主单平仓 if preOrder.OrderCategory == 1 { - if err := db.Model(&DbModels.LinePreOrder{}).Where("id =?", preOrder.Pid).Update("status", 9).Error; err != nil { - logger.Errorf("平仓订单回调失败, 回调订单号:%s 更新主单失败:%v", preOrder.OrderSn, err) - } - } else { - //对冲单回调 - if err := db.Model(&DbModels.LinePreOrder{}). - Where("pid =? AND order_category =? AND order_type =0 AND status <9 ", preOrder.Pid, preOrder.OrderCategory). - Update("status", 9).Error; err != nil { - logger.Errorf("对冲单平仓回调失败, 回调订单号:%s 更新对冲单失败:%v", preOrder.OrderSn, err) - } + ids := []int{preOrder.Pid, preOrder.MainId} + db.Transaction(func(tx *gorm.DB) error { + if err := db.Model(&DbModels.LinePreOrder{}).Where("id IN ? AND status=6", ids).Update("status", 9).Error; err != nil { + logger.Errorf("平仓订单回调失败, 回调订单号:%s 更新主单失败:%v", preOrder.OrderSn, err) + return err + } + + if err := db.Model(&DbModels.LinePreOrder{}).Where("main_id =? AND status=0", preOrder.MainId).Update("status", 4).Error; err != nil { + logger.Errorf("平仓订单回调失败, 回调订单号:%s 更新子单失败:%v", preOrder.OrderSn, err) + return err + } + + return nil + }) + } + + removeSpotLossAndAddPosition(preOrder) } // 止盈成交 func handleSpotTakeProfitFilled(db *gorm.DB, preOrder *DbModels.LinePreOrder) { - if err := db.Model(&DbModels.LinePreOrder{}).Where("id =? AND order_category =?", preOrder.Pid, preOrder.OrderCategory).Update("status", 9).Error; err != nil { - logger.Errorf("止盈订单回调失败, 回调订单号:%s 更新主单失败:%v", preOrder.OrderSn, err) - } + removeSpotLossAndAddPosition(preOrder) + db.Transaction(func(tx *gorm.DB) error { + ids := []int{preOrder.Pid, preOrder.MainId} + if err := db.Model(&DbModels.LinePreOrder{}).Where("id IN ? AND status =6", ids).Update("status", 9).Error; err != nil { + logger.Errorf("止盈订单回调失败, 回调订单号:%s 更新主单失败:%v", preOrder.OrderSn, err) + + return err + } + + if err := db.Model(&DbModels.LinePreOrder{}).Where("main_id =? AND status=0").Update("status", 4).Error; err != nil { + logger.Errorf("止盈订单回调失败, 回调订单号:%s 更新取消状态失败:%v", preOrder.OrderSn, err) + + return err + } + + return nil + }) + +} + +func removeSpotLossAndAddPosition(preOrder *DbModels.LinePreOrder) { stoplossKey := fmt.Sprintf(rediskey.SpotStopLossList, global.EXCHANGE_BINANCE) stoplossVal, _ := helper.DefaultRedis.GetAllList(stoplossKey) - stoploss := DbModels.LinePreOrder{} + addPositionKey := fmt.Sprintf(rediskey.SpotAddPositionList, global.EXCHANGE_BINANCE) + addPositionVal, _ := helper.DefaultRedis.GetAllList(addPositionKey) + stoploss := dto.StopLossRedisList{} + addPosition := AddPositionList{} for _, v := range stoplossVal { sonic.Unmarshal([]byte(v), &stoploss) - if stoploss.Pid == preOrder.Pid { + if stoploss.MainId == preOrder.MainId { _, err := helper.DefaultRedis.LRem(stoplossKey, v) if err != nil { - logger.Errorf("订单回调失败, 回调订单号:%s 删除缓存失败:%v", preOrder.OrderSn, err) + logger.Errorf("订单回调失败, 回调订单号:%s 删除止损缓存失败:%v", preOrder.OrderSn, err) + } + } + } + + for _, v := range addPositionVal { + sonic.Unmarshal([]byte(v), &addPosition) + if addPosition.MainId == preOrder.MainId { + _, err := helper.DefaultRedis.LRem(addPositionKey, v) + + if err != nil { + logger.Errorf("订单回调失败, 回调订单号:%s 删除加仓缓存失败:%v", preOrder.OrderSn, err) } } } @@ -204,6 +388,67 @@ func handleSpotTakeProfitFilled(db *gorm.DB, preOrder *DbModels.LinePreOrder) { // 主单成交 func handleMainOrderFilled(db *gorm.DB, preOrder *DbModels.LinePreOrder) { processTakeProfitAndStopLossOrders(db, preOrder) + tradeSet, _ := GetTradeSet(preOrder.Symbol, 0) + + if tradeSet.Coin == "" { + logger.Errorf("获取交易对配置失败, 回调订单号:%s", preOrder.OrderSn) + return + } + + //主单类型 + if preOrder.OrderCategory == 1 { + //预生成加仓单 + orderExts, err := GetOrderExts(db, preOrder.Id) + + if err != nil { + logger.Errorf("预生成加仓单失败, 回调订单号:%s 获取主单拓展配置失败:%v", preOrder.OrderSn, err) + return + } + + price := utility.StrToDecimal(preOrder.Price) + for _, v := range orderExts { + if v.OrderId == 0 { + var data DbModels.LinePreOrder + + copier.Copy(&data, &v) + + data.Id = 0 + data.Pid = preOrder.Id + data.OrderSn = utility.Int64ToString(snowflakehelper.GetOrderId()) + data.MainId = preOrder.Id + data.CreatedAt = time.Now() + data.MainOrderType = v.AddPositionOrderType + data.Status = 0 + data.OrderCategory = 3 + var percentage decimal.Decimal + + if data.Site == "BUY" { + percentage = decimal.NewFromInt(1).Add(v.AddPositionPriceRatio) + } else { + percentage = decimal.NewFromInt(1).Sub(v.AddPositionPriceRatio) + } + data.Price = price.Mul(percentage).Truncate(int32(tradeSet.PriceDigit)).String() + + err := db.Transaction(func(tx *gorm.DB) error { + if err2 := tx.Create(&data).Error; err2 != nil { + return err2 + } + + v.OrderId = data.Id + if err2 := tx.Model(&v).Update("order_id", data.Id).Error; err2 != nil { + return err2 + } + + return nil + }) + + if err != nil { + logger.Errorf("预生成加仓单失败, 回调订单号:%s 预生成加仓单失败:%v", preOrder.OrderSn, err) + return + } + } + } + } } // 解析订单状态 @@ -279,7 +524,7 @@ func processTakeProfitAndStopLossOrders(db *gorm.DB, preOrder *models.LinePreOrd spotApi := SpotRestApi{} num, _ := decimal.NewFromString(preOrder.Num) - num = num.Mul(decimal.NewFromFloat(0.998)) + // num = num.Mul(decimal.NewFromFloat(0.998)) for i, order := range orders { if i >= 2 { // 最多处理 2 个订单