diff --git a/app/admin/models/line_reduce_strategy_item.go b/app/admin/models/line_reduce_strategy_item.go index 64d70c1..9885dd9 100644 --- a/app/admin/models/line_reduce_strategy_item.go +++ b/app/admin/models/line_reduce_strategy_item.go @@ -11,6 +11,7 @@ type LineReduceStrategyItem struct { ReduceStrategyId int `json:"reduceStrategyId" gorm:"type:bigint;comment:减仓策略id"` LossPercent decimal.Decimal `json:"lossPercent" gorm:"type:decimal(10,2);comment:亏损百分比"` + QuantityPercent decimal.Decimal `json:"quantityPercent" gorm:"type:decimal(10,2);comment:减仓数量百分比"` OrderType string `json:"orderType" gorm:"type:varchar(20);comment:订单类型 LIMIT-限价 MARKET-市价"` ReduceStrategy LineReduceStrategy `json:"reduceStrategy" gorm:"foreignKey:ReduceStrategyId;"` models.ModelTime diff --git a/app/admin/service/dto/line_order_reduce_strategy.go b/app/admin/service/dto/line_order_reduce_strategy.go index 23f3af9..514443c 100644 --- a/app/admin/service/dto/line_order_reduce_strategy.go +++ b/app/admin/service/dto/line_order_reduce_strategy.go @@ -3,7 +3,8 @@ package dto import "github.com/shopspring/decimal" type LineOrderReduceStrategyResp struct { - OrderId int `json:"orderId"` + MainId int `json:"mainId" comment:"主单id"` + OrderId int `json:"orderId" comment:"减仓单id"` Symbol string `json:"symbol"` Side string `json:"side" comment:"BUY SELL"` Items []LineOrderReduceStrategyRespItem `json:"items"` @@ -12,6 +13,7 @@ type LineOrderReduceStrategyResp struct { // 减仓节点 type LineOrderReduceStrategyRespItem struct { Price decimal.Decimal `json:"p" comment:"下单价"` + Num decimal.Decimal `json:"n" comment:"下单数量"` TriggerPrice decimal.Decimal `json:"t" comment:"触发价"` LossPercent decimal.Decimal `json:"l" comment:"亏损百分比"` OrderType string `json:"o" comment:"订单类型 LIMIT-限价 MARKET-市价"` diff --git a/app/admin/service/dto/line_reduce_strategy.go b/app/admin/service/dto/line_reduce_strategy.go index bba58d8..6abea2d 100644 --- a/app/admin/service/dto/line_reduce_strategy.go +++ b/app/admin/service/dto/line_reduce_strategy.go @@ -56,7 +56,7 @@ func (s *LineReduceStrategyInsertReq) Valid() error { return err } - if index > 0 && item.LossPercent.Cmp(s.Items[index].LossPercent) <= 0 { + if index > 0 && item.LossPercent.Cmp(s.Items[index-1].LossPercent) <= 0 { return errors.New("亏损比例必须递增") } } @@ -75,6 +75,7 @@ func (s *LineReduceStrategyInsertReq) Generate(model *models.LineReduceStrategy) strategyItem := models.LineReduceStrategyItem{} strategyItem.OrderType = item.OrderType strategyItem.LossPercent = item.LossPercent + strategyItem.QuantityPercent = item.QuantityPercent model.Items = append(model.Items, strategyItem) } @@ -105,6 +106,7 @@ func (s *LineReduceStrategyUpdateReq) Generate(model *models.LineReduceStrategy) strategyItem := models.LineReduceStrategyItem{} strategyItem.OrderType = item.OrderType strategyItem.LossPercent = item.LossPercent + strategyItem.QuantityPercent = item.QuantityPercent model.Items = append(model.Items, strategyItem) } diff --git a/app/admin/service/dto/line_reduce_strategy_item.go b/app/admin/service/dto/line_reduce_strategy_item.go index cda988f..4e563cd 100644 --- a/app/admin/service/dto/line_reduce_strategy_item.go +++ b/app/admin/service/dto/line_reduce_strategy_item.go @@ -32,8 +32,9 @@ func (m *LineReduceStrategyItemGetPageReq) GetNeedSearch() interface{} { } type LineReduceStrategyItem struct { - LossPercent decimal.Decimal `json:"lossPercent" comment:"止损百分比"` - OrderType string `json:"orderType" comment:"订单类型 LIMIT-限价 MARKET-市价"` + LossPercent decimal.Decimal `json:"lossPercent" comment:"止损百分比"` + QuantityPercent decimal.Decimal `json:"quantityPercent" comment:"数量百分比"` + OrderType string `json:"orderType" comment:"订单类型 LIMIT-限价 MARKET-市价"` } func (s *LineReduceStrategyItem) Valid() error { @@ -46,6 +47,14 @@ func (s *LineReduceStrategyItem) Valid() error { return errors.New("百分比不能大于等于100") } + if s.QuantityPercent.Cmp(decimal.Zero) <= 0 { + return errors.New("百分比不能小于等于0") + } + + if s.QuantityPercent.Cmp(decimal.NewFromInt(100)) >= 0 { + return errors.New("数量百分比不能大于等于100") + } + keys := []string{"LIMIT", "MARKET"} if !utility.ContainsStr(keys, s.OrderType) { diff --git a/app/admin/service/line_pre_order.go b/app/admin/service/line_pre_order.go index ee291dc..f3a8cd0 100644 --- a/app/admin/service/line_pre_order.go +++ b/app/admin/service/line_pre_order.go @@ -1042,6 +1042,7 @@ func createPreReduceOrder(preOrder *models.LinePreOrder, ext models.LinePreOrder // 构建止盈、止盈止损 func makeFuturesTakeAndReduce(preOrder *models.LinePreOrder, ext models.LinePreOrderExt, tradeSet models2.TradeSet) ([]models.LinePreOrder, error) { orders := make([]models.LinePreOrder, 0) + mainId := preOrder.Id var side string if (preOrder.OrderType != 0 && strings.ToUpper(preOrder.Site) == "BUY") || (preOrder.OrderType == 0 && strings.ToUpper(preOrder.Site) == "SELL") { @@ -1050,6 +1051,10 @@ func makeFuturesTakeAndReduce(preOrder *models.LinePreOrder, ext models.LinePreO side = "SELL" } + if preOrder.MainId > 0 { + mainId = preOrder.MainId + } + if ext.TakeProfitRatio.Cmp(decimal.Zero) > 0 { // 止盈单 profitOrder := models.LinePreOrder{} @@ -1060,11 +1065,7 @@ func makeFuturesTakeAndReduce(preOrder *models.LinePreOrder, ext models.LinePreO profitOrder.Pid = preOrder.Id profitOrder.OrderType = 1 profitOrder.Status = 0 - profitOrder.MainId = preOrder.Id - - if preOrder.MainId > 0 { - profitOrder.MainId = preOrder.MainId - } + profitOrder.MainId = mainId profitOrder.BuyPrice = "0" profitOrder.Site = side @@ -1090,7 +1091,7 @@ func makeFuturesTakeAndReduce(preOrder *models.LinePreOrder, ext models.LinePreO lossOrder.Pid = preOrder.Id lossOrder.OrderType = 2 lossOrder.Status = 0 - lossOrder.MainId = preOrder.MainId + lossOrder.MainId = mainId lossOrder.BuyPrice = "0" lossOrder.Num = ext.TotalAfter.Truncate(int32(tradeSet.AmountDigit)).String() lossOrder.Rate = ext.StopLossRatio.Truncate(2).String() @@ -1611,6 +1612,10 @@ func (e *LinePreOrder) ClearAll() error { "futures_reduce_list", "spot_reduce_strategy_list", "fut_reduce_strategy_list", + "future_position", + "spot_position", + "strategy_spot_order_list", + "strategy_fut_order_list", } err = helper.DefaultRedis.DeleteKeysByPrefix(prefixs...) if err != nil { diff --git a/common/const/rediskey/redis_key.go b/common/const/rediskey/redis_key.go index 70dbc31..0290e1e 100644 --- a/common/const/rediskey/redis_key.go +++ b/common/const/rediskey/redis_key.go @@ -26,9 +26,9 @@ const ( PreFutOrderList = "_PreFutOrderList_:%s" // 待触发的订单集合 {交易所类型 exchange_type} //策略现货订单集合 {交易所类型 exchange_type} - StrategySpotOrderList = "strategy_spot_order_list_%s" + StrategySpotOrderList = "strategy_spot_order_list:%s" //策略合约订单集合 {交易所类型 exchange_type} - StrategyFutOrderList = "strategy_fut_order_list_%s" + StrategyFutOrderList = "strategy_fut_order_list:%s" API_USER = "api_user:%v" // api用户 SystemSetting = "system_setting" //系统设置 @@ -51,6 +51,11 @@ const ( //波段合约触发{apiuserid|symbol} StrategyFutTriggerLock = "strategy_fut_trigger_l:%v_%s" + //减仓波段合约触发 {apiuserid|symbol} + ReduceStrategyFutTriggerLock = "reduce_strategy_fut_trigger_l:%v_%s" + //减仓波段现货触发 {apiuserid|symbol} + ReduceStrategySpotTriggerLock = "reduce_strategy_spot_trigger_l:%v_%s" + SpotCallBack = "spot_callback:%s" //现货回调 {ordersn} FutCallBack = "fut_callback:%s" //合约回调 {ordersn} diff --git a/services/binanceservice/futures_judge_service_test.go b/services/binanceservice/futures_judge_service_test.go new file mode 100644 index 0000000..1cf1de1 --- /dev/null +++ b/services/binanceservice/futures_judge_service_test.go @@ -0,0 +1,63 @@ +package binanceservice + +import ( + "fmt" + "go-admin/common/const/rediskey" + "go-admin/common/global" + "go-admin/common/helper" + "go-admin/models" + "go-admin/services/cacheservice" + "testing" + + "github.com/bytedance/sonic" + "github.com/go-admin-team/go-admin-core/logger" + "github.com/go-admin-team/go-admin-core/sdk" + "gorm.io/driver/mysql" + "gorm.io/gorm" +) + +func TestFutureJudge(t *testing.T) { + dsn := "root:123456@tcp(127.0.0.1:3306)/go_exchange_single?charset=utf8mb4&parseTime=True&loc=Local&timeout=1000ms" + db, _ := gorm.Open(mysql.Open(dsn), &gorm.Config{}) + helper.InitDefaultRedis("127.0.0.1:6379", "", 2) + helper.InitLockRedisConn("127.0.0.1:6379", "", "2") + // tradeSet := models.TradeSet{ + // Coin: "ADA", + // Currency: "USDT", + // LastPrice: "0.516", + // } + + key := fmt.Sprintf(rediskey.FuturesReduceList, global.EXCHANGE_BINANCE) + item := `{"id":4,"apiId":49,"mainId":1,"pid":1,"symbol":"ADAUSDT","price":"0.5417","side":"SELL","num":"13","orderSn":"397659701065547776"}` + reduceOrder := ReduceListItem{} + futApi := FutRestApi{} + setting, err := cacheservice.GetSystemSetting(db) + + if err != nil { + logger.Error("获取系统设置失败") + return + } + if err := sonic.Unmarshal([]byte(item), &reduceOrder); err != nil { + logger.Error("反序列化失败") + return + } + // JudgeFuturesReduce(tradeSet) + FuturesReduceTrigger(db, reduceOrder, futApi, setting, key, item, false) +} + +// 测试减仓后减仓触发 +func TestFutureReduceReduce(t *testing.T) { + dsn := "root:123456@tcp(127.0.0.1:3306)/go_exchange_single?charset=utf8mb4&parseTime=True&loc=Local&timeout=1000ms" + db, _ := gorm.Open(mysql.Open(dsn), &gorm.Config{}) + sdk.Runtime.SetDb("default", db) + helper.InitDefaultRedis("127.0.0.1:6379", "", 2) + helper.InitLockRedisConn("127.0.0.1:6379", "", "2") + tradeSet := models.TradeSet{ + Coin: "ADA", + Currency: "USDT", + LastPrice: "0.5307", + } + + // JudgeFuturesReduce(tradeSet) + JudgeFuturesReduce(tradeSet) +} diff --git a/services/binanceservice/futuresjudgeservice.go b/services/binanceservice/futuresjudgeservice.go index 9437d11..e4e6826 100644 --- a/services/binanceservice/futuresjudgeservice.go +++ b/services/binanceservice/futuresjudgeservice.go @@ -10,6 +10,7 @@ import ( "go-admin/common/global" "go-admin/common/helper" "go-admin/models" + "go-admin/pkg/utility" "go-admin/services/cacheservice" "strings" "time" @@ -161,30 +162,83 @@ func JudgeFuturesReduce(trade models.TradeSet) { return } + reduceOrder := ReduceListItem{} tradePrice, _ := decimal.NewFromString(trade.LastPrice) //减仓单减仓策略 - orderReduceVal, _ := helper.DefaultRedis.GetAllList(fmt.Sprintf(rediskey.SpotOrderReduceStrategyList, global.EXCHANGE_BINANCE)) + reduceReduceListKey := fmt.Sprintf(rediskey.FutOrderReduceStrategyList, global.EXCHANGE_BINANCE) + orderReduceVal, _ := helper.DefaultRedis.HGetAllFields(reduceReduceListKey) reduceOrderStrategy := dto.LineOrderReduceStrategyResp{} for _, item := range orderReduceVal { sonic.Unmarshal([]byte(item), &reduceOrderStrategy) - for _, item2 := range reduceOrderStrategy.Items { - if reduceOrderStrategy.Symbol == trade.Coin+trade.Currency { + for index, item2 := range reduceOrderStrategy.Items { + if reduceOrderStrategy.Symbol == trade.Coin+trade.Currency && !item2.Actived { //买入 if item2.TriggerPrice.Cmp(decimal.Zero) > 0 && tradePrice.Cmp(decimal.Zero) > 0 && ((strings.ToUpper(reduceOrderStrategy.Side) == "SELL" && item2.TriggerPrice.Cmp(tradePrice) >= 0) || (strings.ToUpper(reduceOrderStrategy.Side) == "BUY" && item2.TriggerPrice.Cmp(tradePrice) <= 0)) { - //todo 生成订单并触发 - // SpotReduceTrigger(db, reduceOrder, spotApi, setting, key, item) + lock := helper.NewRedisLock(fmt.Sprintf(rediskey.ReduceStrategyFutTriggerLock, reduceOrder.ApiId, reduceOrder.Symbol), 50, 15, 100*time.Millisecond) + + if ok, err := lock.AcquireWait(context.Background()); err != nil { + log.Error("获取锁失败", err) + return + } else if ok { + defer lock.Release() + hasrecord, _ := helper.DefaultRedis.IsElementInList(reduceReduceListKey, item) + + if !hasrecord { + log.Debug("减仓缓存中不存在", item) + return + } + + order, err := CreateReduceReduceOrder(db, reduceOrderStrategy.OrderId, item2.Price, item2.Num, trade.AmountDigit) + + if err != nil { + log.Errorf("%d 生成订单失败", reduceOrderStrategy.OrderId) + } + + reduceOrder.ApiId = order.ApiId + reduceOrder.Id = order.Id + reduceOrder.Pid = order.Pid + reduceOrder.MainId = order.MainId + reduceOrder.Symbol = order.Symbol + reduceOrder.Side = reduceOrderStrategy.Side + reduceOrder.OrderSn = order.OrderSn + reduceOrder.Price = item2.Price + reduceOrder.Num = item2.Num + //下单成功修改策略节点状态 + if FuturesReduceTrigger(db, reduceOrder, futApi, setting, reduceReduceListKey, item, true) { + reduceOrderStrategy.Items[index].Actived = true + allActive := true + orderId := utility.IntToString(reduceOrderStrategy.OrderId) + + for _, item3 := range reduceOrderStrategy.Items { + if !item3.Actived { + allActive = false + break + } + } + + if allActive { + if err := helper.DefaultRedis.HDelField(reduceReduceListKey, orderId); err != nil { + log.Errorf("删除redis reduceReduceListKey失败 %s", err.Error()) + } + } else { + str, _ := sonic.MarshalString(reduceOrderStrategy) + if err := helper.DefaultRedis.HSetField(reduceReduceListKey, orderId, str); err != nil { + log.Errorf("更新redis reduceReduceListKey失败 %s", err.Error()) + } + } + } + } } } } } for _, item := range reduceVal { - reduceOrder := ReduceListItem{} if err := sonic.Unmarshal([]byte(item), &reduceOrder); err != nil { log.Error("反序列化失败") continue @@ -198,52 +252,54 @@ func JudgeFuturesReduce(trade models.TradeSet) { ((strings.ToUpper(reduceOrder.Side) == "SELL" && orderPrice.Cmp(tradePrice) >= 0) || (strings.ToUpper(reduceOrder.Side) == "BUY" && orderPrice.Cmp(tradePrice) <= 0)) { - FuturesReduceTrigger(db, reduceOrder, futApi, setting, key, item) + FuturesReduceTrigger(db, reduceOrder, futApi, setting, key, item, false) } } } } // 触发合约减仓 -func FuturesReduceTrigger(db *gorm.DB, reduceOrder ReduceListItem, futApi FutRestApi, setting DbModels.LineSystemSetting, key, item string) { +// isStrategy 是否是策略减仓 +func FuturesReduceTrigger(db *gorm.DB, reduceOrder ReduceListItem, futApi FutRestApi, setting DbModels.LineSystemSetting, key, item string, isStrategy bool) bool { tradeSet, _ := cacheservice.GetTradeSet(global.EXCHANGE_BINANCE, reduceOrder.Symbol, 1) + result := true if tradeSet.LastPrice == "" { - return + return false } lock := helper.NewRedisLock(fmt.Sprintf(rediskey.FutTrigger, reduceOrder.ApiId, reduceOrder.Symbol), 20, 5, 100*time.Millisecond) if ok, err := lock.AcquireWait(context.Background()); err != nil { log.Error("获取锁失败", err) - return + return false } else if ok { defer lock.Release() takeOrders := make([]DbModels.LinePreOrder, 0) - if err := db.Model(&DbModels.LinePreOrder{}).Where("main_id =? AND order_type =1 AND status IN (1,5)", reduceOrder.MainId).Find(&takeOrders).Error; err != nil { + if err := db.Model(&DbModels.LinePreOrder{}).Where("main_id =? AND order_type IN (1,2,4) AND status IN (1,5)", reduceOrder.MainId).Find(&takeOrders).Error; err != nil { log.Error("查询止盈单失败") - return + return false } hasrecord, _ := helper.DefaultRedis.IsElementInList(key, item) if !hasrecord { log.Debug("减仓缓存中不存在", item) - return + return false } apiInfo, _ := GetApiInfo(reduceOrder.ApiId) if apiInfo.Id == 0 { log.Error("现货减仓 查询api用户不存在") - return + return false } for _, takeOrder := range takeOrders { err := CancelFutOrderByOrderSnLoop(apiInfo, takeOrder.Symbol, takeOrder.OrderSn) if err != nil { - log.Error("合约止盈撤单失败", err) - return + log.Error("撤单失败", err) + return false } } @@ -258,6 +314,7 @@ func FuturesReduceTrigger(db *gorm.DB, reduceOrder ReduceListItem, futApi FutRes } if err := futApi.ClosePositionLoop(reduceOrder.Symbol, reduceOrder.OrderSn, num, reduceOrder.Side, positionSide, apiInfo, "LIMIT", "0", price, 3); err != nil { + result = false log.Errorf("合约减仓挂单失败 id:%s err:%v", reduceOrder.Id, err) if err2 := db.Model(&DbModels.LinePreOrder{}). @@ -276,15 +333,22 @@ func FuturesReduceTrigger(db *gorm.DB, reduceOrder ReduceListItem, futApi FutRes } //处理减仓单减仓策略 - CacheOrderStrategyAndReCreate(db, reduceOrder, 2, tradeSet, setting) + if err := CacheOrderStrategyAndReCreate(db, reduceOrder, 2, tradeSet, setting); err != nil { + log.Errorf("合约减仓策略处理失败 id:%s err:%v", reduceOrder.Id, err) + } } - if _, err := helper.DefaultRedis.LRem(key, item); err != nil { - log.Errorf("合约减仓 删除缓存失败 id:%v err:%v", reduceOrder.Id, err) + if !isStrategy { + if _, err := helper.DefaultRedis.LRem(key, item); err != nil { + log.Errorf("合约减仓 删除缓存失败 id:%v err:%v", reduceOrder.Id, err) + } } } else { log.Error("获取锁失败") + result = false } + + return result } // 判断合约加仓 diff --git a/services/binanceservice/futuresrest.go b/services/binanceservice/futuresrest.go index bd91528..9d0ef5e 100644 --- a/services/binanceservice/futuresrest.go +++ b/services/binanceservice/futuresrest.go @@ -163,9 +163,9 @@ func handleReduceFilled(db *gorm.DB, preOrder *DbModels.LinePreOrder) { positionData := savePosition(db, preOrder) //市价单就跳出 市价减仓不设止盈止损 - if preOrder.MainOrderType == "MARKET" { - return - } + // if preOrder.MainOrderType == "MARKET" { + // return + // } //亏损大于0 重新计算比例 FutTakeProfit(db, preOrder, apiUserInfo, tradeSet, positionData, orderExt, decimal.Zero, decimal.Zero) @@ -435,6 +435,8 @@ func removeFutLossAndAddPosition(mainId int, orderSn string) { stoploss := dto.StopLossRedisList{} addPosition := AddPositionList{} reduce := ReduceListItem{} + //移除减仓后减仓策略 + RemoveReduceReduceCacheByMainId(mainId, 2) //止损缓存 for _, v := range stoplossVal { @@ -598,7 +600,7 @@ func handleFutMainOrderFilled(db *gorm.DB, preOrder *models.LinePreOrder, extOrd } } - processFutStopLossOrder(db, order, price, num) + processFutStopLossOrder(db, order, utility.StrToDecimal(order.Price), num) // case 4: // 减仓 // processFutReduceOrder(order, price, num) } @@ -700,9 +702,13 @@ func updateOrderQuantity(db *gorm.DB, order models.LinePreOrder, preOrder *model // order.Num = num.String() // } else - if first && (order.OrderCategory == 1 || order.OrderCategory == 3) && order.OrderType == 1 && ext.TakeProfitNumRatio.Cmp(decimal.Zero) > 0 && ext.TakeProfitNumRatio.Cmp(decimal.NewFromInt(100)) != 0 { + //止盈止损重算数量 + if first && (order.OrderCategory == 1 || order.OrderCategory == 3) && ext.TakeProfitNumRatio.Cmp(decimal.Zero) > 0 && ext.TakeProfitNumRatio.Cmp(decimal.NewFromInt(100)) != 0 { // 计算止盈数量 - num = num.Mul(ext.TakeProfitNumRatio.Div(decimal.NewFromInt(100))).Truncate(int32(tradeSet.AmountDigit)) + if order.OrderType == 1 { + num = num.Mul(ext.TakeProfitNumRatio.Div(decimal.NewFromInt(100))).Truncate(int32(tradeSet.AmountDigit)) + } + order.Num = num.String() } diff --git a/services/binanceservice/orderservice.go b/services/binanceservice/orderservice.go index 5ae881a..0252fbb 100644 --- a/services/binanceservice/orderservice.go +++ b/services/binanceservice/orderservice.go @@ -1,10 +1,15 @@ package binanceservice import ( + "fmt" "go-admin/app/admin/models" DbModels "go-admin/app/admin/models" + "go-admin/pkg/utility" + "go-admin/pkg/utility/snowflakehelper" + "time" "github.com/go-admin-team/go-admin-core/logger" + "github.com/jinzhu/copier" "github.com/shopspring/decimal" "gorm.io/gorm" ) @@ -170,3 +175,68 @@ func GetChildTpOrder(db *gorm.DB, pid int) (int, error) { return int(count), nil } + +// 创建减仓后减仓单 +func CreateReduceReduceOrder(db *gorm.DB, pid int, price, num decimal.Decimal, amountDigit int) (models.LinePreOrder, error) { + var preOrder models.LinePreOrder + var result models.LinePreOrder + var ext models.LinePreOrderExt + + if err := db.Model(&models.LinePreOrder{}).Preload("Childs").Where("id =? ", pid).First(&preOrder).Error; err != nil { + return preOrder, err + } + + if err := db.Model(&models.LinePreOrderExt{}).Where("order_id =? ", pid).Find(&ext).Error; err != nil { + return preOrder, err + } + + copier.Copy(&result, &preOrder) + + result.Id = 0 + result.OrderSn = utility.Int64ToString(snowflakehelper.GetOrderId()) + result.Status = 0 + result.CreatedAt = time.Now() + result.TriggerTime = nil + result.UpdatedAt = time.Now() + result.BuyPrice = decimal.Zero.String() + result.Price = price.String() + result.Num = num.String() + + for index := range result.Childs { + result.Childs[index].Id = 0 + result.Childs[index].OrderSn = utility.Int64ToString(snowflakehelper.GetOrderId()) + result.Childs[index].Status = 0 + result.Childs[index].CreatedAt = time.Now() + result.Childs[index].TriggerTime = nil + result.Childs[index].UpdatedAt = time.Now() + result.Childs[index].BuyPrice = decimal.Zero.String() + var pricePercent decimal.Decimal + + if result.Childs[index].OrderType == 1 && ext.TakeProfitRatio.Cmp(decimal.Zero) > 0 { + // 减仓单卖出 + if preOrder.Site == "SELL" { + pricePercent = decimal.NewFromInt(100).Add(ext.TakeProfitRatio).Div(decimal.NewFromInt(100)) + } else { + pricePercent = decimal.NewFromInt(100).Sub(ext.TakeProfitRatio).Div(decimal.NewFromInt(100)) + } + + } else if result.Childs[index].OrderType == 2 && ext.StopLossRatio.Cmp(decimal.Zero) > 0 { + if preOrder.Site == "SELL" { + pricePercent = decimal.NewFromInt(100).Sub(ext.StopLossRatio).Div(decimal.NewFromInt(100)) + } else { + pricePercent = decimal.NewFromInt(100).Add(ext.StopLossRatio).Div(decimal.NewFromInt(100)) + } + } + + //重新计算止盈止损价 + if pricePercent.Cmp(decimal.Zero) > 0 { + result.Childs[index].Price = price.Mul(pricePercent).Truncate(int32(amountDigit)).String() + } + } + + if err := db.Create(&result).Error; err != nil { + return result, fmt.Errorf("复制减仓单失败:pid:%d err:%v", pid, err) + } + + return result, nil +} diff --git a/services/binanceservice/reduce_order_strategy_service.go b/services/binanceservice/reduce_order_strategy_service.go index 516a8ea..199d6da 100644 --- a/services/binanceservice/reduce_order_strategy_service.go +++ b/services/binanceservice/reduce_order_strategy_service.go @@ -8,6 +8,7 @@ import ( "go-admin/common/global" "go-admin/common/helper" models2 "go-admin/models" + "go-admin/pkg/utility" "github.com/bytedance/sonic" "github.com/go-admin-team/go-admin-core/logger" @@ -19,7 +20,7 @@ import ( // reduceOrder:原始减仓单 // reduceOrderStrategy:减仓策略 func CacheOrderStrategyAndReCreate(db *gorm.DB, reduceOrder ReduceListItem, symbolType int, tradeSet models2.TradeSet, setting models.LineSystemSetting) error { - reduceOrderStrategy := models.LineReduceStrategy{} + reduceOrderStrategy := models.LineOrderReduceStrategy{} var key string if symbolType == 1 { @@ -28,21 +29,27 @@ func CacheOrderStrategyAndReCreate(db *gorm.DB, reduceOrder ReduceListItem, symb key = fmt.Sprintf(rediskey.FutOrderReduceStrategyList, global.EXCHANGE_BINANCE) } - if err := db.Model(&reduceOrderStrategy).Where("order_id =?", reduceOrder.Id).Find(reduceOrderStrategy).Error; err != nil { + if err := db.Model(&reduceOrderStrategy).Where("order_id =?", reduceOrder.Id).Find(&reduceOrderStrategy).Error; err != nil { logger.Errorf("获取减仓策略失败,err:%v", err) return err } if reduceOrderStrategy.Id > 0 { + items := make([]models.LineReduceStrategyItem, 0) strategyCache := dto.LineOrderReduceStrategyResp{ OrderId: reduceOrder.Id, + Symbol: reduceOrder.Symbol, + MainId: reduceOrder.MainId, + Side: reduceOrder.Side, } - for _, item := range reduceOrderStrategy.Items { + sonic.Unmarshal([]byte(reduceOrderStrategy.ItemContent), &items) + + for _, item := range items { var rate decimal.Decimal var triggerRate decimal.Decimal - if reduceOrder.Side == "BUY" { + if reduceOrder.Side == "SELL" { rate = (decimal.NewFromInt(100).Sub(item.LossPercent)).Div(decimal.NewFromInt(100)) if setting.ReduceEarlyTriggerPercent.Cmp(decimal.Zero) > 0 { @@ -61,19 +68,26 @@ func CacheOrderStrategyAndReCreate(db *gorm.DB, reduceOrder ReduceListItem, symb } price := reduceOrder.Price.Mul(rate).Truncate(int32(tradeSet.PriceDigit)) triggerPrice := reduceOrder.Price.Mul(triggerRate).Truncate(int32(tradeSet.PriceDigit)) + num := reduceOrder.Num + + //百分比大于0就重新计算 否则就是主减仓单数量 + if item.QuantityPercent.Cmp(decimal.Zero) > 0 { + num = reduceOrder.Num.Mul(item.QuantityPercent.Div(decimal.NewFromInt(100))).Truncate(int32(tradeSet.AmountDigit)) + } strategyCache.Items = append(strategyCache.Items, dto.LineOrderReduceStrategyRespItem{ LossPercent: item.LossPercent, OrderType: item.OrderType, Price: price, TriggerPrice: triggerPrice, + Num: num, }) } - str, _ := sonic.MarshalString(reduceOrderStrategy) + str, _ := sonic.MarshalString(strategyCache) if str != "" { - if err := helper.DefaultRedis.SetString(key, str); err != nil { + if err := helper.DefaultRedis.HSetField(key, utility.IntToString(reduceOrder.Id), str); err != nil { logger.Errorf("减仓单缓存减仓策略,err:%v", err) } } @@ -132,3 +146,33 @@ func ReduceCallBack(db *gorm.DB, preOrder *models.LinePreOrder) error { return nil } + +// 移除减仓后减仓策略 +// mainId 主单id +// symbolType 交易对类型 +func RemoveReduceReduceCacheByMainId(mainId int, symbolType int) error { + var key string + switch symbolType { + case 1: + key = fmt.Sprintf(rediskey.SpotOrderReduceStrategyList, global.EXCHANGE_BINANCE) + case 2: + key = fmt.Sprintf(rediskey.FutOrderReduceStrategyList, global.EXCHANGE_BINANCE) + default: + return fmt.Errorf("交易对类型错误") + } + + arrays, _ := helper.DefaultRedis.HGetAllFields(key) + cache := dto.LineOrderReduceStrategyResp{} + + for _, v := range arrays { + sonic.Unmarshal([]byte(v), &cache) + + if cache.MainId == mainId { + if err := helper.DefaultRedis.HDelField(key, utility.IntToString(cache.OrderId)); err != nil { + logger.Errorf("移除减仓单减仓策略失败redis err:%v", err) + } + } + } + + return nil +} diff --git a/services/binanceservice/spotjudgeservice.go b/services/binanceservice/spotjudgeservice.go index 9d6ef31..6fc8bb5 100644 --- a/services/binanceservice/spotjudgeservice.go +++ b/services/binanceservice/spotjudgeservice.go @@ -266,23 +266,76 @@ func JudgeSpotReduce(trade models.TradeSet) { return } + reduceOrder := ReduceListItem{} tradePrice, _ := decimal.NewFromString(trade.LastPrice) //减仓单减仓策略 - orderReduceVal, _ := helper.DefaultRedis.GetAllList(fmt.Sprintf(rediskey.SpotOrderReduceStrategyList, global.EXCHANGE_BINANCE)) + reduceReduceListKey := fmt.Sprintf(rediskey.SpotOrderReduceStrategyList, global.EXCHANGE_BINANCE) + orderReduceVal, _ := helper.DefaultRedis.GetAllList(reduceReduceListKey) reduceOrderStrategy := dto.LineOrderReduceStrategyResp{} for _, item := range orderReduceVal { sonic.Unmarshal([]byte(item), &reduceOrderStrategy) - for _, item2 := range reduceOrderStrategy.Items { + for index, item2 := range reduceOrderStrategy.Items { if reduceOrderStrategy.Symbol == trade.Coin+trade.Currency { //买入 if strings.ToUpper(reduceOrderStrategy.Side) == "SELL" && item2.TriggerPrice.Cmp(tradePrice) >= 0 && item2.TriggerPrice.Cmp(decimal.Zero) > 0 && tradePrice.Cmp(decimal.Zero) > 0 { - //todo 生成订单并触发 - // SpotReduceTrigger(db, reduceOrder, spotApi, setting, key, item) + lock := helper.NewRedisLock(fmt.Sprintf(rediskey.ReduceStrategySpotTriggerLock, reduceOrder.ApiId, reduceOrder.Symbol), 50, 15, 100*time.Millisecond) + + if ok, err := lock.AcquireWait(context.Background()); err != nil { + log.Error("获取锁失败", err) + return + } else if ok { + defer lock.Release() + hasrecord, _ := helper.DefaultRedis.IsElementInList(reduceReduceListKey, item) + + if !hasrecord { + log.Debug("减仓缓存中不存在", item) + return + } + + order, err := CreateReduceReduceOrder(db, reduceOrderStrategy.OrderId, item2.Price, item2.Num, trade.AmountDigit) + + if err != nil { + log.Errorf("%d 生成订单失败", reduceOrderStrategy.OrderId) + } + + reduceOrder.ApiId = order.ApiId + reduceOrder.Id = order.Id + reduceOrder.Pid = order.Pid + reduceOrder.MainId = order.MainId + reduceOrder.Symbol = order.Symbol + reduceOrder.Side = reduceOrderStrategy.Side + reduceOrder.OrderSn = order.OrderSn + reduceOrder.Price = item2.Price + reduceOrder.Num = item2.Num + if SpotReduceTrigger(db, reduceOrder, spotApi, setting, key, item, true) { + reduceOrderStrategy.Items[index].Actived = true + allActive := true + orderId := utility.IntToString(reduceOrderStrategy.OrderId) + + for _, item3 := range reduceOrderStrategy.Items { + if !item3.Actived { + allActive = false + break + } + } + + if allActive { + if err := helper.DefaultRedis.HDelField(reduceReduceListKey, orderId); err != nil { + log.Errorf("删除redis reduceReduceListKey失败 %s", err.Error()) + } + } else { + str, _ := sonic.MarshalString(reduceOrderStrategy) + if err := helper.DefaultRedis.HSetField(reduceReduceListKey, orderId, str); err != nil { + log.Errorf("更新redis reduceReduceListKey失败 %s", err.Error()) + } + } + } + } } } } @@ -292,7 +345,6 @@ func JudgeSpotReduce(trade models.TradeSet) { reduceVal, _ := helper.DefaultRedis.GetAllList(key) for _, item := range reduceVal { - reduceOrder := ReduceListItem{} if err := sonic.Unmarshal([]byte(item), &reduceOrder); err != nil { log.Error("反序列化失败") continue @@ -306,52 +358,53 @@ func JudgeSpotReduce(trade models.TradeSet) { orderPrice.Cmp(decimal.Zero) > 0 && tradePrice.Cmp(decimal.Zero) > 0 { - SpotReduceTrigger(db, reduceOrder, spotApi, setting, key, item) + SpotReduceTrigger(db, reduceOrder, spotApi, setting, key, item, false) } } } } // 触发现货减仓 -func SpotReduceTrigger(db *gorm.DB, reduceOrder ReduceListItem, spotApi SpotRestApi, setting DbModels.LineSystemSetting, key, item string) { +func SpotReduceTrigger(db *gorm.DB, reduceOrder ReduceListItem, spotApi SpotRestApi, setting DbModels.LineSystemSetting, key, item string, isStrategy bool) bool { tradeSet, err := cacheservice.GetTradeSet(global.EXCHANGE_BINANCE, reduceOrder.Symbol, 0) + result := true if err != nil { log.Error("获取交易设置失败") - return + return false } lock := helper.NewRedisLock(fmt.Sprintf(rediskey.SpotTrigger, reduceOrder.ApiId, reduceOrder.Symbol), 20, 5, 100*time.Millisecond) if ok, err := lock.AcquireWait(context.Background()); err != nil { log.Error("获取锁失败", err) - return + return false } else if ok { defer lock.Release() takeOrders := make([]DbModels.LinePreOrder, 0) - if err := db.Model(&DbModels.LinePreOrder{}).Where("main_id =? AND order_type =1 AND status IN (1,5)", reduceOrder.MainId).Find(&takeOrders).Error; err != nil { + if err := db.Model(&DbModels.LinePreOrder{}).Where("main_id =? AND order_type IN (1,2,4) AND status IN (1,5)", reduceOrder.MainId).Find(&takeOrders).Error; err != nil { log.Error("查询止盈单失败") - return + return false } hasrecord, _ := helper.DefaultRedis.IsElementInList(key, item) if !hasrecord { log.Debug("减仓缓存中不存在", item) - return + return false } apiInfo, _ := GetApiInfo(reduceOrder.ApiId) if apiInfo.Id == 0 { log.Error("现货减仓 查询api用户不存在") - return + return false } for _, takeOrder := range takeOrders { err := CancelOpenOrderByOrderSnLoop(apiInfo, takeOrder.Symbol, takeOrder.OrderSn) if err != nil { - log.Error("现货止盈撤单失败", err) - return + log.Error("现货撤单失败", err) + return false } } price := reduceOrder.Price.Mul(decimal.NewFromInt(1).Sub(setting.ReducePremium.Div(decimal.NewFromInt(100)))).Truncate(int32(tradeSet.PriceDigit)) @@ -368,6 +421,7 @@ func SpotReduceTrigger(db *gorm.DB, reduceOrder ReduceListItem, spotApi SpotRest } if err := spotApi.OrderPlaceLoop(db, params, 3); err != nil { + result = false log.Errorf("现货减仓挂单失败 id:%s err:%v", reduceOrder.Id, err) if err2 := db.Model(&DbModels.LinePreOrder{}). @@ -387,15 +441,21 @@ func SpotReduceTrigger(db *gorm.DB, reduceOrder ReduceListItem, spotApi SpotRest } //处理减仓单减仓策略 - CacheOrderStrategyAndReCreate(db, reduceOrder, 1, tradeSet, setting) + if err := CacheOrderStrategyAndReCreate(db, reduceOrder, 1, tradeSet, setting); err != nil { + log.Errorf("现货减仓 处理减仓策略失败 id:%v err:%v", reduceOrder.Id, err) + } } if _, err := helper.DefaultRedis.LRem(key, item); err != nil { + result = false log.Errorf("现货减仓 删除缓存失败 id:%v err:%v", reduceOrder.Id, err) } } else { log.Error("获取锁失败") + result = false } + + return result } // 判断现货加仓 diff --git a/services/binanceservice/spotreset.go b/services/binanceservice/spotreset.go index bba58e4..1620348 100644 --- a/services/binanceservice/spotreset.go +++ b/services/binanceservice/spotreset.go @@ -550,6 +550,9 @@ func removeSpotLossAndAddPosition(mainId int, orderSn string) { addPosition := AddPositionList{} reduce := ReduceListItem{} + //移除减仓后减仓策略 + RemoveReduceReduceCacheByMainId(mainId, 1) + //止损缓存 for _, v := range stoplossVal { sonic.Unmarshal([]byte(v), &stoploss)