1121 lines
35 KiB
Go
1121 lines
35 KiB
Go
|
|
package binanceservice
|
|||
|
|
|
|||
|
|
import (
|
|||
|
|
"context"
|
|||
|
|
"database/sql"
|
|||
|
|
"errors"
|
|||
|
|
"fmt"
|
|||
|
|
DbModels "go-admin/app/admin/models"
|
|||
|
|
"go-admin/common/global"
|
|||
|
|
"go-admin/common/helper"
|
|||
|
|
"go-admin/pkg/maphelper"
|
|||
|
|
"go-admin/pkg/retryhelper"
|
|||
|
|
"go-admin/pkg/utility/snowflakehelper"
|
|||
|
|
"go-admin/services/cacheservice"
|
|||
|
|
"strconv"
|
|||
|
|
"sync"
|
|||
|
|
"time"
|
|||
|
|
|
|||
|
|
"github.com/go-admin-team/go-admin-core/logger"
|
|||
|
|
"github.com/go-admin-team/go-admin-core/sdk/service"
|
|||
|
|
"github.com/shopspring/decimal"
|
|||
|
|
"gorm.io/gorm"
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
// ReverseServiceOptimized 优化后的反向下单服务
|
|||
|
|
type ReverseServiceOptimized struct {
|
|||
|
|
service.Service
|
|||
|
|
// 订单状态更新锁,防止并发状态覆盖
|
|||
|
|
orderStatusMutex sync.RWMutex
|
|||
|
|
// 持仓更新锁,防止持仓数据竞态
|
|||
|
|
positionMutex sync.RWMutex
|
|||
|
|
// 止盈止损订单取消锁
|
|||
|
|
takeProfitMutex sync.RWMutex
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// PositionSyncResult 持仓同步结果
|
|||
|
|
type PositionSyncResult struct {
|
|||
|
|
ExchangePosition decimal.Decimal // 交易所实际持仓
|
|||
|
|
SystemPosition decimal.Decimal // 系统记录持仓
|
|||
|
|
NeedSync bool // 是否需要同步
|
|||
|
|
SyncError error // 同步错误
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// OrderStatusUpdate 订单状态更新结构
|
|||
|
|
type OrderStatusUpdate struct {
|
|||
|
|
OrderSn string
|
|||
|
|
Status int
|
|||
|
|
UpdateData map[string]interface{}
|
|||
|
|
RetryCount int
|
|||
|
|
LastAttempt time.Time
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ReverseOrderOptimized 优化后的反向下单处理
|
|||
|
|
// 增强并发安全性和错误处理
|
|||
|
|
func (e *ReverseServiceOptimized) ReverseOrderOptimized(apiKey string, mapData map[string]interface{}) (bool, error) {
|
|||
|
|
apiInfo := GetApiInfoByKey(apiKey)
|
|||
|
|
if apiInfo.Id == 0 {
|
|||
|
|
e.Log.Errorf("获取apiInfo失败 %s", apiKey)
|
|||
|
|
return false, nil
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
status, _ := maphelper.GetString(mapData, "X")
|
|||
|
|
orderSn, err := maphelper.GetString(mapData, "c")
|
|||
|
|
if err != nil {
|
|||
|
|
return true, err
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 使用Redis分布式锁确保同一订单号的处理原子性
|
|||
|
|
lockKey := fmt.Sprintf("reverse_order_lock:%s", orderSn)
|
|||
|
|
lock := helper.NewRedisLock(lockKey, 30, 5, 100*time.Millisecond)
|
|||
|
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
|||
|
|
defer cancel()
|
|||
|
|
if acquired, err := lock.AcquireWait(ctx); err != nil || !acquired {
|
|||
|
|
return false, fmt.Errorf("获取订单处理锁失败: %s, err: %v", orderSn, err)
|
|||
|
|
}
|
|||
|
|
defer lock.Release()
|
|||
|
|
|
|||
|
|
switch status {
|
|||
|
|
case "NEW", "CANCELED", "EXPIRED", "EXPIRED_IN_MATCH":
|
|||
|
|
return e.handleSimpleStatusChangeOptimized(status, orderSn, mapData)
|
|||
|
|
case "FILLED":
|
|||
|
|
return e.handleFilledOrderOptimized(apiInfo, mapData)
|
|||
|
|
default:
|
|||
|
|
return false, fmt.Errorf("不支持的订单状态 %s", status)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// handleSimpleStatusChangeOptimized 优化的简单状态变更处理
|
|||
|
|
func (e *ReverseServiceOptimized) handleSimpleStatusChangeOptimized(status string, orderSn string, mapData map[string]interface{}) (bool, error) {
|
|||
|
|
statusMap := map[string]int{
|
|||
|
|
"NEW": 2,
|
|||
|
|
"CANCELED": 6,
|
|||
|
|
"EXPIRED": 7,
|
|||
|
|
"EXPIRED_IN_MATCH": 7,
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if newStatus, ok := statusMap[status]; ok {
|
|||
|
|
// 使用重试机制确保状态更新成功
|
|||
|
|
update := &OrderStatusUpdate{
|
|||
|
|
OrderSn: orderSn,
|
|||
|
|
Status: newStatus,
|
|||
|
|
UpdateData: mapData,
|
|||
|
|
}
|
|||
|
|
return false, e.updateOrderStatusWithRetry(update)
|
|||
|
|
}
|
|||
|
|
return false, fmt.Errorf("不支持的订单状态 %s", status)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// handleFilledOrderOptimized 优化的成交订单处理
|
|||
|
|
func (e *ReverseServiceOptimized) handleFilledOrderOptimized(apiInfo DbModels.LineApiUser, mapData map[string]interface{}) (bool, error) {
|
|||
|
|
if apiInfo.ReverseStatus != 1 || apiInfo.OpenStatus != 1 || apiInfo.ReverseApiId <= 0 {
|
|||
|
|
return false, nil
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
reverseApiInfo, err := GetApiInfo(apiInfo.ReverseApiId)
|
|||
|
|
if err != nil {
|
|||
|
|
e.Log.Errorf("获取反向api信息失败 reverseApiId:%d, err:%v", apiInfo.ReverseApiId, err)
|
|||
|
|
return true, err
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
mainOrder, err := e.SaveMainOrderOptimized(mapData, apiInfo)
|
|||
|
|
if err != nil {
|
|||
|
|
return false, err
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 使用事务确保持仓和订单状态的一致性
|
|||
|
|
return e.processMainOrderWithTransaction(&mainOrder, &apiInfo, &reverseApiInfo)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// SaveMainOrderOptimized 优化的主单保存
|
|||
|
|
func (e *ReverseServiceOptimized) SaveMainOrderOptimized(mapData map[string]interface{}, apiInfo DbModels.LineApiUser) (DbModels.LineReverseOrder, error) {
|
|||
|
|
now := time.Now()
|
|||
|
|
symbol, err := maphelper.GetString(mapData, "s")
|
|||
|
|
var reverseOrder DbModels.LineReverseOrder
|
|||
|
|
if err != nil {
|
|||
|
|
return reverseOrder, err
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
orderSn, err := maphelper.GetString(mapData, "c")
|
|||
|
|
if err != nil {
|
|||
|
|
return reverseOrder, err
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 检查订单是否已存在,防止重复处理
|
|||
|
|
existingOrder := DbModels.LineReverseOrder{}
|
|||
|
|
if err := e.Orm.Where("order_sn = ?", orderSn).First(&existingOrder).Error; err == nil {
|
|||
|
|
e.Log.Warnf("订单已存在,跳过处理: %s", orderSn)
|
|||
|
|
return existingOrder, nil
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
side, err := maphelper.GetString(mapData, "S")
|
|||
|
|
if err != nil {
|
|||
|
|
return reverseOrder, err
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
positionSide, err := maphelper.GetString(mapData, "ps")
|
|||
|
|
if err != nil {
|
|||
|
|
return reverseOrder, err
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
mainType, _ := maphelper.GetString(mapData, "ot")
|
|||
|
|
|
|||
|
|
reverseOrder = DbModels.LineReverseOrder{
|
|||
|
|
ApiId: apiInfo.Id,
|
|||
|
|
Category: 0,
|
|||
|
|
OrderSn: orderSn,
|
|||
|
|
TriggerTime: &now,
|
|||
|
|
Status: 3,
|
|||
|
|
Symbol: symbol,
|
|||
|
|
FollowOrderSn: "",
|
|||
|
|
Type: mainType,
|
|||
|
|
Price: maphelper.GetDecimal(mapData, "p"),
|
|||
|
|
FinalPrice: maphelper.GetDecimal(mapData, "ap"),
|
|||
|
|
TotalNum: maphelper.GetDecimal(mapData, "z"),
|
|||
|
|
Side: side,
|
|||
|
|
PositionSide: positionSide,
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
reverseOrder.PriceU = reverseOrder.Price
|
|||
|
|
|
|||
|
|
if !reverseOrder.Price.IsZero() && !reverseOrder.TotalNum.IsZero() {
|
|||
|
|
reverseOrder.BuyPrice = reverseOrder.Price.Mul(reverseOrder.TotalNum)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if id, err := maphelper.GetFloat64(mapData, "i"); err == nil {
|
|||
|
|
reverseOrder.OrderId = strconv.FormatFloat(id, 'f', -1, 64)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
orderType, _ := maphelper.GetString(mapData, "ot")
|
|||
|
|
switch orderType {
|
|||
|
|
case "LIMIT", "MARKET":
|
|||
|
|
reverseOrder.OrderType = 0
|
|||
|
|
case "TAKE_PROFIT_MARKET", "TAKE_PROFIT":
|
|||
|
|
reverseOrder.OrderType = 1
|
|||
|
|
case "STOP_MARKET", "STOP", "TRAILING_STOP_MARKET":
|
|||
|
|
reverseOrder.OrderType = 2
|
|||
|
|
default:
|
|||
|
|
return reverseOrder, fmt.Errorf("不支持的订单类型: %s", orderType)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if reverseOrder.PositionSide == "BOTH" {
|
|||
|
|
return reverseOrder, errors.New("不支持的持仓类型,必须为双向持仓")
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 使用事务保存订单
|
|||
|
|
err = e.Orm.Transaction(func(tx *gorm.DB) error {
|
|||
|
|
return tx.Create(&reverseOrder).Error
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
if err != nil {
|
|||
|
|
e.Log.Errorf("保存主单失败:%v", err)
|
|||
|
|
return reverseOrder, err
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return reverseOrder, nil
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// processMainOrderWithTransaction 在事务中处理主单
|
|||
|
|
func (e *ReverseServiceOptimized) processMainOrderWithTransaction(mainOrder *DbModels.LineReverseOrder, apiInfo *DbModels.LineApiUser, reverseApiInfo *DbModels.LineApiUser) (bool, error) {
|
|||
|
|
var needReverseOrder bool
|
|||
|
|
var closePosition bool
|
|||
|
|
var err error
|
|||
|
|
|
|||
|
|
err = e.Orm.Transaction(func(tx *gorm.DB) error {
|
|||
|
|
// 在事务中处理持仓更新
|
|||
|
|
switch {
|
|||
|
|
case mainOrder.PositionSide == "LONG" && mainOrder.Side == "BUY",
|
|||
|
|
mainOrder.PositionSide == "SHORT" && mainOrder.Side == "SELL":
|
|||
|
|
if mainOrder.Category == 0 {
|
|||
|
|
needReverseOrder, closePosition, err = e.savePositionOptimized(tx, mainOrder, apiInfo, true, false)
|
|||
|
|
if err != nil {
|
|||
|
|
return err
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
case mainOrder.PositionSide == "SHORT" && mainOrder.Side == "BUY",
|
|||
|
|
mainOrder.PositionSide == "LONG" && mainOrder.Side == "SELL":
|
|||
|
|
if mainOrder.Category == 0 {
|
|||
|
|
needReverseOrder, closePosition, err = e.savePositionOptimized(tx, mainOrder, apiInfo, true, true)
|
|||
|
|
if err != nil {
|
|||
|
|
e.Log.Errorf("保存主订单失败: %v", err)
|
|||
|
|
return err
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
default:
|
|||
|
|
return errors.New("不支持的订单类型")
|
|||
|
|
}
|
|||
|
|
return nil
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
if err != nil {
|
|||
|
|
return false, err
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 事务成功后,异步处理反向下单
|
|||
|
|
if needReverseOrder {
|
|||
|
|
go func() {
|
|||
|
|
if err := e.DoAddReverseOrderOptimized(mainOrder, reverseApiInfo, apiInfo.OrderProportion, false, closePosition); err != nil {
|
|||
|
|
e.Log.Errorf("异步反向下单失败: %v", err)
|
|||
|
|
}
|
|||
|
|
}()
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return true, nil
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// savePositionOptimized 优化的持仓保存,增强并发安全性
|
|||
|
|
func (e *ReverseServiceOptimized) savePositionOptimized(tx *gorm.DB, order *DbModels.LineReverseOrder, apiInfo *DbModels.LineApiUser, isMain, reducePosition bool) (bool, bool, error) {
|
|||
|
|
e.positionMutex.Lock()
|
|||
|
|
defer e.positionMutex.Unlock()
|
|||
|
|
|
|||
|
|
position := DbModels.LineReversePosition{}
|
|||
|
|
positionSide := order.PositionSide
|
|||
|
|
side := order.Side
|
|||
|
|
totalNum := order.TotalNum
|
|||
|
|
closePosition := false
|
|||
|
|
needReverseOrder := false
|
|||
|
|
|
|||
|
|
symbol, err1 := cacheservice.GetTradeSet(global.EXCHANGE_BINANCE, order.Symbol, 1)
|
|||
|
|
if err1 != nil {
|
|||
|
|
e.Log.Errorf("获取交易对失败 symbol:%s err:%v", order.Symbol, err1)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
var querySql string
|
|||
|
|
sqlStr := ""
|
|||
|
|
|
|||
|
|
// 持仓同步检查
|
|||
|
|
syncResult := e.syncPositionWithExchange(apiInfo, order.Symbol, order.PositionSide)
|
|||
|
|
if syncResult.NeedSync {
|
|||
|
|
e.Log.Warnf("检测到持仓不同步,交易所持仓: %s, 系统持仓: %s",
|
|||
|
|
syncResult.ExchangePosition.String(), syncResult.SystemPosition.String())
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 如果是主单,存储仓位则是反单的持仓方向
|
|||
|
|
if isMain {
|
|||
|
|
if order.PositionSide == "LONG" {
|
|||
|
|
positionSide = "SHORT"
|
|||
|
|
} else {
|
|||
|
|
positionSide = "LONG"
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 减仓判断是否为平仓
|
|||
|
|
closePosition = e.getClosePositionOptimized(reducePosition, apiInfo, order, order.PositionSide, isMain)
|
|||
|
|
|
|||
|
|
if !reducePosition {
|
|||
|
|
// 反单止盈止损方向相反
|
|||
|
|
if side == "SELL" {
|
|||
|
|
side = "BUY"
|
|||
|
|
} else {
|
|||
|
|
side = "SELL"
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
position.ReverseApiId = apiInfo.ReverseApiId
|
|||
|
|
position.Side = side
|
|||
|
|
position.ApiId = order.ApiId
|
|||
|
|
position.Symbol = order.Symbol
|
|||
|
|
position.Status = 1
|
|||
|
|
position.ReverseStatus = 0
|
|||
|
|
position.PositionSide = positionSide
|
|||
|
|
position.AveragePrice = order.FinalPrice
|
|||
|
|
position.PositionNo = snowflakehelper.GetOrderNo()
|
|||
|
|
}
|
|||
|
|
querySql = "api_id =? and position_side =? and symbol =? and status =1"
|
|||
|
|
|
|||
|
|
// 使用乐观锁防止并发更新冲突
|
|||
|
|
if closePosition {
|
|||
|
|
totalNum = decimal.Zero
|
|||
|
|
sqlStr = "UPDATE line_reverse_position set amount=@totalNum,updated_at=now(),status=2,version=version+1 where id =@id and status!=2 and version=@version"
|
|||
|
|
} else if reducePosition {
|
|||
|
|
sqlStr = "UPDATE line_reverse_position set amount=amount - @totalNum,updated_at=now(),average_price=@averagePrice,version=version+1 where id =@id and status!=2 and version=@version"
|
|||
|
|
} else {
|
|||
|
|
sqlStr = "UPDATE line_reverse_position set total_amount=total_amount + @totalNum,amount=amount + @totalNum,updated_at=now(),average_price=@averagePrice,version=version+1 where id =@id and status!=2 and version=@version"
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
querySql = "reverse_api_id =? and position_side =? and symbol =? and reverse_status in (0,1)"
|
|||
|
|
closePosition = e.getClosePositionOptimized(reducePosition, apiInfo, order, order.PositionSide, isMain)
|
|||
|
|
|
|||
|
|
if closePosition {
|
|||
|
|
totalNum = decimal.Zero
|
|||
|
|
sqlStr = "UPDATE line_reverse_position set reverse_amount=@totalNum,updated_at=now(),reverse_status=2,version=version+1 where id =@id and reverse_status !=2 and version=@version"
|
|||
|
|
} else if reducePosition {
|
|||
|
|
sqlStr = "UPDATE line_reverse_position set reverse_amount=reverse_amount - @totalNum,updated_at=now(),reverse_average_price=@averagePrice,version=version+1 where id =@id and reverse_status !=2 and version=@version"
|
|||
|
|
} else {
|
|||
|
|
sqlStr = "UPDATE line_reverse_position set total_reverse_amount=total_reverse_amount + @totalNum,reverse_amount=reverse_amount + @totalNum,updated_at=now(),reverse_status =1,reverse_average_price=@averagePrice,version=version+1 where id =@id and reverse_status !=2 and version=@version"
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
var averagePrice decimal.Decimal
|
|||
|
|
var remainQuantity decimal.Decimal
|
|||
|
|
var currentVersion int
|
|||
|
|
|
|||
|
|
// 获取当前持仓记录
|
|||
|
|
err1 = tx.Model(&position).Where(querySql, order.ApiId, positionSide, order.Symbol).First(&position).Error
|
|||
|
|
if err1 != nil {
|
|||
|
|
// 主单仓位不存在,创建新仓位
|
|||
|
|
if isMain && errors.Is(err1, gorm.ErrRecordNotFound) && !reducePosition {
|
|||
|
|
if err2 := tx.Create(&position).Error; err2 != nil {
|
|||
|
|
return false, false, err2
|
|||
|
|
}
|
|||
|
|
averagePrice = position.AveragePrice
|
|||
|
|
} else {
|
|||
|
|
return false, false, err1
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
currentVersion = position.Version
|
|||
|
|
// 计算平均价格逻辑保持不变
|
|||
|
|
var totalAmount decimal.Decimal
|
|||
|
|
var totalPrice decimal.Decimal
|
|||
|
|
if !position.Amount.IsZero() && !position.AveragePrice.IsZero() {
|
|||
|
|
if isMain {
|
|||
|
|
totalPrice = position.Amount.Mul(position.AveragePrice)
|
|||
|
|
totalAmount = position.Amount
|
|||
|
|
} else {
|
|||
|
|
totalPrice = position.ReverseAmount.Mul(position.ReverseAveragePrice)
|
|||
|
|
totalAmount = position.ReverseAmount
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if !reducePosition {
|
|||
|
|
totalPrice = totalPrice.Add(order.Price.Mul(order.TotalNum))
|
|||
|
|
totalAmount = totalAmount.Add(order.TotalNum)
|
|||
|
|
} else if reducePosition && !closePosition {
|
|||
|
|
totalPrice = totalPrice.Sub(order.Price.Mul(order.TotalNum))
|
|||
|
|
totalAmount = totalAmount.Sub(order.TotalNum)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if totalAmount.IsZero() || totalPrice.IsZero() {
|
|||
|
|
if isMain {
|
|||
|
|
averagePrice = position.AveragePrice
|
|||
|
|
} else {
|
|||
|
|
if position.ReverseAveragePrice.IsZero() {
|
|||
|
|
averagePrice = order.Price
|
|||
|
|
} else {
|
|||
|
|
averagePrice = position.ReverseAveragePrice
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
if closePosition {
|
|||
|
|
if isMain {
|
|||
|
|
averagePrice = position.AveragePrice
|
|||
|
|
} else {
|
|||
|
|
averagePrice = position.ReverseAveragePrice
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
averagePrice = totalPrice.Div(totalAmount).Truncate(int32(symbol.PriceDigit))
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 关联订单的仓位id
|
|||
|
|
if err2 := tx.Exec("UPDATE line_reverse_order set position_id=@positionId where id=@orderId and position_id = 0",
|
|||
|
|
sql.Named("positionId", position.Id), sql.Named("orderId", order.Id)).Error; err2 != nil {
|
|||
|
|
return false, false, err2
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 使用版本号进行乐观锁更新
|
|||
|
|
dbResult := tx.Exec(sqlStr,
|
|||
|
|
sql.Named("totalNum", totalNum),
|
|||
|
|
sql.Named("id", position.Id),
|
|||
|
|
sql.Named("averagePrice", averagePrice),
|
|||
|
|
sql.Named("version", currentVersion))
|
|||
|
|
|
|||
|
|
if dbResult.Error != nil {
|
|||
|
|
return false, false, dbResult.Error
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
order.PositionId = position.Id
|
|||
|
|
if dbResult.RowsAffected == 0 {
|
|||
|
|
e.Log.Errorf("持仓更新失败,可能存在并发冲突 closePosition:%v order:%v", closePosition, order)
|
|||
|
|
return false, false, errors.New("持仓更新失败,可能存在并发冲突")
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if reducePosition && !isMain {
|
|||
|
|
remainQuantity = position.ReverseAmount.Sub(totalNum)
|
|||
|
|
} else if !isMain {
|
|||
|
|
remainQuantity = position.ReverseAmount.Add(totalNum)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 主单且对手单没有平仓
|
|||
|
|
if isMain && position.ReverseStatus != 2 {
|
|||
|
|
needReverseOrder = true
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 异步处理止盈止损
|
|||
|
|
if !isMain && !closePosition {
|
|||
|
|
go func() {
|
|||
|
|
if err := e.doDefaultTakeStopOptimized(order, apiInfo, remainQuantity); err != nil {
|
|||
|
|
e.Log.Errorf("设置默认止盈止损失败: %v", err)
|
|||
|
|
}
|
|||
|
|
}()
|
|||
|
|
} else if !isMain && closePosition {
|
|||
|
|
// 异步取消剩余委托
|
|||
|
|
go func() {
|
|||
|
|
if err := e.DoCancelTakeAndStopOptimized(order.Symbol, position.PositionSide, apiInfo); err != nil {
|
|||
|
|
e.Log.Errorf("取消止盈止损订单失败: %v", err)
|
|||
|
|
}
|
|||
|
|
}()
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return needReverseOrder, closePosition, nil
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// syncPositionWithExchange 同步交易所持仓数据
|
|||
|
|
func (e *ReverseServiceOptimized) syncPositionWithExchange(apiInfo *DbModels.LineApiUser, symbol, positionSide string) *PositionSyncResult {
|
|||
|
|
result := &PositionSyncResult{}
|
|||
|
|
|
|||
|
|
// 获取交易所实际持仓
|
|||
|
|
futApi := FutRestApi{}
|
|||
|
|
holdData := HoldeData{}
|
|||
|
|
err := futApi.GetPositionData(apiInfo, symbol, positionSide, &holdData)
|
|||
|
|
if err != nil {
|
|||
|
|
result.SyncError = err
|
|||
|
|
return result
|
|||
|
|
}
|
|||
|
|
result.ExchangePosition = holdData.TotalQuantity
|
|||
|
|
|
|||
|
|
// 获取系统记录的持仓
|
|||
|
|
position := DbModels.LineReversePosition{}
|
|||
|
|
err = e.Orm.Where("api_id = ? AND position_side = ? AND symbol = ? AND status = 1",
|
|||
|
|
apiInfo.Id, positionSide, symbol).First(&position).Error
|
|||
|
|
if err != nil {
|
|||
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|||
|
|
result.SystemPosition = decimal.Zero
|
|||
|
|
} else {
|
|||
|
|
result.SyncError = err
|
|||
|
|
return result
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
result.SystemPosition = position.Amount
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 判断是否需要同步(允许小幅误差)
|
|||
|
|
diff := result.ExchangePosition.Sub(result.SystemPosition).Abs()
|
|||
|
|
threshold := decimal.NewFromFloat(0.001) // 0.001的误差阈值
|
|||
|
|
result.NeedSync = diff.GreaterThan(threshold)
|
|||
|
|
|
|||
|
|
return result
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// getClosePositionOptimized 优化的平仓判断
|
|||
|
|
func (e *ReverseServiceOptimized) getClosePositionOptimized(reducePosition bool, apiInfo *DbModels.LineApiUser, order *DbModels.LineReverseOrder, positionSide string, isMain bool) bool {
|
|||
|
|
closePosition := false
|
|||
|
|
|
|||
|
|
if reducePosition {
|
|||
|
|
// 使用重试机制获取持仓数据
|
|||
|
|
opts := retryhelper.DefaultRetryOptions()
|
|||
|
|
opts.MaxRetries = 3
|
|||
|
|
opts.InitialInterval = time.Second
|
|||
|
|
|
|||
|
|
holdData, err := retryhelper.RetryWithResult(func() (*HoldeData, error) {
|
|||
|
|
futApi := FutRestApi{}
|
|||
|
|
holdData := &HoldeData{}
|
|||
|
|
err := futApi.GetPositionData(apiInfo, order.Symbol, positionSide, holdData)
|
|||
|
|
return holdData, err
|
|||
|
|
}, opts)
|
|||
|
|
|
|||
|
|
if err != nil {
|
|||
|
|
e.Log.Errorf("获取剩余持仓信息失败 symbol:%s err:%v", order.Symbol, err)
|
|||
|
|
// 降级到数据库查询
|
|||
|
|
lastPosition := DbModels.LineReversePosition{}
|
|||
|
|
if err2 := e.Orm.Model(&DbModels.LineReversePosition{}).Where("position_side =? and symbol =? and status =1", positionSide, order.Symbol).First(&lastPosition).Error; err2 != nil {
|
|||
|
|
e.Log.Errorf("获取上一次持仓信息失败 symbol:%s err:%v", order.Symbol, err2)
|
|||
|
|
} else if isMain && lastPosition.Amount.Cmp(order.TotalNum) <= 0 {
|
|||
|
|
closePosition = true
|
|||
|
|
} else if !isMain && lastPosition.ReverseAmount.Cmp(order.TotalNum) <= 0 {
|
|||
|
|
closePosition = true
|
|||
|
|
}
|
|||
|
|
} else if holdData.TotalQuantity.IsZero() {
|
|||
|
|
closePosition = true
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return closePosition
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// updateOrderStatusWithRetry 带重试的订单状态更新
|
|||
|
|
func (e *ReverseServiceOptimized) updateOrderStatusWithRetry(update *OrderStatusUpdate) error {
|
|||
|
|
e.orderStatusMutex.Lock()
|
|||
|
|
defer e.orderStatusMutex.Unlock()
|
|||
|
|
|
|||
|
|
opts := retryhelper.DefaultRetryOptions()
|
|||
|
|
opts.MaxRetries = 3
|
|||
|
|
opts.InitialInterval = 500 * time.Millisecond
|
|||
|
|
|
|||
|
|
return retryhelper.Retry(func() error {
|
|||
|
|
data := map[string]interface{}{
|
|||
|
|
"status": update.Status,
|
|||
|
|
"updated_at": time.Now(),
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if update.Status == 3 {
|
|||
|
|
now := time.Now()
|
|||
|
|
if orderId, ok := update.UpdateData["i"].(float64); ok {
|
|||
|
|
data["order_id"] = orderId
|
|||
|
|
}
|
|||
|
|
if ap, ok := update.UpdateData["ap"].(string); ok {
|
|||
|
|
data["final_price"], _ = decimal.NewFromString(ap)
|
|||
|
|
}
|
|||
|
|
if num, ok := update.UpdateData["z"].(string); ok {
|
|||
|
|
data["total_num"], _ = decimal.NewFromString(num)
|
|||
|
|
}
|
|||
|
|
data["trigger_time"] = &now
|
|||
|
|
} else if update.Status == 2 {
|
|||
|
|
if orderId, ok := update.UpdateData["i"].(float64); ok {
|
|||
|
|
data["order_id"] = orderId
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
db := e.Orm.Model(&DbModels.LineReverseOrder{}).
|
|||
|
|
Where("order_sn = ? and status != 3", update.OrderSn).
|
|||
|
|
Updates(data)
|
|||
|
|
|
|||
|
|
if db.Error != nil {
|
|||
|
|
e.Log.Errorf("修改订单状态失败 orderSn:%s, err:%v", update.OrderSn, db.Error)
|
|||
|
|
return db.Error
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if db.RowsAffected == 0 {
|
|||
|
|
e.Log.Warnf("修改订单状态失败 orderSn:%s, 未找到订单或状态未变更", update.OrderSn)
|
|||
|
|
}
|
|||
|
|
return nil
|
|||
|
|
}, opts)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// DoAddReverseOrderOptimized 优化的反向下单
|
|||
|
|
func (e *ReverseServiceOptimized) DoAddReverseOrderOptimized(mainOrder *DbModels.LineReverseOrder, reverseApiInfo *DbModels.LineApiUser, orderProportion decimal.Decimal, reducePosition, closePosition bool) error {
|
|||
|
|
// 使用分布式锁防止重复下单
|
|||
|
|
lockKey := fmt.Sprintf("reverse_add_order:%s", mainOrder.OrderSn)
|
|||
|
|
lock := helper.NewRedisLock(lockKey, 30, 5, 100*time.Millisecond)
|
|||
|
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
|||
|
|
defer cancel()
|
|||
|
|
if acquired, err := lock.AcquireWait(ctx); err != nil || !acquired {
|
|||
|
|
return fmt.Errorf("获取反向下单锁失败: %s, err: %v", mainOrder.OrderSn, err)
|
|||
|
|
}
|
|||
|
|
defer lock.Release()
|
|||
|
|
|
|||
|
|
// 检查是否已经存在反向订单
|
|||
|
|
existingOrder := DbModels.LineReverseOrder{}
|
|||
|
|
if err := e.Orm.Where("follow_order_sn = ? AND category = 1", mainOrder.OrderSn).First(&existingOrder).Error; err == nil {
|
|||
|
|
e.Log.Warnf("反向订单已存在,跳过处理: %s", mainOrder.OrderSn)
|
|||
|
|
return nil
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
order := DbModels.LineReverseOrder{}
|
|||
|
|
order.ApiId = reverseApiInfo.Id
|
|||
|
|
order.Category = 1
|
|||
|
|
order.OrderSn = helper.GetOrderNo()
|
|||
|
|
order.FollowOrderSn = mainOrder.OrderSn
|
|||
|
|
order.Symbol = mainOrder.Symbol
|
|||
|
|
order.OrderType = 0
|
|||
|
|
|
|||
|
|
// 设置持仓方向和交易方向
|
|||
|
|
switch mainOrder.PositionSide {
|
|||
|
|
case "LONG":
|
|||
|
|
order.PositionSide = "SHORT"
|
|||
|
|
case "SHORT":
|
|||
|
|
order.PositionSide = "LONG"
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
switch mainOrder.Side {
|
|||
|
|
case "SELL":
|
|||
|
|
order.Side = "BUY"
|
|||
|
|
case "BUY":
|
|||
|
|
order.Side = "SELL"
|
|||
|
|
default:
|
|||
|
|
return fmt.Errorf("不支持的订单类型 side:%s", mainOrder.Side)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if reducePosition && closePosition {
|
|||
|
|
order.OrderType = 4
|
|||
|
|
} else if reducePosition {
|
|||
|
|
order.OrderType = 3
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
symbol, err := cacheservice.GetTradeSet(global.EXCHANGE_BINANCE, mainOrder.Symbol, 1)
|
|||
|
|
if err != nil {
|
|||
|
|
e.Log.Errorf("获取交易对信息失败 symbol:%s custom:%s :%v", mainOrder.Symbol, mainOrder.OrderSn, err)
|
|||
|
|
return err
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
setting, err := cacheservice.GetReverseSetting(e.Orm)
|
|||
|
|
if err != nil {
|
|||
|
|
e.Log.Errorf("获取反单设置失败 symbol:%s custom:%s :%v", mainOrder.Symbol, mainOrder.OrderSn, err)
|
|||
|
|
return err
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 获取最新价格
|
|||
|
|
signPrice, _ := decimal.NewFromString(symbol.LastPrice)
|
|||
|
|
price := signPrice.Truncate(int32(symbol.PriceDigit))
|
|||
|
|
|
|||
|
|
if price.Cmp(decimal.Zero) <= 0 {
|
|||
|
|
e.Log.Errorf("获取最新价格失败 symbol:%s custom:%s 单价小于0", mainOrder.Symbol, mainOrder.OrderSn)
|
|||
|
|
return errors.New("获取最新价格失败")
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 计算溢价
|
|||
|
|
var percent decimal.Decimal
|
|||
|
|
switch {
|
|||
|
|
case order.PositionSide == "LONG" && order.Side == "BUY", order.PositionSide == "SHORT" && order.Side == "BUY":
|
|||
|
|
percent = decimal.NewFromInt(100).Add(setting.ReversePremiumRatio)
|
|||
|
|
case order.PositionSide == "SHORT" && order.Side == "SELL", order.PositionSide == "LONG" && order.Side == "SELL":
|
|||
|
|
percent = decimal.NewFromInt(100).Sub(setting.ReversePremiumRatio)
|
|||
|
|
default:
|
|||
|
|
return fmt.Errorf("不支持的订单类型 ps:%s, side:%s", order.PositionSide, order.Side)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
percent = percent.Div(decimal.NewFromInt(100)).Truncate(4)
|
|||
|
|
price = price.Mul(percent).Truncate(int32(symbol.PriceDigit))
|
|||
|
|
|
|||
|
|
var amount decimal.Decimal
|
|||
|
|
|
|||
|
|
// 计算下单数量
|
|||
|
|
if closePosition {
|
|||
|
|
var position DbModels.LineReversePosition
|
|||
|
|
if err1 := e.Orm.Model(position).
|
|||
|
|
Where("position_side = ? and reverse_api_id = ? and reverse_status = 1", order.PositionSide, order.ApiId).
|
|||
|
|
Select("reverse_amount").
|
|||
|
|
Find(&position).Error; err1 != nil {
|
|||
|
|
e.Log.Errorf("获取剩余仓位失败 symbol:%s custom:%s :%v", order.Symbol, order.OrderSn, err1)
|
|||
|
|
if len(err1.Error()) < 255 {
|
|||
|
|
order.Remark = err1.Error()
|
|||
|
|
} else {
|
|||
|
|
order.Remark = err1.Error()[:254]
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
amount = position.ReverseAmount
|
|||
|
|
if amount.IsZero() {
|
|||
|
|
order.Remark = "没有持仓数量"
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
proportion := decimal.NewFromInt(100)
|
|||
|
|
if orderProportion.Cmp(decimal.Zero) > 0 {
|
|||
|
|
proportion = orderProportion
|
|||
|
|
}
|
|||
|
|
proportion = proportion.Div(decimal.NewFromInt(100)).Truncate(4)
|
|||
|
|
amount = mainOrder.TotalNum.Mul(proportion).Truncate(int32(symbol.AmountDigit))
|
|||
|
|
|
|||
|
|
logger.Info("反向下单比例 %s ,原始数量:%s,反向下单数量:%s",
|
|||
|
|
proportion.String(), mainOrder.TotalNum.String(), amount.String())
|
|||
|
|
|
|||
|
|
if amount.Cmp(decimal.Zero) <= 0 {
|
|||
|
|
e.Log.Errorf("计算数量失败 symbol:%s custom:%s 数量小于0", mainOrder.Symbol, mainOrder.OrderSn)
|
|||
|
|
return errors.New("计算数量失败")
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
order.TotalNum = amount
|
|||
|
|
order.Price = price
|
|||
|
|
order.PriceU = price
|
|||
|
|
order.BuyPrice = amount.Mul(price).Truncate(int32(symbol.PriceDigit))
|
|||
|
|
order.Status = 1
|
|||
|
|
order.Type = setting.ReverseOrderType
|
|||
|
|
order.SignPrice = signPrice
|
|||
|
|
order.PositionId = mainOrder.PositionId
|
|||
|
|
|
|||
|
|
if order.Remark != "" {
|
|||
|
|
order.Status = 8
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 保存反向订单
|
|||
|
|
if err := e.Orm.Model(&order).Create(&order).Error; err != nil {
|
|||
|
|
e.Log.Errorf("保存反单失败 symbol:%s custom:%s :%v", mainOrder.Symbol, mainOrder.OrderSn, err)
|
|||
|
|
return err
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 如果状态正常,进行下单
|
|||
|
|
if order.Status == 1 {
|
|||
|
|
err = e.DoBianceOrderOptimized(&order, reverseApiInfo, &setting, reducePosition, closePosition)
|
|||
|
|
// 下单成功且为平仓单时,取消止盈止损
|
|||
|
|
if err == nil && closePosition {
|
|||
|
|
go func() {
|
|||
|
|
if err := e.DoCancelTakeProfitBatchOptimized(symbol.GetSymbol(), order.PositionSide, order.Side, 1, reverseApiInfo); err != nil {
|
|||
|
|
e.Log.Errorf("取消止盈止损订单失败: %v", err)
|
|||
|
|
}
|
|||
|
|
}()
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return err
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// DoBianceOrderOptimized 优化的币安下单
|
|||
|
|
func (e *ReverseServiceOptimized) DoBianceOrderOptimized(order *DbModels.LineReverseOrder, apiInfo *DbModels.LineApiUser, setting *DbModels.LineReverseSetting, reducePosition, closePosition bool) error {
|
|||
|
|
// 使用重试机制进行下单
|
|||
|
|
opts := retryhelper.DefaultRetryOptions()
|
|||
|
|
opts.MaxRetries = 3
|
|||
|
|
opts.InitialInterval = time.Second
|
|||
|
|
opts.RetryableErrFn = func(err error) bool {
|
|||
|
|
// 某些错误不需要重试
|
|||
|
|
if err != nil {
|
|||
|
|
errStr := err.Error()
|
|||
|
|
// 余额不足、订单重复等错误不重试
|
|||
|
|
if contains(errStr, []string{"余额不足", "订单重复", "API-key", "无效"}) {
|
|||
|
|
return false
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return true
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return retryhelper.Retry(func() error {
|
|||
|
|
futApiV2 := FutRestApi{}
|
|||
|
|
params := FutOrderPlace{
|
|||
|
|
ApiId: apiInfo.Id,
|
|||
|
|
Symbol: order.Symbol,
|
|||
|
|
Side: order.Side,
|
|||
|
|
PositionSide: order.PositionSide,
|
|||
|
|
OrderType: setting.ReverseOrderType,
|
|||
|
|
Quantity: order.TotalNum,
|
|||
|
|
Price: order.Price,
|
|||
|
|
NewClientOrderId: order.OrderSn,
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if err := futApiV2.OrderPlaceLoop(e.Orm, params, 3); err != nil {
|
|||
|
|
e.Log.Errorf("币安下单失败 orderSn:%s, err:%v", order.OrderSn, err)
|
|||
|
|
// 更新订单状态为失败
|
|||
|
|
e.Orm.Model(&DbModels.LineReverseOrder{}).
|
|||
|
|
Where("id = ?", order.Id).
|
|||
|
|
Updates(map[string]interface{}{
|
|||
|
|
"status": 8,
|
|||
|
|
"remark": err.Error()[:min(len(err.Error()), 254)],
|
|||
|
|
"updated_at": time.Now(),
|
|||
|
|
})
|
|||
|
|
return err
|
|||
|
|
}
|
|||
|
|
return nil
|
|||
|
|
}, opts)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// DoCancelTakeProfitBatchOptimized 优化的批量取消止盈止损订单
|
|||
|
|
func (e *ReverseServiceOptimized) DoCancelTakeProfitBatchOptimized(symbol, positionSide, side string, orderType int, apiInfo *DbModels.LineApiUser) error {
|
|||
|
|
e.takeProfitMutex.Lock()
|
|||
|
|
defer e.takeProfitMutex.Unlock()
|
|||
|
|
|
|||
|
|
// 获取需要取消的订单
|
|||
|
|
orderSnList := e.GetTakeProfitBatchOptimized(symbol, positionSide, side, apiInfo.Id, orderType)
|
|||
|
|
if len(orderSnList) == 0 {
|
|||
|
|
return nil
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 分批取消,每次最多10个
|
|||
|
|
batchSize := 10
|
|||
|
|
for i := 0; i < len(orderSnList); i += batchSize {
|
|||
|
|
end := i + batchSize
|
|||
|
|
if end > len(orderSnList) {
|
|||
|
|
end = len(orderSnList)
|
|||
|
|
}
|
|||
|
|
batch := orderSnList[i:end]
|
|||
|
|
|
|||
|
|
// 使用重试机制取消订单
|
|||
|
|
opts := retryhelper.DefaultRetryOptions()
|
|||
|
|
opts.MaxRetries = 3
|
|||
|
|
opts.InitialInterval = time.Second
|
|||
|
|
|
|||
|
|
err := retryhelper.Retry(func() error {
|
|||
|
|
futApi := FutRestApi{}
|
|||
|
|
return futApi.CancelBatchFutOrder(*apiInfo, symbol, batch)
|
|||
|
|
}, opts)
|
|||
|
|
|
|||
|
|
if err != nil {
|
|||
|
|
e.Log.Errorf("批量取消止盈止损订单失败 symbol:%s, batch:%v, err:%v", symbol, batch, err)
|
|||
|
|
// 继续处理下一批,不因为一批失败而中断
|
|||
|
|
continue
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
e.Log.Infof("成功取消止盈止损订单 symbol:%s, count:%d", symbol, len(batch))
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return nil
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// GetTakeProfitBatchOptimized 优化的获取止盈止损订单列表
|
|||
|
|
func (e *ReverseServiceOptimized) GetTakeProfitBatchOptimized(symbol, positionSide, side string, apiId int, orderType int) []string {
|
|||
|
|
var orders []DbModels.LineReverseOrder
|
|||
|
|
query := e.Orm.Model(&DbModels.LineReverseOrder{}).
|
|||
|
|
Where("symbol = ? AND position_side = ? AND side = ? AND api_id = ? AND order_type = ? AND status = 2",
|
|||
|
|
symbol, positionSide, side, apiId, orderType).
|
|||
|
|
Select("order_sn")
|
|||
|
|
|
|||
|
|
if err := query.Find(&orders).Error; err != nil {
|
|||
|
|
e.Log.Errorf("查询止盈止损订单失败: %v", err)
|
|||
|
|
return nil
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
orderSnList := make([]string, 0, len(orders))
|
|||
|
|
for _, order := range orders {
|
|||
|
|
orderSnList = append(orderSnList, order.OrderSn)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return orderSnList
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// DoCancelTakeAndStopOptimized 优化的取消止盈止损订单
|
|||
|
|
func (e *ReverseServiceOptimized) DoCancelTakeAndStopOptimized(symbol, positionSide string, apiInfo *DbModels.LineApiUser) error {
|
|||
|
|
e.takeProfitMutex.Lock()
|
|||
|
|
defer e.takeProfitMutex.Unlock()
|
|||
|
|
|
|||
|
|
var orders []DbModels.LineReverseOrder
|
|||
|
|
if err := e.Orm.Model(&DbModels.LineReverseOrder{}).
|
|||
|
|
Where("symbol = ? AND position_side = ? AND api_id = ? AND order_type IN (1,2) AND status = 2",
|
|||
|
|
symbol, positionSide, apiInfo.Id).
|
|||
|
|
Select("order_sn").
|
|||
|
|
Find(&orders).Error; err != nil {
|
|||
|
|
e.Log.Errorf("查询止盈止损订单失败: %v", err)
|
|||
|
|
return err
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if len(orders) == 0 {
|
|||
|
|
return nil
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
orderSnList := make([]string, 0, len(orders))
|
|||
|
|
for _, order := range orders {
|
|||
|
|
orderSnList = append(orderSnList, order.OrderSn)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 分批取消
|
|||
|
|
batchSize := 10
|
|||
|
|
for i := 0; i < len(orderSnList); i += batchSize {
|
|||
|
|
end := i + batchSize
|
|||
|
|
if end > len(orderSnList) {
|
|||
|
|
end = len(orderSnList)
|
|||
|
|
}
|
|||
|
|
batch := orderSnList[i:end]
|
|||
|
|
|
|||
|
|
opts := retryhelper.DefaultRetryOptions()
|
|||
|
|
opts.MaxRetries = 3
|
|||
|
|
opts.InitialInterval = time.Second
|
|||
|
|
|
|||
|
|
err := retryhelper.Retry(func() error {
|
|||
|
|
futApi := FutRestApi{}
|
|||
|
|
return futApi.CancelBatchFutOrder(*apiInfo, symbol, batch)
|
|||
|
|
}, opts)
|
|||
|
|
|
|||
|
|
if err != nil {
|
|||
|
|
e.Log.Errorf("批量取消止盈止损订单失败 symbol:%s, batch:%v, err:%v", symbol, batch, err)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return nil
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// doDefaultTakeStopOptimized 优化的默认止盈止损设置
|
|||
|
|
func (e *ReverseServiceOptimized) doDefaultTakeStopOptimized(order *DbModels.LineReverseOrder, apiInfo *DbModels.LineApiUser, totalNum decimal.Decimal) error {
|
|||
|
|
// 异步处理,避免阻塞主流程
|
|||
|
|
go func() {
|
|||
|
|
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
|||
|
|
defer cancel()
|
|||
|
|
|
|||
|
|
// 使用重试机制
|
|||
|
|
opts := retryhelper.DefaultRetryOptions()
|
|||
|
|
opts.MaxRetries = 3
|
|||
|
|
opts.InitialInterval = 2 * time.Second
|
|||
|
|
|
|||
|
|
err := retryhelper.Retry(func() error {
|
|||
|
|
select {
|
|||
|
|
case <-ctx.Done():
|
|||
|
|
return ctx.Err()
|
|||
|
|
default:
|
|||
|
|
return e.processDefaultTakeStop(order, apiInfo, totalNum)
|
|||
|
|
}
|
|||
|
|
}, opts)
|
|||
|
|
|
|||
|
|
if err != nil {
|
|||
|
|
e.Log.Errorf("设置默认止盈止损失败: %v", err)
|
|||
|
|
}
|
|||
|
|
}()
|
|||
|
|
|
|||
|
|
return nil
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// processDefaultTakeStop 处理默认止盈止损逻辑
|
|||
|
|
func (e *ReverseServiceOptimized) processDefaultTakeStop(order *DbModels.LineReverseOrder, apiInfo *DbModels.LineApiUser, totalNum decimal.Decimal) error {
|
|||
|
|
// 获取反单设置
|
|||
|
|
setting, err := cacheservice.GetReverseSetting(e.Orm)
|
|||
|
|
if err != nil {
|
|||
|
|
return fmt.Errorf("获取反单设置失败: %v", err)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 获取交易对信息
|
|||
|
|
symbol, err := cacheservice.GetTradeSet(global.EXCHANGE_BINANCE, order.Symbol, 1)
|
|||
|
|
if err != nil {
|
|||
|
|
return fmt.Errorf("获取交易对信息失败: %v", err)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 获取当前价格
|
|||
|
|
lastPrice, _ := decimal.NewFromString(symbol.LastPrice)
|
|||
|
|
if lastPrice.IsZero() {
|
|||
|
|
return errors.New("获取最新价格失败")
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
now := time.Now()
|
|||
|
|
|
|||
|
|
// 设置止盈订单
|
|||
|
|
if setting.TakeProfitRatio.GreaterThan(decimal.Zero) {
|
|||
|
|
takeProfitPrice := e.calculatePriceOptimized(order.PositionSide, lastPrice, setting.TakeProfitRatio, true)
|
|||
|
|
takeProfitParams := CreateOrderParams{
|
|||
|
|
ApiInfo: apiInfo,
|
|||
|
|
Order: order,
|
|||
|
|
Symbol: &symbol,
|
|||
|
|
Side: e.getOppositeSideOptimized(order.Side),
|
|||
|
|
OrderType: "TAKE_PROFIT_MARKET",
|
|||
|
|
Price: takeProfitPrice,
|
|||
|
|
TotalNum: totalNum,
|
|||
|
|
Now: now,
|
|||
|
|
LastPrice: lastPrice,
|
|||
|
|
Close: false,
|
|||
|
|
PositionId: order.PositionId,
|
|||
|
|
OrderSn: helper.GetOrderNo(),
|
|||
|
|
PositionSide: order.PositionSide,
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if err := e.createReverseOrderOptimized(takeProfitParams); err != nil {
|
|||
|
|
e.Log.Errorf("创建止盈订单失败: %v", err)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 设置止损订单
|
|||
|
|
if setting.StopLossRatio.GreaterThan(decimal.Zero) {
|
|||
|
|
stopLossPrice := e.calculatePriceOptimized(order.PositionSide, lastPrice, setting.StopLossRatio, false)
|
|||
|
|
stopLossParams := CreateOrderParams{
|
|||
|
|
ApiInfo: apiInfo,
|
|||
|
|
Order: order,
|
|||
|
|
Symbol: &symbol,
|
|||
|
|
Side: e.getOppositeSideOptimized(order.Side),
|
|||
|
|
OrderType: "STOP_MARKET",
|
|||
|
|
Price: stopLossPrice,
|
|||
|
|
TotalNum: totalNum,
|
|||
|
|
Now: now,
|
|||
|
|
LastPrice: lastPrice,
|
|||
|
|
Close: false,
|
|||
|
|
PositionId: order.PositionId,
|
|||
|
|
OrderSn: helper.GetOrderNo(),
|
|||
|
|
PositionSide: order.PositionSide,
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if err := e.createReverseOrderOptimized(stopLossParams); err != nil {
|
|||
|
|
e.Log.Errorf("创建止损订单失败: %v", err)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return nil
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// calculatePriceOptimized 优化的价格计算
|
|||
|
|
func (e *ReverseServiceOptimized) calculatePriceOptimized(positionSide string, base decimal.Decimal, ratio decimal.Decimal, isTakeProfit bool) decimal.Decimal {
|
|||
|
|
var multiplier decimal.Decimal
|
|||
|
|
if isTakeProfit {
|
|||
|
|
if positionSide == "LONG" {
|
|||
|
|
multiplier = decimal.NewFromInt(1).Add(ratio.Div(decimal.NewFromInt(100)))
|
|||
|
|
} else {
|
|||
|
|
multiplier = decimal.NewFromInt(1).Sub(ratio.Div(decimal.NewFromInt(100)))
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
if positionSide == "LONG" {
|
|||
|
|
multiplier = decimal.NewFromInt(1).Sub(ratio.Div(decimal.NewFromInt(100)))
|
|||
|
|
} else {
|
|||
|
|
multiplier = decimal.NewFromInt(1).Add(ratio.Div(decimal.NewFromInt(100)))
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return base.Mul(multiplier)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// getOppositeSideOptimized 获取相反的交易方向
|
|||
|
|
func (e *ReverseServiceOptimized) getOppositeSideOptimized(side string) string {
|
|||
|
|
if side == "BUY" {
|
|||
|
|
return "SELL"
|
|||
|
|
}
|
|||
|
|
return "BUY"
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// CreateOrderParams 创建订单参数
|
|||
|
|
// CreateOrderParams 已在 reverse_service.go 中定义
|
|||
|
|
|
|||
|
|
// createReverseOrderOptimized 优化的创建反向订单
|
|||
|
|
func (e *ReverseServiceOptimized) createReverseOrderOptimized(params CreateOrderParams) error {
|
|||
|
|
// 使用事务确保数据一致性
|
|||
|
|
return e.Orm.Transaction(func(tx *gorm.DB) error {
|
|||
|
|
reverseOrder := DbModels.LineReverseOrder{
|
|||
|
|
ApiId: params.ApiInfo.Id,
|
|||
|
|
Category: 1,
|
|||
|
|
OrderSn: params.OrderSn,
|
|||
|
|
FollowOrderSn: params.Order.OrderSn,
|
|||
|
|
Symbol: params.Order.Symbol,
|
|||
|
|
Side: params.Side,
|
|||
|
|
PositionSide: params.PositionSide,
|
|||
|
|
Price: params.Price,
|
|||
|
|
PriceU: params.Price,
|
|||
|
|
TotalNum: params.TotalNum,
|
|||
|
|
BuyPrice: params.Price.Mul(params.TotalNum),
|
|||
|
|
SignPrice: params.LastPrice,
|
|||
|
|
Status: 1,
|
|||
|
|
PositionId: params.PositionId,
|
|||
|
|
TriggerTime: ¶ms.Now,
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 设置订单类型
|
|||
|
|
switch params.OrderType {
|
|||
|
|
case "TAKE_PROFIT_MARKET", "TAKE_PROFIT":
|
|||
|
|
reverseOrder.OrderType = 1
|
|||
|
|
reverseOrder.Type = "TAKE_PROFIT_MARKET"
|
|||
|
|
case "STOP_MARKET", "STOP":
|
|||
|
|
reverseOrder.OrderType = 2
|
|||
|
|
reverseOrder.Type = "STOP_MARKET"
|
|||
|
|
default:
|
|||
|
|
return fmt.Errorf("不支持的订单类型: %s", params.OrderType)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 保存订单到数据库
|
|||
|
|
if err := tx.Create(&reverseOrder).Error; err != nil {
|
|||
|
|
e.Log.Errorf("保存反向订单失败: %v", err)
|
|||
|
|
return err
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 下单到交易所
|
|||
|
|
futApi := FutRestApi{}
|
|||
|
|
orderParams := FutOrderPlace{
|
|||
|
|
ApiId: params.ApiInfo.Id,
|
|||
|
|
Symbol: reverseOrder.Symbol,
|
|||
|
|
Side: reverseOrder.Side,
|
|||
|
|
PositionSide: reverseOrder.PositionSide,
|
|||
|
|
OrderType: "MARKET",
|
|||
|
|
Quantity: reverseOrder.TotalNum,
|
|||
|
|
StopPrice: reverseOrder.Price,
|
|||
|
|
NewClientOrderId: reverseOrder.OrderSn,
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if err := futApi.OrderPlaceLoop(tx, orderParams, 3); err != nil {
|
|||
|
|
e.Log.Errorf("下单失败: %v", err)
|
|||
|
|
// 更新订单状态为失败
|
|||
|
|
tx.Model(&reverseOrder).Updates(map[string]interface{}{
|
|||
|
|
"status": 8,
|
|||
|
|
"remark": err.Error()[:min(len(err.Error()), 254)],
|
|||
|
|
"updated_at": time.Now(),
|
|||
|
|
})
|
|||
|
|
return err
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return nil
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// contains 检查字符串是否包含任意一个子字符串
|
|||
|
|
func contains(s string, substrs []string) bool {
|
|||
|
|
for _, substr := range substrs {
|
|||
|
|
if len(substr) > 0 && len(s) >= len(substr) {
|
|||
|
|
for i := 0; i <= len(s)-len(substr); i++ {
|
|||
|
|
if s[i:i+len(substr)] == substr {
|
|||
|
|
return true
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return false
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// min 返回两个整数中的较小值
|
|||
|
|
func min(a, b int) int {
|
|||
|
|
if a < b {
|
|||
|
|
return a
|
|||
|
|
}
|
|||
|
|
return b
|
|||
|
|
}
|