1、减仓后减仓

This commit is contained in:
2025-04-09 09:09:25 +08:00
parent 8e8c78ec0b
commit 1512216bab
10 changed files with 155 additions and 34 deletions

View File

@ -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 // 添加这而,需要记录是被谁更新的
}

View File

@ -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()

View File

@ -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)

View File

@ -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)

View File

@ -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
}

View File

@ -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()
}
}

View File

@ -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)
}
}

View File

@ -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)
}

View File

@ -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)

View File

@ -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
}