2025-02-06 11:14:33 +08:00
package binanceservice
import (
"context"
2025-02-06 18:03:09 +08:00
"errors"
2025-02-06 11:14:33 +08:00
"fmt"
"go-admin/app/admin/models"
DbModels "go-admin/app/admin/models"
2025-02-06 18:03:09 +08:00
"go-admin/app/admin/service/dto"
2025-02-06 11:14:33 +08:00
"go-admin/common/const/rediskey"
2025-02-06 18:03:09 +08:00
"go-admin/common/global"
2025-02-06 11:14:33 +08:00
"go-admin/common/helper"
"go-admin/pkg/utility"
"strconv"
"strings"
"time"
"github.com/bytedance/sonic"
"github.com/go-admin-team/go-admin-core/logger"
"github.com/shopspring/decimal"
"gorm.io/gorm"
)
/ *
订单回调
* /
func ChangeSpotOrder ( mapData map [ string ] interface { } ) {
// 检查订单号是否存在
orderSn , ok := mapData [ "c" ]
2025-02-08 14:05:57 +08:00
originOrderSn := mapData [ "C" ] //取消操作 代表原始订单号
2025-02-06 11:14:33 +08:00
if ! ok {
logger . Error ( "订单回调失败, 没有订单号" , mapData )
return
}
2025-02-08 14:05:57 +08:00
if originOrderSn != "" {
orderSn = originOrderSn
}
2025-02-06 11:14:33 +08:00
// 获取数据库连接
db := GetDBConnection ( )
if db == nil {
logger . Error ( "订单回调失败, 无法获取数据库连接" )
return
}
// 获取 Redis 锁
lockKey := fmt . Sprintf ( rediskey . SpotCallBack , orderSn )
lock := helper . NewRedisLock ( lockKey , 200 , 5 , 100 * time . Millisecond )
if err := acquireLock ( lock , orderSn ) ; err != nil {
return
}
defer lock . Release ( )
// 查询订单
preOrder , err := getPreOrder ( db , orderSn )
if err != nil {
logger . Error ( "订单回调失败, 查询订单失败:" , orderSn , " err:" , err )
return
}
// 解析订单状态
status , ok := mapData [ "X" ]
if ! ok {
logMapData ( mapData )
return
}
// 更新订单状态
2025-02-06 18:03:09 +08:00
orderStatus , reason := parseOrderStatus ( status , mapData )
2025-02-06 11:14:33 +08:00
if orderStatus == 0 {
logger . Error ( "订单回调失败,状态错误:" , orderSn , " status:" , status , " reason:" , reason )
return
}
if err := updateOrderStatus ( db , preOrder , orderStatus , reason , true , mapData ) ; err != nil {
logger . Error ( "修改订单状态失败:" , orderSn , " err:" , err )
return
}
// 根据订单类型和状态处理逻辑
handleOrderByType ( db , preOrder , orderStatus )
}
// 获取 Redis 锁
func acquireLock ( lock * helper . RedisLock , orderSn interface { } ) error {
acquired , err := lock . AcquireWait ( context . Background ( ) )
if err != nil {
logger . Error ( "订单回调失败, 获取锁失败:" , orderSn , " err:" , err )
return err
}
if ! acquired {
logger . Error ( "订单回调失败, 获取锁失败:" , orderSn )
return fmt . Errorf ( "failed to acquire lock" )
}
return nil
}
// 记录 mapData 数据
func logMapData ( mapData map [ string ] interface { } ) {
mapStr , _ := sonic . Marshal ( & mapData )
logger . Error ( "订单回调失败, 没有状态:" , string ( mapStr ) )
}
// 根据订单类型和状态处理逻辑
func handleOrderByType ( db * gorm . DB , preOrder * DbModels . LinePreOrder , orderStatus int ) {
switch {
// 主单成交
2025-02-06 18:03:09 +08:00
case preOrder . OrderType == 0 && orderStatus == 6 :
2025-02-06 11:14:33 +08:00
handleMainOrderFilled ( db , preOrder )
//主单取消
case preOrder . OrderType == 0 && preOrder . Pid == 0 && orderStatus == 4 :
2025-02-06 18:03:09 +08:00
handleMainOrderCancel ( preOrder )
2025-02-06 11:14:33 +08:00
// 止盈成交
2025-02-06 18:03:09 +08:00
case preOrder . OrderType == 1 && orderStatus == 6 :
2025-02-06 11:14:33 +08:00
handleSpotTakeProfitFilled ( db , preOrder )
2025-02-06 18:03:09 +08:00
//平仓单
case preOrder . OrderType == 3 && orderStatus == 6 :
2025-02-06 11:14:33 +08:00
handleMainOrderClosePosition ( db , preOrder )
2025-02-06 18:03:09 +08:00
//主单止损回调
case preOrder . OrderType == 2 && preOrder . OrderCategory == 1 && orderStatus == 6 :
if preOrder . CoverType == 0 {
if err := db . Model ( & DbModels . LinePreOrder { } ) . Where ( "id =?" , preOrder . Pid ) . Update ( "status" , 9 ) . Error ; err != nil {
logger . Errorf ( "主单止损回调 订单号:%s 修改主单状态失败:%v" , preOrder . OrderSn , err )
}
2025-02-08 14:05:57 +08:00
} else {
order , err := GetOrderById ( db , preOrder . Pid )
if err != nil {
logger . Errorf ( "主单止损回调 获取主单失败 订单号:%s err:%v" , preOrder . OrderSn , err )
return
}
triggerHedgeOrder ( order , db , preOrder )
2025-02-06 18:03:09 +08:00
}
2025-02-06 11:14:33 +08:00
}
}
2025-02-06 18:03:09 +08:00
// 主单取消
func handleMainOrderCancel ( preOrder * DbModels . LinePreOrder ) {
preOrderKey := fmt . Sprintf ( rediskey . PreSpotOrderList , global . EXCHANGE_BINANCE )
preSpotOrders , _ := helper . DefaultRedis . GetAllList ( preOrderKey )
preOrderCache := DbModels . LinePreOrder { }
for _ , v := range preSpotOrders {
if v == "" {
continue
}
sonic . Unmarshal ( [ ] byte ( v ) , & preOrderCache )
if preOrderCache . Pid == preOrder . Id {
_ , err := helper . DefaultRedis . LRem ( preOrderKey , v )
if err != nil {
logger . Errorf ( "订单回调失败, 回调订单号:%s 删除缓存失败:%v" , preOrder . OrderSn , err )
}
}
}
}
// 平仓单
2025-02-06 11:14:33 +08:00
func handleMainOrderClosePosition ( db * gorm . DB , preOrder * DbModels . LinePreOrder ) {
2025-02-06 18:03:09 +08:00
//主单平仓
if preOrder . OrderCategory == 1 {
if err := db . Model ( & DbModels . LinePreOrder { } ) . Where ( "id =?" , preOrder . Pid ) . Update ( "status" , 9 ) . Error ; err != nil {
logger . Errorf ( "平仓订单回调失败, 回调订单号:%s 更新主单失败:%v" , preOrder . OrderSn , err )
}
} else {
//对冲单回调
if err := db . Model ( & DbModels . LinePreOrder { } ) .
Where ( "pid =? AND order_category =? AND order_type =0 AND status <9 " , preOrder . Pid , preOrder . OrderCategory ) .
Update ( "status" , 9 ) . Error ; err != nil {
logger . Errorf ( "对冲单平仓回调失败, 回调订单号:%s 更新对冲单失败:%v" , preOrder . OrderSn , err )
}
}
2025-02-06 11:14:33 +08:00
}
2025-02-06 18:03:09 +08:00
// 止盈成交
2025-02-06 11:14:33 +08:00
func handleSpotTakeProfitFilled ( db * gorm . DB , preOrder * DbModels . LinePreOrder ) {
2025-02-06 18:03:09 +08:00
if err := db . Model ( & DbModels . LinePreOrder { } ) . Where ( "id =? AND order_category =?" , preOrder . Pid , preOrder . OrderCategory ) . Update ( "status" , 9 ) . Error ; err != nil {
logger . Errorf ( "止盈订单回调失败, 回调订单号:%s 更新主单失败:%v" , preOrder . OrderSn , err )
}
stoplossKey := fmt . Sprintf ( rediskey . SpotStopLossList , global . EXCHANGE_BINANCE )
stoplossVal , _ := helper . DefaultRedis . GetAllList ( stoplossKey )
stoploss := DbModels . LinePreOrder { }
for _ , v := range stoplossVal {
sonic . Unmarshal ( [ ] byte ( v ) , & stoploss )
2025-02-08 14:05:57 +08:00
if stoploss . Pid == preOrder . Pid {
2025-02-06 18:03:09 +08:00
_ , err := helper . DefaultRedis . LRem ( stoplossKey , v )
2025-02-06 11:14:33 +08:00
2025-02-06 18:03:09 +08:00
if err != nil {
logger . Errorf ( "订单回调失败, 回调订单号:%s 删除缓存失败:%v" , preOrder . OrderSn , err )
}
}
}
2025-02-06 11:14:33 +08:00
}
2025-02-06 18:03:09 +08:00
// 主单成交
2025-02-06 11:14:33 +08:00
func handleMainOrderFilled ( db * gorm . DB , preOrder * DbModels . LinePreOrder ) {
2025-02-06 18:03:09 +08:00
processTakeProfitAndStopLossOrders ( db , preOrder )
2025-02-06 11:14:33 +08:00
}
// 解析订单状态
2025-02-06 18:03:09 +08:00
// 5:委托中 6:完全成交 4:取消 9:已平仓
func parseOrderStatus ( status interface { } , mapData map [ string ] interface { } ) ( int , string ) {
2025-02-06 11:14:33 +08:00
reason , _ := mapData [ "r" ] . ( string )
if strings . ToLower ( reason ) == "none" {
reason = ""
}
switch status {
case "NEW" : // 未成交
return 5 , reason
case "FILLED" : // 完全成交
2025-02-06 18:03:09 +08:00
return 6 , reason
2025-02-06 11:14:33 +08:00
case "CANCELED" , "EXPIRED" : // 取消
return 4 , reason
default :
return 0 , reason
}
}
// 更新订单状态
func updateOrderStatus ( db * gorm . DB , preOrder * models . LinePreOrder , status int , reason string , isSpot bool , mapData map [ string ] interface { } ) error {
params := map [ string ] interface { } { "status" : strconv . Itoa ( status ) , "desc" : reason }
switch isSpot {
case true :
total := decimal . Zero
totalAmount := decimal . Zero
if totalStr , ok := mapData [ "Z" ] . ( string ) ; ok {
total , _ = decimal . NewFromString ( totalStr )
}
if totalAmountStr , ok := mapData [ "z" ] . ( string ) ; ok {
totalAmount , _ = decimal . NewFromString ( totalAmountStr )
}
//主单 修改单价 和成交数量
if total . Cmp ( decimal . Zero ) > 0 && totalAmount . Cmp ( decimal . Zero ) > 0 {
num := totalAmount . Div ( decimal . NewFromFloat ( 100 ) ) . Mul ( decimal . NewFromFloat ( 99.8 ) )
params [ "num" ] = num
params [ "price" ] = total . Div ( totalAmount )
preOrder . Num = num . String ( )
2025-02-08 14:05:57 +08:00
preOrder . Price = total . Div ( totalAmount ) . String ( )
2025-02-06 11:14:33 +08:00
}
case false :
status , _ := mapData [ "X" ] . ( string )
if status == "FILLED" {
num , _ := decimal . NewFromString ( mapData [ "z" ] . ( string ) )
params [ "num" ] = num . Mul ( decimal . NewFromFloat ( 0.998 ) ) . String ( )
2025-02-08 14:05:57 +08:00
price , _ := mapData [ "ap" ] . ( string )
2025-02-06 11:14:33 +08:00
2025-02-08 14:05:57 +08:00
params [ "price" ] = price
2025-02-06 11:14:33 +08:00
preOrder . Num = num . String ( )
2025-02-08 14:05:57 +08:00
preOrder . Price = price
2025-02-06 11:14:33 +08:00
}
}
return db . Model ( & DbModels . LinePreOrder { } ) . Where ( "order_sn = ? AND status < 6" , preOrder . OrderSn ) .
Updates ( params ) . Error
}
// 主单成交 处理止盈止损订单
2025-02-06 18:03:09 +08:00
// preOrder 主单
2025-02-06 11:14:33 +08:00
func processTakeProfitAndStopLossOrders ( db * gorm . DB , preOrder * models . LinePreOrder ) {
orders := [ ] models . LinePreOrder { }
2025-02-06 18:03:09 +08:00
if err := db . Model ( & DbModels . LinePreOrder { } ) . Where ( "pid = ? AND order_category = 1 AND order_type > 0 AND status = '0' " , preOrder . Id ) . Find ( & orders ) . Error ; err != nil {
2025-02-06 11:14:33 +08:00
logger . Error ( "订单回调查询止盈止损单失败:" , err )
return
}
spotApi := SpotRestApi { }
num , _ := decimal . NewFromString ( preOrder . Num )
2025-02-06 18:03:09 +08:00
num = num . Mul ( decimal . NewFromFloat ( 0.998 ) )
2025-02-06 11:14:33 +08:00
for i , order := range orders {
if i >= 2 { // 最多处理 2 个订单
break
}
2025-02-06 18:03:09 +08:00
if err := db . Model ( & order ) . Update ( "num" , num ) . Error ; err != nil {
logger . Errorf ( "修改止盈止损数量失败 订单号:%s err:%v" , order . OrderSn , err )
}
2025-02-06 11:14:33 +08:00
switch order . OrderType {
case 1 : // 止盈
processTakeProfitOrder ( db , spotApi , order , num )
case 2 : // 止损
2025-02-06 18:03:09 +08:00
processStopLossOrder ( order )
2025-02-06 11:14:33 +08:00
}
}
}
// 处理止盈订单
func processTakeProfitOrder ( db * gorm . DB , spotApi SpotRestApi , order models . LinePreOrder , num decimal . Decimal ) {
tradeSet , _ := GetTradeSet ( order . Symbol , 0 )
if tradeSet . Coin == "" {
logger . Error ( "获取交易对失败" )
return
}
price , _ := decimal . NewFromString ( order . Price )
params := OrderPlacementService {
ApiId : order . ApiId ,
Symbol : order . Symbol ,
Side : order . Site ,
Price : price . Truncate ( int32 ( tradeSet . PriceDigit ) ) ,
Quantity : num . Truncate ( int32 ( tradeSet . AmountDigit ) ) ,
Type : "TAKE_PROFIT_LIMIT" ,
TimeInForce : "GTC" ,
StopPrice : price . Truncate ( int32 ( tradeSet . PriceDigit ) ) ,
NewClientOrderId : order . OrderSn ,
}
err := spotApi . OrderPlace ( db , params )
if err != nil {
for x := 0 ; x < 5 ; x ++ {
if strings . Contains ( err . Error ( ) , "LOT_SIZE" ) {
break
}
err = spotApi . OrderPlace ( db , params )
if err == nil {
break
}
}
}
if err != nil {
logger . Error ( "现货止盈下单失败:" , order . OrderSn , " err:" , err )
if err := db . Model ( & DbModels . LinePreOrder { } ) . Where ( "id = ?" , order . Id ) .
2025-02-06 18:03:09 +08:00
Updates ( map [ string ] interface { } { "status" : "2" , "desc" : err . Error ( ) , "num" : params . Quantity } ) . Error ; err != nil {
2025-02-06 11:14:33 +08:00
logger . Error ( "现货止盈下单失败,更新状态失败:" , order . OrderSn , " err:" , err )
}
} else {
if err := db . Model ( & DbModels . LinePreOrder { } ) . Where ( "id = ? and status ='0'" , order . Id ) .
Updates ( map [ string ] interface { } { "status" : "1" , "num" : num . String ( ) } ) . Error ; err != nil {
logger . Error ( "现货止盈下单成功,更新状态失败:" , order . OrderSn , " err:" , err )
}
}
}
// 处理止损订单
// order 止损单
2025-02-06 18:03:09 +08:00
func processStopLossOrder ( order models . LinePreOrder ) error {
price := utility . StrToDecimal ( order . Price )
stopLoss := dto . StopLossRedisList {
Id : order . Id ,
PId : order . Pid ,
ApiId : order . ApiId ,
OrderTye : order . OrderType ,
OrderCategory : order . OrderCategory ,
Price : price ,
SymbolType : order . SymbolType ,
Symbol : order . Symbol ,
Site : order . Site ,
}
2025-02-06 11:14:33 +08:00
2025-02-06 18:03:09 +08:00
stoplossVal , _ := sonic . MarshalString ( & stopLoss )
stoplossKey := fmt . Sprintf ( rediskey . SpotStopLossList , global . EXCHANGE_BINANCE )
2025-02-06 11:14:33 +08:00
2025-02-06 18:03:09 +08:00
if stoplossVal == "" {
return errors . New ( "stoplossVal is empty" )
}
2025-02-06 11:14:33 +08:00
2025-02-06 18:03:09 +08:00
if err := helper . DefaultRedis . RPushList ( stoplossKey , stoplossVal ) ; err != nil {
return err
2025-02-06 11:14:33 +08:00
}
2025-02-06 18:03:09 +08:00
return nil
2025-02-06 11:14:33 +08:00
}
func GetSystemSetting ( db * gorm . DB ) ( models . LineSystemSetting , error ) {
key := fmt . Sprintf ( rediskey . SystemSetting )
val , _ := helper . DefaultRedis . GetString ( key )
setting := models . LineSystemSetting { }
if val != "" {
sonic . UnmarshalString ( val , & setting )
}
if setting . Id > 0 {
return setting , nil
}
var err error
setting , err = ResetSystemSetting ( db )
if err != nil {
return setting , err
}
return setting , nil
}
func ResetSystemSetting ( db * gorm . DB ) ( DbModels . LineSystemSetting , error ) {
setting := DbModels . LineSystemSetting { }
if err := db . Model ( & setting ) . First ( & setting ) . Error ; err != nil {
return setting , err
}
settVal , _ := sonic . MarshalString ( & setting )
if settVal != "" {
if err := helper . DefaultRedis . SetString ( rediskey . SystemSetting , settVal ) ; err != nil {
logger . Error ( "redis添加系统设置失败" , err )
}
}
return DbModels . LineSystemSetting { } , nil
}
// NEW
// PENDING_NEW
// PARTIALLY_FILLED
// FILLED
// CANCELED
// PENDING_CANCEL
// REJECTED
// EXPIRED
// EXPIRED_IN_MATCH