diff --git a/app/admin/service/dto/line_system_setting.go b/app/admin/service/dto/line_system_setting.go index 2d0bea2..a1b19bf 100644 --- a/app/admin/service/dto/line_system_setting.go +++ b/app/admin/service/dto/line_system_setting.go @@ -57,14 +57,15 @@ func (s *LineSystemSettingInsertReq) GetId() interface{} { } type LineSystemSettingUpdateReq struct { - Id int `uri:"id" comment:"id"` // id - Time int64 `json:"time" comment:"导入:挂单时长达到时间后失效"` - BatchTime int64 `json:"batchTime" comment:"批量:挂单时长达到时间后失效"` - ProfitRate string `json:"profitRate" comment:"平仓盈利比例"` - CoverOrderTypeBRate string `json:"coverOrderTypeBRate" comment:"b账户限价补单的买入百分比"` - StopLossPremium decimal.Decimal `json:"stopLossPremium" comment:"限价止损溢价"` - AddPositionPremium decimal.Decimal `json:"addPositionPremium" comment:"限价加仓溢价"` - ReducePremium decimal.Decimal `json:"reducePremium" comment:"限价减仓溢价"` + Id int `uri:"id" comment:"id"` // id + Time int64 `json:"time" comment:"导入:挂单时长达到时间后失效"` + BatchTime int64 `json:"batchTime" comment:"批量:挂单时长达到时间后失效"` + ProfitRate string `json:"profitRate" comment:"平仓盈利比例"` + CoverOrderTypeBRate string `json:"coverOrderTypeBRate" comment:"b账户限价补单的买入百分比"` + StopLossPremium decimal.Decimal `json:"stopLossPremium" comment:"限价止损溢价"` + AddPositionPremium decimal.Decimal `json:"addPositionPremium" comment:"限价加仓溢价"` + ReducePremium decimal.Decimal `json:"reducePremium" comment:"限价减仓溢价"` + ReduceEarlyTriggerPercent decimal.Decimal `json:"reduceEarlyTriggerPercent" comment:"减仓提前触发百分比"` common.ControlBy } @@ -79,6 +80,7 @@ func (s *LineSystemSettingUpdateReq) Generate(model *models.LineSystemSetting) { model.StopLossPremium = s.StopLossPremium model.AddPositionPremium = s.AddPositionPremium model.ReducePremium = s.ReducePremium + model.ReduceEarlyTriggerPercent = s.ReduceEarlyTriggerPercent model.UpdateBy = s.UpdateBy // 添加这而,需要记录是被谁更新的 } diff --git a/common/helper/redis_helper.go b/common/helper/redis_helper.go index 9e551a9..0ce5ba6 100644 --- a/common/helper/redis_helper.go +++ b/common/helper/redis_helper.go @@ -573,6 +573,33 @@ func (r *RedisHelper) HKeys(key string) ([]string, error) { return fields, nil } +func (r *RedisHelper) HExists(key, field, value string) (bool, error) { + exists, err := r.client.HExists(r.ctx, key, field).Result() + if err != nil { + return false, fmt.Errorf("check existence failed: %v", err) + } + if !exists { + return false, nil + } + + storedValue, err := r.client.HGet(r.ctx, key, field).Result() + if err != nil { + return false, fmt.Errorf("get value failed: %v", err) + } + + // 如果值是 JSON,比较前反序列化 + var storedObj, inputObj interface{} + if err := sonic.UnmarshalString(storedValue, &storedObj); err != nil { + return false, fmt.Errorf("unmarshal stored value failed: %v", err) + } + if err := sonic.UnmarshalString(value, &inputObj); err != nil { + return false, fmt.Errorf("unmarshal input value failed: %v", err) + } + + // 比较两个对象(需要根据实际类型调整) + return fmt.Sprintf("%v", storedObj) == fmt.Sprintf("%v", inputObj), nil +} + // DelSet 从集合中删除元素 func (r *RedisHelper) DelSet(key string, value string) error { _, err := r.client.SRem(r.ctx, key, value).Result() diff --git a/services/binanceservice/futures_judge_service_test.go b/services/binanceservice/futures_judge_service_test.go index 1cf1de1..5e198c6 100644 --- a/services/binanceservice/futures_judge_service_test.go +++ b/services/binanceservice/futures_judge_service_test.go @@ -28,7 +28,7 @@ func TestFutureJudge(t *testing.T) { // } 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"}` + item := `{"id":50,"apiId":49,"mainId":47,"pid":47,"symbol":"ADAUSDT","price":"0.5936","side":"SELL","num":"12","orderSn":"397913127842217984"}` reduceOrder := ReduceListItem{} futApi := FutRestApi{} setting, err := cacheservice.GetSystemSetting(db) @@ -42,7 +42,7 @@ func TestFutureJudge(t *testing.T) { return } // JudgeFuturesReduce(tradeSet) - FuturesReduceTrigger(db, reduceOrder, futApi, setting, key, item, false) + FuturesReduceTrigger(db, reduceOrder, futApi, setting, key, item, false, 0) } // 测试减仓后减仓触发 @@ -53,9 +53,10 @@ func TestFutureReduceReduce(t *testing.T) { 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", + Coin: "ADA", + Currency: "USDT", + LastPrice: "0.5817", + PriceDigit: 4, } // JudgeFuturesReduce(tradeSet) diff --git a/services/binanceservice/futuresjudgeservice.go b/services/binanceservice/futuresjudgeservice.go index e4e6826..935da5c 100644 --- a/services/binanceservice/futuresjudgeservice.go +++ b/services/binanceservice/futuresjudgeservice.go @@ -186,14 +186,14 @@ func JudgeFuturesReduce(trade models.TradeSet) { return } else if ok { defer lock.Release() - hasrecord, _ := helper.DefaultRedis.IsElementInList(reduceReduceListKey, item) + hasrecord, _ := helper.DefaultRedis.HExists(reduceReduceListKey, utility.IntToString(reduceOrderStrategy.OrderId), item) if !hasrecord { log.Debug("减仓缓存中不存在", item) return } - order, err := CreateReduceReduceOrder(db, reduceOrderStrategy.OrderId, item2.Price, item2.Num, trade.AmountDigit) + order, err := CreateReduceReduceOrder(db, reduceOrderStrategy.OrderId, item2.Price, item2.Num, trade.PriceDigit) if err != nil { log.Errorf("%d 生成订单失败", reduceOrderStrategy.OrderId) @@ -209,7 +209,7 @@ func JudgeFuturesReduce(trade models.TradeSet) { reduceOrder.Price = item2.Price reduceOrder.Num = item2.Num //下单成功修改策略节点状态 - if FuturesReduceTrigger(db, reduceOrder, futApi, setting, reduceReduceListKey, item, true) { + if FuturesReduceTrigger(db, reduceOrder, futApi, setting, reduceReduceListKey, item, true, reduceOrderStrategy.OrderId) { reduceOrderStrategy.Items[index].Actived = true allActive := true orderId := utility.IntToString(reduceOrderStrategy.OrderId) @@ -252,7 +252,7 @@ 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, false) + FuturesReduceTrigger(db, reduceOrder, futApi, setting, key, item, false, 0) } } } @@ -260,7 +260,8 @@ func JudgeFuturesReduce(trade models.TradeSet) { // 触发合约减仓 // isStrategy 是否是策略减仓 -func FuturesReduceTrigger(db *gorm.DB, reduceOrder ReduceListItem, futApi FutRestApi, setting DbModels.LineSystemSetting, key, item string, isStrategy bool) bool { +// reduceId 父减仓单id +func FuturesReduceTrigger(db *gorm.DB, reduceOrder ReduceListItem, futApi FutRestApi, setting DbModels.LineSystemSetting, key, item string, isStrategy bool, reduceId int) bool { tradeSet, _ := cacheservice.GetTradeSet(global.EXCHANGE_BINANCE, reduceOrder.Symbol, 1) result := true @@ -281,7 +282,13 @@ func FuturesReduceTrigger(db *gorm.DB, reduceOrder ReduceListItem, futApi FutRes return false } - hasrecord, _ := helper.DefaultRedis.IsElementInList(key, item) + var hasrecord bool + + if isStrategy { + hasrecord, _ = helper.DefaultRedis.HExists(key, utility.IntToString(reduceId), item) + } else { + hasrecord, _ = helper.DefaultRedis.IsElementInList(key, item) + } if !hasrecord { log.Debug("减仓缓存中不存在", item) diff --git a/services/binanceservice/futuresrest.go b/services/binanceservice/futuresrest.go index 9d0ef5e..c0c3ed1 100644 --- a/services/binanceservice/futuresrest.go +++ b/services/binanceservice/futuresrest.go @@ -137,7 +137,12 @@ func handleReduceFilled(db *gorm.DB, preOrder *DbModels.LinePreOrder) { //修改减仓单减仓策略状态 ReduceCallBack(db, preOrder) orderExt := models.LinePreOrderExt{} - db.Model(&orderExt).Where("order_id =?", preOrder.Id).First(&orderExt) + //减仓策略单获取主减仓单的拓展信息 + if preOrder.ReduceOrderId > 0 { + db.Model(&orderExt).Where("order_id =?", preOrder.ReduceOrderId).First(&orderExt) + } else { + db.Model(&orderExt).Where("order_id =?", preOrder.Id).First(&orderExt) + } // rate := utility.StringAsFloat(orderExt.AddPositionVal) // 100%减仓 终止流程 @@ -248,7 +253,7 @@ func nextFuturesReduceTrigger(db *gorm.DB, mainId int, totalNum decimal.Decimal, nextOrder := DbModels.LinePreOrder{} nextExt := DbModels.LinePreOrderExt{} - if err := db.Model(&models.LinePreOrder{}).Where("main_id =? AND order_type =4 AND status=0", mainId).Order("rate asc").First(&nextOrder).Error; err != nil { + if err := db.Model(&models.LinePreOrder{}).Where("main_id =? AND order_type =4 AND status=0 AND reduce_order_id=0", mainId).Order("rate asc").First(&nextOrder).Error; err != nil { logger.Errorf("获取下一个单失败 err:%v", err) return } diff --git a/services/binanceservice/orderservice.go b/services/binanceservice/orderservice.go index 0252fbb..ae3d7ba 100644 --- a/services/binanceservice/orderservice.go +++ b/services/binanceservice/orderservice.go @@ -177,7 +177,7 @@ func GetChildTpOrder(db *gorm.DB, pid int) (int, error) { } // 创建减仓后减仓单 -func CreateReduceReduceOrder(db *gorm.DB, pid int, price, num decimal.Decimal, amountDigit int) (models.LinePreOrder, error) { +func CreateReduceReduceOrder(db *gorm.DB, pid int, price, num decimal.Decimal, priceDigit int) (models.LinePreOrder, error) { var preOrder models.LinePreOrder var result models.LinePreOrder var ext models.LinePreOrderExt @@ -201,6 +201,7 @@ func CreateReduceReduceOrder(db *gorm.DB, pid int, price, num decimal.Decimal, a result.BuyPrice = decimal.Zero.String() result.Price = price.String() result.Num = num.String() + result.ReduceOrderId = preOrder.Id for index := range result.Childs { result.Childs[index].Id = 0 @@ -230,7 +231,7 @@ func CreateReduceReduceOrder(db *gorm.DB, pid int, price, num decimal.Decimal, a //重新计算止盈止损价 if pricePercent.Cmp(decimal.Zero) > 0 { - result.Childs[index].Price = price.Mul(pricePercent).Truncate(int32(amountDigit)).String() + result.Childs[index].Price = price.Mul(pricePercent).Truncate(int32(priceDigit)).String() } } diff --git a/services/binanceservice/reduce_order_strategy_service.go b/services/binanceservice/reduce_order_strategy_service.go index 199d6da..84ee9df 100644 --- a/services/binanceservice/reduce_order_strategy_service.go +++ b/services/binanceservice/reduce_order_strategy_service.go @@ -124,14 +124,14 @@ func ReduceCallBack(db *gorm.DB, preOrder *models.LinePreOrder) error { return fmt.Errorf("交易对类型错误") } - arrays, _ := helper.DefaultRedis.GetAllList(key) + arrays, _ := helper.DefaultRedis.HGetAllFields(key) cache := dto.LineOrderReduceStrategyResp{} for _, v := range arrays { sonic.Unmarshal([]byte(v), &cache) if cache.OrderId == reduceOrderId { - if _, err := helper.DefaultRedis.LRem(key, v); err != nil { + if err := helper.DefaultRedis.HDelField(key, utility.IntToString(cache.OrderId)); err != nil { logger.Errorf("移除减仓单减仓策略失败redis err:%v", err) } } diff --git a/services/binanceservice/spot_judge_service_test.go b/services/binanceservice/spot_judge_service_test.go new file mode 100644 index 0000000..d1c954e --- /dev/null +++ b/services/binanceservice/spot_judge_service_test.go @@ -0,0 +1,64 @@ +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 TestSpotJudge(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.SpotReduceList, global.EXCHANGE_BINANCE) + item := `{"id":66,"apiId":49,"mainId":63,"pid":63,"symbol":"ADAUSDT","price":"0.5912","side":"SELL","num":"12.6","orderSn":"397919643961917440"}` + reduceOrder := ReduceListItem{} + spotApi := SpotRestApi{} + 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) + SpotReduceTrigger(db, reduceOrder, spotApi, setting, key, item, false, 0) +} + +// 测试减仓后减仓触发 +func TestSpotReduceReduce(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.5793", + PriceDigit: 4, + } + + // JudgeFuturesReduce(tradeSet) + JudgeSpotReduce(tradeSet) +} diff --git a/services/binanceservice/spotjudgeservice.go b/services/binanceservice/spotjudgeservice.go index 6fc8bb5..57b56ed 100644 --- a/services/binanceservice/spotjudgeservice.go +++ b/services/binanceservice/spotjudgeservice.go @@ -270,7 +270,7 @@ func JudgeSpotReduce(trade models.TradeSet) { tradePrice, _ := decimal.NewFromString(trade.LastPrice) //减仓单减仓策略 reduceReduceListKey := fmt.Sprintf(rediskey.SpotOrderReduceStrategyList, global.EXCHANGE_BINANCE) - orderReduceVal, _ := helper.DefaultRedis.GetAllList(reduceReduceListKey) + orderReduceVal, _ := helper.DefaultRedis.HGetAllFields(reduceReduceListKey) reduceOrderStrategy := dto.LineOrderReduceStrategyResp{} for _, item := range orderReduceVal { @@ -290,14 +290,14 @@ func JudgeSpotReduce(trade models.TradeSet) { return } else if ok { defer lock.Release() - hasrecord, _ := helper.DefaultRedis.IsElementInList(reduceReduceListKey, item) + hasrecord, _ := helper.DefaultRedis.HExists(reduceReduceListKey, utility.IntTostring(reduceOrderStrategy.OrderId), item) if !hasrecord { log.Debug("减仓缓存中不存在", item) return } - order, err := CreateReduceReduceOrder(db, reduceOrderStrategy.OrderId, item2.Price, item2.Num, trade.AmountDigit) + order, err := CreateReduceReduceOrder(db, reduceOrderStrategy.OrderId, item2.Price, item2.Num, trade.PriceDigit) if err != nil { log.Errorf("%d 生成订单失败", reduceOrderStrategy.OrderId) @@ -312,7 +312,7 @@ func JudgeSpotReduce(trade models.TradeSet) { reduceOrder.OrderSn = order.OrderSn reduceOrder.Price = item2.Price reduceOrder.Num = item2.Num - if SpotReduceTrigger(db, reduceOrder, spotApi, setting, key, item, true) { + if SpotReduceTrigger(db, reduceOrder, spotApi, setting, reduceReduceListKey, item, true, reduceOrderStrategy.OrderId) { reduceOrderStrategy.Items[index].Actived = true allActive := true orderId := utility.IntToString(reduceOrderStrategy.OrderId) @@ -358,14 +358,16 @@ func JudgeSpotReduce(trade models.TradeSet) { orderPrice.Cmp(decimal.Zero) > 0 && tradePrice.Cmp(decimal.Zero) > 0 { - SpotReduceTrigger(db, reduceOrder, spotApi, setting, key, item, false) + SpotReduceTrigger(db, reduceOrder, spotApi, setting, key, item, false, 0) } } } } // 触发现货减仓 -func SpotReduceTrigger(db *gorm.DB, reduceOrder ReduceListItem, spotApi SpotRestApi, setting DbModels.LineSystemSetting, key, item string, isStrategy bool) bool { +// isStrategy 是否是策略减仓单 +// reduceId 策略主减仓单id +func SpotReduceTrigger(db *gorm.DB, reduceOrder ReduceListItem, spotApi SpotRestApi, setting DbModels.LineSystemSetting, key, item string, isStrategy bool, reduceId int) bool { tradeSet, err := cacheservice.GetTradeSet(global.EXCHANGE_BINANCE, reduceOrder.Symbol, 0) result := true @@ -385,7 +387,13 @@ func SpotReduceTrigger(db *gorm.DB, reduceOrder ReduceListItem, spotApi SpotRest log.Error("查询止盈单失败") return false } - hasrecord, _ := helper.DefaultRedis.IsElementInList(key, item) + var hasrecord bool + + if isStrategy { + hasrecord, _ = helper.DefaultRedis.HExists(key, utility.IntToString(reduceId), item) + } else { + hasrecord, _ = helper.DefaultRedis.IsElementInList(key, item) + } if !hasrecord { log.Debug("减仓缓存中不存在", item) diff --git a/services/binanceservice/spotreset.go b/services/binanceservice/spotreset.go index 1620348..d943c80 100644 --- a/services/binanceservice/spotreset.go +++ b/services/binanceservice/spotreset.go @@ -182,7 +182,13 @@ func handleMainReduceFilled(db *gorm.DB, preOrder *DbModels.LinePreOrder) { if err := db.Model(&DbModels.LinePreOrderStatus{}).Where("order_id =? ", preOrder.MainId).Update("reduce_status", 1).Error; err != nil { logger.Errorf("handleMainReduceFilled 更新主单减仓状态失败,订单号:%s", preOrder.OrderSn) } - db.Model(&orderExt).Where("order_id =?", preOrder.Id).Find(&orderExt) + + //策略减仓单 获取主减仓单拓展信息 + if preOrder.ReduceOrderId > 0 { + db.Model(&orderExt).Where("order_id =?", preOrder.ReduceOrderId).Find(&orderExt) + } else { + db.Model(&orderExt).Where("order_id =?", preOrder.Id).Find(&orderExt) + } lock := helper.NewRedisLock(fmt.Sprintf(rediskey.SpotReduceCallback, preOrder.ApiId, preOrder.Symbol), 120, 20, 100*time.Millisecond) @@ -282,7 +288,7 @@ func nextSpotReduceTrigger(db *gorm.DB, mainId int, totalNum decimal.Decimal, tr nextExt := DbModels.LinePreOrderExt{} // var percentag decimal.Decimal - if err := db.Model(&models.LinePreOrder{}).Where("main_id =? AND order_type =4 AND status=0", mainId).Order("rate asc").First(&nextOrder).Error; err != nil { + if err := db.Model(&models.LinePreOrder{}).Where("main_id =? AND order_type =4 AND status=0 AND reduce_order_id =0", mainId).Order("rate asc").First(&nextOrder).Error; err != nil { logger.Errorf("获取下一个单失败 err:%v", err) return true }