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

610 lines
20 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 JudgeSpotPrice(trade models.TradeSet) {
key := fmt.Sprintf(rediskey.PreSpotOrderList, global.EXCHANGE_BINANCE)
preOrderVal, _ := helper.DefaultRedis.GetAllList(key)
db := commonservice.GetDBConnection()
if len(preOrderVal) == 0 {
return
}
spotApi := SpotRestApi{}
orderService := orderservice.OrderService{}
orderService.Orm = db
for _, item := range preOrderVal {
preOrder := dto.PreOrderRedisList{}
if err := sonic.Unmarshal([]byte(item), &preOrder); err != nil {
log.Error("反序列化失败")
continue
}
if preOrder.Symbol == trade.Coin+trade.Currency {
orderPrice, _ := decimal.NewFromString(preOrder.Price)
tradePrice, _ := decimal.NewFromString(trade.LastPrice)
//买入
if strings.ToUpper(preOrder.Site) == "BUY" && orderPrice.Cmp(tradePrice) >= 0 && orderPrice.Cmp(decimal.Zero) > 0 && tradePrice.Cmp(decimal.Zero) > 0 {
//判断个人websocket是否超时
apiInfo, _ := GetApiInfo(preOrder.ApiId)
if err := commonservice.JudgeWebsocketTimeout(apiInfo.ApiKey, 1); err != nil {
log.Errorf("现货行情订阅超时,apiKey:%s err:%v", err)
if err1 := orderService.ErrorTrigger(&preOrder, fmt.Sprintf("行情触发失败,err:%v", err)); err1 != nil {
log.Error("触发失败", err1)
}
continue
}
SpotOrderLock(db, &preOrder, item, spotApi)
}
}
}
}
// 分布式锁下单
// v 预下单信息
// item 预下单源文本
func SpotOrderLock(db *gorm.DB, v *dto.PreOrderRedisList, item string, spotApi SpotRestApi) {
lock := helper.NewRedisLock(fmt.Sprintf(rediskey.SpotTrigger, 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()
key := fmt.Sprintf(rediskey.PreSpotOrderList, 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, _ := decimal.NewFromString(v.Price)
num, _ := decimal.NewFromString(preOrder.Num)
params := OrderPlacementService{
ApiId: v.ApiId,
Symbol: v.Symbol,
Side: v.Site,
Type: preOrder.MainOrderType,
TimeInForce: "GTC",
Price: price,
Quantity: num,
NewClientOrderId: v.OrderSn,
}
preOrderVal, _ := sonic.MarshalString(&v)
if err := spotApi.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")
}
return
} else {
log.Error("获取锁失败")
return
}
}
// 判断是否触发止损
func JudgeSpotStopLoss(trade models.TradeSet) {
key := fmt.Sprintf(rediskey.SpotStopLossList, global.EXCHANGE_BINANCE)
stopLossVal, _ := helper.DefaultRedis.GetAllList(key)
if len(stopLossVal) == 0 {
return
}
db := commonservice.GetDBConnection()
spotApi := SpotRestApi{}
setting, err := cacheservice.GetSystemSetting(db)
if err != nil {
log.Error("获取系统设置失败")
return
}
tradeSet, err := cacheservice.GetTradeSet(global.EXCHANGE_BINANCE, trade.Coin+trade.Currency, 1)
if err != nil {
log.Error("获取交易设置失败")
return
}
for _, item := range stopLossVal {
stopOrder := dto.StopLossRedisList{}
if err := sonic.Unmarshal([]byte(item), &stopOrder); err != nil {
log.Error("反序列化失败")
continue
}
if stopOrder.Symbol == trade.Coin+trade.Currency {
orderPrice := stopOrder.Price
tradePrice, _ := decimal.NewFromString(trade.LastPrice)
//买入
if strings.ToUpper(stopOrder.Site) == "SELL" &&
orderPrice.Cmp(tradePrice) >= 0 &&
orderPrice.Cmp(decimal.Zero) > 0 &&
tradePrice.Cmp(decimal.Zero) > 0 {
SpotStopLossTrigger(db, stopOrder, spotApi, setting, tradeSet, key, item)
}
}
}
}
// 触发现货止损
func SpotStopLossTrigger(db *gorm.DB, stopOrder dto.StopLossRedisList, spotApi SpotRestApi, setting DbModels.LineSystemSetting, tradeSet models.TradeSet, key string, item string) {
lock := helper.NewRedisLock(fmt.Sprintf(rediskey.SpotTrigger, stopOrder.ApiId, stopOrder.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()
takeOrder := DbModels.LinePreOrder{}
if err := db.Model(&DbModels.LinePreOrder{}).Where("pid =? AND order_type =1", stopOrder.PId).Find(&takeOrder).Error; err != nil {
log.Error("查询止盈单失败")
return
}
hasrecord, _ := helper.DefaultRedis.IsElementInList(key, item)
if !hasrecord {
log.Debug("减仓缓存中不存在", item)
return
}
apiInfo, _ := GetApiInfo(takeOrder.ApiId)
if apiInfo.Id == 0 {
log.Error("现货止损 查询api用户不存在")
return
}
var err error
for x := 1; x <= 4; x++ {
err = spotApi.CancelOpenOrderByOrderSn(apiInfo, takeOrder.Symbol, takeOrder.OrderSn)
if err == nil || strings.Contains(err.Error(), "该交易对没有订单") {
err = nil
break
}
}
if err != nil {
log.Error("现货止损撤单失败", err)
return
}
stopPreOrder, _ := GetOrderById(db, stopOrder.Id)
price := stopOrder.Price.Mul(decimal.NewFromInt(1).Sub(setting.StopLossPremium.Div(decimal.NewFromInt(100)))).Truncate(int32(tradeSet.PriceDigit))
num := utility.StrToDecimal(takeOrder.Num).Truncate(int32(tradeSet.AmountDigit))
params := OrderPlacementService{
ApiId: takeOrder.ApiId,
Side: takeOrder.Site,
Type: "LIMIT",
TimeInForce: "GTC",
Symbol: takeOrder.Symbol,
Price: price,
Quantity: num,
NewClientOrderId: stopPreOrder.OrderSn,
}
if err := spotApi.OrderPlaceLoop(db, params, 3); err != nil {
log.Errorf("现货止损挂单失败 id%s err:%v", stopOrder.Id, err)
} else {
if err := db.Model(&DbModels.LinePreOrder{}).Where("id = ? ", stopOrder.Id).Updates(map[string]interface{}{"trigger_time": time.Now()}).Error; err != nil {
log.Errorf("现货止损更新状态失败 id%s err:%v", stopOrder.Id, err)
}
}
if _, err := helper.DefaultRedis.LRem(key, item); err != nil {
log.Errorf("现货止损 删除缓存失败 id:%v err:%v", stopOrder.Id, err)
}
} else {
log.Error("获取锁失败")
}
}
// 判断是否触发现货减仓
func JudgeSpotReduce(trade models.TradeSet) {
key := fmt.Sprintf(rediskey.SpotReduceList, global.EXCHANGE_BINANCE)
db := commonservice.GetDBConnection()
spotApi := SpotRestApi{}
setting, err := cacheservice.GetSystemSetting(db)
if err != nil {
log.Error("获取系统设置失败")
return
}
reduceOrder := positiondto.ReduceListItem{}
tradePrice, _ := decimal.NewFromString(trade.LastPrice)
//减仓单减仓策略
reduceReduceListKey := fmt.Sprintf(rediskey.SpotOrderReduceStrategyList, 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 {
//买入
if strings.ToUpper(reduceOrderStrategy.Side) == "SELL" &&
item2.TriggerPrice.Cmp(tradePrice) >= 0 &&
item2.TriggerPrice.Cmp(decimal.Zero) > 0 &&
tradePrice.Cmp(decimal.Zero) > 0 {
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.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 SpotReduceTrigger(db, reduceOrder, spotApi, 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())
}
}
}
}
}
}
}
}
//减仓单
reduceVal, _ := helper.DefaultRedis.GetAllList(key)
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 strings.ToUpper(reduceOrder.Side) == "SELL" &&
orderPrice.Cmp(tradePrice) >= 0 &&
orderPrice.Cmp(decimal.Zero) > 0 &&
tradePrice.Cmp(decimal.Zero) > 0 {
SpotReduceTrigger(db, reduceOrder, spotApi, setting, key, item, false, 0)
}
}
}
}
// 触发现货减仓
// isStrategy 是否是策略减仓单
// reduceId 策略主减仓单id
func SpotReduceTrigger(db *gorm.DB, reduceOrder positiondto.ReduceListItem, spotApi SpotRestApi, setting DbModels.LineSystemSetting, key, item string, isStrategy bool, reduceId int) bool {
tradeSet, err := cacheservice.GetTradeSet(global.EXCHANGE_BINANCE, reduceOrder.Symbol, 1)
result := true
if err != nil {
log.Error("获取交易设置失败")
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 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 := CancelOpenOrderByOrderSnLoop(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))
params := OrderPlacementService{
ApiId: reduceOrder.ApiId,
Side: reduceOrder.Side,
Type: "LIMIT",
TimeInForce: "GTC",
Symbol: reduceOrder.Symbol,
Price: price,
Quantity: reduceOrder.Num,
NewClientOrderId: reduceOrder.OrderSn,
}
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{}).
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)
}
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, 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
}
// 判断现货加仓
func JudgeSpotAddPosition(trade models.TradeSet) {
key := fmt.Sprintf(rediskey.SpotAddPositionList, global.EXCHANGE_BINANCE)
preOrderVal, _ := helper.DefaultRedis.GetAllList(key)
db := commonservice.GetDBConnection()
if len(preOrderVal) == 0 {
return
}
spotApi := SpotRestApi{}
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 strings.ToUpper(preOrder.Side) == "BUY" && orderPrice.Cmp(tradePrice) >= 0 && orderPrice.Cmp(decimal.Zero) > 0 && tradePrice.Cmp(decimal.Zero) > 0 {
SpotAddPositionTrigger(db, &preOrder, item, spotApi)
}
}
}
}
func SpotAddPositionTrigger(db *gorm.DB, v *positiondto.AddPositionList, item string, spotApi SpotRestApi) {
lock := helper.NewRedisLock(fmt.Sprintf(rediskey.SpotTrigger, 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, 1)
if tradeSet.LastPrice == "" {
log.Errorf("现货加仓触发 查询交易对失败 交易对:%s ordersn:%s", v.Symbol, v.OrderSn)
return
}
key := fmt.Sprintf(rediskey.SpotAddPositionList, 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
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 := OrderPlacementService{
ApiId: v.ApiId,
Symbol: v.Symbol,
Side: v.Side,
Type: preOrder.MainOrderType,
TimeInForce: "GTC",
Price: price,
Quantity: num.Truncate(int32(tradeSet.AmountDigit)),
NewClientOrderId: v.OrderSn,
}
preOrderVal, _ := sonic.MarshalString(&v)
if err := spotApi.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)
}
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
}
}