1、阶段减仓

This commit is contained in:
2025-04-07 18:36:36 +08:00
parent cdd3f951a2
commit 8e8c78ec0b
13 changed files with 393 additions and 59 deletions

View File

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

View File

@ -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-市价"`

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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
}
// 判断合约加仓

View File

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

View File

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

View File

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

View File

@ -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
}
// 判断现货加仓

View File

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