Files
exchange_go/services/binanceservice/futuresjudgeservice.go
2025-10-14 19:58:59 +08:00

493 lines
16 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package binanceservice
import (
"context"
"errors"
"fmt"
DbModels "go-admin/app/admin/models"
"go-admin/app/admin/service/dto"
"go-admin/common/const/rediskey"
"go-admin/common/global"
"go-admin/common/helper"
"go-admin/models"
"go-admin/models/positiondto"
"go-admin/pkg/utility"
"go-admin/services/cacheservice"
"go-admin/services/commonservice"
"go-admin/services/orderservice"
"strings"
"time"
"github.com/bytedance/sonic"
log "github.com/go-admin-team/go-admin-core/logger"
"github.com/shopspring/decimal"
"gorm.io/gorm"
)
/*
判断合约触发
*/
func JudgeFuturesPrice(tradeSet models.TradeSet) {
preOrderVal, _ := helper.DefaultRedis.GetAllList(fmt.Sprintf(rediskey.PreFutOrderList, global.EXCHANGE_BINANCE))
db := commonservice.GetDBConnection()
if len(preOrderVal) == 0 {
return
}
futApi := FutRestApi{}
for _, item := range preOrderVal {
preOrder := dto.PreOrderRedisList{}
if err := sonic.Unmarshal([]byte(item), &preOrder); err != nil {
log.Error("反序列化失败")
continue
}
if preOrder.Symbol != tradeSet.Coin+tradeSet.Currency {
continue
}
orderPrice, _ := decimal.NewFromString(preOrder.Price)
tradePrice, _ := decimal.NewFromString(tradeSet.LastPrice)
if orderPrice.Cmp(decimal.Zero) == 0 || tradePrice.Cmp(decimal.Zero) == 0 {
continue
}
//多
if (strings.ToUpper(preOrder.Site) == "BUY" && orderPrice.Cmp(tradePrice) >= 0) ||
(strings.ToUpper(preOrder.Site) == "SELL" && orderPrice.Cmp(tradePrice) <= 0) {
futTriggerOrder(db, &preOrder, item, futApi)
}
}
}
// 分布式锁下单
func futTriggerOrder(db *gorm.DB, v *dto.PreOrderRedisList, item string, futApi FutRestApi) {
lock := helper.NewRedisLock(fmt.Sprintf(rediskey.SpotTrigger, v.ApiId, v.Symbol), 200, 5, 100*time.Millisecond)
if ok, err := lock.AcquireWait(context.Background()); err != nil {
log.Debug("获取锁失败", err)
return
} else if ok {
defer lock.Release()
key := fmt.Sprintf(rediskey.PreFutOrderList, global.EXCHANGE_BINANCE)
preOrder := DbModels.LinePreOrder{}
if err := db.Where("id = ?", v.Id).First(&preOrder).Error; err != nil {
log.Error("获取预下单失败", err)
if errors.Is(err, gorm.ErrRecordNotFound) {
log.Error("不存在待触发主单", item)
helper.DefaultRedis.LRem(key, item)
}
return
}
hasrecord, _ := helper.DefaultRedis.IsElementInList(key, item)
if !hasrecord {
log.Debug("预下单缓存中不存在", item)
return
}
price, _ := decimal.NewFromString(v.Price)
num, _ := decimal.NewFromString(preOrder.Num)
if price.Cmp(decimal.Zero) == 0 {
log.Error("价格不能为0")
return
}
params := FutOrderPlace{
ApiId: v.ApiId,
Symbol: v.Symbol,
Side: v.Site,
OrderType: preOrder.MainOrderType,
SideType: preOrder.MainOrderType,
Price: price,
Quantity: num,
NewClientOrderId: v.OrderSn,
}
preOrderVal, _ := sonic.MarshalString(&v)
if err := futApi.OrderPlaceLoop(db, params, 3); err != nil {
log.Error("下单失败", v.Symbol, " err:", err)
err := db.Model(&DbModels.LinePreOrder{}).Where("id =? and status='0'", preOrder.Id).Updates(map[string]interface{}{"status": "2", "desc": err.Error()}).Error
if err != nil {
log.Error("更新预下单状态失败")
}
if preOrderVal != "" {
if _, err := helper.DefaultRedis.LRem(key, preOrderVal); err != nil {
log.Error("删除redis 预下单失败:", err)
}
}
return
}
if preOrderVal != "" {
if _, err := helper.DefaultRedis.LRem(key, preOrderVal); err != nil {
log.Error("删除redis 预下单失败:", err)
}
}
if err := db.Model(&DbModels.LinePreOrder{}).Where("id =? ", preOrder.Id).Updates(map[string]interface{}{"trigger_time": time.Now()}).Error; err != nil {
log.Error("更新预下单状态失败 ordersn:", preOrder.OrderSn, " status:1")
}
if err := db.Model(&DbModels.LinePreOrder{}).Where("id =? AND status ='0'", preOrder.Id).Updates(map[string]interface{}{"status": "1"}).Error; err != nil {
log.Error("更新预下单状态失败 ordersn:", v.OrderSn, " status:1")
}
return
} else {
log.Error("获取锁失败")
return
}
}
// 判断是否触发合约减仓
func JudgeFuturesReduce(trade models.TradeSet) {
key := fmt.Sprintf(rediskey.FuturesReduceList, global.EXCHANGE_BINANCE)
reduceVal, _ := helper.DefaultRedis.GetAllList(key)
db := commonservice.GetDBConnection()
futApi := FutRestApi{}
setting, err := cacheservice.GetSystemSetting(db)
if err != nil {
log.Error("获取系统设置失败")
return
}
reduceOrder := positiondto.ReduceListItem{}
tradePrice, _ := decimal.NewFromString(trade.LastPrice)
//减仓单减仓策略
reduceReduceListKey := fmt.Sprintf(rediskey.FutOrderReduceStrategyList, global.EXCHANGE_BINANCE)
orderReduceVal, _ := helper.DefaultRedis.HGetAllFields(reduceReduceListKey)
reduceOrderStrategy := dto.LineOrderReduceStrategyResp{}
orderService := orderservice.OrderService{}
orderService.Orm = db
for _, item := range orderReduceVal {
sonic.Unmarshal([]byte(item), &reduceOrderStrategy)
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)) {
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.HExists(reduceReduceListKey, utility.IntToString(reduceOrderStrategy.OrderId), item)
if !hasrecord {
log.Debug("减仓缓存中不存在", item)
return
}
order, err := orderService.CreateReduceReduceOrder(db, reduceOrderStrategy.OrderId, item2.Price, item2.Num, trade.PriceDigit)
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.OrderId) {
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 {
if err := sonic.Unmarshal([]byte(item), &reduceOrder); err != nil {
log.Error("反序列化失败")
continue
}
if reduceOrder.Symbol == trade.Coin+trade.Currency {
orderPrice := reduceOrder.Price
//买入
if orderPrice.Cmp(decimal.Zero) > 0 &&
tradePrice.Cmp(decimal.Zero) > 0 &&
((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, 0)
}
}
}
}
// 触发合约减仓
// isStrategy 是否是策略减仓
// reduceId 父减仓单id
func FuturesReduceTrigger(db *gorm.DB, reduceOrder positiondto.ReduceListItem, futApi FutRestApi, setting DbModels.LineSystemSetting, key, item string, isStrategy bool, reduceId int) bool {
tradeSet, _ := cacheservice.GetTradeSet(global.EXCHANGE_BINANCE, reduceOrder.Symbol, 2)
result := true
if tradeSet.LastPrice == "" {
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 false
} else if ok {
defer lock.Release()
takeOrders := make([]DbModels.LinePreOrder, 0)
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 false
}
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)
return false
}
apiInfo, _ := GetApiInfo(reduceOrder.ApiId)
if apiInfo.Id == 0 {
log.Error("现货减仓 查询api用户不存在")
return false
}
for _, takeOrder := range takeOrders {
err := CancelFutOrderByOrderSnLoop(apiInfo, takeOrder.Symbol, takeOrder.OrderSn)
if err != nil {
log.Error("撤单失败", err)
return false
}
}
price := reduceOrder.Price.Mul(decimal.NewFromInt(1).Sub(setting.ReducePremium.Div(decimal.NewFromInt(100)))).Truncate(int32(tradeSet.PriceDigit))
num := reduceOrder.Num.Truncate(int32(tradeSet.AmountDigit))
var positionSide string
if reduceOrder.Side == "BUY" {
positionSide = "SHORT"
} else {
positionSide = "LONG"
}
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{}).
Where("id = ? AND status =0", reduceOrder.Id).
Updates(map[string]interface{}{"status": 2, "desc": err.Error()}).Error; err2 != nil {
log.Errorf("合约减仓更新状态失败 id%s err:%v", reduceOrder.Id, err2)
}
} else {
if err := db.Model(&DbModels.LinePreOrder{}).Where("id =? ", reduceOrder.Id).Updates(map[string]interface{}{"trigger_time": time.Now()}).Error; err != nil {
log.Error("更新减仓单状态失败 ordersn:", reduceOrder.OrderSn, " status:1")
}
if err := db.Model(&DbModels.LinePreOrder{}).
Where("id = ? AND status =0", reduceOrder.Id).Updates(map[string]interface{}{"status": 1}).Error; err != nil {
log.Errorf("合约减仓更新状态失败 id%s err:%v", reduceOrder.Id, err)
}
//处理减仓单减仓策略
if err := CacheOrderStrategyAndReCreate(db, reduceOrder, 2, tradeSet, setting); err != nil {
log.Errorf("合约减仓策略处理失败 id%s 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
}
// 判断合约加仓
func JudgeFutAddPosition(trade models.TradeSet) {
key := fmt.Sprintf(rediskey.FuturesAddPositionList, global.EXCHANGE_BINANCE)
preOrderVal, _ := helper.DefaultRedis.GetAllList(key)
db := commonservice.GetDBConnection()
if len(preOrderVal) == 0 {
return
}
futApi := FutRestApi{}
for _, item := range preOrderVal {
preOrder := positiondto.AddPositionList{}
if err := sonic.Unmarshal([]byte(item), &preOrder); err != nil {
log.Error("反序列化失败")
continue
}
if preOrder.Symbol == trade.Coin+trade.Currency {
orderPrice := preOrder.Price
tradePrice, _ := decimal.NewFromString(trade.LastPrice)
if orderPrice.Cmp(decimal.Zero) == 0 || tradePrice.Cmp(decimal.Zero) == 0 {
continue
}
//多
if (strings.ToUpper(preOrder.Side) == "BUY" && orderPrice.Cmp(tradePrice) >= 0) ||
(strings.ToUpper(preOrder.Side) == "SELL" && orderPrice.Cmp(tradePrice) <= 0) {
FutAddPositionTrigger(db, &preOrder, item, futApi)
}
}
}
}
// 合约加仓触发
func FutAddPositionTrigger(db *gorm.DB, v *positiondto.AddPositionList, item string, futApi FutRestApi) {
lock := helper.NewRedisLock(fmt.Sprintf(rediskey.FutTrigger, v.ApiId, v.Symbol), 20, 5, 100*time.Millisecond)
if ok, err := lock.AcquireWait(context.Background()); err != nil {
log.Error("获取锁失败", err)
return
} else if ok {
defer lock.Release()
setting, _ := cacheservice.GetSystemSetting(db)
tradeSet, _ := cacheservice.GetTradeSet(global.EXCHANGE_BINANCE, v.Symbol, 2)
if tradeSet.LastPrice == "" {
log.Errorf("合约加仓触发 查询交易对失败 交易对:%s ordersn:%s", v.Symbol, v.OrderSn)
return
}
key := fmt.Sprintf(rediskey.FuturesAddPositionList, global.EXCHANGE_BINANCE)
preOrder := DbModels.LinePreOrder{}
if err := db.Where("id = ?", v.Id).First(&preOrder).Error; err != nil {
log.Error("获取预下单失败", err)
if errors.Is(err, gorm.ErrRecordNotFound) {
log.Error("不存在待触发加仓主单", item)
helper.DefaultRedis.LRem(key, item)
}
return
}
hasrecord, _ := helper.DefaultRedis.IsElementInList(key, item)
if !hasrecord {
log.Error("不存在待触发加仓主单", item)
return
}
price := v.Price.Truncate(int32(tradeSet.PriceDigit))
num, _ := decimal.NewFromString(preOrder.Num)
if setting.AddPositionPremium.Cmp(decimal.Zero) > 0 {
price = price.Mul(decimal.NewFromInt(1).Sub(setting.AddPositionPremium.Div(decimal.NewFromInt(100)))).Truncate(int32(tradeSet.PriceDigit))
}
params := FutOrderPlace{
ApiId: v.ApiId,
Symbol: v.Symbol,
Side: v.Side,
OrderType: "LIMIT",
Price: price,
Quantity: num.Truncate(int32(tradeSet.AmountDigit)),
NewClientOrderId: v.OrderSn,
}
preOrderVal, _ := sonic.MarshalString(&v)
if err := futApi.OrderPlaceLoop(db, params, 3); err != nil {
log.Error("下单失败", v.Symbol, " err:", err)
err := db.Model(&DbModels.LinePreOrder{}).Where("id =? AND status =0", preOrder.Id).Updates(map[string]interface{}{"status": "2", "desc": err.Error()}).Error
if err != nil {
log.Error("下单失败后修改订单失败")
}
if preOrderVal != "" {
if _, err := helper.DefaultRedis.LRem(key, preOrderVal); err != nil {
log.Error("删除redis 预下单失败:", err)
}
}
return
}
if preOrderVal != "" {
if _, err := helper.DefaultRedis.LRem(key, preOrderVal); err != nil {
log.Error("删除redis 预下单失败:", err)
}
}
if err := db.Model(&DbModels.LinePreOrder{}).Where("id =? ", preOrder.Id).Updates(map[string]interface{}{"trigger_time": time.Now()}).Error; err != nil {
log.Error("更新预下单状态失败 ordersn:", v.OrderSn, " status:1")
}
if err := db.Model(&DbModels.LinePreOrder{}).Where("id =? AND status ='0'", preOrder.Id).Updates(map[string]interface{}{"status": "1"}).Error; err != nil {
log.Error("更新预下单状态失败 ordersn:", v.OrderSn, " status:1")
}
return
} else {
log.Error("获取锁失败")
return
}
}